-
Notifications
You must be signed in to change notification settings - Fork 9
fix(messagequeue): run queue garbage collection on busy partitions #622
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2268,6 +2268,67 @@ func (s *SQLQueueIntegrationSuite) TestIdleLeaseRelease() { | |
| t.Logf("Idle lease released and partition resurrected on new traffic") | ||
| } | ||
|
|
||
| // TestGCReclaimsAckedRowsUnderContinuousTraffic verifies that garbage | ||
| // collection reclaims acked rows on a partition that never idles. GC was gated | ||
| // on idle poll ticks, so a continuously busy partition grew its message log | ||
| // without bound; it must now run on its own tick cadence regardless of traffic. | ||
| func (s *SQLQueueIntegrationSuite) TestGCReclaimsAckedRowsUnderContinuousTraffic() { | ||
| t := s.T() | ||
|
|
||
| topic := "gc_busy_topic" | ||
| partition := "gc-busy-part" | ||
| consumerGroup := "gc-busy-cg" | ||
|
|
||
| signalCh := make(chan queueMySQL.HookSignal, 100) | ||
|
|
||
| q, err := queueMySQL.NewQueue(queueMySQL.Params{ | ||
| DB: s.db, Logger: zaptest.NewLogger(t), MetricsScope: tally.NoopScope, | ||
| OnSignal: signalCh, | ||
| }) | ||
| require.NoError(t, err) | ||
| defer q.Close() | ||
|
|
||
| // Fast poll so the 100-tick GC cadence elapses quickly; at the 100ms | ||
| // default it would take 10s of continuous traffic before reclamation. | ||
| cfg := testSubConfig("worker-gc-busy", consumerGroup) | ||
| cfg.PollIntervalMs = 50 | ||
| deliveryChan, err := q.Subscriber().Subscribe(s.ctx, topic, cfg) | ||
| require.NoError(t, err) | ||
|
|
||
| countMessages := func() int { | ||
| var n int | ||
| require.NoError(t, s.db.QueryRowContext(s.ctx, | ||
| "SELECT COUNT(*) FROM queue_messages WHERE topic = ? AND partition_key = ?", | ||
| topic, partition).Scan(&n)) | ||
| return n | ||
| } | ||
|
|
||
| // Establish an acked backlog: 200 acked rows that survive consumption | ||
| // because only GC deletes message rows. | ||
| const initialBatch = 200 | ||
| for i := 0; i < initialBatch; i++ { | ||
| require.NoError(t, q.Publisher().Publish(s.ctx, topic, | ||
| entityqueue.NewMessage(fmt.Sprintf("gc-%d", i), []byte("x"), partition, nil))) | ||
| } | ||
| receiveN(t, deliveryChan, initialBatch, func(d extqueue.Delivery, _ int) { | ||
| require.NoError(t, d.Ack(s.ctx)) | ||
| }) | ||
| require.Equal(t, initialBatch, countMessages()) | ||
|
Comment on lines
+2312
to
+2315
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This assertion assumes GC hasn't fired yet, but the change under test is what can invalidate that — the drain above is busy ticks, which now increment The subscription is live before the publishes, so the counter accrues across both the publish phase and the drain. At Asserting |
||
|
|
||
| // Continuous traffic keeps the partition busy for well over 100 poll ticks; | ||
| // GC must reclaim the acked backlog without ever observing an idle tick. | ||
| const continuousTrafficIterations = 150 | ||
| for i := 0; i < continuousTrafficIterations; i++ { | ||
| require.NoError(t, q.Publisher().Publish(s.ctx, topic, | ||
| entityqueue.NewMessage(fmt.Sprintf("gc-busy-%d", i), []byte("y"), partition, nil))) | ||
| delivery := receive(t, deliveryChan) | ||
| require.NoError(t, delivery.Ack(s.ctx)) | ||
| } | ||
|
|
||
| waitForCondition(t, signalCh, func() bool { | ||
| return countMessages() < initialBatch | ||
| }, "acked backlog should be garbage collected while the partition stays busy") | ||
| } | ||
|
|
||
| // TestNackDoesNotBlockOtherMessages verifies that nacking a message does not | ||
| // block delivery of subsequent messages in the same partition. The nacked | ||
| // message should be skipped (invisible) while later messages are delivered. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This early return now suppresses the
messages_deliveredcounter on a busy tick.Before this change GC could only run when
messageCount == 0, so thegarbage collecterror path could never skip theif messageCount > 0metrics block a few lines below. Now it can: the messages were already pushed todeliveryCh, but the counter is dropped — so throughput reads low exactly when the store is unhealthy and you most want the number.Moving the metrics block above the GC block (or folding it into the existing
defer) fixes it.