All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.kernel.impl.api.index.IndexingService Maven / Gradle / Ivy

Go to download

Neo4j kernel is a lightweight, embedded Java database designed to store data structured as graphs rather than tables. For more information, see http://neo4j.org.

There is a newer version: 5.25.1
Show newest version
/*
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [http://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.kernel.impl.api.index;

import org.eclipse.collections.api.LongIterable;
import org.eclipse.collections.api.block.procedure.primitive.LongObjectProcedure;
import org.eclipse.collections.api.map.primitive.MutableLongObjectMap;
import org.eclipse.collections.api.set.primitive.LongSet;
import org.eclipse.collections.api.set.primitive.MutableLongSet;
import org.eclipse.collections.impl.map.mutable.primitive.LongObjectHashMap;
import org.eclipse.collections.impl.set.mutable.primitive.LongHashSet;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;

import org.neo4j.common.EntityType;
import org.neo4j.common.Subject;
import org.neo4j.common.TokenNameLookup;
import org.neo4j.configuration.Config;
import org.neo4j.dbms.database.readonly.DatabaseReadOnlyChecker;
import org.neo4j.exceptions.KernelException;
import org.neo4j.exceptions.UnderlyingStorageException;
import org.neo4j.function.ThrowingConsumer;
import org.neo4j.graphdb.ResourceIterator;
import org.neo4j.internal.helpers.collection.Iterators;
import org.neo4j.internal.kernel.api.IndexMonitor;
import org.neo4j.internal.kernel.api.InternalIndexState;
import org.neo4j.internal.kernel.api.exceptions.schema.IndexNotFoundKernelException;
import org.neo4j.internal.schema.IndexDescriptor;
import org.neo4j.internal.schema.IndexPrototype;
import org.neo4j.internal.schema.IndexProviderDescriptor;
import org.neo4j.internal.schema.IndexType;
import org.neo4j.internal.schema.SchemaState;
import org.neo4j.io.pagecache.context.CursorContext;
import org.neo4j.io.pagecache.tracing.PageCacheTracer;
import org.neo4j.kernel.api.exceptions.index.IndexActivationFailedKernelException;
import org.neo4j.kernel.api.exceptions.index.IndexEntryConflictException;
import org.neo4j.kernel.api.exceptions.index.IndexPopulationFailedKernelException;
import org.neo4j.kernel.api.exceptions.schema.UniquePropertyValueValidationException;
import org.neo4j.kernel.api.index.IndexPopulator;
import org.neo4j.kernel.api.index.IndexProvider;
import org.neo4j.kernel.api.index.IndexUpdater;
import org.neo4j.kernel.impl.api.index.sampling.IndexSamplingController;
import org.neo4j.kernel.impl.api.index.stats.IndexStatisticsStore;
import org.neo4j.kernel.impl.transaction.state.storeview.IndexStoreViewFactory;
import org.neo4j.kernel.lifecycle.LifecycleAdapter;
import org.neo4j.logging.Log;
import org.neo4j.logging.LogProvider;
import org.neo4j.memory.MemoryTracker;
import org.neo4j.scheduler.JobScheduler;
import org.neo4j.storageengine.api.IndexEntryUpdate;
import org.neo4j.storageengine.api.IndexUpdateListener;
import org.neo4j.storageengine.api.NodePropertyAccessor;
import org.neo4j.util.Preconditions;
import org.neo4j.values.storable.Value;

import static java.lang.String.format;
import static org.neo4j.common.EntityType.NODE;
import static org.neo4j.common.EntityType.RELATIONSHIP;
import static org.neo4j.common.Subject.SYSTEM;
import static org.neo4j.internal.helpers.collection.Iterables.asList;
import static org.neo4j.internal.helpers.collection.Iterators.asResourceIterator;
import static org.neo4j.internal.helpers.collection.Iterators.iterator;
import static org.neo4j.internal.kernel.api.InternalIndexState.FAILED;
import static org.neo4j.internal.kernel.api.InternalIndexState.ONLINE;
import static org.neo4j.internal.kernel.api.InternalIndexState.POPULATING;
import static org.neo4j.kernel.impl.api.index.IndexPopulationFailure.failure;

/**
 * Manages neo4j indexes. Each index has an {@link IndexDescriptor}, which it uses to filter
 * changes that come into the database. Changes that apply to the the rule are indexed. This way, "normal" changes to
 * the database can be replayed to perform recovery after a crash.
 * 

*

Recovery procedure

*

* Each index has a state, as defined in {@link InternalIndexState}, which is used during * recovery. If an index is anything but {@link InternalIndexState#ONLINE}, it will simply be * destroyed and re-created. *

* If, however, it is {@link InternalIndexState#ONLINE}, the index provider is required to * also guarantee that the index had been flushed to disk. */ public class IndexingService extends LifecycleAdapter implements IndexUpdateListener, IndexingProvidersService { private static final String INDEX_SERVICE_INDEX_CLOSING_TAG = "indexServiceIndexClosing"; private final IndexSamplingController samplingController; private final IndexProxyCreator indexProxyCreator; private final IndexProviderMap providerMap; private final IndexMapReference indexMapRef; private final Iterable indexDescriptors; private final Log internalLog; private final Log userLog; private final IndexStatisticsStore indexStatisticsStore; private final PageCacheTracer pageCacheTracer; private final MemoryTracker memoryTracker; private final String databaseName; private final DatabaseReadOnlyChecker readOnlyChecker; private final Config config; private final TokenNameLookup tokenNameLookup; private final JobScheduler jobScheduler; private final LogProvider internalLogProvider; private final IndexMonitor monitor; private final SchemaState schemaState; private final IndexPopulationJobController populationJobController; private static final String INIT_TAG = "Initialize IndexingService"; private final IndexStoreView storeView; enum State { NOT_STARTED, STARTING, RUNNING, STOPPED } private volatile State state = State.NOT_STARTED; IndexingService( IndexProxyCreator indexProxyCreator, IndexProviderMap providerMap, IndexMapReference indexMapRef, IndexStoreViewFactory indexStoreViewFactory, Iterable indexDescriptors, IndexSamplingController samplingController, TokenNameLookup tokenNameLookup, JobScheduler scheduler, SchemaState schemaState, LogProvider internalLogProvider, LogProvider userLogProvider, IndexMonitor monitor, IndexStatisticsStore indexStatisticsStore, PageCacheTracer pageCacheTracer, MemoryTracker memoryTracker, String databaseName, DatabaseReadOnlyChecker readOnlyChecker, Config config ) { this.indexProxyCreator = indexProxyCreator; this.providerMap = providerMap; this.indexMapRef = indexMapRef; this.indexDescriptors = indexDescriptors; this.samplingController = samplingController; this.tokenNameLookup = tokenNameLookup; this.jobScheduler = scheduler; this.schemaState = schemaState; this.internalLogProvider = internalLogProvider; this.monitor = monitor; this.populationJobController = new IndexPopulationJobController( scheduler ); this.internalLog = internalLogProvider.getLog( getClass() ); this.userLog = userLogProvider.getLog( getClass() ); this.indexStatisticsStore = indexStatisticsStore; this.pageCacheTracer = pageCacheTracer; this.memoryTracker = memoryTracker; this.databaseName = databaseName; this.readOnlyChecker = readOnlyChecker; this.config = config; this.storeView = indexStoreViewFactory.createTokenIndexStoreView( descriptor -> indexMapRef.getIndexProxy( descriptor.getId() ) ); } /** * Called while the database starts up, before recovery. */ @Override public void init() throws IOException { validateDefaultProviderExisting(); try ( var cursorContext = new CursorContext( pageCacheTracer.createPageCursorTracer( INIT_TAG ) ) ) { indexMapRef.modify( indexMap -> { Map> indexStates = new EnumMap<>( InternalIndexState.class ); for ( IndexDescriptor descriptor : indexDescriptors ) { // No index (except NLI) is allowed to have the name generated for NLI. if ( descriptor.getName().equals( IndexDescriptor.NLI_GENERATED_NAME ) && !(descriptor.schema().isAnyTokenSchemaDescriptor() && descriptor.schema().entityType() == NODE) ) { throw new IllegalStateException( "Index '" + descriptor.userDescription( tokenNameLookup ) + "' is using a reserved name: '" + IndexDescriptor.NLI_GENERATED_NAME + "'. This index must be removed on an earlier version " + "to be able to use binaries for version 4.3 or newer." ); } IndexProxy indexProxy; IndexProviderDescriptor providerDescriptor = descriptor.getIndexProvider(); IndexProvider provider = providerMap.lookup( providerDescriptor ); InternalIndexState initialState = provider.getInitialState( descriptor, cursorContext ); indexStates.computeIfAbsent( initialState, internalIndexState -> new ArrayList<>() ).add( new IndexLogRecord( descriptor ) ); internalLog.debug( indexStateInfo( "init", initialState, descriptor ) ); switch ( initialState ) { case ONLINE: monitor.initialState(databaseName, descriptor, ONLINE ); indexProxy = indexProxyCreator.createOnlineIndexProxy( descriptor ); break; case POPULATING: // The database was shut down during population, or a crash has occurred, or some other sad thing. monitor.initialState( databaseName, descriptor, POPULATING ); indexProxy = indexProxyCreator.createRecoveringIndexProxy( descriptor ); break; case FAILED: monitor.initialState( databaseName, descriptor, FAILED ); IndexPopulationFailure failure = failure( provider.getPopulationFailure( descriptor, cursorContext ) ); indexProxy = indexProxyCreator.createFailedIndexProxy( descriptor, failure ); break; default: throw new IllegalArgumentException( "" + initialState ); } indexMap.putIndexProxy( indexProxy ); } logIndexStateSummary( "init", indexStates ); return indexMap; } ); } indexStatisticsStore.init(); } private void validateDefaultProviderExisting() { if ( providerMap == null || providerMap.getDefaultProvider() == null ) { throw new IllegalStateException( "You cannot run the database without an index provider, " + "please make sure that a valid provider (subclass of " + IndexProvider.class.getName() + ") is on your classpath." ); } } // Recovery semantics: This is to be called after init, and after the database has run recovery. @Override public void start() throws Exception { state = State.STARTING; // Recovery will not do refresh (update read views) while applying recovered transactions and instead // do it at one point after recovery... i.e. here indexMapRef.indexMapSnapshot().forEachIndexProxy( indexProxyOperation( "refresh", IndexProxy::refresh ) ); final MutableLongObjectMap rebuildingDescriptors = new LongObjectHashMap<>(); indexMapRef.modify( indexMap -> { Map> indexStates = new EnumMap<>( InternalIndexState.class ); // Find all indexes that are not already online, do not require rebuilding, and create them indexMap.forEachIndexProxy( ( indexId, proxy ) -> { InternalIndexState state = proxy.getState(); IndexDescriptor descriptor = proxy.getDescriptor(); IndexLogRecord indexLogRecord = new IndexLogRecord( descriptor ); indexStates.computeIfAbsent( state, internalIndexState -> new ArrayList<>() ) .add( indexLogRecord ); internalLog.debug( indexStateInfo( "start", state, descriptor ) ); switch ( state ) { case ONLINE: case FAILED: proxy.start(); break; case POPULATING: // Remember for rebuilding right below in this method rebuildingDescriptors.put( indexId, descriptor ); break; default: throw new IllegalStateException( "Unknown state: " + state ); } } ); logIndexStateSummary( "start", indexStates ); dontRebuildIndexesInReadOnlyMode( rebuildingDescriptors ); // Drop placeholder proxies for indexes that need to be rebuilt dropRecoveringIndexes( indexMap, rebuildingDescriptors.keySet() ); // Rebuild indexes by recreating and repopulating them populateIndexesOfAllTypes( rebuildingDescriptors, indexMap ); return indexMap; } ); indexStatisticsStore.start(); samplingController.recoverIndexSamples(); samplingController.start(); // So at this point we've started population of indexes that needs to be rebuilt in the background. // Indexes backing uniqueness constraints are normally built within the transaction creating the constraint // and so we shouldn't leave such indexes in a populating state after recovery. // This is why we now go and wait for those indexes to be fully populated. rebuildingDescriptors.forEachKeyValue( ( indexId, index ) -> { if ( !index.isUnique() ) { // It's not a uniqueness constraint, so don't wait for it to be rebuilt return; } IndexProxy proxy; try { proxy = getIndexProxy( index ); } catch ( IndexNotFoundKernelException e ) { throw new IllegalStateException( "What? This index was seen during recovery just now, why isn't it available now?", e ); } if ( proxy.getDescriptor().getOwningConstraintId().isEmpty() ) { // Even though this is an index backing a uniqueness constraint, the uniqueness constraint wasn't created // so there's no gain in waiting for this index. return; } monitor.awaitingPopulationOfRecoveredIndex( index ); awaitOnlineAfterRecovery( proxy ); } ); state = State.RUNNING; } private void dontRebuildIndexesInReadOnlyMode( MutableLongObjectMap rebuildingDescriptors ) { if ( readOnlyChecker.isReadOnly() && rebuildingDescriptors.notEmpty() ) { String indexString = rebuildingDescriptors.values().stream() .map( String::valueOf ) .collect( Collectors.joining( ", ", "{", "}" ) ); throw new IllegalStateException( "Some indexes need to be rebuilt. This is not allowed in read only mode. Please start db in writable mode to rebuild indexes. " + "Indexes needing rebuild: " + indexString ); } } private void populateIndexesOfAllTypes( MutableLongObjectMap rebuildingDescriptors, IndexMap indexMap ) { Map> rebuildingDescriptorsByType = new EnumMap<>( EntityType.class ); for ( IndexDescriptor descriptor : rebuildingDescriptors ) { rebuildingDescriptorsByType.computeIfAbsent( descriptor.schema().entityType(), type -> new LongObjectHashMap<>() ) .put( descriptor.getId(), descriptor ); } for ( Map.Entry> descriptorToPopulate : rebuildingDescriptorsByType.entrySet() ) { IndexPopulationJob populationJob = newIndexPopulationJob( descriptorToPopulate.getKey(), false, SYSTEM ); populate( descriptorToPopulate.getValue(), indexMap, populationJob ); } } private void populate( MutableLongObjectMap rebuildingDescriptors, IndexMap indexMap, IndexPopulationJob populationJob ) { rebuildingDescriptors.forEachKeyValue( ( indexId, descriptor ) -> { IndexProxy proxy = indexProxyCreator.createPopulatingIndexProxy( descriptor, monitor, populationJob ); proxy.start(); indexMap.putIndexProxy( proxy ); } ); startIndexPopulation( populationJob ); } /** * Polls the {@link IndexProxy#getState() state of the index} and waits for it to be either {@link InternalIndexState#ONLINE}, * in which case the wait is over, or {@link InternalIndexState#FAILED}, in which an exception is logged. * * This method is only called during startup, and might be called as part of recovery. If we threw an exception here, it could * render the database unrecoverable. That's why we only log a message about failed indexes. */ private void awaitOnlineAfterRecovery( IndexProxy proxy ) { while ( true ) { switch ( proxy.getState() ) { case ONLINE: return; case FAILED: String message = String.format( "Index %s entered %s state while recovery waited for it to be fully populated.", proxy.getDescriptor(), FAILED ); IndexPopulationFailure populationFailure = proxy.getPopulationFailure(); String causeOfFailure = populationFailure.asString(); // Log as INFO because at this point we don't know if the constraint index was ever bound to a constraint or not. // If it was really bound to a constraint, then we actually ought to log as WARN or ERROR, I suppose. // But by far the most likely scenario is that the constraint itself was never created. internalLog.info( IndexPopulationFailure.appendCauseOfFailure( message, causeOfFailure ) ); return; case POPULATING: // Sleep a short while and look at state again the next loop iteration try { Thread.sleep( 10 ); } catch ( InterruptedException e ) { throw new IllegalStateException( "Waiting for index to become ONLINE was interrupted", e ); } break; default: throw new IllegalStateException( proxy.getState().name() ); } } } // while indexes will be closed on shutdown we need to stop ongoing jobs before we will start shutdown to prevent // races between checkpoint flush and index jobs @Override public void stop() throws Exception { samplingController.stop(); populationJobController.stop(); indexStatisticsStore.stop(); } // We need to stop indexing service on shutdown since we can have transactions that are ongoing/finishing // after we start stopping components and those transactions should be able to finish successfully @Override public void shutdown() throws IOException { state = State.STOPPED; closeAllIndexes(); indexStatisticsStore.shutdown(); } @Override public void validateBeforeCommit( IndexDescriptor index, Value[] tuple, long entityId ) { indexMapRef.validateBeforeCommit( index, tuple, entityId ); } @Override public void validateIndexPrototype( IndexPrototype prototype ) { IndexProvider provider = providerMap.lookup( prototype.getIndexProvider() ); provider.validatePrototype( prototype ); } @Override public IndexProviderDescriptor getDefaultProvider() { return providerMap.getDefaultProvider().getProviderDescriptor(); } @Override public IndexProviderDescriptor getFulltextProvider() { return providerMap.getFulltextProvider().getProviderDescriptor(); } @Override public IndexProviderDescriptor getTokenIndexProvider() { return providerMap.getTokenIndexProvider().getProviderDescriptor(); } @Override public IndexProviderDescriptor getTextIndexProvider() { return providerMap.getTextIndexProvider().getProviderDescriptor(); } @Override public IndexProviderDescriptor getRangeIndexProvider() { return providerMap.getRangeIndexProvider().getProviderDescriptor(); } @Override public IndexProviderDescriptor getPointIndexProvider() { return providerMap.getPointIndexProvider().getProviderDescriptor(); } @Override public IndexDescriptor completeConfiguration( IndexDescriptor index ) { return providerMap.completeConfiguration( index ); } @Override public IndexProviderDescriptor indexProviderByName( String providerName ) { return providerMap.lookup( providerName ).getProviderDescriptor(); } @Override public IndexType indexTypeByProviderName( String providerName ) { return providerMap.lookup( providerName ).getIndexType(); } /** * Applies the given updates, which may contain updates for one or more indexes. * * @param updates {@link IndexEntryUpdate updates} to apply. * @throws UncheckedIOException potentially thrown from index updating. * @throws KernelException potentially thrown from index updating. */ @Override public void applyUpdates( Iterable> updates, CursorContext cursorContext ) throws KernelException { if ( state == State.NOT_STARTED ) { // We're in recovery, which means we'll be telling indexes to apply with additional care for making // idempotent changes. apply( updates, IndexUpdateMode.RECOVERY, cursorContext ); } else if ( state == State.RUNNING || state == State.STARTING ) { apply( updates, IndexUpdateMode.ONLINE, cursorContext ); } else { throw new IllegalStateException( "Can't apply index updates " + asList( updates ) + " while indexing service is " + state ); } } private void apply( Iterable> updates, IndexUpdateMode updateMode, CursorContext cursorContext ) throws KernelException { try ( IndexUpdaterMap updaterMap = indexMapRef.createIndexUpdaterMap( updateMode ) ) { for ( IndexEntryUpdate indexUpdate : updates ) { processUpdate( updaterMap, indexUpdate, cursorContext ); } } } /** * Creates one or more indexes. They will all be populated by one and the same store scan. * * This code is called from the transaction infrastructure during transaction commits, which means that * it is *vital* that it is stable, and handles errors very well. Failing here means that the entire db * will shut down. * * {@link IndexPopulator#verifyDeferredConstraints(NodePropertyAccessor)} will not be called as part of populating these indexes, * instead that will be done by code that activates the indexes later. * * @param subject subject that triggered the index creation. * This is used for monitoring purposes, so work related to index creation and population can be linked to its originator. */ @Override public void createIndexes( Subject subject, IndexDescriptor... rules ) { createIndexes( false, subject, rules ); } /** * Creates one or more indexes. They will all be populated by one and the same store scan. * * This code is called from the transaction infrastructure during transaction commits, which means that * it is *vital* that it is stable, and handles errors very well. Failing here means that the entire db * will shut down. * * @param verifyBeforeFlipping whether or not to call {@link IndexPopulator#verifyDeferredConstraints(NodePropertyAccessor)} * as part of population, before flipping to a successful state. */ public void createIndexes( boolean verifyBeforeFlipping, Subject subject, IndexDescriptor... rules ) { IndexDescriptor[] newlyCreated = filterOutAndHandleInjectedTokenIndex( rules ); IndexPopulationStarter populationStarter = new IndexPopulationStarter( verifyBeforeFlipping, subject, newlyCreated ); indexMapRef.modify( populationStarter ); populationStarter.startPopulation(); } /** * Once upon a time there was something called label scan store. * In essence, it was a btree used for indexing nodes, but it was not managed like other indexes. * Most importantly, it did not have a record in the schema store. * In 4.3, the label scan store was turned into a proper index referred to as node label index * and got a corresponding record in a schema store. * However, during a rolling upgrade until the cluster is fully upgraded to a version >= 4.3, * a former label scan store behaves like a node label index, but does not have a record in schema store. * We call such node label index as "injected". * During an upgrade, a record in a schema store is created for the injected node label index. * The purpose of this code is to catch the event when a new index descriptor for the injected node label index * is submitted, filter it from the list of to-be-created indexes and swap the old descriptor on * the injected node label index with the newly submitted descriptor. * In other words, adding identity to an injected index is the only and very special case when * {@link #createIndexes} is called with a descriptor for an already existing index * and such operation needs to cause the index being linked to the new descriptor instead of * a new index being created and because we effectively "remove" the old injected index and replace * with the new we also need to clear the schema state. * Of course it would be much simpler to close the injected index and create a new one with the new identity. * The reason why the elaborate migration was introduced is not to interrupt any ongoing operations * against the index by keeping the file/accessor of the injected label index open. */ private IndexDescriptor[] filterOutAndHandleInjectedTokenIndex( IndexDescriptor[] rules ) { IndexProxy nli = indexMapRef.indexMapSnapshot().getIndexProxy( IndexDescriptor.INJECTED_NLI_ID ); if ( nli == null ) { return rules; } List filteredRules = new ArrayList<>(); for ( IndexDescriptor rule : rules ) { if ( rule.schema().isAnyTokenSchemaDescriptor() && rule.schema().entityType() == NODE ) { nli.changeIdentity( rule ); indexMapRef.modify( indexMap -> { indexMap.putIndexProxy( nli ); indexMap.removeIndexProxy( IndexDescriptor.INJECTED_NLI_ID ); return indexMap; } ); schemaState.clear(); } else { filteredRules.add( rule ); } } return filteredRules.toArray( IndexDescriptor[]::new ); } private static void processUpdate( IndexUpdaterMap updaterMap, IndexEntryUpdate indexUpdate, CursorContext cursorContext ) throws IndexEntryConflictException { IndexUpdater updater = updaterMap.getUpdater( indexUpdate.indexKey(), cursorContext ); if ( updater != null ) { updater.process( indexUpdate ); } } @Override public void dropIndex( IndexDescriptor rule ) { Preconditions.checkState( state == State.RUNNING || state == State.NOT_STARTED, "Dropping index in unexpected state %s", state.name() ); indexMapRef.modify( indexMap -> { long indexId = rule.getId(); IndexProxy index = indexMap.removeIndexProxy( indexId ); if ( state == State.RUNNING ) { Preconditions.checkState( index != null, "Index %s doesn't exists", rule ); index.drop(); } else if ( index != null ) { try { index.drop(); } catch ( Exception e ) { // This is OK to get during recovery because the underlying index can be in any unknown state // while we're recovering. Let's just move on to closing it instead. try ( var cursorContext = new CursorContext( pageCacheTracer.createPageCursorTracer( INDEX_SERVICE_INDEX_CLOSING_TAG ) ) ) { index.close( cursorContext ); } catch ( IOException closeException ) { // This is OK for the same reason as above } } } return indexMap; } ); } public void triggerIndexSampling( IndexSamplingMode mode ) { internalLog.info( "Manual trigger for sampling all indexes [" + mode + "]" ); monitor.indexSamplingTriggered( mode ); samplingController.sampleIndexes( mode ); } public void triggerIndexSampling( IndexDescriptor index, IndexSamplingMode mode ) { String description = index.userDescription( tokenNameLookup ); internalLog.info( "Manual trigger for sampling index " + description + " [" + mode + "]" ); samplingController.sampleIndex( index.getId(), mode ); } private static void dropRecoveringIndexes( IndexMap indexMap, LongIterable indexesToRebuild ) { indexesToRebuild.forEach( idx -> { IndexProxy indexProxy = indexMap.removeIndexProxy( idx ); assert indexProxy != null; indexProxy.drop(); } ); } @Override public void activateIndex( IndexDescriptor descriptor ) throws IndexNotFoundKernelException, IndexActivationFailedKernelException, IndexPopulationFailedKernelException { try { if ( state == State.RUNNING ) // don't do this during recovery. { IndexProxy index = getIndexProxy( descriptor ); index.awaitStoreScanCompleted( 0, TimeUnit.MILLISECONDS ); index.activate(); internalLog.info( "Constraint %s is %s.", index.getDescriptor(), ONLINE.name() ); } } catch ( InterruptedException e ) { Thread.interrupted(); throw new IndexActivationFailedKernelException( e, "Unable to activate index, thread was interrupted." ); } } public IndexProxy getIndexProxy( IndexDescriptor index ) throws IndexNotFoundKernelException { return indexMapRef.getIndexProxy( index.getId() ); } @Deprecated public IndexProxy getIndexProxy( long indexId ) throws IndexNotFoundKernelException { return indexMapRef.getIndexProxy( indexId ); } @Override public void validateIndex( long indexId ) throws IndexNotFoundKernelException, IndexPopulationFailedKernelException, UniquePropertyValueValidationException { indexMapRef.getIndexProxy( indexId ).validate(); } public void forceAll( CursorContext cursorContext ) throws IOException { indexStatisticsStore.checkpoint( cursorContext ); indexMapRef.indexMapSnapshot().forEachIndexProxy( indexProxyOperation( "force", proxy -> proxy.force( cursorContext ) ) ); } private LongObjectProcedure indexProxyOperation( String name, ThrowingConsumer operation ) { return ( id, indexProxy ) -> { try { operation.accept( indexProxy ); } catch ( Exception e ) { try { IndexProxy proxy = indexMapRef.getIndexProxy( id ); throw new UnderlyingStorageException( "Unable to " + name + " " + proxy, e ); } catch ( IndexNotFoundKernelException infe ) { // index was dropped while trying to operate on it, we can continue to other indexes } } }; } private void closeAllIndexes() { try ( var cursorContext = new CursorContext( pageCacheTracer.createPageCursorTracer( INDEX_SERVICE_INDEX_CLOSING_TAG ) ) ) { indexMapRef.modify( indexMap -> { Iterable indexesToStop = indexMap.getAllIndexProxies(); for ( IndexProxy index : indexesToStop ) { try { index.close( cursorContext ); } catch ( Exception e ) { internalLog.error( "Unable to close index", e ); } } // Effectively clearing it return new IndexMap(); } ); } } public LongSet getIndexIds() { Iterable indexProxies = indexMapRef.getAllIndexProxies(); MutableLongSet indexIds = new LongHashSet(); for ( IndexProxy indexProxy : indexProxies ) { indexIds.add( indexProxy.getDescriptor().getId() ); } return indexIds; } public ResourceIterator snapshotIndexFiles() throws IOException { Collection> snapshots = new ArrayList<>(); snapshots.add( asResourceIterator( iterator( indexStatisticsStore.storeFile() ) ) ); for ( IndexProxy indexProxy : indexMapRef.getAllIndexProxies() ) { snapshots.add( indexProxy.snapshotFiles() ); } return Iterators.concatResourceIterators( snapshots.iterator() ); } public IndexMonitor getMonitor() { return monitor; } private IndexPopulationJob newIndexPopulationJob( EntityType type, boolean verifyBeforeFlipping, Subject subject ) { MultipleIndexPopulator multiPopulator = new MultipleIndexPopulator( storeView, internalLogProvider, type, schemaState, jobScheduler, tokenNameLookup, pageCacheTracer, memoryTracker, databaseName, subject, config ); return new IndexPopulationJob( multiPopulator, monitor, verifyBeforeFlipping, pageCacheTracer, memoryTracker, databaseName, subject, NODE, config ); } private void startIndexPopulation( IndexPopulationJob job ) { if ( storeView.isEmpty() ) { // Creating indexes and constraints on an empty database, before ingesting data doesn't need to do unnecessary scheduling juggling, // instead just run it on the caller thread. job.run(); } else { populationJobController.startIndexPopulation( job ); } } private String indexStateInfo( String tag, InternalIndexState state, IndexDescriptor descriptor ) { return format( "IndexingService.%s: index %d on %s is %s", tag, descriptor.getId(), descriptor.schema().userDescription( tokenNameLookup ), state.name() ); } private void logIndexStateSummary( String method, Map> indexStates ) { if ( indexStates.isEmpty() ) { return; } int mostPopularStateCount = Integer.MIN_VALUE; InternalIndexState mostPopularState = null; for ( Map.Entry> indexStateEntry : indexStates.entrySet() ) { if ( indexStateEntry.getValue().size() > mostPopularStateCount ) { mostPopularState = indexStateEntry.getKey(); mostPopularStateCount = indexStateEntry.getValue().size(); } } indexStates.remove( mostPopularState ); for ( Map.Entry> indexStateEntry : indexStates.entrySet() ) { InternalIndexState state = indexStateEntry.getKey(); List logRecords = indexStateEntry.getValue(); for ( IndexLogRecord logRecord : logRecords ) { internalLog.info( indexStateInfo( method, state, logRecord.getDescriptor() ) ); } } internalLog.info( format( "IndexingService.%s: indexes not specifically mentioned above are %s", method, mostPopularState ) ); } private final class IndexPopulationStarter implements UnaryOperator { private final boolean verifyBeforeFlipping; private final Subject subject; private final IndexDescriptor[] descriptors; private IndexPopulationJob nodePopulationJob; private IndexPopulationJob relationshipPopulationJob; IndexPopulationStarter( boolean verifyBeforeFlipping, Subject subject, IndexDescriptor[] descriptors ) { this.verifyBeforeFlipping = verifyBeforeFlipping; this.subject = subject; this.descriptors = descriptors; } @Override public IndexMap apply( IndexMap indexMap ) { for ( IndexDescriptor descriptor : descriptors ) { IndexProxy index = indexMap.getIndexProxy( descriptor ); if ( index != null && state == State.NOT_STARTED ) { // This index already has a proxy. No need to build another. continue; } if ( state == State.RUNNING ) { if ( descriptor.schema().entityType() == NODE ) { nodePopulationJob = nodePopulationJob == null ? newIndexPopulationJob( NODE, verifyBeforeFlipping, subject ) : nodePopulationJob; index = indexProxyCreator.createPopulatingIndexProxy( descriptor, monitor, nodePopulationJob ); index.start(); } else { relationshipPopulationJob = relationshipPopulationJob == null ? newIndexPopulationJob( RELATIONSHIP, verifyBeforeFlipping, subject ) : relationshipPopulationJob; index = indexProxyCreator.createPopulatingIndexProxy( descriptor, monitor, relationshipPopulationJob ); index.start(); } } else { index = indexProxyCreator.createRecoveringIndexProxy( descriptor ); } indexMap.putIndexProxy( index ); } return indexMap; } void startPopulation() { if ( nodePopulationJob != null ) { startIndexPopulation( nodePopulationJob ); } if ( relationshipPopulationJob != null ) { startIndexPopulation( relationshipPopulationJob ); } } } private static final class IndexLogRecord { private final IndexDescriptor descriptor; IndexLogRecord( IndexDescriptor descriptor ) { this.descriptor = descriptor; } public long getIndexId() { return descriptor.getId(); } public IndexDescriptor getDescriptor() { return descriptor; } } @FunctionalInterface public interface IndexProxyProvider { IndexProxy getIndexProxy( IndexDescriptor indexDescriptor ) throws IndexNotFoundKernelException; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy