Keep one entry per AS in the AS handler's batch (closes #217)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
HandleMessage added one entry to the batch for every AS of every path, and waited for the write in progress once the batch held 300,000. In a burst of the feed its queue then filled, and the streamer dropped messages. The batch is now a map from AS number to the earliest and the latest time of its updates. It holds one entry per AS, so it no longer grows with the feed's rate. HandleMessage never waits for a write. asnBatchSize now counts ASes. Stop logs the entries of the batch being written and of the next batch under "batch_entries". An AS in both counts twice. Model: opus-5-5
This commit was merged in pull request #219.
This commit is contained in:
1 parent
38b43549b9
commit
6b004dfbc3
6 files changed
+145
-154
No files matched your search
@@ -178,6 +178,7 @@ type stopLog struct {
|
||||
Updates int `json:"updates"`
|
||||
TableChanges int `json:"table_changes"`
|
||||
Paths int `json:"paths"`
|
||||
BatchEntries int `json:"batch_entries"`
|
||||
}
|
||||
|
||||
// newErrorLogger returns a logger that writes only errors, as JSON lines, to
|
||||
@@ -495,20 +496,20 @@ func TestPrefixHandlerStopCountsTheTableChangesOfAWriteInProgress(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
// TestASHandlerSkipsItsLastWriteAfterTheStopDeadline also checks what Stop
|
||||
// logs: the asnBatchSize entries of the held write and the entry for testASNB,
|
||||
// handled since, are not written.
|
||||
func TestASHandlerSkipsItsLastWriteAfterTheStopDeadline(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Each message gives an update for its origin AS and one for each AS
|
||||
// in its path: fill asnBatchSize+1, extra 2.
|
||||
const wantUpdates = asnBatchSize + 1 + 2
|
||||
const wantEntries = asnBatchSize + 1
|
||||
|
||||
var logs bytes.Buffer
|
||||
|
||||
store := newBlockingStore()
|
||||
h := newASHandler(store, newErrorLogger(&logs), longerThanAnyTest)
|
||||
fill := ristypes.ASPath{Sequence: slices.Repeat([]int{testASNA}, asnBatchSize)}
|
||||
stopWhileAWriteIsBlocked(t, store, h, h.done,
|
||||
[]*ristypes.RISMessage{{Path: fill}},
|
||||
[]*ristypes.RISMessage{{Path: pathOfASes(asnBatchSize)}},
|
||||
&ristypes.RISMessage{Path: ristypes.ASPath{Sequence: []int{testASNB}}})
|
||||
|
||||
store.mu.Lock()
|
||||
@@ -518,26 +519,27 @@ func TestASHandlerSkipsItsLastWriteAfterTheStopDeadline(t *testing.T) {
|
||||
t.Errorf("AS%d was written after the stop deadline", testASNB)
|
||||
}
|
||||
|
||||
if logged := lastLogged(t, &logs); logged.Updates != wantUpdates {
|
||||
t.Errorf("Stop logged %d updates not written, want %d",
|
||||
logged.Updates, wantUpdates)
|
||||
if logged := lastLogged(t, &logs); logged.BatchEntries != wantEntries {
|
||||
t.Errorf("Stop logged %d batch entries not written, want %d",
|
||||
logged.BatchEntries, wantEntries)
|
||||
}
|
||||
}
|
||||
|
||||
// TestASHandlerSkipsAFullBatchAfterTheStopDeadline hands the AS handler a full
|
||||
// batch while a write is held, so the batch's wake-up of the flush goroutine
|
||||
// waits. The stop deadline passes meanwhile. When the write ends, the flush
|
||||
// goroutine must stop instead of writing the batch.
|
||||
// waits. The full batch holds the fill's ASes again, and testASNB. The stop
|
||||
// deadline passes meanwhile. When the write ends, the flush goroutine must stop
|
||||
// instead of writing the batch.
|
||||
func TestASHandlerSkipsAFullBatchAfterTheStopDeadline(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
store := newBlockingStore()
|
||||
h := newASHandler(store, logger.New(), longerThanAnyTest)
|
||||
fill := ristypes.ASPath{Sequence: slices.Repeat([]int{testASNA}, asnBatchSize)}
|
||||
full := ristypes.ASPath{Sequence: slices.Repeat([]int{testASNB}, asnBatchSize)}
|
||||
fill := &ristypes.RISMessage{Path: pathOfASes(asnBatchSize)}
|
||||
stopWhileAWriteIsBlocked(t, store, h, h.done,
|
||||
[]*ristypes.RISMessage{{Path: fill}},
|
||||
&ristypes.RISMessage{Path: full})
|
||||
[]*ristypes.RISMessage{fill},
|
||||
fill,
|
||||
&ristypes.RISMessage{Path: ristypes.ASPath{Sequence: []int{testASNB}}})
|
||||
|
||||
store.mu.Lock()
|
||||
defer store.mu.Unlock()
|
||||
@@ -703,9 +705,8 @@ func TestASHandlerHandlesMessagesWhileAWriteIsBlocked(t *testing.T) {
|
||||
|
||||
store := newBlockingStore()
|
||||
h := newASHandler(store, logger.New(), longerThanAnyTest)
|
||||
fill := ristypes.ASPath{Sequence: slices.Repeat([]int{testASNA}, asnBatchSize)}
|
||||
handleWhileAWriteIsBlocked(t, store, h,
|
||||
[]*ristypes.RISMessage{{Path: fill}},
|
||||
[]*ristypes.RISMessage{{Path: pathOfASes(asnBatchSize)}},
|
||||
&ristypes.RISMessage{Path: ristypes.ASPath{Sequence: []int{testASNB}}})
|
||||
|
||||
store.mu.Lock()
|
||||
@@ -762,25 +763,41 @@ func TestPrefixHandlerWaitsAtTheMaximumBatchSize(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestASHandlerWaitsAtTheMaximumBatchSize(t *testing.T) {
|
||||
// TestASHandlerNeverWaitsForAWrite starts a write that the store holds. Then it
|
||||
// hands the AS handler twenty messages whose paths each hold one AS
|
||||
// asnBatchSize times. HandleMessage must return each time, and the batch must
|
||||
// hold one entry, for that AS.
|
||||
func TestASHandlerNeverWaitsForAWrite(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const messages = 20
|
||||
|
||||
store := newBlockingStore()
|
||||
h := newASHandler(store, logger.New(), longerThanAnyTest)
|
||||
h.batchMaxSize = asnBatchSize
|
||||
fill := ristypes.ASPath{Sequence: slices.Repeat([]int{testASNA}, asnBatchSize)}
|
||||
toMax := ristypes.ASPath{Sequence: slices.Repeat([]int{testASNA}, asnBatchSize)}
|
||||
handleAtMaxBatchSize(t, store, h,
|
||||
[]*ristypes.RISMessage{{Path: fill}},
|
||||
[]*ristypes.RISMessage{{Path: toMax}},
|
||||
&ristypes.RISMessage{Path: ristypes.ASPath{Sequence: []int{testASNB}}})
|
||||
|
||||
store.mu.Lock()
|
||||
defer store.mu.Unlock()
|
||||
defer stopInTime(t, h)
|
||||
defer store.releaseWrites()
|
||||
|
||||
if !store.asns[testASNB] {
|
||||
t.Errorf("Stop did not write AS%d, handled after the batch reached its "+
|
||||
"maximum size", testASNB)
|
||||
startBlockedWrite(t, store, h,
|
||||
[]*ristypes.RISMessage{{Path: pathOfASes(asnBatchSize)}})
|
||||
|
||||
oneAS := &ristypes.RISMessage{
|
||||
Path: ristypes.ASPath{Sequence: slices.Repeat([]int{testASNB}, asnBatchSize)},
|
||||
}
|
||||
|
||||
if !returnsInTime(func() {
|
||||
for range messages {
|
||||
h.HandleMessage(oneAS)
|
||||
}
|
||||
}) {
|
||||
t.Fatal("HandleMessage did not return while a database write was blocked")
|
||||
}
|
||||
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
if len(h.batch) != 1 {
|
||||
t.Errorf("the batch holds %d entries for one AS, want 1", len(h.batch))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -844,13 +861,11 @@ func TestHandlersWakeTheFlushGoroutineWhenTheBatchIsFull(t *testing.T) {
|
||||
batchSize: asnBatchSize,
|
||||
handle: func(n int) chan struct{} {
|
||||
h := &ASHandler{
|
||||
batchMaxSize: asnBatchMaxSize,
|
||||
batchFullCh: make(chan struct{}, 1),
|
||||
batch: make(map[int]database.SeenTimes),
|
||||
batchFullCh: make(chan struct{}, 1),
|
||||
}
|
||||
// The origin AS and each AS in the path are one update each.
|
||||
h.HandleMessage(&ristypes.RISMessage{
|
||||
Path: ristypes.ASPath{Sequence: slices.Repeat([]int{testASNA}, n-1)},
|
||||
})
|
||||
// Each of the path's n ASes is one entry of the batch.
|
||||
h.HandleMessage(&ristypes.RISMessage{Path: pathOfASes(n)})
|
||||
|
||||
return h.batchFullCh
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user