45
45
import com .apple .foundationdb .record .provider .common .StoreTimer ;
46
46
import com .apple .foundationdb .record .provider .common .StoreTimerSnapshot ;
47
47
import com .apple .foundationdb .record .provider .foundationdb .indexing .IndexingRangeSet ;
48
- import com .apple .foundationdb .record .provider .foundationdb .synchronizedsession .SynchronizedSessionRunner ;
49
48
import com .apple .foundationdb .record .query .plan .RecordQueryPlanner ;
50
49
import com .apple .foundationdb .record .query .plan .synthetic .SyntheticRecordFromStoredRecordPlan ;
51
50
import com .apple .foundationdb .record .query .plan .synthetic .SyntheticRecordPlanner ;
52
51
import com .apple .foundationdb .subspace .Subspace ;
53
- import com .apple .foundationdb .synchronizedsession .SynchronizedSession ;
54
- import com .apple .foundationdb .synchronizedsession .SynchronizedSessionLockedException ;
55
52
import com .apple .foundationdb .tuple .ByteArrayUtil2 ;
56
53
import com .apple .foundationdb .tuple .Tuple ;
57
54
import com .google .protobuf .Message ;
81
78
import java .util .concurrent .atomic .AtomicReference ;
82
79
import java .util .function .BiFunction ;
83
80
import java .util .function .Function ;
84
- import java .util .function .Supplier ;
85
81
import java .util .stream .Collectors ;
86
82
87
83
/**
@@ -106,6 +102,7 @@ public abstract class IndexingBase {
106
102
private final long startingTimeMillis ;
107
103
private long lastTypeStampCheckMillis ;
108
104
private Map <String , IndexingMerger > indexingMergerMap = null ;
105
+ private IndexingHeartbeat heartbeat = null ; // this will stay null for index scrubbing
109
106
110
107
IndexingBase (@ Nonnull IndexingCommon common ,
111
108
@ Nonnull OnlineIndexer .IndexingPolicy policy ) {
@@ -157,28 +154,13 @@ public CompletableFuture<Void> buildIndexAsync(boolean markReadable, boolean use
157
154
KeyValueLogMessage message = KeyValueLogMessage .build ("build index online" ,
158
155
LogMessageKeys .SHOULD_MARK_READABLE , markReadable );
159
156
long startNanos = System .nanoTime ();
160
- final CompletableFuture <Void > buildIndexAsyncFuture ;
161
157
FDBDatabaseRunner runner = getRunner ();
162
- Index index = common . getPrimaryIndex ();
163
- if (runner . getTimer () != null ) {
164
- lastProgressSnapshot = StoreTimerSnapshot .from (runner . getTimer () );
158
+ final FDBStoreTimer timer = runner . getTimer ();
159
+ if ( timer != null ) {
160
+ lastProgressSnapshot = StoreTimerSnapshot .from (timer );
165
161
}
166
- if (useSyncLock ) {
167
- buildIndexAsyncFuture = runner
168
- .runAsync (context -> openRecordStore (context ).thenApply (store -> IndexingSubspaces .indexBuildLockSubspace (store , index )),
169
- common .indexLogMessageKeyValues ("IndexingBase::indexBuildLockSubspace" ))
170
- .thenCompose (lockSubspace -> runner .startSynchronizedSessionAsync (lockSubspace , common .config .getLeaseLengthMillis ()))
171
- .thenCompose (synchronizedRunner -> {
172
- message .addKeyAndValue (LogMessageKeys .SESSION_ID , synchronizedRunner .getSessionId ());
173
- return runWithSynchronizedRunnerAndEndSession (synchronizedRunner ,
174
- () -> handleStateAndDoBuildIndexAsync (markReadable , message ));
175
- });
176
- } else {
177
- message .addKeyAndValue (LogMessageKeys .SESSION_ID , "none" );
178
- common .setSynchronizedSessionRunner (null );
179
- buildIndexAsyncFuture = handleStateAndDoBuildIndexAsync (markReadable , message );
180
- }
181
- return buildIndexAsyncFuture .whenComplete ((vignore , ex ) -> {
162
+ message .addKeyAndValue (LogMessageKeys .SESSION_ID , common .getUuid ());
163
+ return handleStateAndDoBuildIndexAsync (markReadable , message ).whenComplete ((vignore , ex ) -> {
182
164
message .addKeysAndValues (indexingLogMessageKeyValues ()) // add these here to pick up state accumulated during build
183
165
.addKeysAndValues (common .indexLogMessageKeyValues ())
184
166
.addKeyAndValue (LogMessageKeys .TOTAL_MICROS , TimeUnit .NANOSECONDS .toMicros (System .nanoTime () - startNanos ));
@@ -193,36 +175,6 @@ public CompletableFuture<Void> buildIndexAsync(boolean markReadable, boolean use
193
175
});
194
176
}
195
177
196
- @ SuppressWarnings ("PMD.CloseResource" )
197
- private <T > CompletableFuture <T > runWithSynchronizedRunnerAndEndSession (
198
- @ Nonnull SynchronizedSessionRunner newSynchronizedRunner , @ Nonnull Supplier <CompletableFuture <T >> runnable ) {
199
- final SynchronizedSessionRunner currentSynchronizedRunner1 = common .getSynchronizedSessionRunner ();
200
- if (currentSynchronizedRunner1 == null ) {
201
- common .setSynchronizedSessionRunner (newSynchronizedRunner );
202
- return MoreAsyncUtil .composeWhenComplete (runnable .get (), (result , ex ) -> {
203
- final SynchronizedSessionRunner currentSynchronizedRunner2 = common .getSynchronizedSessionRunner ();
204
- if (newSynchronizedRunner .equals (currentSynchronizedRunner2 )) {
205
- common .setSynchronizedSessionRunner (null );
206
- } else {
207
- if (LOGGER .isWarnEnabled ()) {
208
- LOGGER .warn (KeyValueLogMessage .build ("synchronizedSessionRunner was modified during the run" ,
209
- LogMessageKeys .SESSION_ID , newSynchronizedRunner .getSessionId (),
210
- LogMessageKeys .INDEXER_SESSION_ID , currentSynchronizedRunner2 == null ? null : currentSynchronizedRunner2 .getSessionId ())
211
- .addKeysAndValues (common .indexLogMessageKeyValues ())
212
- .toString ());
213
- }
214
- }
215
- return newSynchronizedRunner .endSessionAsync ();
216
- }, getRunner ().getDatabase ()::mapAsyncToSyncException );
217
- } else {
218
- return newSynchronizedRunner .endSessionAsync ().thenApply (vignore -> {
219
- throw new RecordCoreException ("another synchronized session is running on the indexer" ,
220
- LogMessageKeys .SESSION_ID , newSynchronizedRunner .getSessionId (),
221
- LogMessageKeys .INDEXER_SESSION_ID , currentSynchronizedRunner1 .getSessionId ());
222
- });
223
- }
224
- }
225
-
226
178
abstract List <Object > indexingLogMessageKeyValues ();
227
179
228
180
@ Nonnull
@@ -314,7 +266,7 @@ private CompletableFuture<Void> markIndexesWriteOnly(boolean continueBuild, FDBR
314
266
@ Nonnull
315
267
public CompletableFuture <Boolean > markReadableIfBuilt () {
316
268
AtomicBoolean allReadable = new AtomicBoolean (true );
317
- return common .getNonSynchronizedRunner ().runAsync (context -> openRecordStore (context ).thenCompose (store ->
269
+ return common .getRunner ().runAsync (context -> openRecordStore (context ).thenCompose (store ->
318
270
forEachTargetIndex (index -> {
319
271
if (store .isIndexReadable (index )) {
320
272
return AsyncUtil .DONE ;
@@ -335,6 +287,7 @@ public CompletableFuture<Boolean> markReadableIfBuilt() {
335
287
).thenApply (ignore -> allReadable .get ()), common .indexLogMessageKeyValues ("IndexingBase::markReadableIfBuilt" ));
336
288
}
337
289
290
+
338
291
@ Nonnull
339
292
public CompletableFuture <Boolean > markIndexReadable (boolean markReadablePlease ) {
340
293
if (!markReadablePlease ) {
@@ -360,12 +313,16 @@ public CompletableFuture<Boolean> markIndexReadable(boolean markReadablePlease)
360
313
private CompletableFuture <Boolean > markIndexReadableSingleTarget (Index index , AtomicBoolean anythingChanged ,
361
314
AtomicReference <RuntimeException > runtimeExceptionAtomicReference ) {
362
315
// An extension function to reduce markIndexReadable's complexity
363
- return common .getNonSynchronizedRunner ().runAsync (context ->
316
+ return common .getRunner ().runAsync (context ->
364
317
common .getRecordStoreBuilder ().copyBuilder ().setContext (context ).openAsync ()
365
- .thenCompose (store ->
366
- policy .shouldAllowUniquePendingState (store ) ?
367
- store .markIndexReadableOrUniquePending (index ) :
368
- store .markIndexReadable (index ))
318
+ .thenCompose (store -> {
319
+ if (heartbeat != null ) {
320
+ heartbeat .clearHeartbeat (store , index );
321
+ }
322
+ return policy .shouldAllowUniquePendingState (store ) ?
323
+ store .markIndexReadableOrUniquePending (index ) :
324
+ store .markIndexReadable (index );
325
+ })
369
326
).handle ((changed , ex ) -> {
370
327
if (ex == null ) {
371
328
if (Boolean .TRUE .equals (changed )) {
@@ -388,6 +345,7 @@ public void enforceStampOverwrite() {
388
345
private CompletableFuture <Void > setIndexingTypeOrThrow (FDBRecordStore store , boolean continuedBuild ) {
389
346
// continuedBuild is set if this session isn't a continuation of a previous indexing
390
347
IndexBuildProto .IndexBuildIndexingStamp indexingTypeStamp = getIndexingTypeStamp (store );
348
+ heartbeat = new IndexingHeartbeat (common .getUuid (), indexingTypeStamp .getMethod ());
391
349
392
350
return forEachTargetIndex (index -> setIndexingTypeOrThrow (store , continuedBuild , index , indexingTypeStamp ));
393
351
}
@@ -428,21 +386,6 @@ private CompletableFuture<Void> setIndexingTypeOrThrow(FDBRecordStore store, boo
428
386
}
429
387
// Here: check if type conversion is allowed
430
388
if (continuedBuild && shouldAllowTypeConversionContinue (newStamp , savedStamp )) {
431
- // Special case: partly built by another indexing method, but may be continued with the current one
432
- if (savedStamp .getMethod ().equals (IndexBuildProto .IndexBuildIndexingStamp .Method .MULTI_TARGET_BY_RECORDS )) {
433
- // Here: throw an exception if there is an active multi-target session that includes this index
434
- final String otherPrimaryIndexName = savedStamp .getTargetIndex (0 );
435
- if (!otherPrimaryIndexName .equals (common .getPrimaryIndex ().getName ())) {
436
- // Note: For protection, avoid breaking an active multi-target session. This leads to a certain
437
- // inconsistency for buildIndex that is called with a false `useSyncLock` - sync lock will be
438
- // checked during a method conversion, but not during a simple "same method" continue.
439
- return throwIfSyncedLock (otherPrimaryIndexName , store , newStamp , savedStamp )
440
- .thenCompose (ignore -> {
441
- store .saveIndexingTypeStamp (index , newStamp );
442
- return AsyncUtil .DONE ;
443
- });
444
- }
445
- }
446
389
store .saveIndexingTypeStamp (index , newStamp );
447
390
return AsyncUtil .DONE ;
448
391
}
@@ -476,23 +419,6 @@ private static IndexBuildProto.IndexBuildIndexingStamp blocklessStampOf(IndexBui
476
419
.build ();
477
420
}
478
421
479
- CompletableFuture <Void > throwIfSyncedLock (String otherIndexName , FDBRecordStore store , IndexBuildProto .IndexBuildIndexingStamp newStamp , IndexBuildProto .IndexBuildIndexingStamp savedStamp ) {
480
- final Index otherIndex = store .getRecordMetaData ().getIndex (otherIndexName );
481
- final Subspace mainLockSubspace = IndexingSubspaces .indexBuildLockSubspace (store , otherIndex );
482
- return SynchronizedSession .checkActiveSessionExists (store .ensureContextActive (), mainLockSubspace )
483
- .thenApply (hasActiveSession -> {
484
- if (Boolean .TRUE .equals (hasActiveSession )) {
485
- throw new SynchronizedSessionLockedException ("Failed to takeover indexing while part of a multi-target with an existing session in progress" )
486
- .addLogInfo (LogMessageKeys .SUBSPACE , mainLockSubspace )
487
- .addLogInfo (LogMessageKeys .PRIMARY_INDEX , otherIndexName )
488
- .addLogInfo (LogMessageKeys .EXPECTED , PartlyBuiltException .stampToString (newStamp ))
489
- .addLogInfo (LogMessageKeys .ACTUAL , PartlyBuiltException .stampToString (savedStamp ));
490
- }
491
- return null ;
492
- });
493
-
494
- }
495
-
496
422
@ Nonnull
497
423
private CompletableFuture <Void > throwAsByRecordsUnlessNoRecordWasScanned (boolean noRecordScanned ,
498
424
FDBRecordStore store ,
@@ -885,21 +811,43 @@ private CompletableFuture<Boolean> hadTransactionReachedLimits(FDBRecordStore st
885
811
}
886
812
887
813
private CompletableFuture <Void > validateTypeStamp (@ Nonnull FDBRecordStore store ) {
814
+ if (shouldValidate ()) {
815
+ // check other heartbeats (if exclusive) & typestamp
816
+ final IndexBuildProto .IndexBuildIndexingStamp expectedTypeStamp = getIndexingTypeStamp (store );
817
+ return forEachTargetIndex (index -> CompletableFuture .allOf (
818
+ updateHeartbeat (true , store , index ),
819
+ store .loadIndexingTypeStampAsync (index )
820
+ .thenAccept (typeStamp -> validateTypeStamp (typeStamp , expectedTypeStamp , index ))
821
+ ));
822
+ } else {
823
+ // update only
824
+ return forEachTargetIndex (index -> updateHeartbeat (false , store , index ));
825
+ }
826
+ }
827
+
828
+ private CompletableFuture <Void > updateHeartbeat (boolean validate , FDBRecordStore store , Index index ) {
829
+ if (heartbeat != null ) {
830
+ if (validate ) {
831
+ return heartbeat .checkAndUpdateHeartbeat (store , index );
832
+ }
833
+ heartbeat .updateHeartbeat (store , index );
834
+ }
835
+ return AsyncUtil .DONE ;
836
+ }
837
+
838
+ private boolean shouldValidate () {
888
839
final long minimalInterval = policy .getCheckIndexingMethodFrequencyMilliseconds ();
889
840
if (minimalInterval < 0 || isScrubber ) {
890
- return AsyncUtil . DONE ;
841
+ return false ;
891
842
}
892
843
if (minimalInterval > 0 ) {
893
844
final long now = System .currentTimeMillis ();
894
845
if (now < lastTypeStampCheckMillis + minimalInterval ) {
895
- return AsyncUtil . DONE ;
846
+ return false ;
896
847
}
897
848
lastTypeStampCheckMillis = now ;
898
849
}
899
- final IndexBuildProto .IndexBuildIndexingStamp expectedTypeStamp = getIndexingTypeStamp (store );
900
- return forEachTargetIndex (index ->
901
- store .loadIndexingTypeStampAsync (index )
902
- .thenAccept (typeStamp -> validateTypeStamp (typeStamp , expectedTypeStamp , index )));
850
+ return true ;
903
851
}
904
852
905
853
private void validateTypeStamp (final IndexBuildProto .IndexBuildIndexingStamp typeStamp ,
0 commit comments