Search in sources :

Example 1 with IndexEntry

use of org.neo4j.consistency.store.synthetic.IndexEntry in project neo4j by neo4j.

the class IndexChecker method checkVsEntities.

private void checkVsEntities(List<IndexContext> indexes, long fromEntityId, long toEntityId) {
    // This is one thread
    CheckerContext noReportingContext = context.withoutReporting();
    try (var cursorContext = new CursorContext(context.pageCacheTracer.createPageCursorTracer(CONSISTENCY_INDEX_ENTITY_CHECK_TAG));
        RecordReader<NodeRecord> nodeReader = new RecordReader<>(context.neoStores.getNodeStore(), true, cursorContext);
        RecordReader<DynamicRecord> labelReader = new RecordReader<>(context.neoStores.getNodeStore().getDynamicLabelStore(), false, cursorContext);
        SafePropertyChainReader propertyReader = new SafePropertyChainReader(noReportingContext, cursorContext)) {
        ProgressListener localScanProgress = scanProgress.threadLocalReporter();
        IntObjectHashMap<Value> allValues = new IntObjectHashMap<>();
        var client = cacheAccess.client();
        int numberOfIndexes = indexes.size();
        for (long entityId = fromEntityId; entityId < toEntityId && !context.isCancelled(); entityId++) {
            NodeRecord nodeRecord = nodeReader.read(entityId);
            if (nodeRecord.inUse()) {
                long[] entityTokens = safeGetNodeLabels(noReportingContext, nodeRecord.getId(), nodeRecord.getLabelField(), labelReader, cursorContext);
                lightClear(allValues);
                boolean propertyChainRead = entityTokens != null && propertyReader.read(allValues, nodeRecord, noReportingContext.reporter::forNode, cursorContext);
                if (propertyChainRead) {
                    for (int i = 0; i < numberOfIndexes; i++) {
                        IndexContext index = indexes.get(i);
                        IndexDescriptor descriptor = index.descriptor;
                        long cachedValue = client.getFromCache(entityId, i);
                        boolean nodeIsInIndex = (cachedValue & IN_USE_MASK) != 0;
                        Value[] values = RecordLoading.entityIntersectionWithSchema(entityTokens, allValues, descriptor.schema());
                        if (index.descriptor.schema().isFulltextSchemaDescriptor()) {
                            // The strategy for fulltext indexes is way simpler. Simply check of the sets of tokens (label tokens and property key tokens)
                            // and if they match the index schema descriptor then the node should be in the index, otherwise not
                            int[] nodePropertyKeys = allValues.keySet().toArray();
                            int[] indexPropertyKeys = index.descriptor.schema().getPropertyIds();
                            boolean nodeShouldBeInIndex = index.descriptor.schema().isAffected(entityTokens) && containsAny(indexPropertyKeys, nodePropertyKeys) && valuesContainTextProperty(values);
                            if (nodeShouldBeInIndex && !nodeIsInIndex) {
                                getReporter(context.recordLoader.node(entityId, cursorContext)).notIndexed(descriptor, new Object[0]);
                            } else if (!nodeShouldBeInIndex && nodeIsInIndex) {
                                // Fulltext indexes created before 4.3.0-drop02 can contain empty documents (added when the schema matched but the values
                                // were not text). The index still works with those empty documents present, so we don't want to report them as
                                // inconsistencies and force rebuilds.
                                // This utilizes the fact that countIndexedEntities in FulltextIndexReader with non-text values will ask
                                // about documents that doesn't contain those property keys - a document found by that query should be an empty
                                // document we just want to ignore.
                                Value[] noValues = new Value[indexPropertyKeys.length];
                                Arrays.fill(noValues, NO_VALUE);
                                long docsWithNoneOfProperties = indexAccessors.readers().reader(descriptor).countIndexedEntities(entityId, cursorContext, indexPropertyKeys, noValues);
                                if (docsWithNoneOfProperties != 1) {
                                    getReporter(context.recordLoader.node(entityId, cursorContext)).notIndexed(descriptor, new Object[0]);
                                }
                            }
                        } else {
                            if (values != null) {
                                // This node should really be in the index, is it?
                                if (!nodeIsInIndex) {
                                    // It wasn't, report it
                                    getReporter(context.recordLoader.node(entityId, cursorContext)).notIndexed(descriptor, Values.asObjects(values));
                                } else if (index.hasValues) {
                                    int cachedChecksum = (int) cachedValue & CHECKSUM_MASK;
                                    int actualChecksum = checksum(values);
                                    if (cachedChecksum != actualChecksum) {
                                        getReporter(context.recordLoader.node(entityId, cursorContext)).notIndexed(descriptor, Values.asObjects(values));
                                    }
                                }
                            } else {
                                if (nodeIsInIndex) {
                                    reporter.forIndexEntry(new IndexEntry(descriptor, context.tokenNameLookup, entityId)).nodeNotInUse(context.recordLoader.node(entityId, cursorContext));
                                }
                            }
                        }
                    }
                }
            // else this would be reported elsewhere
            } else {
                // This node shouldn't be in any index
                for (int i = 0; i < numberOfIndexes; i++) {
                    boolean isInIndex = (client.getFromCache(entityId, i) & IN_USE_MASK) != 0;
                    if (isInIndex) {
                        reporter.forIndexEntry(new IndexEntry(indexes.get(i).descriptor, context.tokenNameLookup, entityId)).nodeNotInUse(context.recordLoader.node(entityId, cursorContext));
                    }
                }
            }
            localScanProgress.add(1);
        }
        localScanProgress.done();
    }
}
Also used : DynamicRecord(org.neo4j.kernel.impl.store.record.DynamicRecord) IntObjectHashMap(org.eclipse.collections.impl.map.mutable.primitive.IntObjectHashMap) IndexEntry(org.neo4j.consistency.store.synthetic.IndexEntry) CursorContext(org.neo4j.io.pagecache.context.CursorContext) IndexDescriptor(org.neo4j.internal.schema.IndexDescriptor) NodeRecord(org.neo4j.kernel.impl.store.record.NodeRecord) ProgressListener(org.neo4j.internal.helpers.progress.ProgressListener) Value(org.neo4j.values.storable.Value)

