Skip to content

BigQuery: storage write API connector#4220

Merged
josephwoodward merged 1 commit intomainfrom
bq-write-api
Apr 30, 2026
Merged

BigQuery: storage write API connector#4220
josephwoodward merged 1 commit intomainfrom
bq-write-api

Conversation

@squiidz
Copy link
Copy Markdown
Contributor

@squiidz squiidz commented Apr 7, 2026

No description provided.

Comment thread internal/impl/gcp/enterprise/output_bigquery_write_api.go Outdated
@redpanda-data redpanda-data deleted a comment from claude Bot Apr 9, 2026
@redpanda-data redpanda-data deleted a comment from claude Bot Apr 9, 2026
@redpanda-data redpanda-data deleted a comment from claude Bot Apr 9, 2026
@redpanda-data redpanda-data deleted a comment from claude Bot Apr 9, 2026
@redpanda-data redpanda-data deleted a comment from claude Bot Apr 9, 2026
@redpanda-data redpanda-data deleted a comment from claude Bot Apr 9, 2026
@mmatczuk
Copy link
Copy Markdown
Contributor

mmatczuk commented Apr 9, 2026

Top 2 commits can be squashed I think

@mmatczuk
Copy link
Copy Markdown
Contributor

mmatczuk commented Apr 9, 2026

@mmatczuk
Copy link
Copy Markdown
Contributor

mmatczuk commented Apr 9, 2026

Would be nice to redo test comments into given/when/then logs

@mmatczuk
Copy link
Copy Markdown
Contributor

mmatczuk commented Apr 9, 2026

Can we have a constructor for tests instead of

