Skip to content

Commit 40571fa

Browse files
authored
feat(bigquery/storage/managedwriter): wire in flow controller (#4501)
* feat(bigquery/storage/managedwriter): wire in flow controller This adds a flow controller to the managed stream, guarding AppendRows. This change puts the release responsibility for the flow controller in the markDone() method of the pendingWrite. Flow controller code is already well tested via unit tests, so this doesn't add any additional tests.
1 parent b085384 commit 40571fa

File tree

6 files changed

+38
-19
lines changed

6 files changed

+38
-19
lines changed

Diff for: bigquery/storage/managedwriter/appendresult.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ func newPendingWrite(appends [][]byte, offset int64) *pendingWrite {
106106

107107
// markDone propagates finalization of an append request to associated
108108
// AppendResult references.
109-
func (pw *pendingWrite) markDone(startOffset int64, err error) {
109+
func (pw *pendingWrite) markDone(startOffset int64, err error, fc *flowController) {
110110
curOffset := startOffset
111111
for _, ar := range pw.results {
112112
if err != nil {
@@ -124,4 +124,9 @@ func (pw *pendingWrite) markDone(startOffset int64, err error) {
124124
}
125125
// Clear the reference to the request.
126126
pw.request = nil
127+
// if there's a flow controller, signal release. The only time this should be nil is when
128+
// encountering issues with flow control during enqueuing the initial request.
129+
if fc != nil {
130+
fc.release(pw.reqSize)
131+
}
127132
}

Diff for: bigquery/storage/managedwriter/appendresult_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func TestPendingWrite(t *testing.T) {
4545
if pending.request.GetOffset() != nil {
4646
t.Errorf("request should have no offset, but is present: %q", pending.request.GetOffset().GetValue())
4747
}
48-
pending.markDone(NoStreamOffset, nil)
48+
pending.markDone(NoStreamOffset, nil, nil)
4949
for k, ar := range pending.results {
5050
if ar.offset != NoStreamOffset {
5151
t.Errorf("mismatch on completed AppendResult(%d) without offset: got %d want %d", k, ar.offset, NoStreamOffset)
@@ -90,7 +90,7 @@ func TestPendingWrite(t *testing.T) {
9090
// verify completion behavior
9191
reportedOffset := int64(101)
9292
wantErr := fmt.Errorf("foo")
93-
pending.markDone(reportedOffset, wantErr)
93+
pending.markDone(reportedOffset, wantErr, nil)
9494

9595
if pending.request != nil {
9696
t.Errorf("expected request to be cleared, is present: %#v", pending.request)

Diff for: bigquery/storage/managedwriter/client.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,13 @@ func (c *Client) buildManagedStream(ctx context.Context, streamFunc streamClient
119119
streamName = resp.GetName()
120120
}
121121
ms.streamSettings.streamID = streamName
122-
// TODO(followup CLs): instantiate an appendstream client, flow controller, etc.
123122
}
124123
}
125-
124+
if ms.streamSettings != nil {
125+
ms.fc = newFlowController(ms.streamSettings.MaxInflightRequests, ms.streamSettings.MaxInflightBytes)
126+
} else {
127+
ms.fc = newFlowController(0, 0)
128+
}
126129
return ms, nil
127130
}
128131

Diff for: bigquery/storage/managedwriter/integration_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ func TestIntegration_ManagedWriter_BasicOperation(t *testing.T) {
177177
t.Errorf("failed to marshal message %d: %v", k, err)
178178
}
179179
data := [][]byte{b}
180-
results, err = ms.AppendRows(data, NoStreamOffset)
180+
results, err = ms.AppendRows(ctx, data, NoStreamOffset)
181181
if err != nil {
182182
t.Errorf("single-row append %d failed: %v", k, err)
183183
}
@@ -195,7 +195,7 @@ func TestIntegration_ManagedWriter_BasicOperation(t *testing.T) {
195195
t.Errorf("failed to marshal message %d: %v", k, err)
196196
}
197197
data := append(data, b)
198-
results, err = ms.AppendRows(data, NoStreamOffset)
198+
results, err = ms.AppendRows(ctx, data, NoStreamOffset)
199199
if err != nil {
200200
t.Errorf("grouped-row append failed: %v", err)
201201
}

Diff for: bigquery/storage/managedwriter/managed_stream.go

+18-10
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ type ManagedStream struct {
7171
schemaDescriptor *descriptorpb.DescriptorProto
7272
destinationTable string
7373
c *Client
74+
fc *flowController
7475

7576
// aspects of the stream client
7677
ctx context.Context // retained context for the stream
@@ -204,7 +205,7 @@ func (ms *ManagedStream) openWithRetry() (storagepb.BigQueryWrite_AppendRowsClie
204205
// The channel relationship with its ARC is 1:1. If we get a new ARC, create a new chan
205206
// and fire up the associated receive processor.
206207
ch := make(chan *pendingWrite)
207-
go recvProcessor(ms.ctx, arc, ch)
208+
go recvProcessor(ms.ctx, arc, ms.fc, ch)
208209
// Also, replace the sync.Once for setting up a new stream, as we need to do "special" work
209210
// for every new connection.
210211
ms.streamSetup = new(sync.Once)
@@ -293,11 +294,17 @@ func (ms *ManagedStream) Close() error {
293294
}
294295

295296
// AppendRows sends the append requests to the service, and returns one AppendResult per row.
296-
func (ms *ManagedStream) AppendRows(data [][]byte, offset int64) ([]*AppendResult, error) {
297+
func (ms *ManagedStream) AppendRows(ctx context.Context, data [][]byte, offset int64) ([]*AppendResult, error) {
297298
pw := newPendingWrite(data, offset)
299+
// check flow control
300+
if err := ms.fc.acquire(ctx, pw.reqSize); err != nil {
301+
// in this case, we didn't acquire, so don't pass the flow controller reference to avoid a release.
302+
pw.markDone(NoStreamOffset, err, nil)
303+
}
304+
// proceed to call
298305
if err := ms.append(pw); err != nil {
299-
// pending write is DOA, mark it done.
300-
pw.markDone(NoStreamOffset, err)
306+
// pending write is DOA.
307+
pw.markDone(NoStreamOffset, err, ms.fc)
301308
return nil, err
302309
}
303310
return pw.results, nil
@@ -307,7 +314,7 @@ func (ms *ManagedStream) AppendRows(data [][]byte, offset int64) ([]*AppendResul
307314
//
308315
// The receive processor only deals with a single instance of a connection/channel, and thus should never interact
309316
// with the mutex lock.
310-
func recvProcessor(ctx context.Context, arc storagepb.BigQueryWrite_AppendRowsClient, ch <-chan *pendingWrite) {
317+
func recvProcessor(ctx context.Context, arc storagepb.BigQueryWrite_AppendRowsClient, fc *flowController, ch <-chan *pendingWrite) {
311318
// TODO: We'd like to re-send requests that are in an ambiguous state due to channel errors. For now, we simply
312319
// ensure that pending writes get acknowledged with a terminal state.
313320
for {
@@ -319,7 +326,7 @@ func recvProcessor(ctx context.Context, arc storagepb.BigQueryWrite_AppendRowsCl
319326
if !ok {
320327
return
321328
}
322-
pw.markDone(NoStreamOffset, ctx.Err())
329+
pw.markDone(NoStreamOffset, ctx.Err(), fc)
323330
}
324331
case nextWrite, ok := <-ch:
325332
if !ok {
@@ -330,19 +337,20 @@ func recvProcessor(ctx context.Context, arc storagepb.BigQueryWrite_AppendRowsCl
330337
// block until we get a corresponding response or err from stream.
331338
resp, err := arc.Recv()
332339
if err != nil {
333-
nextWrite.markDone(NoStreamOffset, err)
340+
nextWrite.markDone(NoStreamOffset, err, fc)
334341
}
335342

336343
if status := resp.GetError(); status != nil {
337-
nextWrite.markDone(NoStreamOffset, grpcstatus.ErrorProto(status))
344+
fc.release(nextWrite.reqSize)
345+
nextWrite.markDone(NoStreamOffset, grpcstatus.ErrorProto(status), fc)
338346
continue
339347
}
340348
success := resp.GetAppendResult()
341349
off := success.GetOffset()
342350
if off != nil {
343-
nextWrite.markDone(off.GetValue(), nil)
351+
nextWrite.markDone(off.GetValue(), nil, fc)
344352
}
345-
nextWrite.markDone(NoStreamOffset, nil)
353+
nextWrite.markDone(NoStreamOffset, nil, fc)
346354
}
347355
}
348356
}

Diff for: bigquery/storage/managedwriter/managed_stream_test.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ func TestManagedStream_OpenWithRetry(t *testing.T) {
8787

8888
func TestManagedStream_FirstAppendBehavior(t *testing.T) {
8989

90+
ctx := context.Background()
91+
9092
var testARC *testAppendRowsClient
9193
testARC = &testAppendRowsClient{
9294
recvF: func() (*storagepb.AppendRowsResponse, error) {
@@ -104,12 +106,13 @@ func TestManagedStream_FirstAppendBehavior(t *testing.T) {
104106
}
105107

106108
ms := &ManagedStream{
107-
ctx: context.Background(),
109+
ctx: ctx,
108110
open: func() (storagepb.BigQueryWrite_AppendRowsClient, error) {
109111
testARC.openCount = testARC.openCount + 1
110112
return testARC, nil
111113
},
112114
streamSettings: defaultStreamSettings(),
115+
fc: newFlowController(0, 0),
113116
}
114117
ms.streamSettings.streamID = "FOO"
115118
ms.streamSettings.TracePrefix = "TRACE"
@@ -123,7 +126,7 @@ func TestManagedStream_FirstAppendBehavior(t *testing.T) {
123126
wantReqs := 3
124127

125128
for i := 0; i < wantReqs; i++ {
126-
_, err := ms.AppendRows(fakeData, NoStreamOffset)
129+
_, err := ms.AppendRows(ctx, fakeData, NoStreamOffset)
127130
if err != nil {
128131
t.Errorf("AppendRows; %v", err)
129132
}

0 commit comments

Comments
 (0)