Example 2 with IndexEntry

use of org.neo4j.consistency.store.synthetic.IndexEntry in project neo4j by neo4j.

the class NodeChecker method checkIndexVsNodes.

private void checkIndexVsNodes(LongRange range, IndexDescriptor descriptor, boolean lastRange) throws Exception {
    CacheAccess.Client client = context.cacheAccess.client();
    IndexAccessor accessor = context.indexAccessors.accessorFor(descriptor);
    RelationshipCounter.NodeLabelsLookup nodeLabelsLookup = observedCounts.nodeLabelsLookup();
    SchemaDescriptor schema = descriptor.schema();
    PropertySchemaType propertySchemaType = schema.propertySchemaType();
    long[] indexEntityTokenIds = toLongArray(schema.getEntityTokenIds());
    indexEntityTokenIds = sortAndDeduplicate(indexEntityTokenIds);
    try (var cursorContext = new CursorContext(context.pageCacheTracer.createPageCursorTracer(NODE_INDEXES_CHECKER_TAG));
        var allEntriesReader = accessor.newAllEntriesValueReader(range.from(), lastRange ? Long.MAX_VALUE : range.to(), cursorContext)) {
        for (long entityId : allEntriesReader) {
            try {
                boolean entityExists = client.getBooleanFromCache(entityId, CacheSlots.NodeLink.SLOT_IN_USE);
                if (!entityExists) {
                    reporter.forIndexEntry(new IndexEntry(descriptor, context.tokenNameLookup, entityId)).nodeNotInUse(recordLoader.node(entityId, cursorContext));
                } else {
                    long[] entityTokenIds = nodeLabelsLookup.nodeLabels(entityId);
                    compareTwoSortedLongArrays(propertySchemaType, entityTokenIds, indexEntityTokenIds, indexLabel -> reporter.forIndexEntry(new IndexEntry(descriptor, context.tokenNameLookup, entityId)).nodeDoesNotHaveExpectedLabel(recordLoader.node(entityId, cursorContext), indexLabel), storeLabel -> {
                    /*here we're only interested in what the the index has that the store doesn't have*/
                    });
                }
            } catch (ArrayIndexOutOfBoundsException e) {
                // OK so apparently the index has a node way outside node highId
                reporter.forIndexEntry(new IndexEntry(descriptor, context.tokenNameLookup, entityId)).nodeNotInUse(recordLoader.node(entityId, cursorContext));
            }
        }
    }
}
Also used : SchemaDescriptor(org.neo4j.internal.schema.SchemaDescriptor) IndexAccessor(org.neo4j.kernel.api.index.IndexAccessor) CacheAccess(org.neo4j.consistency.checking.cache.CacheAccess) IndexEntry(org.neo4j.consistency.store.synthetic.IndexEntry) CursorContext(org.neo4j.io.pagecache.context.CursorContext) PropertySchemaType(org.neo4j.internal.schema.PropertySchemaType) RelationshipCounter(org.neo4j.internal.recordstorage.RelationshipCounter)

Example 3 with IndexEntry

use of org.neo4j.consistency.store.synthetic.IndexEntry in project neo4j by neo4j.

the class IndexChecker method cacheIndex.

