Skip to content

Commit e0fae3c

Browse files
committed
rangefeed: move back to static (but configurable) send queue limit
The interactions here probably requires more thought before doing anything more complicated than a simple static limit. Epic: none Release note: None
1 parent 6470590 commit e0fae3c

File tree

11 files changed

+162
-184
lines changed

11 files changed

+162
-184
lines changed

pkg/kv/kvserver/kvserverbase/base.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,3 +383,16 @@ var MaxCommandSize = settings.RegisterByteSizeSetting(
383383
MaxCommandSizeDefault,
384384
settings.ByteSizeWithMinimum(MaxCommandSizeFloor),
385385
)
386+
387+
// DefaultRangefeedEventCap is the channel capacity of the rangefeed processor
388+
// and each registration. It is also used to calculate the default capacity
389+
// limit for the buffered sender.
390+
//
391+
// The size of an event is 72 bytes, so this will result in an allocation on the
392+
// order of ~300KB per RangeFeed. That's probably ok given the number of ranges
393+
// on a node that we'd like to support with active rangefeeds, but it's
394+
// certainly on the upper end of the range.
395+
//
396+
// Note that processors also must reserve memory from one of two memory monitors
397+
// for each event.
398+
const DefaultRangefeedEventCap = 4096

pkg/kv/kvserver/rangefeed/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ go_library(
3030
"//pkg/kv/kvpb",
3131
"//pkg/kv/kvserver/concurrency/isolation",
3232
"//pkg/kv/kvserver/concurrency/lock",
33+
"//pkg/kv/kvserver/kvserverbase",
3334
"//pkg/roachpb",
3435
"//pkg/settings",
3536
"//pkg/settings/cluster",

pkg/kv/kvserver/rangefeed/buffered_sender.go

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import (
1010
"time"
1111

1212
"github.com/cockroachdb/cockroach/pkg/kv/kvpb"
13+
"github.com/cockroachdb/cockroach/pkg/kv/kvserver/kvserverbase"
14+
"github.com/cockroachdb/cockroach/pkg/settings"
15+
"github.com/cockroachdb/cockroach/pkg/settings/cluster"
1316
"github.com/cockroachdb/cockroach/pkg/util/retry"
1417
"github.com/cockroachdb/cockroach/pkg/util/stop"
1518
"github.com/cockroachdb/cockroach/pkg/util/syncutil"
@@ -45,6 +48,38 @@ import (
4548
// BufferedPerRangeEventSink.Send BufferedPerRangeEventSink.SendError
4649
//
4750

51+
// RangefeedSingleBufferedSenderQueueMaxSize is the maximum number of events
52+
// that the buffered sender will buffer before it starts returning capacity
53+
// exceeded errors. Updates to this setting are only applied to need
54+
// MuxRangefeedCalls, existing streams will use the previous value until
55+
// restarted.
56+
//
57+
// # The main goal of this limit is to provide a backstop against the
58+
//
59+
// The default here has been somewhat arbitrarily chosen considering that:
60+
//
61+
// - We want to avoid capacity exceeded errors that wouldn't have occurred
62+
// when the buffered registrations were in use.
63+
//
64+
// - We don't want to drastically increase the amount of queueing allowed for a
65+
// single registration.
66+
//
67+
// - One buffered sender is feeding a single gRPC client.
68+
//
69+
// - Events emitted during catchup scans have their own per-registration buffer
70+
// still.
71+
//
72+
// TODO(ssd): This is a bit of a stop-gap so that we have a knob to turn if we
73+
// need to. We probably want each buffered sender (or each consumerID) to be
74+
// able to hold up to some fraction of the total rangefeed budget. But we are
75+
// starting here for now.
76+
var RangefeedSingleBufferedSenderQueueMaxSize = settings.RegisterIntSetting(
77+
settings.SystemOnly,
78+
"kv.rangefeed.buffered_sender.queue_max_size",
79+
"max size of a buffered senders event queue (0 for no max)",
80+
kvserverbase.DefaultRangefeedEventCap*8,
81+
)
82+
4883
// BufferedSender is embedded in every rangefeed.BufferedPerRangeEventSink,
4984
// serving as a helper which buffers events before forwarding events to the
5085
// underlying gRPC stream.
@@ -58,21 +93,7 @@ type BufferedSender struct {
5893
syncutil.Mutex
5994
stopped bool
6095
buffer *eventQueue
61-
// capacity is the maximum number of events that can be buffered. It
62-
// dynamically scales based on the number of active registrations.
63-
//
64-
// The capacity is calculated as:
65-
// - Minimum: minBufferedSenderQueueCapacity (20 registrations)
66-
// - Active streams: perUnbufferedRegCapacity * number of active
67-
// registrations
68-
//
69-
// This scaling is based on the intuition that each stream should have
70-
// equivalent buffer space to what unbuffered registrations receive (4096
71-
// events per registration). Since all registrations share the same buffered
72-
// sender, registrations may affect each other.
73-
//
74-
// Note that when capacity shrinks, events already buffered will not be
75-
// dropped. Capacity is not adjusted during shutdown.
96+
// capacity is the maximum number of events that can be buffered.
7697
capacity int64
7798
overflowed bool
7899
}
@@ -88,14 +109,8 @@ type BufferedSender struct {
88109
metrics *BufferedSenderMetrics
89110
}
90111

91-
// TODO(wenyihu6): This value is set to the same value as
92-
// https://github.com/cockroachdb/cockroach/blob/5536f0828f50bb21ec1577c77d388c4303d124a4/pkg/kv/kvserver/replica_rangefeed.go#L121.
93-
// Should I move this const to kvserverbase and import this value?
94-
const perUnbufferedRegCapacity = int64(4096)
95-
const minBufferedSenderQueueCapacity = int64(4096 * 20)
96-
97112
func NewBufferedSender(
98-
sender ServerStreamSender, bsMetrics *BufferedSenderMetrics,
113+
sender ServerStreamSender, settings *cluster.Settings, bsMetrics *BufferedSenderMetrics,
99114
) *BufferedSender {
100115
bs := &BufferedSender{
101116
sender: sender,
@@ -104,7 +119,7 @@ func NewBufferedSender(
104119
bs.queueMu.buffer = newEventQueue()
105120
bs.notifyDataC = make(chan struct{}, 1)
106121
bs.queueMu.buffer = newEventQueue()
107-
bs.queueMu.capacity = minBufferedSenderQueueCapacity
122+
bs.queueMu.capacity = RangefeedSingleBufferedSenderQueueMaxSize.Get(&settings.SV)
108123
return bs
109124
}
110125

@@ -123,7 +138,7 @@ func (bs *BufferedSender) sendBuffered(
123138
if bs.queueMu.overflowed {
124139
return newRetryErrBufferCapacityExceeded()
125140
}
126-
if bs.queueMu.buffer.len() >= bs.queueMu.capacity {
141+
if bs.queueMu.capacity > 0 && bs.queueMu.buffer.len() >= bs.queueMu.capacity {
127142
bs.queueMu.overflowed = true
128143
return newRetryErrBufferCapacityExceeded()
129144
}
@@ -208,21 +223,16 @@ func (bs *BufferedSender) cleanup(ctx context.Context) {
208223
bs.metrics.BufferedSenderQueueSize.Dec(remaining)
209224
}
210225

211-
// onStreamConnectOrDisconnect is called when a stream is added or removed. This
212-
// is currently used to dynamically adjust its queue capacity based on number of
213-
// active registrations. Note that this is not called during shutdown, so strict
214-
// dependency on this contract is discouraged. And note that we do not drop
215-
// events if the capacity is shrunk.
216-
func (bs *BufferedSender) onStreamConnectOrDisconnect(activeStreamCount int64) {
226+
func (bs *BufferedSender) len() int {
217227
bs.queueMu.Lock()
218228
defer bs.queueMu.Unlock()
219-
bs.queueMu.capacity = max(minBufferedSenderQueueCapacity, activeStreamCount*perUnbufferedRegCapacity)
229+
return int(bs.queueMu.buffer.len())
220230
}
221231

222-
func (bs *BufferedSender) len() int {
232+
func (bs *BufferedSender) overflowed() bool {
223233
bs.queueMu.Lock()
224234
defer bs.queueMu.Unlock()
225-
return int(bs.queueMu.buffer.len())
235+
return bs.queueMu.overflowed
226236
}
227237

228238
// Used for testing only.

pkg/kv/kvserver/rangefeed/buffered_sender_test.go

Lines changed: 64 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/cockroachdb/cockroach/pkg/kv/kvpb"
1515
"github.com/cockroachdb/cockroach/pkg/roachpb"
16+
"github.com/cockroachdb/cockroach/pkg/settings/cluster"
1617
"github.com/cockroachdb/cockroach/pkg/testutils"
1718
"github.com/cockroachdb/cockroach/pkg/util/hlc"
1819
"github.com/cockroachdb/cockroach/pkg/util/leaktest"
@@ -35,7 +36,8 @@ func TestBufferedSenderDisconnectStream(t *testing.T) {
3536
defer stopper.Stop(ctx)
3637
testServerStream := newTestServerStream()
3738
smMetrics := NewStreamManagerMetrics()
38-
bs := NewBufferedSender(testServerStream, NewBufferedSenderMetrics())
39+
st := cluster.MakeTestingClusterSettings()
40+
bs := NewBufferedSender(testServerStream, st, NewBufferedSenderMetrics())
3941
sm := NewStreamManager(bs, smMetrics)
4042
require.NoError(t, sm.Start(ctx, stopper))
4143
defer sm.Stop(ctx)
@@ -88,7 +90,8 @@ func TestBufferedSenderChaosWithStop(t *testing.T) {
8890
testServerStream := newTestServerStream()
8991

9092
smMetrics := NewStreamManagerMetrics()
91-
bs := NewBufferedSender(testServerStream, NewBufferedSenderMetrics())
93+
st := cluster.MakeTestingClusterSettings()
94+
bs := NewBufferedSender(testServerStream, st, NewBufferedSenderMetrics())
9295
sm := NewStreamManager(bs, smMetrics)
9396
require.NoError(t, sm.Start(ctx, stopper))
9497

@@ -176,29 +179,33 @@ func TestBufferedSenderOnOverflow(t *testing.T) {
176179
stopper := stop.NewStopper()
177180
defer stopper.Stop(ctx)
178181
testServerStream := newTestServerStream()
179-
bs := NewBufferedSender(testServerStream, NewBufferedSenderMetrics())
180-
require.Equal(t, minBufferedSenderQueueCapacity, bs.queueMu.capacity)
182+
st := cluster.MakeTestingClusterSettings()
183+
184+
queueCap := int64(24)
185+
RangefeedSingleBufferedSenderQueueMaxSize.Override(ctx, &st.SV, queueCap)
186+
bs := NewBufferedSender(testServerStream, st, NewBufferedSenderMetrics())
187+
require.Equal(t, queueCap, bs.queueMu.capacity)
181188

182189
val1 := roachpb.Value{RawBytes: []byte("val"), Timestamp: hlc.Timestamp{WallTime: 1}}
183190
ev1 := new(kvpb.RangeFeedEvent)
184191
ev1.MustSetValue(&kvpb.RangeFeedValue{Key: keyA, Value: val1})
185192
muxEv := &kvpb.MuxRangeFeedEvent{RangeFeedEvent: *ev1, RangeID: 0, StreamID: 1}
186193

187-
for i := int64(0); i < minBufferedSenderQueueCapacity; i++ {
194+
for range queueCap {
188195
require.NoError(t, bs.sendBuffered(muxEv, nil))
189196
}
190-
require.Equal(t, minBufferedSenderQueueCapacity, int64(bs.len()))
197+
require.Equal(t, queueCap, int64(bs.len()))
191198
e, success, overflowed, remains := bs.popFront()
192199
require.Equal(t, sharedMuxEvent{
193200
ev: muxEv,
194201
alloc: nil,
195202
}, e)
196203
require.True(t, success)
197204
require.False(t, overflowed)
198-
require.Equal(t, minBufferedSenderQueueCapacity-1, remains)
199-
require.Equal(t, minBufferedSenderQueueCapacity-1, int64(bs.len()))
205+
require.Equal(t, queueCap-1, remains)
206+
require.Equal(t, queueCap-1, int64(bs.len()))
200207
require.NoError(t, bs.sendBuffered(muxEv, nil))
201-
require.Equal(t, minBufferedSenderQueueCapacity, int64(bs.len()))
208+
require.Equal(t, queueCap, int64(bs.len()))
202209

203210
// Overflow now.
204211
require.Equal(t, bs.sendBuffered(muxEv, nil).Error(),
@@ -207,7 +214,6 @@ func TestBufferedSenderOnOverflow(t *testing.T) {
207214

208215
// TestBufferedSenderOnStreamShutdown tests that BufferedSender and
209216
// StreamManager handle overflow and shutdown properly.
210-
211217
func TestBufferedSenderOnStreamShutdown(t *testing.T) {
212218
defer leaktest.AfterTest(t)()
213219
defer log.Scope(t).Close(t)
@@ -217,101 +223,70 @@ func TestBufferedSenderOnStreamShutdown(t *testing.T) {
217223
defer stopper.Stop(ctx)
218224
testServerStream := newTestServerStream()
219225
smMetrics := NewStreamManagerMetrics()
220-
bs := NewBufferedSender(testServerStream, NewBufferedSenderMetrics())
221-
require.Equal(t, minBufferedSenderQueueCapacity, bs.queueMu.capacity)
226+
st := cluster.MakeTestingClusterSettings()
227+
228+
queueCap := int64(24)
229+
RangefeedSingleBufferedSenderQueueMaxSize.Override(ctx, &st.SV, queueCap)
230+
bs := NewBufferedSender(testServerStream, st, NewBufferedSenderMetrics())
231+
require.Equal(t, queueCap, bs.queueMu.capacity)
232+
222233
sm := NewStreamManager(bs, smMetrics)
223234
require.NoError(t, sm.Start(ctx, stopper))
224235
defer sm.Stop(ctx)
225236

226237
p, h, pStopper := newTestProcessor(t, withRangefeedTestType(scheduledProcessorWithBufferedSender))
227238
defer pStopper.Stop(ctx)
228239

240+
streamID := int64(42)
241+
229242
val1 := roachpb.Value{RawBytes: []byte("val"), Timestamp: hlc.Timestamp{WallTime: 1}}
230243
ev1 := new(kvpb.RangeFeedEvent)
231244
ev1.MustSetValue(&kvpb.RangeFeedValue{Key: keyA, Value: val1})
232-
muxEv := &kvpb.MuxRangeFeedEvent{RangeFeedEvent: *ev1, RangeID: 0, StreamID: 1}
245+
muxEv := &kvpb.MuxRangeFeedEvent{RangeFeedEvent: *ev1, RangeID: 0, StreamID: streamID}
233246

234-
// Add 21 streams and overflow the buffer.
235-
t.Run("add 21 streams", func(t *testing.T) {
236-
numStreams := int64(21)
237-
expectedCapacity := perUnbufferedRegCapacity * numStreams
238-
// Block the stream to help the queue to overflow.
239-
unblock := testServerStream.BlockSend()
240-
for id := int64(0); id < numStreams; id++ {
241-
registered, d, _ := p.Register(ctx, h.span, hlc.Timestamp{}, nil, /* catchUpIter */
242-
false /* withDiff */, false /* withFiltering */, false /* withOmitRemote */, noBulkDelivery,
243-
sm.NewStream(id, 1 /*rangeID*/))
244-
require.True(t, registered)
245-
sm.AddStream(id, d)
246-
}
247-
require.Equal(t, expectedCapacity, bs.queueMu.capacity)
248-
for int64(bs.len()) != expectedCapacity {
249-
require.NoError(t, sm.sender.sendBuffered(muxEv, nil))
250-
}
251-
require.Equal(t, expectedCapacity, int64(bs.len()))
252-
require.Equal(t, bs.sendBuffered(muxEv, nil).Error(),
253-
newRetryErrBufferCapacityExceeded().Error())
254-
require.True(t, bs.queueMu.overflowed)
255-
unblock()
256-
})
247+
// Block the stream so that we can overflow later.
248+
unblock := testServerStream.BlockSend()
249+
defer unblock()
257250

258-
t.Run("overflow clean up", func(t *testing.T) {
259-
// All events buffered should still be sent to the stream.
251+
waitForQueueLen := func(len int) {
260252
testutils.SucceedsSoon(t, func() error {
261-
if bs.len() == 0 {
253+
if bs.len() == len {
262254
return nil
263255
}
264-
return errors.Newf("expected 0 registrations, found %d", bs.len())
256+
return errors.Newf("expected %d events, found %d", len, bs.len())
265257
})
266-
// Overflow cleanup.
267-
err := <-sm.Error()
268-
require.Equal(t, newRetryErrBufferCapacityExceeded().Error(), err.Error())
269-
// Note that we expect the stream manager to shut down here, but no actual
270-
// error events would be sent during the shutdown.
271-
require.Equal(t, bs.sendBuffered(muxEv, nil).Error(), newRetryErrBufferCapacityExceeded().Error())
272-
})
273-
}
274-
275-
// TestBufferedSenderOnStreamDisconnect tests that BufferedSender dynamically
276-
// adjusts its capacity when streams are connected or disconnected.
277-
func TestBufferedSenderOnStreamDisconnect(t *testing.T) {
278-
defer leaktest.AfterTest(t)()
279-
defer log.Scope(t).Close(t)
280-
ctx := context.Background()
281-
282-
stopper := stop.NewStopper()
283-
defer stopper.Stop(ctx)
284-
testServerStream := newTestServerStream()
285-
smMetrics := NewStreamManagerMetrics()
286-
bs := NewBufferedSender(testServerStream, NewBufferedSenderMetrics())
287-
require.Equal(t, minBufferedSenderQueueCapacity, bs.queueMu.capacity)
288-
289-
sm := NewStreamManager(bs, smMetrics)
290-
require.NoError(t, sm.Start(ctx, stopper))
291-
292-
p, h, pStopper := newTestProcessor(t, withRangefeedTestType(scheduledProcessorWithBufferedSender))
293-
defer pStopper.Stop(ctx)
294-
defer sm.Stop(ctx)
258+
}
295259

296-
numStreams := int64(21)
297-
expectedCapacity := perUnbufferedRegCapacity * numStreams
298-
for id := int64(0); id < numStreams; id++ {
299-
registered, d, _ := p.Register(ctx, h.span, hlc.Timestamp{}, nil, /* catchUpIter */
300-
false /* withDiff */, false /* withFiltering */, false /* withOmitRemote */, noBulkDelivery,
301-
sm.NewStream(id, 1 /*rangeID*/))
302-
require.True(t, registered)
303-
sm.AddStream(id, d)
260+
// Add our stream to the stream manager.
261+
registered, d, _ := p.Register(ctx, h.span, hlc.Timestamp{}, nil, /* catchUpIter */
262+
false /* withDiff */, false /* withFiltering */, false /* withOmitRemote */, noBulkDelivery,
263+
sm.NewStream(streamID, 1 /*rangeID*/))
264+
require.True(t, registered)
265+
sm.AddStream(streamID, d)
266+
267+
require.NoError(t, sm.sender.sendBuffered(muxEv, nil))
268+
// At this point we actually have sent 2 events. 1 checkpoint event sent by
269+
// register and 1 event sent on the line above. We wait for 1 of these events
270+
// to be pulled off the queue and block in the sender, leaving 1 in the queue.
271+
waitForQueueLen(1)
272+
// Now fill the rest of the queue.
273+
for range queueCap - 1 {
274+
require.NoError(t, sm.sender.sendBuffered(muxEv, nil))
304275
}
305-
require.Equal(t, expectedCapacity, bs.queueMu.capacity)
306-
sm.DisconnectStream(int64(0), newErrBufferCapacityExceeded())
307-
testServerStream.waitForEvent(t, makeMuxRangefeedErrorEvent(0, 1, newErrBufferCapacityExceeded()))
308-
testutils.SucceedsSoon(t, func() error {
309-
bs.queueMu.Lock()
310-
defer bs.queueMu.Unlock()
311-
if bs.queueMu.capacity == expectedCapacity-perUnbufferedRegCapacity {
312-
return nil
313-
}
314-
return errors.Newf("expected %d cap to be %d", bs.queueMu.capacity,
315-
expectedCapacity-perUnbufferedRegCapacity)
316-
})
276+
277+
// The next write should overflow.
278+
capExceededErrStr := newRetryErrBufferCapacityExceeded().Error()
279+
err := sm.sender.sendBuffered(muxEv, nil)
280+
require.EqualError(t, err, capExceededErrStr)
281+
require.True(t, bs.overflowed())
282+
283+
unblock()
284+
waitForQueueLen(0)
285+
// Overflow cleanup.
286+
err = <-sm.Error()
287+
require.EqualError(t, err, capExceededErrStr)
288+
// Note that we expect the stream manager to shut down here, but no actual
289+
// error events would be sent during the shutdown.
290+
err = sm.sender.sendBuffered(muxEv, nil)
291+
require.EqualError(t, err, capExceededErrStr)
317292
}

0 commit comments

Comments
 (0)