@Override public void processNode( RecordStore<NodeRecord> store, NodeRecord node ) { if ( node.isDense() ) { checkNode( store, node, denseNodeChecker ); } else { checkNode( store, node, sparseNodeChecker ); } }
@Override public void processNode( RecordStore<NodeRecord> store, NodeRecord node ) { cacheAccess.client().incAndGetCount( node.isDense() ? Counts.Type.nodeDense : Counts.Type.nodeSparse ); super.processNode( store, node ); }
assert firstNode.getNextRel() != rel.getId() || firstNode.isDense(); assert secondNode.getNextRel() != rel.getId() || secondNode.isDense(); if ( !firstNode.isDense() ) if ( !secondNode.isDense() ) if ( !firstNode.isDense() ) if ( !secondNode.isDense() ) if ( !firstNode.isDense() ) if ( !secondNode.isDense() )
@Override protected void processCache() { RecordStore<NodeRecord> nodeStore = storeAccess.getNodeStore(); CacheAccess.Client client = cacheAccess.client(); long highId = nodeStore.getHighId(); for ( long nodeId = 0; nodeId < highId; nodeId++ ) { if ( client.getFromCache( nodeId, CacheSlots.NextRelationship.SLOT_FIRST_IN_TARGET ) == 0 ) { NodeRecord node = nodeStore.getRecord( nodeId, nodeStore.newRecord(), FORCE ); if ( node.inUse() && !node.isDense() ) { storeProcessor.processNode( nodeStore, node ); } } } } }
@Override public RecordKey<NodeRecord> node() { return ( written, read ) -> { assertEquals( written.getNextProp(), read.getNextProp() ); assertEquals( written.getNextRel(), read.getNextRel() ); assertEquals( written.getLabelField(), read.getLabelField() ); assertEquals( written.isDense(), read.isDense() ); }; }
private void convertNodeToDenseIfNecessary( NodeRecord node, RecordAccess<RelationshipRecord, Void> relRecords, RecordAccess<RelationshipGroupRecord, Integer> relGroupRecords, ResourceLocker locks ) { if ( node.isDense() ) { return; } long relId = node.getNextRel(); if ( relId != Record.NO_NEXT_RELATIONSHIP.intValue() ) { RecordProxy<RelationshipRecord, Void> relChange = relRecords.getOrLoad( relId, null ); RelationshipRecord rel = relChange.forReadingLinkage(); if ( relCount( node.getId(), rel ) >= denseNodeThreshold ) { locks.acquireExclusive( LockTracer.NONE, ResourceTypes.RELATIONSHIP, relId ); // Re-read the record after we've locked it since another transaction might have // changed in the meantime. relChange = relRecords.getOrLoad( relId, null ); convertNodeToDenseNode( node, relChange.forChangingLinkage(), relRecords, relGroupRecords, locks ); } } }
private String diff( NodeRecord expected, NodeRecord actual ) { if ( actual.getId() == expected.getId() && actual.getNextRel() == expected.getNextRel() && actual.getLabelField() == expected.getLabelField() && actual.getNextProp() == expected.getNextProp() && actual.isDense() == expected.isDense() && actual.isLight() == expected.isLight() ) { return null; } return describeDiff( expected.toString(), actual.toString() ); }
private void assertRelationshipGroupsInOrder( NeoStores neoStores, long nodeId, int... types ) { NodeStore nodeStore = neoStores.getNodeStore(); NodeRecord node = nodeStore.getRecord( nodeId, nodeStore.newRecord(), NORMAL ); assertTrue( "Node should be dense, is " + node, node.isDense() ); long groupId = node.getNextRel(); int cursor = 0; List<RelationshipGroupRecord> seen = new ArrayList<>(); while ( groupId != Record.NO_NEXT_RELATIONSHIP.intValue() ) { RecordStore<RelationshipGroupRecord> relationshipGroupStore = neoStores.getRelationshipGroupStore(); RelationshipGroupRecord group = relationshipGroupStore.getRecord( groupId, relationshipGroupStore.newRecord(), NORMAL ); seen.add( group ); assertEquals( "Invalid type, seen groups so far " + seen, types[cursor++], group.getType() ); groupId = group.getNext(); } assertEquals( "Not enough relationship group records found in chain for " + node, types.length, cursor ); }
private void markRelGroupNotInUse( long nodeId, TestRelType... types ) { NodeRecord node = getNodeRecord( nodeId ); assertTrue( node.isDense() ); Set<TestRelType> typesToRemove = asSet( types ); long relGroupId = node.getNextRel(); while ( relGroupId != NO_NEXT_RELATIONSHIP.intValue() ) { RelationshipGroupRecord relGroup = getRelGroupRecord( relGroupId ); TestRelType type = relTypeForId( relGroup.getType() ); if ( typesToRemove.contains( type ) ) { relGroup.setInUse( false ); update( relGroup ); } relGroupId = relGroup.getNext(); } }
byte extra = record.isDense() ? (byte)1 : (byte)0; cursor.putByte( extra );
@Test public void mustSplitUpRelationshipChainsWhenCreatingDenseNodes() { BatchInserter inserter = globalInserter; long node1 = inserter.createNode( null ); long node2 = inserter.createNode( null ); for ( int i = 0; i < 1000; i++ ) { for ( MyRelTypes relType : MyRelTypes.values() ) { inserter.createRelationship( node1, node2, relType, null ); } } NeoStores neoStores = getFlushedNeoStores( inserter ); NodeRecord record = getRecord( neoStores.getNodeStore(), node1 ); assertTrue( "Node " + record + " should have been dense", record.isDense() ); }
private void markRandomRelsInGroupNotInUse( long nodeId, TestRelType type ) { NodeRecord node = getNodeRecord( nodeId ); assertTrue( node.isDense() ); long relGroupId = node.getNextRel(); while ( relGroupId != NO_NEXT_RELATIONSHIP.intValue() ) { RelationshipGroupRecord relGroup = getRelGroupRecord( relGroupId ); if ( type == relTypeForId( relGroup.getType() ) ) { markRandomRelsInChainNotInUse( relGroup.getFirstOut() ); markRandomRelsInChainNotInUse( relGroup.getFirstIn() ); markRandomRelsInChainNotInUse( relGroup.getFirstLoop() ); return; } relGroupId = relGroup.getNext(); } throw new IllegalStateException( "No relationship group with type: " + type + " found" ); }
@Test public void shouldConvertToDenseNodeRepresentationWhenHittingThresholdWithTheSameTypeSameDirection() throws Exception { // GIVEN a node with a total of denseNodeThreshold-1 relationships NeoStores neoStores = neoStoresRule.builder() .with( GraphDatabaseSettings.dense_node_threshold.name(), "8" ).build(); TransactionRecordState tx = newTransactionRecordState( neoStores ); long nodeId = neoStores.getNodeStore().nextId(); int typeA = 0; tx.nodeCreate( nodeId ); tx.createRelationshipTypeToken( "A", typeA ); createRelationships( neoStores, tx, nodeId, typeA, OUTGOING, 8 ); // here we're at the edge assertFalse( recordChangeSet.getNodeRecords().getOrLoad( nodeId, null ).forReadingData().isDense() ); // WHEN creating the relationship that pushes us over the threshold createRelationships( neoStores, tx, nodeId, typeA, OUTGOING, 1 ); // THEN the node should have been converted into a dense node assertTrue( recordChangeSet.getNodeRecords().getOrLoad( nodeId, null ).forReadingData().isDense() ); assertDenseRelationshipCounts( recordChangeSet, nodeId, typeA, 9, 0 ); }
@Test public void shouldConvertToDenseNodeRepresentationWhenHittingThresholdWithTheSameTypeDifferentDirection() throws Exception { // GIVEN a node with a total of denseNodeThreshold-1 relationships NeoStores neoStores = neoStoresRule.builder() .with( GraphDatabaseSettings.dense_node_threshold.name(), "49" ).build(); TransactionRecordState tx = newTransactionRecordState( neoStores ); long nodeId = neoStores.getNodeStore().nextId(); int typeA = 0; tx.nodeCreate( nodeId ); tx.createRelationshipTypeToken( "A", typeA ); createRelationships( neoStores, tx, nodeId, typeA, OUTGOING, 24 ); createRelationships( neoStores, tx, nodeId, typeA, INCOMING, 25 ); // here we're at the edge assertFalse( recordChangeSet.getNodeRecords().getOrLoad( nodeId, null ).forReadingData().isDense() ); // WHEN creating the relationship that pushes us over the threshold createRelationships( neoStores, tx, nodeId, typeA, INCOMING, 1 ); // THEN the node should have been converted into a dense node assertTrue( recordChangeSet.getNodeRecords().getOrLoad( nodeId, null ).forReadingData().isDense() ); assertDenseRelationshipCounts( recordChangeSet, nodeId, typeA, 24, 26 ); }
if ( node.isDense() )
private void writeNodeRecord( WritableChannel channel, NodeRecord record ) throws IOException { byte flags = bitFlags( bitFlag( record.inUse(), Record.IN_USE.byteValue() ), bitFlag( record.isCreated(), Record.CREATED_IN_TX ), bitFlag( record.requiresSecondaryUnit(), Record.REQUIRE_SECONDARY_UNIT ), bitFlag( record.hasSecondaryUnitId(), Record.HAS_SECONDARY_UNIT ), bitFlag( record.isUseFixedReferences(), Record.USES_FIXED_REFERENCE_FORMAT ) ); channel.put( flags ); if ( record.inUse() ) { channel.put( record.isDense() ? (byte) 1 : (byte) 0 ); channel.putLong( record.getNextRel() ).putLong( record.getNextProp() ); channel.putLong( record.getLabelField() ); if ( record.hasSecondaryUnitId() ) { channel.putLong( record.getSecondaryUnitId() ); } } // Always write dynamic label records because we want to know which ones have been deleted // especially if the node has been deleted. writeDynamicRecords( channel, record.getDynamicLabelRecords() ); } }
if ( change == null ) assert node.isDense() : "Node " + node + " should have been dense at this point"; long id = idGenerator.nextId(); change = relGroupRecords.create( id, type );
createRelationships( neoStores, tx, nodeId, typeC, INCOMING, 10 ); assertFalse( recordChangeSet.getNodeRecords().getOrLoad( nodeId, null ).forReadingData().isDense() ); assertTrue( recordChangeSet.getNodeRecords().getOrLoad( nodeId, null ).forReadingData().isDense() ); assertDenseRelationshipCounts( recordChangeSet, nodeId, typeA, 6, 7 ); assertDenseRelationshipCounts( recordChangeSet, nodeId, typeB, 8, 9 );