private void cacheIndex(IndexContext index, LongRange nodeIdRange, boolean firstRange, CursorContext cursorContext) throws Exception {
    IndexAccessor accessor = indexAccessors.accessorFor(index.descriptor);
    IndexEntriesReader[] partitions = accessor.newAllEntriesValueReader(context.execution.getNumberOfThreads(), cursorContext);
    try {
        Value[][] firstValues = new Value[partitions.length][];
        Value[][] lastValues = new Value[partitions.length][];
        long[] firstEntityIds = new long[partitions.length];
        long[] lastEntityIds = new long[partitions.length];
        ThrowingRunnable[] workers = new ThrowingRunnable[partitions.length];
        for (int i = 0; i < partitions.length; i++) {
            IndexEntriesReader partition = partitions[i];
            int slot = i;
            workers[i] = () -> {
                int lastChecksum = 0;
                int progressPart = 0;
                ProgressListener localCacheProgress = cacheProgress.threadLocalReporter();
                var client = cacheAccess.client();
                try (var context = new CursorContext(this.context.pageCacheTracer.createPageCursorTracer(CONSISTENCY_INDEX_CACHER_TAG))) {
                    while (partition.hasNext() && !this.context.isCancelled()) {
                        long entityId = partition.next();
                        if (!nodeIdRange.isWithinRangeExclusiveTo(entityId)) {
                            if (firstRange && entityId >= this.context.highNodeId) {
                                reporter.forIndexEntry(new IndexEntry(index.descriptor, this.context.tokenNameLookup, entityId)).nodeNotInUse(this.context.recordLoader.node(entityId, context));
                            } else if (firstRange && index.descriptor.isUnique() && index.hasValues) {
                                // We check all values belonging to unique indexes while we are checking the first range, to not
                                // miss duplicated values belonging to different ranges.
                                Value[] indexedValues = partition.values();
                                int checksum = checksum(indexedValues);
                                assert checksum <= CHECKSUM_MASK;
                                lastChecksum = verifyUniquenessInPartition(index, firstValues, lastValues, firstEntityIds, lastEntityIds, slot, lastChecksum, context, entityId, indexedValues, checksum);
                            }
                            continue;
                        }
                        int data = IN_USE_MASK;
                        if (index.hasValues) {
                            Value[] indexedValues = partition.values();
                            int checksum = checksum(indexedValues);
                            assert checksum <= CHECKSUM_MASK;
                            data |= checksum;
                            // Also take the opportunity to verify uniqueness, if the index is a uniqueness index
                            if (firstRange && index.descriptor.isUnique()) {
                                lastChecksum = verifyUniquenessInPartition(index, firstValues, lastValues, firstEntityIds, lastEntityIds, slot, lastChecksum, context, entityId, indexedValues, checksum);
                            }
                        }
                        client.putToCacheSingle(entityId, index.cacheSlotOffset, data);
                        if (++progressPart == INDEX_CACHING_PROGRESS_FACTOR) {
                            localCacheProgress.add(1);
                            progressPart = 0;
                        }
                    }
                }
                localCacheProgress.done();
            };
        }
        // Run the workers that cache the index contents and that do partition-local uniqueness checking, if index is unique
        context.execution.run("Cache index", workers);
        // Then, also if the index is unique then do uniqueness checking of the seams between the partitions
        if (firstRange && index.descriptor.isUnique() && !context.isCancelled()) {
            for (int i = 0; i < partitions.length - 1; i++) {
                Value[] left = lastValues[i];
                Value[] right = firstValues[i + 1];
                // Skip any empty partition - can be empty if all entries in a partition of the index were for nodes outside of the current range.
                if (left != null && right != null && Arrays.equals(left, right)) {
                    long leftEntityId = lastEntityIds[i];
                    long rightEntityId = firstEntityIds[i + 1];
                    reporter.forNode(context.recordLoader.node(leftEntityId, cursorContext)).uniqueIndexNotUnique(index.descriptor, left, rightEntityId);
                }
            }
        }
    } finally {
        IOUtils.closeAll(partitions);
    }
}
Also used : IndexAccessor(org.neo4j.kernel.api.index.IndexAccessor) IndexEntry(org.neo4j.consistency.store.synthetic.IndexEntry) CursorContext(org.neo4j.io.pagecache.context.CursorContext) ThrowingRunnable(org.neo4j.consistency.checker.ParallelExecution.ThrowingRunnable) IndexEntriesReader(org.neo4j.kernel.api.index.IndexEntriesReader) ProgressListener(org.neo4j.internal.helpers.progress.ProgressListener) Value(org.neo4j.values.storable.Value)

Aggregations

IndexEntry (org.neo4j.consistency.store.synthetic.IndexEntry)3 CursorContext (org.neo4j.io.pagecache.context.CursorContext)3 ProgressListener (org.neo4j.internal.helpers.progress.ProgressListener)2 IndexAccessor (org.neo4j.kernel.api.index.IndexAccessor)2 Value (org.neo4j.values.storable.Value)2 IntObjectHashMap (org.eclipse.collections.impl.map.mutable.primitive.IntObjectHashMap)1 ThrowingRunnable (org.neo4j.consistency.checker.ParallelExecution.ThrowingRunnable)1 CacheAccess (org.neo4j.consistency.checking.cache.CacheAccess)1 RelationshipCounter (org.neo4j.internal.recordstorage.RelationshipCounter)1 IndexDescriptor (org.neo4j.internal.schema.IndexDescriptor)1 PropertySchemaType (org.neo4j.internal.schema.PropertySchemaType)1 SchemaDescriptor (org.neo4j.internal.schema.SchemaDescriptor)1 IndexEntriesReader (org.neo4j.kernel.api.index.IndexEntriesReader)1 DynamicRecord (org.neo4j.kernel.impl.store.record.DynamicRecord)1 NodeRecord (org.neo4j.kernel.impl.store.record.NodeRecord)1