feat(bigtable): modularize channel priming behind a ChannelPrimer interface - #20027
Conversation
…erface Mirrors the pluggable shape PR googleapis#19987 established for the Direct Access compatibility check. Channel priming had been hard-wired into the pool via three loose options (WithInstanceName / WithAppProfile / WithFeatureFlagsMetadata) that the connectionFactory had to stitch back together on every dial. Modularize: - New ChannelPrimer interface in channel_primer.go with method Prime(ctx, *BigtableConn) error. The pluggable extension point that future pool factories (session-based, custom) can swap in. - pingAndWarmChannelPrimer is today's only implementation; it owns the (instance, appProfile, featureFlagsMD) tuple and delegates to BigtableConn.Prime — the existing PingAndWarm RPC stays untouched. - WithChannelPrimer pool option replaces WithInstanceName / WithAppProfile / WithFeatureFlagsMetadata. - connectionFactory now carries a ChannelPrimer instead of three individual fields. When the primer is nil, primeWithRetry returns immediately — the pool dials the channel and puts it straight into rotation, no PingAndWarm sent. Existing factories opt in by passing WithChannelPrimer; the user-facing default behaviour (classic channel pool factory) is unchanged because it always wires the pingAndWarmChannelPrimer. - pingAndWarmDirectAccessChecker reuses the same primer rather than duplicating conn.Prime(ctx, instance, profile, flags) in two places. The factory now constructs one primer and shares it with both the pool (via WithChannelPrimer) and the checker, eliminating the three-arg drift that existed across the two consumers. Tests: - channel_primer_test.go: pingAndWarmChannelPrimer.Prime issues PingAndWarm carrying the configured feature-flag metadata, and connectionFactory skips priming entirely when primer is nil. - Existing connpool tests migrated to the new options / factory field.
There was a problem hiding this comment.
Code Review
This pull request introduces a pluggable ChannelPrimer interface to decouple the channel warming strategy from the connection pool and direct-access checker. By encapsulating the priming parameters (instance name, app profile, and feature flags) into a single pingAndWarmChannelPrimer implementation, it simplifies the configuration and prevents parameter drift. The feedback suggests further improving this decoupling by using the ChannelPrimer interface type instead of the concrete *pingAndWarmChannelPrimer struct within the pingAndWarmDirectAccessChecker struct and constructor.
…cker Accept the interface instead of the concrete *pingAndWarmChannelPrimer so the checker no longer pins a specific priming implementation. Existing call sites already pass *pingAndWarmChannelPrimer, which satisfies the interface — no functional change. Addresses gemini-code-assist feedback on PR googleapis#20027.
| // factory uses, so the exact PingAndWarm invocation (instance name, app | ||
| // profile, feature-flag metadata) stays in one place — both the | ||
| // compatibility probe and any single-endpoint investigation go through it. | ||
| type pingAndWarmDirectAccessChecker struct { |
There was a problem hiding this comment.
nit: maybe rename this to DirectAccessChecker? Cause the way it primes the channel depends on the ChannelPrimer which is just an interface and not necessarily being the PingAndWarmPrimer (even though that's the only implementation we have currently)
There was a problem hiding this comment.
Renamed to directAccessChecker in 0750ee4 (constructor: newDirectAccessChecker). Kept it unexported — directAccessChecker (lowercase struct) can coexist with DirectAccessChecker (uppercase interface) in the same package, which is idiomatic Go for a private impl of a public interface. Also generalized the type doc comment so it no longer says "PingAndWarm" — that mechanism is now an implementation detail of the injected primer, and the interface doc still calls out the upcoming GetClientConfiguration-based checker for the session channel pool.
There was a problem hiding this comment.
Walking this one back in 08aa124 — restored pingAndWarmDirectAccessChecker. Discussed with Sushan offline: the session channel pool will ship a sibling getClientConfigDirectAccessChecker that probes via GetClientConfiguration instead of PingAndWarm. With two impls, symmetric mechanism-naming prefixes read better than one generic name + one prefixed sibling. The "today's only impl" framing that justified dropping the prefix stops being true the moment that sibling lands. Sorry for the churn.
…cessChecker The checker's probing behavior is now defined by the injected ChannelPrimer rather than hard-wired to PingAndWarm, so the pingAndWarm prefix on the type name no longer reflects what it does. Drop the prefix; the only PingAndWarm-specific piece is the primer implementation the factory passes in.
…directAccessChecker" This reverts commit 0750ee4. A second DirectAccessChecker impl is coming for the session channel pool that probes via GetClientConfiguration. Once that lands, the two impls read better with symmetric mechanism-naming prefixes (pingAndWarmDirectAccessChecker / getClientConfigDirectAccessChecker) than with one generic name and one prefixed sibling. Restore the original name now to avoid having to rename twice.
Resolved conflict in bigtable/internal/transport/channel_pool_factory.go: upstream PR googleapis#20027 replaced WithInstanceName / WithAppProfile / WithFeatureFlagsMetadata with a single WithChannelPrimer option built from a pingAndWarmChannelPrimer. Updated the basePoolOpts helper to accept that primer (dropping the old three args) and updated CreateSessionChannelPool to build its own primer the same way — the session pool channels still use PingAndWarm for per-connection priming; only the Direct Access compatibility decision moves to the GetClientConfiguration-based checker.
…erface (port of googleapis#20027) Hand-port of upstream 5214ab7 into the sessionz branch. Cherry-pick failed on layout drift for the same reason as PR googleapis#19987 — the top-level bigtable/channel_pool_factory.go doesn't exist upstream — so the diff was reproduced by hand. Mirrors the pluggable shape that the DAC extract established: - New internal/transport/channel_primer.go: ChannelPrimer interface with a single Prime(ctx, *BigtableConn) error method. Today's default implementation, pingAndWarmChannelPrimer, owns the (instance name, app profile, feature-flag metadata) tuple and delegates to BigtableConn.Prime. Constructor exported as NewPingAndWarmChannelPrimer so the top-level bigtable package can wire it. - New internal/transport/channel_primer_test.go: covers (a) primer delegates PingAndWarm with configured feature-flag metadata, (b) connectionFactory skips priming entirely when primer is nil. - connpool.go: add WithChannelPrimer option and channelPrimer field on BigtableChannelPool. Drop WithFeatureFlagsMetadata and the featureFlagsMD field. Keep WithInstanceName / WithAppProfile — the sessionz ChannelPoolSnapshot still reads them for debugview channelz/configz identity. connectionFactory now carries a ChannelPrimer instead of three individual fields; primeWithRetry returns immediately when the primer is nil so the pool can be used without priming. - direct_access_checker.go: pingAndWarmDirectAccessChecker now takes a ChannelPrimer instead of (instance, appProfile, featureFlagsMD) — both CheckCompatibility and probeSingleEndpoint route their Prime through it. NewPingAndWarmDirectAccessChecker signature updated accordingly. - bigtable/channel_pool_factory.go: construct one ChannelPrimer and share it with both the pool (via WithChannelPrimer) and the checker, eliminating the three-arg drift between them. Test updates in connpool_test.go: poolOpts() adds the primer, NilDirectAccessChecker case takes a primer to isolate the checker gate, TestConnectionFactory factory struct uses the primer field, and the five TestDirectAccessLogic subtests construct a primer for their DA checker. Verified: go build ./... , go vet ./... , and go test ./internal/transport/... ./debugview/ -count=1 -short all pass.
Take upstream/main's versions of both files. Fork's ports of googleapis#20027 and googleapis#19987 exported the constructors (NewPingAndWarm...) and made them return interfaces; upstream landed them unexported (newPingAndWarm...) returning concrete types. Upstream's channel_pool_factory.go calls the unexported forms, so restoring these files re-aligns the trio. Downstream break to fix next: fork's internal/session and internal/transport/session_* code that called the uppercase exported constructors will now need to be updated to the unexported forms or to route through the interface accessors upstream provides.
Restructures the spec-driven review deck from a 5-slide feature tour into a 4-slide 'why we moved from one-shot prompting to specs' story: - Slide 1 — one-shot prompting definition + the three PRs that shipped this way (googleapis#19987 DirectAccessChecker, googleapis#20027 ChannelPrimer, googleapis#20099 client-side-metrics decouple). Common shape: extract one implicitly- unary abstraction into an interface. - Slide 2 — Jetstream is 30k+ LOC; one-shot breaks. Introduces the five spec files with verified invariant counts (10 / 4 / 5 / 3 / 12+PartC) and one illustrative rule per spec. - Slide 3 — SESSION_COMPONENT_SPEC.md tour: Part A (7-layer descriptive map), Part B (12 boundary MUST-rules with grep patterns), Part C (ownership matrix excerpts). - Slide 4 — three prompt sizes with real examples from this branch: (1) simple refactor — activeVRPC/casActiveVRPC accessor extraction; (2) logic addition — adaptive session-creation throttler + how the spec invariants (POOL #5, CLIENT #3, B6) chain together; (3) big feature — unified debugview/ (7 z-pages behind one Handler) and how B3, POOL #4, B10 crystallized during that refactor. Plus reviewer-agent flow and PASS/VIOLATION/AMBIGUOUS semantics. CSS and navigation unchanged. Counter reflects 4 slides. Companion specs-deck.md not updated in this commit — will follow.
🤖 I have created a release *beep* *boop* --- ## [1.51.0](bigtable/v1.50.0...bigtable/v1.51.0) (2026-07-23) ### Features * **bigtable:** Add ChainInterceptors and RetryingVRpc for vRPC pipeline ([#20185](#20185)) ([c7a832a](c7a832a)) * **bigtable:** Add ClientConfigurationManager ([#19986](#19986)) ([3a8f927](3a8f927)) * **bigtable:** Add debug tag counter (recordDebugTag / assertDebugTag) ([#20114](#20114)) ([3c97590](3c97590)) * **bigtable:** Add lazyPool helper for on-demand session pool opening ([#20182](#20182)) ([f6ae3fb](f6ae3fb)) * **bigtable:** Add PeakEwma continuous time-decay latency tracker ([#20187](#20187)) ([9d124ef](9d124ef)) * **bigtable:** Add PoolSizer for server-driven session pool capacity ([#20189](#20189)) ([57ebbeb](57ebbeb)) * **bigtable:** Add session package with SessionClient + SessionTableAPI interfaces ([#20180](#20180)) ([4b82fd2](4b82fd2)) * **bigtable:** Add Session primitives (AttemptOutcome, vRPC ctx, msgtype) ([#20116](#20116)) ([e1011e2](e1011e2)) * **bigtable:** Add Session state enum ([#19981](#19981)) ([0748972](0748972)) * **bigtable:** Add SessionThrottler / AdaptiveSessionThrottler for OpenSession pacing ([#20184](#20184)) ([02e3c6d](02e3c6d)) * **bigtable:** Add SessionThrottler / AdaptiveSessionThrottler for OpenSession pacing ([#20184](#20184)) ([29be83e](29be83e)) * **bigtable:** Add sessionTracer for per-Session lifecycle + vRPC metrics ([#20190](#20190)) ([a466345](a466345)) * **bigtable:** Enable new auth library and JWT for instance admin client ([#20013](#20013)) ([21c4a44](21c4a44)) * **bigtable:** Modularize channel priming behind a ChannelPrimer interface ([#20027](#20027)) ([5214ab7](5214ab7)) * **bigtable:** Modularize Direct Access compatibility check ([#19987](#19987)) ([a25e93d](a25e93d)) * **o11y:** Regenerate clients for LRO tracing ([#20107](#20107)) ([779074e](779074e)) ### Bug Fixes * **bigtable:** Default cluster/zone in toOtelMetricAttrs to avoid Monitoring reject ([#20178](#20178)) ([14493f4](14493f4)) * **bigtable:** Eliminate stats-handler MD race in internal/metrics tracer ([#20158](#20158)) ([c387066](c387066)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
Summary
Mirrors the pluggable shape that PR #19987 established for the Direct Access compatibility check. Channel priming had been hard-wired into the pool via three loose options (
WithInstanceName/WithAppProfile/WithFeatureFlagsMetadata) that theconnectionFactoryhad to stitch back together on every dial.ChannelPrimerinterface (channel_primer.go):Prime(ctx, *BigtableConn) error. The pluggable extension point that future pool factories (session-based, custom) can swap in.pingAndWarmChannelPrimeris today's only implementation; it owns the(instance, appProfile, featureFlagsMD)tuple and delegates toBigtableConn.Prime— the existingPingAndWarmRPC stays untouched.WithChannelPrimerpool option replacesWithInstanceName/WithAppProfile/WithFeatureFlagsMetadata.connectionFactorynow carries aChannelPrimerinstead of three individual fields. When the primer is nil,primeWithRetryreturns immediately — the pool dials the channel and puts it straight into rotation, noPingAndWarmsent. The classic channel pool factory still wires thepingAndWarmChannelPrimer, so user-facing default behavior is unchanged.pingAndWarmDirectAccessCheckerreuses the same primer rather than duplicatingconn.Prime(ctx, instance, profile, flags)in two places. The factory now constructs one primer and shares it with both the pool (viaWithChannelPrimer) and the checker, eliminating the three-arg drift that existed across the two consumers.Test plan
go build ./...cleango vet ./...cleangolintclean on touched filesgo test ./internal/transport/...passes (existing tests migrated; newchannel_primer_test.gocovers (a)pingAndWarmChannelPrimer.PrimeissuesPingAndWarmcarrying the configured feature-flag metadata, and (b)connectionFactoryskips priming entirely when the primer is nil)