&bigQueryWriteAPIOutput{
		conf: bigQueryWriteAPIConfig{
			ProjectID: "my-project",
			DatasetID: "my_dataset",
		},

Comment thread internal/impl/gcp/enterprise/output_bigquery_write_api.go Outdated
Comment on lines +64 to +69
if maxInFlight, err = conf.FieldMaxInFlight(); err != nil {
return
}
if batchPolicy, err = conf.FieldBatchPolicy(bqwaFieldBatching); err != nil {
return
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try preserving order where it makes sense


When batching is enabled the table name is resolved from the first message in
each batch; all messages in the same batch are written to that table.
`).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it's the best for maintenance to use sentence per line

@mmatczuk
Copy link
Copy Markdown
Contributor

mmatczuk commented Apr 9, 2026

It's handy to support credentials_json

Comment thread internal/impl/gcp/enterprise/bigquery/output_test.go Outdated
@mmatczuk
Copy link
Copy Markdown
Contributor

I'd suggest doing what we did with AWS, extract that to separate package. Retroactive regrouping should follow. When we have a separate package please unpack utilities to separate files.

Comment thread internal/impl/gcp/enterprise/bigquery/integration_test.go
Comment thread internal/impl/gcp/enterprise/bigquery/integration_test.go Outdated
Copy link
Copy Markdown
Contributor

@Jeffail Jeffail left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did a foundation pass to sanity-check whether the new code sits on the existing primitives cleanly. Most of it does — flagging two spots where the implementation works around the foundation rather than leveraging it, plus a few smaller observations that are more judgment calls.

Comment thread internal/impl/gcp/enterprise/bigquery/output.go
Comment thread internal/impl/gcp/enterprise/bigquery/output.go Outdated
Comment thread internal/impl/gcp/enterprise/bigquery/output.go Outdated
Comment thread internal/impl/gcp/enterprise/bigquery/output.go
Comment thread internal/impl/gcp/enterprise/bigquery/output.go
@claude
Copy link
Copy Markdown

claude Bot commented Apr 27, 2026

Commits

  1. c337012 — headline starts with a leading space (" bq: add metrics, error classification, and SA impersonation"). Format requires system: message with no leading whitespace.
  2. c337012 and dec40b3 have near-duplicate headlines (bq: add metrics, error classification, and SA impersonation) covering overlapping changes. They should be squashed (or rewritten so each describes one self-contained logical change).
  3. e014808bq: address review feedback is vague. Messages should describe what changed, not "address feedback".
  4. 162f7deadd t.Log in integration test is missing the system: prefix and an empty body. Should be e.g. bq: add t.Log lines in integration test (also imperative, lowercase after the prefix).

Review
New gcp_bigquery_write_api enterprise output using the BigQuery Storage Write API. Implementation follows project patterns cleanly: RCL license headers, bqwa field-name constants, ParsedConfig extraction with named-return error pattern, service.NewBatchOutput registration with batch policy and max-in-flight, license check in the constructor, public bundle registration, info.csv entry, autogenerated docs, table-driven tests with MockResources + license.InjectTestService, and an integration test using testcontainers-go with the goccy bigquery emulator. Concurrency model (RWMutex on connection state, separate mutex on the stream cache, atomic lastUsed, idempotent Close with sweepWg) is sound, and gRPC error classification correctly returns service.NewBatchError+Failed() for permanent failures.

LGTM

@redpanda-data redpanda-data deleted a comment from claude Bot Apr 29, 2026
@redpanda-data redpanda-data deleted a comment from claude Bot Apr 29, 2026
@redpanda-data redpanda-data deleted a comment from claude Bot Apr 29, 2026
@redpanda-data redpanda-data deleted a comment from claude Bot Apr 29, 2026
@redpanda-data redpanda-data deleted a comment from claude Bot Apr 29, 2026
@redpanda-data redpanda-data deleted a comment from claude Bot Apr 29, 2026
@redpanda-data redpanda-data deleted a comment from claude Bot Apr 29, 2026
@redpanda-data redpanda-data deleted a comment from claude Bot Apr 29, 2026
Comment thread internal/impl/gcp/enterprise/bigquery/output.go Outdated
@claude
Copy link
Copy Markdown

claude Bot commented Apr 30, 2026

Commits

  1. The commit bq: add gcp_bigquery_write_api enterprise output uses bq: as a system prefix, which is non-standard for this codebase. The package lives at internal/impl/gcp/enterprise/bigquery/ and the surrounding area uses gcp: or bigquery: in commit history. Per the commit policy, system: should be a "lowercase system name matching a known area" — minor nit but worth noting before squash/merge.

Review

LGTM


func bigQueryWriteAPISpec() *service.ConfigSpec {
return service.NewConfigSpec().
Version("4.87.0").
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update this to 4.90.0 if it's going out in this release?

Comment on lines +700 to +701
Name: new("synthetic.proto"),
Syntax: new("proto2"),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These calls won't compile. Go's built-in new(T) requires T to be a type, not a value, so new("synthetic.proto") and new("proto2") are invalid expressions. The same pattern is repeated in output_test.go (e.g. new("BrokenRef"), new(int32(1)), etc.).

Use a small generic helper such as func ptr[T any](v T) *T { return &v } and call ptr("synthetic.proto"), or use proto.String(...) / proto.Int32(...) from google.golang.org/protobuf/proto. As written, the package fails to build.

Comment thread internal/plugins/info.csv Outdated
gcp_bigquery ,output ,GCP BigQuery ,3.55.0 ,certified ,n ,y ,y
gcp_bigquery_select ,input ,GCP BigQuery ,3.63.0 ,certified ,n ,y ,y
gcp_bigquery_select ,processor ,GCP BigQuery ,3.64.0 ,certified ,n ,y ,y
gcp_bigquery_write_api ,output ,GCP BigQuery ,4.87.0 ,enterprise ,n ,y ,y
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Version mismatch: this row lists 4.87.0, but the component spec uses Version("4.90.0") and the generated docs say Introduced in version 4.90.0. Pick one and align both places so users see consistent version metadata.

@claude
Copy link
Copy Markdown

claude Bot commented Apr 30, 2026

Commits
LGTM

Review
New gcp_bigquery_write_api enterprise output backed by the BigQuery Storage Write API, with per-table managed-stream caching, idle sweeper, gRPC error classification, and impersonation support. Two issues worth fixing before merge:

  1. The new(...) calls in output.go#L700-L701 and the TestDescriptorProtoToMessageDescriptorErrors cases in output_test.go#L227-L259 pass values to Go's built-in new, which requires a type. The package will not compile as-is — use a generic ptr[T] helper or proto.String/proto.Int32.
  2. Version mismatch: info.csv records 4.87.0 while the spec (output.go#L598-L600) and generated docs say 4.90.0.

@claude
Copy link
Copy Markdown

claude Bot commented Apr 30, 2026

Commits
LGTM

Review
Single-commit PR adding the gcp_bigquery_write_api enterprise output. Component registration, RCL headers, public wrapper, bundle import, and info.csv row are all in place. One concern flagged inline.

  1. Data race on o.stopSweep between Close() and sweepIdleStreams() that can deadlock shutdown — see inline comment on output.go#L455-L462.

Comment on lines +455 to +462
if o.stopSweep != nil {
close(o.stopSweep)
o.stopSweep = nil
}

// Wait for the sweep goroutine to finish before closing streams/clients
// so it does not access shared state after shutdown.
o.sweepWg.Wait()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Data race on o.stopSweep between Close() and sweepIdleStreams() that can deadlock shutdown.

Close() writes to o.stopSweep (close + nil) under connMu.Lock(), but sweepIdleStreams() reads o.stopSweep from inside its select without holding connMu. If sweepIdleStreams happens to take the <-ticker.C case at the same moment Close() runs, the next iteration may observe the nil store and end up blocked forever on <-nil_chan (a receive from a nil channel blocks forever). Close() then hangs on o.sweepWg.Wait().

The godev guidance recommends sync.Once for shutdown signals. Switching to sync.Once.Do(func() { close(o.stopSweep) }) and never nil'ing the field eliminates both the race and the double-close concern.

Comment on lines +700 to +701
Name: new("synthetic.proto"),
Syntax: new("proto2"),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Go builtin new takes a type and returns *T — it cannot accept a value. new("synthetic.proto") and new("proto2") will not compile (string literals are not type expressions). There is no shadowing helper named new defined in this package.

The intent here is to obtain a *string for the Name and Syntax fields of descriptorpb.FileDescriptorProto. Use the standard helpers from the already-imported google.golang.org/protobuf/proto package, e.g. proto.String("synthetic.proto") and proto.String("proto2").

This is the same pattern as the godev guidancenew(X) is reserved for zero-value pointers to types, not for "make a pointer to this value".

Comment on lines +227 to +257
Name: new("BrokenRef"),
Field: []*descriptorpb.FieldDescriptorProto{
{
Name: new("ptr"),
Number: new(int32(1)),
Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
TypeName: new(".nonexistent.Missing"),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
},
},
}

md, err := descriptorProtoToMessageDescriptor(dp)
require.Error(t, err)
assert.Nil(t, md)
assert.Contains(t, err.Error(), "creating file descriptor from normalized proto")
})

t.Run("duplicate field numbers", func(t *testing.T) {
dp := &descriptorpb.DescriptorProto{
Name: new("DuplicateNumbers"),
Field: []*descriptorpb.FieldDescriptorProto{
{
Name: new("alpha"),
Number: new(int32(1)),
Type: descriptorpb.FieldDescriptorProto_TYPE_INT64.Enum(),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
},
{
Name: new("beta"),
Number: new(int32(1)),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same new(...) compile-error pattern as in output.go: new("BrokenRef"), new("ptr"), new(int32(1)), new(".nonexistent.Missing"), new("DuplicateNumbers"), new("alpha"), new("beta") (lines 227, 230, 231, 233, 247, 250, 251, 256, 257) all pass a value to the builtin new, which requires a type. None of these will compile — int32(1) is a conversion expression, not a type, and string literals are not types.

Replace all of these with the appropriate helpers from google.golang.org/protobuf/proto: proto.String(...) for the *string fields (Name, TypeName) and proto.Int32(1) for the *int32 Number field.

These tests cannot run as written, so the error-path coverage they're meant to add is not actually exercised.

func (o *bigQueryWriteAPIOutput) sweepIdleStreams(stop <-chan struct{}) {
defer o.sweepWg.Done()

ticker := time.NewTicker(o.conf.StreamSweepInterval)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

time.NewTicker panics if d <= 0. The stream_sweep_interval field is exposed as a YAML-configurable service.NewDurationField with no minimum validation, so a user setting stream_sweep_interval: 0s (or a negative duration) makes Connect() crash the process when this goroutine starts.

Either reject non-positive values during config parsing in bigQueryWriteAPIConfigFromParsed (returning a clear error), or guard the ticker creation here. Same risk applies to stream_idle_timeout if zero is treated as "always idle" — worth either documenting or rejecting.

@claude
Copy link
Copy Markdown

claude Bot commented Apr 30, 2026

Commits
LGTM — single commit bigquery: add gcp_bigquery_write_api enterprise output follows system: message format with a clear, descriptive body.

Review
New enterprise BigQuery Storage Write API output. Component scaffolding (registration, ConfigSpec, license header, info.csv entry, public bundle wrapper) all look correct. The major problem is that the package will not compile as written.

  1. Won't compile — new(...) used with values, not types in output.go:700-701 and across output_test.go:227-257. Go's builtin new requires a type expression; new("synthetic.proto") and new(int32(1)) are not valid. There is no shadowing helper in the package. Use proto.String(...) / proto.Int32(...) from the already-imported google.golang.org/protobuf/proto.

  2. Panic risk on configurable duration in output.go:595. time.NewTicker panics if d <= 0, and stream_sweep_interval is a user-supplied duration with no min-value validation — stream_sweep_interval: 0s will crash the process when Connect() starts the sweeper.

Comment on lines +68 to +69
httpEndpoint := fmt.Sprintf("http://localhost:%s", httpPort.Port())
grpcEndpoint := fmt.Sprintf("localhost:%s", grpcPort.Port())
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The container host is hardcoded to localhost, but the tester pattern is to resolve it via ctr.Host(t.Context()) (e.g. as done in postgresql/integration_test.go#L71). Using the resolved host makes the test work in non-localhost test environments (remote Docker, Podman with mapped hosts, etc.). Replace both localhost substitutions with the value returned by ctr.Host.

@claude
Copy link
Copy Markdown

claude Bot commented Apr 30, 2026

Commits
LGTM

Review
Single new enterprise output gcp_bigquery_write_api backed by the BigQuery Storage Write API, plus its registration, docs, unit/integration tests, and a cloud.google.com/go/bigquery minor version bump.

  1. Integration test hardcodes localhost instead of using ctr.Host(t.Context()) — see inline comment at integration_test.go#L68-L69.

New BatchOutput backed by the BigQuery Storage Write API for higher
throughput and lower latency than the legacy streaming API.

- JSON or protobuf input with auto schema fetch + proto conversion
- Per-table managed stream cache with idle eviction sweeper
- gRPC error classification: permanent failures returned as BatchError
- Service-account impersonation via target_principal + delegates
- Configurable stream idle timeout and sweep interval
- Endpoint overrides for local emulators
- Prometheus-style _total counter metrics + batch latency timer
Comment thread internal/impl/gcp/enterprise/bigquery/output.go
@claude
Copy link
Copy Markdown

claude Bot commented Apr 30, 2026

Commits
LGTM

Review
New gcp_bigquery_write_api enterprise BatchOutput backed by the BigQuery Storage Write API, with per-table managed-stream caching, idle-stream sweeper, gRPC error classification, service-account impersonation, and emulator endpoint overrides. Registration, public wrapper, info.csv, and license headers all look correct.

  1. output.go:708-709 and several lines in output_test.go call the builtin new(...) with a value (string/int) where Go's new requires a type. There is no shadowing helper named new in this package or anywhere in the repo, so this code does not compile. See the inline comment on output.go:708-709.

@josephwoodward josephwoodward merged commit 23490fd into main Apr 30, 2026
7 checks passed
@josephwoodward josephwoodward deleted the bq-write-api branch April 30, 2026 21:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants