Skip to content

Commit 3b8bfb4

Browse files
authored
feat(bigquery/storage/managedwriter): expose connection multiplexing as experimental (#7673)
This PR exposes the necessary options to control the new experimental multiplexing features within the managedwriter package, and attempts to document them sufficiently for correct consumption. Towards: https://togithub.com/googleapis/google-cloud-go/issues/7103
1 parent e660af8 commit 3b8bfb4

File tree

4 files changed

+154
-30
lines changed

4 files changed

+154
-30
lines changed

bigquery/storage/managedwriter/doc.go

+35
Original file line numberDiff line numberDiff line change
@@ -208,5 +208,40 @@ With write retries enabled, failed writes will be automatically attempted a fini
208208
In support of the retry changes, the AppendResult returned as part of an append call now includes
209209
TotalAttempts(), which returns the number of times that specific append was enqueued to the service.
210210
Values larger than 1 are indicative of a specific append being enqueued multiple times.
211+
212+
# Connection Sharing (Multiplexing)
213+
214+
Note: This feature is EXPERIMENTAL and subject to change.
215+
216+
The BigQuery Write API enforces a limit on the number of concurrent open connections, documented
217+
here: https://cloud.google.com/bigquery/quotas#write-api-limits
218+
219+
Users can now choose to enable connection sharing (multiplexing) when using ManagedStream writers
220+
that use default streams. The intent of this feature is to simplify connection management for users
221+
who wish to write to many tables, at a cardinality beyond the open connection quota. Please note that
222+
explicit streams (Committed, Buffered, and Pending) cannot leverage the connection sharing feature.
223+
224+
Multiplexing features are controlled by the package-specific custom ClientOption options exposed within
225+
this package. Additionally, some of the connection-related WriterOptions that can be specified when
226+
constructing ManagedStream writers are ignored for writers that leverage the shared multiplex connections.
227+
228+
At a high level, multiplexing uses some heuristics based on the flow control of the shared connections
229+
to infer whether the pool should add additional connections up to a user-specific limit per region,
230+
and attempts to balance traffic from writers to those connections.
231+
232+
To enable multiplexing for writes to default streams, simply instantiate the client with the desired options:
233+
234+
ctx := context.Background()
235+
client, err := managedwriter.NewClient(ctx, projectID,
236+
WithMultiplexing,
237+
WithMultiplexPoolLimit(3),
238+
)
239+
if err != nil {
240+
// TODO: Handle error.
241+
}
242+
243+
Special Consideration: Users who would like to utilize many connections associated with a single Client
244+
may benefit from setting the WithGRPCConnectionPool ClientOption, documented here:
245+
https://pkg.go.dev/google.golang.org/api/option#WithGRPCConnectionPool
211246
*/
212247
package managedwriter

bigquery/storage/managedwriter/integration_test.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -1345,7 +1345,10 @@ func testProtoNormalization(ctx context.Context, t *testing.T, mwClient *Client,
13451345
}
13461346

13471347
func TestIntegration_MultiplexWrites(t *testing.T) {
1348-
mwClient, bqClient := getTestClients(context.Background(), t, enableMultiplex(true, 5))
1348+
mwClient, bqClient := getTestClients(context.Background(), t,
1349+
WithMultiplexing(),
1350+
WithMultiplexPoolLimit(2),
1351+
)
13491352
defer mwClient.Close()
13501353
defer bqClient.Close()
13511354

bigquery/storage/managedwriter/options.go

+80-19
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,19 @@ func newWriterClientConfig(opts ...option.ClientOption) *writerClientConfig {
3939
wOpt.ApplyWriterOpt(conf)
4040
}
4141
}
42+
43+
// Normalize the config to ensure we're dealing with sane values.
44+
if conf.useMultiplex {
45+
if conf.maxMultiplexPoolSize < 1 {
46+
conf.maxMultiplexPoolSize = 1
47+
}
48+
}
49+
if conf.defaultInflightBytes < 0 {
50+
conf.defaultInflightBytes = 0
51+
}
52+
if conf.defaultInflightRequests < 0 {
53+
conf.defaultInflightRequests = 0
54+
}
4255
return conf
4356
}
4457

@@ -48,31 +61,55 @@ type writerClientOption interface {
4861
ApplyWriterOpt(*writerClientConfig)
4962
}
5063

