4141import com .apple .foundationdb .record .metadata .Key ;
4242import com .apple .foundationdb .record .metadata .MetaDataException ;
4343import com .apple .foundationdb .record .metadata .expressions .KeyExpression ;
44+ import com .apple .foundationdb .record .provider .common .StoreTimer ;
4445import com .apple .foundationdb .record .provider .foundationdb .FDBIndexableRecord ;
4546import com .apple .foundationdb .record .provider .foundationdb .FDBIndexedRawRecord ;
4647import com .apple .foundationdb .record .provider .foundationdb .FDBRecord ;
48+ import com .apple .foundationdb .record .provider .foundationdb .FDBStoreTimer ;
4749import com .apple .foundationdb .record .provider .foundationdb .IndexMaintainer ;
4850import com .apple .foundationdb .record .provider .foundationdb .IndexMaintainerState ;
4951import 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