Skip to content

Commit f5b3efd

Browse files
authored
Define sliding window counters and timing events (#4278)
This PR instruments the sliding-window index maintainer so its operational behavior is observable through `FDBStoreTimer`. A new `SlidingWindowCounter` `enum` partitions every update path — inserts during window fill-up, overflow appends, boundary evictions, deletes split by where the entry sat (overflow, in-window non-boundary, boundary itself), promotions from overflow after a window delete, window shrinkage when no overflow is available, partition emptying, the preemptive delete on `updateWhileWriteOnly`, and `deleteWhere` partition clears. Two integrity counters (`EVICTED_RECORD_MISSING`, `PROMOTED_RECORD_MISSING`) cover the cases where a primary key in the entries subspace fails to resolve to a record, which should never happen in normal operation but is worth surfacing as a queryable signal rather than only as a thrown exception. A companion `SlidingWindowEvent` `enum` adds latency tracking around the three multi-step async operations that dominate write cost: the full evict-and-replace round-trip, re-election from overflow after a window delete, and the boundary rescan that runs inside both. Counters and events are accessed through small `incrementCounter` and `instrument` helpers that no-op when the context has no timer attached, so this change is invisible to callers that don't opt in.
1 parent 18f20c3 commit f5b3efd

4 files changed

Lines changed: 646 additions & 59 deletions

File tree

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/indexes/SlidingWindowIndexMaintainer.java

Lines changed: 144 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,11 @@
4141
import com.apple.foundationdb.record.metadata.Key;
4242
import com.apple.foundationdb.record.metadata.MetaDataException;
4343
import com.apple.foundationdb.record.metadata.expressions.KeyExpression;
44+
import com.apple.foundationdb.record.provider.common.StoreTimer;
4445
import com.apple.foundationdb.record.provider.foundationdb.FDBIndexableRecord;
4546
import com.apple.foundationdb.record.provider.foundationdb.FDBIndexedRawRecord;
4647
import com.apple.foundationdb.record.provider.foundationdb.FDBRecord;
48+
import com.apple.foundationdb.record.provider.foundationdb.FDBStoreTimer;
4749
import com.apple.foundationdb.record.provider.foundationdb.IndexMaintainer;
4850
import com.apple.foundationdb.record.provider.foundationdb.IndexMaintainerState;
4951
import com.apple.foundationdb.record.provider.foundationdb.IndexMaintenanceFilter;
@@ -410,6 +412,9 @@ public <M extends Message> CompletableFuture<Void> updateWhileWriteOnly(@Nullabl
410412
//
411413
// The net effect is that after this method completes, newRecord is indexed exactly
412414
// once with its current values, and the counter accurately reflects the window size.
415+
if (newRecord != null) {
416+
incrementCounter(SlidingWindowCounter.SW_PREEMPTIVE_DELETE_WRITE_ONLY);
417+
}
413418
update(newRecord, null);
414419
return update(oldRecord, newRecord);
415420
}
@@ -452,10 +457,13 @@ private <M extends Message> CompletableFuture<Void> handleInsert(@Nonnull final
452457
final long count = counterBytes == null ? 0L : decodeLong(counterBytes);
453458

454459
if (count < windowSize) {
460+
incrementCounter(SlidingWindowCounter.SW_ITEM_ADDED_TO_WINDOW_FILLING);
455461
// Window not full: add to delegate, update count, maybe update boundary
456-
return delegate.update(null, savedRecord).thenCompose(vignore ->
462+
return instrument(SlidingWindowEvent.SW_DELEGATE_INSERT,
463+
delegate.update(null, savedRecord)).thenCompose(vignore ->
457464
tr.get(boundaryMetaKey).thenAccept(boundaryBytes -> {
458465
tr.set(counterKey, encodeLong(count + 1));
466+
recordSize(SlidingWindowSizeEvent.SW_WINDOW_COUNT, count + 1);
459467
if (boundaryBytes == null || extremumType.isWorseOrEqual(entryKey,
460468
Tuple.fromBytes(boundaryBytes))) {
461469
tr.set(boundaryMetaKey, entryKey.pack());
@@ -470,16 +478,17 @@ private <M extends Message> CompletableFuture<Void> handleInsert(@Nonnull final
470478
.addLogInfo(LogMessageKeys.INDEX_NAME, state.index.getName());
471479
}
472480
final Tuple boundaryEntryKey = Tuple.fromBytes(boundaryBytes);
473-
474481
if (!extremumType.isBetter(entryKey, boundaryEntryKey)) {
482+
incrementCounter(SlidingWindowCounter.SW_ITEM_ADDED_TO_ENTRIES_ONLY);
475483
// New entry is not better than boundary: it's already written to entries
476484
// subspace on the overflow side. Nothing more to do.
477485
return AsyncUtil.DONE;
478486
}
479487

480488
// New entry is better: evict boundary from delegate, add new to delegate
481-
return evictBoundaryAndReplace(savedRecord, entryKey, entriesSubspace, tr,
482-
boundaryEntryKey, boundaryMetaKey);
489+
return instrument(SlidingWindowEvent.SW_EVICT_AND_REPLACE,
490+
evictBoundaryAndReplace(savedRecord, entryKey, entriesSubspace, tr,
491+
boundaryEntryKey, boundaryMetaKey));
483492
});
484493
}
485494
});
@@ -506,6 +515,7 @@ private <M extends Message> CompletableFuture<Void> handleDelete(@Nonnull final
506515
// Check if this entry exists in the entries subspace
507516
return tr.get(packedEntryKey).thenCompose(entryValue -> {
508517
if (entryValue == null) {
518+
incrementCounter(SlidingWindowCounter.SW_DELETE_UNTRACKED);
509519
// Not tracked, no-op
510520
return AsyncUtil.DONE;
511521
}
@@ -524,6 +534,7 @@ private <M extends Message> CompletableFuture<Void> handleDelete(@Nonnull final
524534
final Tuple boundaryEntryKey = Tuple.fromBytes(boundaryBytes);
525535

526536
if (!extremumType.isInWindow(entryKey, boundaryEntryKey)) {
537+
incrementCounter(SlidingWindowCounter.SW_OVERFLOW_ENTRY_DELETED);
527538
// Entry was in overflow: already removed from entries, nothing else to do
528539
return AsyncUtil.DONE;
529540
}
@@ -533,14 +544,17 @@ private <M extends Message> CompletableFuture<Void> handleDelete(@Nonnull final
533544
final long count = counterBytes == null ? 0L : decodeLong(counterBytes);
534545
final long newCount = Math.max(0, count - 1);
535546
tr.set(counterKey, encodeLong(newCount));
547+
recordSize(SlidingWindowSizeEvent.SW_WINDOW_COUNT, newCount);
536548

537-
return delegate.update(savedRecord, null)
549+
return instrument(SlidingWindowEvent.SW_DELEGATE_DELETE,
550+
delegate.update(savedRecord, null))
538551
.thenCompose(vignore -> updateBoundaryAfterDelete(
539552
entriesSubspace, tr, entryKey, boundaryEntryKey,
540553
boundaryMetaKey, packedEntryKey))
541-
.thenCompose(currentBoundaryPacked -> reElectFromOverflow(
542-
entriesSubspace, tr, currentBoundaryPacked,
543-
boundaryMetaKey, counterKey, newCount));
554+
.thenCompose(currentBoundaryPacked -> instrument(
555+
SlidingWindowEvent.SW_RE_ELECT_FROM_OVERFLOW,
556+
reElectFromOverflow(entriesSubspace, tr, currentBoundaryPacked,
557+
boundaryMetaKey, counterKey, newCount)));
544558
});
545559
});
546560
});
@@ -564,11 +578,19 @@ private <M extends Message> CompletableFuture<Void> evictBoundaryAndReplace(
564578
final byte[] oldBoundaryPackedKey = entriesSubspace.pack(boundaryEntryKey);
565579

566580
return state.store.loadRecordAsync(boundaryPrimaryKey)
567-
.thenCompose(evictedRecord -> evictedRecord != null
568-
? delegate.update(evictedRecord, null) : AsyncUtil.DONE)
569-
.thenCompose(v -> delegate.update(null, newRecord))
570-
.thenCompose(v -> extremumType.getNewBoundaryAfterEviction(entriesSubspace, tr,
571-
oldBoundaryPackedKey))
581+
.thenCompose(evictedRecord -> {
582+
if (evictedRecord == null) {
583+
incrementCounter(SlidingWindowCounter.SW_EVICTED_RECORD_MISSING);
584+
return AsyncUtil.DONE;
585+
}
586+
return instrument(SlidingWindowEvent.SW_DELEGATE_DELETE,
587+
delegate.update(evictedRecord, null));
588+
})
589+
.thenCompose(v -> instrument(SlidingWindowEvent.SW_DELEGATE_INSERT,
590+
delegate.update(null, newRecord)))
591+
.thenCompose(v -> instrument(SlidingWindowEvent.SW_BOUNDARY_RESCAN_AFTER_EVICT,
592+
extremumType.getNewBoundaryAfterEviction(entriesSubspace, tr,
593+
oldBoundaryPackedKey)))
572594
.thenAccept(newBoundaryKV -> {
573595
if (newBoundaryKV != null) {
574596
final Tuple newBoundaryKey = entriesSubspace.unpack(newBoundaryKV.getKey());
@@ -596,15 +618,18 @@ private CompletableFuture<byte[]> updateBoundaryAfterDelete(
596618
@Nonnull byte[] boundaryMetaKey,
597619
@Nonnull byte[] packedEntryKey) {
598620
if (!entryKey.equals(boundaryEntryKey)) {
621+
incrementCounter(SlidingWindowCounter.SW_WINDOW_ENTRY_DELETED);
599622
return CompletableFuture.completedFuture(entriesSubspace.pack(boundaryEntryKey));
600623
}
601-
return extremumType.getNewBoundaryAfterEviction(entriesSubspace, tr, packedEntryKey)
624+
return instrument(SlidingWindowEvent.SW_BOUNDARY_RESCAN_AFTER_DELETE,
625+
extremumType.getNewBoundaryAfterEviction(entriesSubspace, tr, packedEntryKey))
602626
.thenApply(newBoundaryKV -> {
603627
if (newBoundaryKV != null) {
604628
final Tuple newBKey = entriesSubspace.unpack(newBoundaryKV.getKey());
605629
tr.set(boundaryMetaKey, newBKey.pack());
606630
return newBoundaryKV.getKey();
607631
} else {
632+
incrementCounter(SlidingWindowCounter.SW_PARTITION_EMPTIED);
608633
tr.clear(boundaryMetaKey);
609634
return null;
610635
}
@@ -629,15 +654,24 @@ private CompletableFuture<Void> reElectFromOverflow(
629654
return extremumType.getBestInOverflow(entriesSubspace, tr, currentBoundaryPacked)
630655
.thenCompose(bestKV -> {
631656
if (bestKV == null) {
657+
incrementCounter(SlidingWindowCounter.SW_WINDOW_SHRUNK_NO_OVERFLOW);
632658
return AsyncUtil.DONE;
633659
}
660+
incrementCounter(SlidingWindowCounter.SW_ITEM_PROMOTED_FROM_OVERFLOW);
634661
final Tuple bestEntryKey = entriesSubspace.unpack(bestKV.getKey());
635662
final Tuple bestPrimaryKey = Tuple.fromBytes(bestKV.getValue());
636663
tr.set(boundaryMetaKey, bestEntryKey.pack());
637664
tr.set(counterKey, encodeLong(newCount + 1));
665+
recordSize(SlidingWindowSizeEvent.SW_WINDOW_COUNT, newCount + 1);
638666
return state.store.loadRecordAsync(bestPrimaryKey)
639-
.thenCompose(promotedRecord -> promotedRecord != null
640-
? delegate.update(null, promotedRecord) : AsyncUtil.DONE);
667+
.thenCompose(promotedRecord -> {
668+
if (promotedRecord == null) {
669+
incrementCounter(SlidingWindowCounter.SW_PROMOTED_RECORD_MISSING);
670+
return AsyncUtil.DONE;
671+
}
672+
return instrument(SlidingWindowEvent.SW_DELEGATE_INSERT,
673+
delegate.update(null, promotedRecord));
674+
});
641675
});
642676
}
643677

@@ -648,6 +682,7 @@ public CompletableFuture<Void> deleteWhere(@Nonnull Transaction tr, @Nonnull Tup
648682
Verify.verify(partitionKeyColumnSize >= prefix.size(),
649683
"deleteWhere prefix size %s exceeds partition key column size %s",
650684
prefix.size(), partitionKeyColumnSize);
685+
incrementCounter(SlidingWindowCounter.SW_PARTITION_CLEARED);
651686
final byte[] key = getSlidingWindowSubspace().pack(prefix);
652687
Range indexRange = new Range(key, ByteArrayUtil.strinc(key));
653688
state.context.clear(indexRange);
@@ -661,4 +696,97 @@ private static byte[] encodeLong(long value) {
661696
private static long decodeLong(byte[] bytes) {
662697
return Tuple.fromBytes(bytes).getLong(0);
663698
}
699+
700+
private void incrementCounter(@Nonnull SlidingWindowCounter counter) {
701+
final FDBStoreTimer timer = state.context.getTimer();
702+
if (timer != null) {
703+
timer.increment(counter);
704+
}
705+
}
706+
707+
private void recordSize(@Nonnull SlidingWindowSizeEvent event, long size) {
708+
final FDBStoreTimer timer = state.context.getTimer();
709+
if (timer != null) {
710+
timer.recordSize(event, size);
711+
}
712+
}
713+
714+
@Nonnull
715+
private <T> CompletableFuture<T> instrument(@Nonnull final SlidingWindowEvent event,
716+
@Nonnull final CompletableFuture<T> future) {
717+
final FDBStoreTimer timer = state.context.getTimer();
718+
if (timer == null) {
719+
return future;
720+
}
721+
return timer.instrument(event, future);
722+
}
723+
724+
public enum SlidingWindowCounter implements StoreTimer.Count {
725+
SW_ITEM_ADDED_TO_WINDOW_FILLING("item added to window while filling up"),
726+
SW_ITEM_ADDED_TO_ENTRIES_ONLY("item worse than boundary added to entries"),
727+
SW_DELETE_UNTRACKED("delete called for untracked record"),
728+
SW_OVERFLOW_ENTRY_DELETED("overflow entry deleted from entries"),
729+
SW_WINDOW_ENTRY_DELETED("in-window non-boundary entry deleted"),
730+
SW_ITEM_PROMOTED_FROM_OVERFLOW("item promoted from overflow into window"),
731+
SW_WINDOW_SHRUNK_NO_OVERFLOW("window shrunk: no overflow available for re-election"),
732+
SW_PARTITION_EMPTIED("partition emptied (no entries remain)"),
733+
SW_EVICTED_RECORD_MISSING("boundary record could not be loaded for eviction"),
734+
SW_PROMOTED_RECORD_MISSING("overflow record could not be loaded for promotion"),
735+
SW_PREEMPTIVE_DELETE_WRITE_ONLY("preemptive delete during write-only index build"),
736+
SW_PARTITION_CLEARED("partition cleared via deleteWhere");
737+
738+
@Nonnull
739+
private final String title;
740+
741+
SlidingWindowCounter(@Nonnull final String title) {
742+
this.title = title;
743+
}
744+
745+
@Override
746+
public boolean isSize() {
747+
return false;
748+
}
749+
750+
@Override
751+
public String title() {
752+
return title;
753+
}
754+
}
755+
756+
public enum SlidingWindowEvent implements StoreTimer.DetailEvent {
757+
SW_EVICT_AND_REPLACE("evict boundary and insert better entry"),
758+
SW_RE_ELECT_FROM_OVERFLOW("re-elect overflow entry into window"),
759+
SW_BOUNDARY_RESCAN_AFTER_EVICT("rescan to locate new boundary after eviction"),
760+
SW_BOUNDARY_RESCAN_AFTER_DELETE("rescan to locate new boundary after boundary delete"),
761+
SW_DELEGATE_INSERT("insert into the delegate index"),
762+
SW_DELEGATE_DELETE("delete from the delegate index");
763+
764+
@Nonnull
765+
private final String title;
766+
767+
SlidingWindowEvent(@Nonnull final String title) {
768+
this.title = title;
769+
}
770+
771+
@Override
772+
public String title() {
773+
return title;
774+
}
775+
}
776+
777+
public enum SlidingWindowSizeEvent implements StoreTimer.SizeEvent {
778+
SW_WINDOW_COUNT("window count after update");
779+
780+
@Nonnull
781+
private final String title;
782+
783+
SlidingWindowSizeEvent(@Nonnull final String title) {
784+
this.title = title;
785+
}
786+
787+
@Override
788+
public String title() {
789+
return title;
790+
}
791+
}
664792
}

0 commit comments

Comments
 (0)