/** * Before caching any relationship groups all group counts for all nodes are incremented by calling * this method once for every encountered group (its node id). * * @param nodeId node to increment group count for. */ public void incrementGroupCount( long nodeId ) { int count = groupCount( nodeId ); count++; if ( (count & ~0xFFFF) != 0 ) { throw new IllegalStateException( "Invalid number of relationship groups for node " + nodeId + " " + count ); } groupCountCache.setShort( nodeId, 0, (short) count ); }
private void findNextNodeWithGroupsIfNeeded() { if ( countLeftForThisNode == 0 ) { do { nodeId++; countLeftForThisNode = nodeId >= groupCountCache.length() ? 0 : groupCount( nodeId ); } while ( countLeftForThisNode == 0 && nodeId < groupCountCache.length() ); } } };
private int countLeftForThisNode = groupCount( nodeId );
/** * Looks at max amount of configured memory (in constructor) and figures out for how many nodes their groups * can be cached. Before the first call to this method all {@link #incrementGroupCount(long)} calls * must have been made. After a call to this there should be a sequence of {@link #put(RelationshipGroupRecord)} * calls to cache the groups. If this call returns a node id which is lower than the highest node id in the * store then more rounds of caching should be performed after completing this round. * * @param fromNodeId inclusive * @return toNodeId exclusive */ public long prepare( long fromNodeId ) { cache.clear(); // this will have all the "first" bytes set to 0, which means !inUse this.fromNodeId = fromNodeId; // keep for use in put later on highCacheId = 0; for ( long nodeId = fromNodeId; nodeId < highNodeId; nodeId++ ) { int count = groupCount( nodeId ); if ( highCacheId + count > maxCacheLength ) { // Cannot include this one, so up until the previous is good return this.toNodeId = nodeId; } offsets.set( rebase( nodeId ), highCacheId ); highCacheId += count; } return this.toNodeId = highNodeId; }
/** * Caches a relationship group into this cache, it will be cached if the * {@link RelationshipGroupRecord#getOwningNode() owner} is within the {@link #prepare(long) prepared} range, * where {@code true} will be returned, otherwise {@code false}. * * @param groupRecord {@link RelationshipGroupRecord} to cache. * @return whether or not the group was cached, i.e. whether or not it was within the prepared range. */ public boolean put( RelationshipGroupRecord groupRecord ) { long nodeId = groupRecord.getOwningNode(); assert nodeId < highNodeId; if ( nodeId < fromNodeId || nodeId >= toNodeId ) { return false; } long baseIndex = offsets.get( rebase( nodeId ) ); // grouCount is extra validation, really int groupCount = groupCount( nodeId ); long index = scanForFreeFrom( baseIndex, groupCount, groupRecord.getType(), nodeId ); // Put the group at this index cache.setByte( index, 0, (byte) 1 ); cache.set3ByteInt( index, 1, groupRecord.getType() ); cache.set6ByteLong( index, 1 + 3, groupRecord.getFirstOut() ); cache.set6ByteLong( index, 1 + 3 + 6, groupRecord.getFirstIn() ); cache.set6ByteLong( index, 1 + 3 + 6 + 6, groupRecord.getFirstLoop() ); return true; }
@Test public void shouldHandleGroupCountBeyondSignedShortRange() { // GIVEN long nodeId = 0; int limit = Short.MAX_VALUE + 10; RelationshipGroupCache cache = new RelationshipGroupCache( HEAP, ByteUnit.kibiBytes( 100 ), nodeId + 1 ); // WHEN first counting all groups per node for ( int type = 0; type < limit; type++ ) { cache.incrementGroupCount( nodeId ); } // and WHEN later putting group records into the cache RelationshipGroupRecord group = new RelationshipGroupRecord( -1 ); group.setOwningNode( nodeId ); for ( int type = 0; type < limit; type++ ) { group.setId( type ); group.setFirstOut( type ); // just some relationship group.setType( type ); cache.put( group ); } long prepared = cache.prepare( nodeId ); // THEN that should work, because it used to fail inside prepare, but we can also ask // the groupCount method to be sure assertEquals( nodeId, prepared ); assertEquals( limit, cache.groupCount( nodeId ) ); }
/** * Before caching any relationship groups all group counts for all nodes are incremented by calling * this method once for every encountered group (its node id). * * @param nodeId node to increment group count for. */ public void incrementGroupCount( long nodeId ) { int count = groupCount( nodeId ); count++; if ( (count & ~0xFFFF) != 0 ) { throw new IllegalStateException( "Invalid number of relationship groups for node " + nodeId + " " + count ); } groupCountCache.setShort( nodeId, 0, (short) count ); }
private void findNextNodeWithGroupsIfNeeded() { if ( countLeftForThisNode == 0 ) { do { nodeId++; countLeftForThisNode = nodeId >= groupCountCache.length() ? 0 : groupCount( nodeId ); } while ( countLeftForThisNode == 0 && nodeId < groupCountCache.length() ); } } };
private int countLeftForThisNode = groupCount( nodeId );
/** * Looks at max amount of configured memory (in constructor) and figures out for how many nodes their groups * can be cached. Before the first call to this method all {@link #incrementGroupCount(long)} calls * must have been made. After a call to this there should be a sequence of {@link #put(RelationshipGroupRecord)} * calls to cache the groups. If this call returns a node id which is lower than the highest node id in the * store then more rounds of caching should be performed after completing this round. * * @param fromNodeId inclusive * @return toNodeId exclusive */ public long prepare( long fromNodeId ) { cache.clear(); // this will have all the "first" bytes set to 0, which means !inUse this.fromNodeId = fromNodeId; // keep for use in put later on highCacheId = 0; for ( long nodeId = fromNodeId; nodeId < highNodeId; nodeId++ ) { int count = groupCount( nodeId ); if ( highCacheId + count > maxCacheLength ) { // Cannot include this one, so up until the previous is good return this.toNodeId = nodeId; } offsets.set( rebase( nodeId ), highCacheId ); highCacheId += count; } return this.toNodeId = highNodeId; }
/** * Caches a relationship group into this cache, it will be cached if the * {@link RelationshipGroupRecord#getOwningNode() owner} is within the {@link #prepare(long) prepared} range, * where {@code true} will be returned, otherwise {@code false}. * * @param groupRecord {@link RelationshipGroupRecord} to cache. * @return whether or not the group was cached, i.e. whether or not it was within the prepared range. */ public boolean put( RelationshipGroupRecord groupRecord ) { long nodeId = groupRecord.getOwningNode(); assert nodeId < highNodeId; if ( nodeId < fromNodeId || nodeId >= toNodeId ) { return false; } long baseIndex = offsets.get( rebase( nodeId ) ); // grouCount is extra validation, really int groupCount = groupCount( nodeId ); long index = scanForFreeFrom( baseIndex, groupCount, groupRecord.getType(), nodeId ); // Put the group at this index cache.setByte( index, 0, (byte) 1 ); cache.set3ByteInt( index, 1, groupRecord.getType() ); cache.set6ByteLong( index, 1 + 3, groupRecord.getFirstOut() ); cache.set6ByteLong( index, 1 + 3 + 6, groupRecord.getFirstIn() ); cache.set6ByteLong( index, 1 + 3 + 6 + 6, groupRecord.getFirstLoop() ); return true; }