51-
// enableMultiplex enables multiplex behavior in the client.
52-
// maxSize indicates the maximum number of shared multiplex connections
53-
// in a given location/region
64+
// WithMultiplexing is an EXPERIMENTAL option that controls connection sharing
65+
// when instantiating the Client. Only writes to default streams can leverage the
66+
// multiplex pool. Internally, the client maintains a pool of connections per BigQuery
67+
// destination region, and will grow the pool to it's maximum allowed size if there's
68+
// sufficient traffic on the shared connection(s).
5469
//
55-
// TODO: export this as part of the multiplex feature launch.
56-
func enableMultiplex(enable bool, maxSize int) option.ClientOption {
57-
return &enableMultiplexSetting{useMultiplex: enable, maxSize: maxSize}
70+
// This ClientOption is EXPERIMENTAL and subject to change.
71+
func WithMultiplexing() option.ClientOption {
72+
return &enableMultiplexSetting{useMultiplex: true}
5873
}
5974

6075
type enableMultiplexSetting struct {
6176
internaloption.EmbeddableAdapter
6277
useMultiplex bool
63-
maxSize int
6478
}
6579

6680
func (s *enableMultiplexSetting) ApplyWriterOpt(c *writerClientConfig) {
6781
c.useMultiplex = s.useMultiplex
82+
}
83+
84+
// WithMultiplexPoolLimit is an EXPERIMENTAL option that sets the maximum
85+
// shared multiplex pool size when instantiating the Client. If multiplexing
86+
// is not enabled, this setting is ignored. By default, the limit is a single
87+
// shared connection. This limit is applied per destination region.
88+
//
89+
// This ClientOption is EXPERIMENTAL and subject to change.
90+
func WithMultiplexPoolLimit(maxSize int) option.ClientOption {
91+
return &maxMultiplexPoolSizeSetting{maxSize: maxSize}
92+
}
93+
94+
type maxMultiplexPoolSizeSetting struct {
95+
internaloption.EmbeddableAdapter
96+
maxSize int
97+
}
98+
99+
func (s *maxMultiplexPoolSizeSetting) ApplyWriterOpt(c *writerClientConfig) {
68100
c.maxMultiplexPoolSize = s.maxSize
69101
}
70102

71-
// defaultMaxInflightRequests sets the default flow controller limit for requests for
72-
// all AppendRows connections created by this client.
103+
// WithDefaultInflightRequests is an EXPERIMENTAL ClientOption for controlling
104+
// the default limit of how many individual AppendRows write requests can
105+
// be in flight on a connection at a time. This limit is enforced on all connections
106+
// created by the instantiated Client.
73107
//
74-
// TODO: export this as part of the multiplex feature launch.
75-
func defaultMaxInflightRequests(n int) option.ClientOption {
108+
// Note: the WithMaxInflightRequests WriterOption can still be used to control
109+
// the behavior for individual ManagedStream writers when not using multiplexing.
110+
//
111+
// This ClientOption is EXPERIMENTAL and subject to change.
112+
func WithDefaultInflightRequests(n int) option.ClientOption {
76113
return &defaultInflightRequestsSetting{maxRequests: n}
77114
}
78115

@@ -85,11 +122,16 @@ func (s *defaultInflightRequestsSetting) ApplyWriterOpt(c *writerClientConfig) {
85122
c.defaultInflightRequests = s.maxRequests
86123
}
87124

88-
// defaultMaxInflightBytes sets the default flow controller limit for bytes for
89-
// all AppendRows connections created by this client.
125+
// WithDefaultInflightBytes is an EXPERIMENTAL ClientOption for controlling
126+
// the default byte limit for how many individual AppendRows write requests can
127+
// be in flight on a connection at a time. This limit is enforced on all connections
128+
// created by the instantiated Client.
129+
//
130+
// Note: the WithMaxInflightBytes WriterOption can still be used to control
131+
// the behavior for individual ManagedStream writers when not using multiplexing.
90132
//
91-
// TODO: export this as part of the multiplex feature launch.
92-
func defaultMaxInflightBytes(n int) option.ClientOption {
133+
// This ClientOption is EXPERIMENTAL and subject to change.
134+
func WithDefaultInflightBytes(n int) option.ClientOption {
93135
return &defaultInflightBytesSetting{maxBytes: n}
94136
}
95137

@@ -102,11 +144,18 @@ func (s *defaultInflightBytesSetting) ApplyWriterOpt(c *writerClientConfig) {
102144
c.defaultInflightBytes = s.maxBytes
103145
}
104146

105-
// defaultAppendRowsCallOptions sets a gax.CallOption passed when opening
106-
// the AppendRows bidi connection.
147+
// WithDefaultAppendRowsCallOption is an EXPERIMENTAL ClientOption for controlling
148+
// the gax.CallOptions passed when opening the underlying AppendRows bidi
149+
// stream connections used by this library to communicate with the BigQuery
150+
// Storage service. This option is propagated to all
151+
// connections created by the instantiated Client.
107152
//
108-
// TODO: export this as part of the multiplex feature launch.
109-
func defaultAppendRowsCallOption(o gax.CallOption) option.ClientOption {
153+
// Note: the WithAppendRowsCallOption WriterOption can still be used to control
154+
// the behavior for individual ManagedStream writers that don't participate
155+
// in multiplexing.
156+
//
157+
// This ClientOption is EXPERIMENTAL and subject to change.
158+
func WithDefaultAppendRowsCallOption(o gax.CallOption) option.ClientOption {
110159
return &defaultAppendRowsCallOptionSetting{opt: o}
111160
}
112161

@@ -152,13 +201,21 @@ func WithDestinationTable(destTable string) WriterOption {
152201
}
153202

154203
// WithMaxInflightRequests bounds the inflight appends on the write connection.
204+
//
205+
// Note: See the WithDefaultInflightRequests ClientOption for setting a default
206+
// when instantiating a client, rather than setting this limit per-writer.
207+
// This WriterOption is ignored for ManagedStreams that participate in multiplexing.
155208
func WithMaxInflightRequests(n int) WriterOption {
156209
return func(ms *ManagedStream) {
157210
ms.streamSettings.MaxInflightRequests = n
158211
}
159212
}
160213

161214
// WithMaxInflightBytes bounds the inflight append request bytes on the write connection.
215+
//
216+
// Note: See the WithDefaultInflightBytes ClientOption for setting a default
217+
// when instantiating a client, rather than setting this limit per-writer.
218+
// This WriterOption is ignored for ManagedStreams that participate in multiplexing.
162219
func WithMaxInflightBytes(n int) WriterOption {
163220
return func(ms *ManagedStream) {
164221
ms.streamSettings.MaxInflightBytes = n
@@ -191,6 +248,10 @@ func WithDataOrigin(dataOrigin string) WriterOption {
191248

192249
// WithAppendRowsCallOption is used to supply additional call options to the ManagedStream when
193250
// it opens the underlying append stream.
251+
//
252+
// Note: See the DefaultAppendRowsCallOption ClientOption for setting defaults
253+
// when instantiating a client, rather than setting this limit per-writer. This WriterOption
254+
// is ignored for ManagedStream writers that participate in multiplexing.
194255
func WithAppendRowsCallOption(o gax.CallOption) WriterOption {
195256
return func(ms *ManagedStream) {
196257
ms.streamSettings.appendCallOptions = append(ms.streamSettings.appendCallOptions, o)

bigquery/storage/managedwriter/options_test.go

+35-10
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,28 @@ func TestCustomClientOptions(t *testing.T) {
3636
want: &writerClientConfig{},
3737
},
3838
{
39-
desc: "multiplex",
39+
desc: "multiplex enable",
4040
options: []option.ClientOption{
41-
enableMultiplex(true, 4),
41+
WithMultiplexing(),
4242
},
4343
want: &writerClientConfig{
4444
useMultiplex: true,
45-
maxMultiplexPoolSize: 4,
45+
maxMultiplexPoolSize: 1,
46+
},
47+
},
48+
{
49+
desc: "multiplex max",
50+
options: []option.ClientOption{
51+
WithMultiplexPoolLimit(99),
52+
},
53+
want: &writerClientConfig{
54+
maxMultiplexPoolSize: 99,
4655
},
4756
},
4857
{
4958
desc: "default requests",
5059
options: []option.ClientOption{
51-
defaultMaxInflightRequests(42),
60+
WithDefaultInflightRequests(42),
5261
},
5362
want: &writerClientConfig{
5463
defaultInflightRequests: 42,
@@ -57,7 +66,7 @@ func TestCustomClientOptions(t *testing.T) {
5766
{
5867
desc: "default bytes",
5968
options: []option.ClientOption{
60-
defaultMaxInflightBytes(123),
69+
WithDefaultInflightBytes(123),
6170
},
6271
want: &writerClientConfig{
6372
defaultInflightBytes: 123,
@@ -66,21 +75,37 @@ func TestCustomClientOptions(t *testing.T) {
6675
{
6776
desc: "default call options",
6877
options: []option.ClientOption{
69-
defaultAppendRowsCallOption(gax.WithGRPCOptions(grpc.MaxCallSendMsgSize(1))),
78+
WithDefaultAppendRowsCallOption(gax.WithGRPCOptions(grpc.MaxCallSendMsgSize(1))),
7079
},
7180
want: &writerClientConfig{
7281
defaultAppendRowsCallOptions: []gax.CallOption{
7382
gax.WithGRPCOptions(grpc.MaxCallSendMsgSize(1)),
7483
},
7584
},
7685
},
86+
{
87+
desc: "unusual values",
88+
options: []option.ClientOption{
89+
WithMultiplexing(),
90+
WithMultiplexPoolLimit(-8),
91+
WithDefaultInflightBytes(-1),
92+
WithDefaultInflightRequests(-99),
93+
},
94+
want: &writerClientConfig{
95+
useMultiplex: true,
96+
maxMultiplexPoolSize: 1,
97+
defaultInflightRequests: 0,
98+
defaultInflightBytes: 0,
99+
},
100+
},
77101
{
78102
desc: "multiple options",
79103
options: []option.ClientOption{
80-
enableMultiplex(true, 10),
81-
defaultMaxInflightRequests(99),
82-
defaultMaxInflightBytes(12345),
83-
defaultAppendRowsCallOption(gax.WithGRPCOptions(grpc.MaxCallSendMsgSize(1))),
104+
WithMultiplexing(),
105+
WithMultiplexPoolLimit(10),
106+
WithDefaultInflightRequests(99),
107+
WithDefaultInflightBytes(12345),
108+
WithDefaultAppendRowsCallOption(gax.WithGRPCOptions(grpc.MaxCallSendMsgSize(1))),
84109
},
85110
want: &writerClientConfig{
86111
useMultiplex: true,

0 commit comments

Comments
 (0)