Skip to content

Commit

Permalink
Fixed references in enterprise format
Browse files Browse the repository at this point in the history
Because of variable length encoding of references decoding and encoding or records in enterprise format takes some additional time.
To minimize damage for common case of small records possibility of fixed records introduced.
From now on in case if record can be encoded in a fixed reference format (criteria based on references that are used in record) it will be used instead of variable-length format.
In case if record can't be encoded in fixed references format - default variable-length format will be used.
  • Loading branch information
MishaDemianenko committed Aug 2, 2016
1 parent ed0e510 commit 3f295e1
Show file tree
Hide file tree
Showing 8 changed files with 373 additions and 58 deletions.
Expand Up @@ -41,6 +41,7 @@ public abstract class AbstractBaseRecord implements CloneableInPublic
private boolean requiresSecondaryUnit;
private boolean inUse;
private boolean created;
private boolean useFixedReferences;

protected AbstractBaseRecord( long id )
{
Expand All @@ -54,6 +55,7 @@ protected AbstractBaseRecord initialize( boolean inUse )
this.created = false;
this.secondaryUnitId = NO_ID;
this.requiresSecondaryUnit = false;
this.useFixedReferences = false;
return this;
}

Expand All @@ -69,6 +71,7 @@ public void clear()
created = false;
secondaryUnitId = NO_ID;
requiresSecondaryUnit = false;
this.useFixedReferences = false;
}

public long getId()
Expand Down Expand Up @@ -143,6 +146,16 @@ public final boolean isCreated()
return created;
}

public boolean isUseFixedReferences()
{
return useFixedReferences;
}

public void setUseFixedReferences( boolean useFixedReferences )
{
this.useFixedReferences = useFixedReferences;
}

@Override
public int hashCode()
{
Expand Down
Expand Up @@ -84,6 +84,7 @@ abstract class BaseHighLimitRecordFormat<RECORD extends AbstractBaseRecord>
static final long NULL = Record.NULL_REFERENCE.intValue();
static final int HEADER_BIT_RECORD_UNIT = 0b0000_0010;
static final int HEADER_BIT_FIRST_RECORD_UNIT = 0b0000_0100;
private static final int HEADER_BIT_FIXED_REFERENCE = 0b0000_0100;

protected BaseHighLimitRecordFormat( Function<StoreHeader,Integer> recordSize, int recordHeaderSize )
{
Expand Down Expand Up @@ -140,6 +141,7 @@ public void read( RECORD record, PageCursor primaryCursor, RecordLoad mode, int
}
else
{
record.setUseFixedReferences( has( headerByte, HEADER_BIT_FIXED_REFERENCE ) );
doReadInternal( record, primaryCursor, recordSize, headerByte, inUse );
}
}
Expand All @@ -164,7 +166,14 @@ public void write( RECORD record, PageCursor primaryCursor, int recordSize )
") collides with format-generic header bits";
headerByte = set( headerByte, IN_USE_BIT, record.inUse() );
headerByte = set( headerByte, HEADER_BIT_RECORD_UNIT, record.requiresSecondaryUnit() );
headerByte = set( headerByte, HEADER_BIT_FIRST_RECORD_UNIT, true );
if ( record.requiresSecondaryUnit() )
{
headerByte = set( headerByte, HEADER_BIT_FIRST_RECORD_UNIT, true );
}
else
{
headerByte = set( headerByte, HEADER_BIT_FIXED_REFERENCE, record.isUseFixedReferences() );
}
primaryCursor.putByte( headerByte );

if ( record.requiresSecondaryUnit() )
Expand Down Expand Up @@ -232,18 +241,24 @@ public final void prepare( RECORD record, int recordSize, IdSequence idSequence
{
if ( record.inUse() )
{
int requiredLength = HEADER_BYTE + requiredDataLength( record );
boolean requiresSecondaryUnit = requiredLength > recordSize;
record.setRequiresSecondaryUnit( requiresSecondaryUnit );
if ( record.requiresSecondaryUnit() && !record.hasSecondaryUnitId() )
record.setUseFixedReferences( canUseFixedReferences( record ));
if ( !record.isUseFixedReferences() )
{
// Allocate a new id at this point, but this is not the time to free this ID the the case where
// this record doesn't need this secondary unit anymore... that needs to be done when applying to store.
record.setSecondaryUnitId( idSequence.nextId() );
int requiredLength = HEADER_BYTE + requiredDataLength( record );
boolean requiresSecondaryUnit = requiredLength > recordSize;
record.setRequiresSecondaryUnit( requiresSecondaryUnit );
if ( record.requiresSecondaryUnit() && !record.hasSecondaryUnitId() )
{
// Allocate a new id at this point, but this is not the time to free this ID the the case where
// this record doesn't need this secondary unit anymore... that needs to be done when applying to store.
record.setSecondaryUnitId( idSequence.nextId() );
}
}
}
}

protected abstract boolean canUseFixedReferences( RECORD record );

/**
* Required length of the data in the given record (without the header byte).
*
Expand Down
Expand Up @@ -22,6 +22,7 @@
import java.io.IOException;

import org.neo4j.io.pagecache.PageCursor;
import org.neo4j.kernel.impl.store.format.BaseRecordFormat;
import org.neo4j.kernel.impl.store.record.NodeRecord;
import org.neo4j.kernel.impl.store.record.Record;

Expand Down Expand Up @@ -69,13 +70,32 @@ protected void doReadInternal( NodeRecord record, PageCursor cursor, int recordS
{
// Interpret the header byte
boolean dense = has( headerByte, DENSE_NODE_BIT );
if ( record.isUseFixedReferences() )
{
byte modifiers = cursor.getByte();
long relModifier = (modifiers & 0b0000_0001L) << 32;
long propModifier = (modifiers & 0b0000_0110L) << 31;

// Now read the rest of the data. The adapter will take care of moving the cursor over to the
// other unit when we've exhausted the first one.
long nextRel = decodeCompressedReference( cursor, headerByte, HAS_RELATIONSHIP_BIT, NULL );
long nextProp = decodeCompressedReference( cursor, headerByte, HAS_PROPERTY_BIT, NULL );
long labelField = decodeCompressedReference( cursor, headerByte, HAS_LABELS_BIT, NULL_LABELS );
record.initialize( inUse, nextProp, dense, nextRel, labelField );
long nextRel = cursor.getInt() & 0xFFFFFFFFL;
long nextProp = cursor.getInt() & 0xFFFFFFFFL;

long lsbLabels = cursor.getInt() & 0xFFFFFFFFL;
long hsbLabels = cursor.getByte() & 0xFF; // so that a negative byte won't fill the "extended" bits with ones.
long labels = lsbLabels | (hsbLabels << 32);

record.initialize( inUse,
BaseRecordFormat.longFromIntAndMod( nextProp, propModifier ), dense,
BaseRecordFormat.longFromIntAndMod( nextRel, relModifier ), labels );
}
else
{
// Now read the rest of the data. The adapter will take care of moving the cursor over to the
// other unit when we've exhausted the first one.
long nextRel = decodeCompressedReference( cursor, headerByte, HAS_RELATIONSHIP_BIT, NULL );
long nextProp = decodeCompressedReference( cursor, headerByte, HAS_PROPERTY_BIT, NULL );
long labelField = decodeCompressedReference( cursor, headerByte, HAS_LABELS_BIT, NULL_LABELS );
record.initialize( inUse, nextProp, dense, nextRel, labelField );
}
}

@Override
Expand All @@ -97,12 +117,44 @@ protected byte headerBits( NodeRecord record )
return header;
}

@Override
protected boolean canUseFixedReferences( NodeRecord record )
{
return (((record.getNextProp() == NULL) || ((record.getNextProp() & 0xFFFF_FFFC_0000_0000L) == 0)) &&
((record.getNextRel() == NULL) || ((record.getNextRel() & 0xFFFF_FFFE_0000_0000L) == 0)));
}

@Override
protected void doWriteInternal( NodeRecord record, PageCursor cursor )
throws IOException
{
encode( cursor, record.getNextRel(), NULL );
encode( cursor, record.getNextProp(), NULL );
encode( cursor, record.getLabelField(), NULL_LABELS );
if ( record.isUseFixedReferences() )
{
long nextRel = record.getNextRel();
long nextProp = record.getNextProp();

short relModifier = nextRel == Record.NO_NEXT_RELATIONSHIP.intValue() ? 0 : (short)((nextRel & 0x1_0000_0000L) >> 32);
short propModifier = nextProp == Record.NO_NEXT_PROPERTY.intValue() ? 0 : (short) ((nextProp & 0x3_0000_0000L) >> 31);

// [ ,xxxx] higher bits for rel id
// [xxxx, ] higher bits for prop id
short modifiers = (short) ( relModifier | propModifier );

cursor.putByte( (byte) modifiers );
cursor.putInt( (int) nextRel );
cursor.putInt( (int) nextProp );

// lsb of labels
long labelField = record.getLabelField();
cursor.putInt( (int) labelField );
// msb of labels
cursor.putByte( (byte) ((labelField & 0xFF_0000_0000L) >> 32) );
}
else
{
encode( cursor, record.getNextRel(), NULL );
encode( cursor, record.getNextProp(), NULL );
encode( cursor, record.getLabelField(), NULL_LABELS );
}
}
}
Expand Up @@ -23,12 +23,15 @@

import org.neo4j.io.pagecache.PageCursor;
import org.neo4j.kernel.impl.store.format.BaseOneByteHeaderRecordFormat;
import org.neo4j.kernel.impl.store.format.BaseRecordFormat;
import org.neo4j.kernel.impl.store.record.PropertyBlock;
import org.neo4j.kernel.impl.store.record.PropertyRecord;
import org.neo4j.kernel.impl.store.record.Record;
import org.neo4j.kernel.impl.store.record.RecordLoad;

import static org.neo4j.kernel.impl.store.format.highlimit.Reference.toAbsolute;
import static org.neo4j.kernel.impl.store.format.highlimit.Reference.toRelative;
import static org.neo4j.kernel.impl.store.record.Record.NULL_REFERENCE;


/**
Expand Down Expand Up @@ -68,13 +71,31 @@ public void read( PropertyRecord record, PageCursor cursor, RecordLoad mode, int
int offset = cursor.getOffset();
byte headerByte = cursor.getByte();
boolean inUse = isInUse( headerByte );
record.setUseFixedReferences( has( headerByte, 0b0000_0100 ));
if ( mode.shouldLoad( inUse ) )
{
int blockCount = headerByte >>> 4;
long recordId = record.getId();
record.initialize( inUse,
toAbsolute( Reference.decode( cursor ), recordId ),
toAbsolute( Reference.decode( cursor ), recordId ) );

if (record.isUseFixedReferences())
{
// since fixed reference limits property reference to 34 bits, 6 bytes is ample.
long prevMod = (cursor.getShort() & 0xFFFFL);
long prevProp = cursor.getInt() & 0xFFFFFFFFL;
long nextMod = (cursor.getShort() & 0xFFFFL);
long nextProp = cursor.getInt() & 0xFFFFFFFFL;
record.initialize( true,
BaseRecordFormat.longFromIntAndMod( prevProp, prevMod << 32 ),
BaseRecordFormat.longFromIntAndMod( nextProp, nextMod << 32 ) );
//skip 3 bytes before start reading property blocks
cursor.setOffset(cursor.getOffset() + 3);
}
else
{
record.initialize( inUse,
toAbsolute( Reference.decode( cursor ), recordId ),
toAbsolute( Reference.decode( cursor ), recordId ) );
}
if ( (blockCount > record.getBlockCapacity()) | (RECORD_SIZE - (cursor.getOffset() - offset) < blockCount * Long.BYTES) )
{
cursor.setCursorException( "PropertyRecord claims to contain more blocks than can fit in a record" );
Expand All @@ -93,10 +114,31 @@ public void write( PropertyRecord record, PageCursor cursor, int recordSize )
{
if ( record.inUse() )
{
cursor.putByte( (byte) ((record.inUse() ? IN_USE_BIT : 0) | numberOfBlocks( record ) << 4) );
byte headerByte = (byte) ((record.inUse() ? IN_USE_BIT : 0) | numberOfBlocks( record ) << 4);
boolean canUseFixedReferences = canUseFixedReferences( record );
record.setUseFixedReferences( canUseFixedReferences );
headerByte = set( headerByte, 0b0000_0100, canUseFixedReferences );
cursor.putByte( headerByte );

long recordId = record.getId();
Reference.encode( toRelative( record.getPrevProp(), recordId), cursor );
Reference.encode( toRelative( record.getNextProp(), recordId), cursor );

if ( canUseFixedReferences )
{
// Set up the record header
short prevModifier = record.getPrevProp() == Record.NO_NEXT_PROPERTY.intValue() ? 0 : (short) ((record.getPrevProp() & 0xFFFF_0000_0000L) >> 32);
short nextModifier = record.getNextProp() == Record.NO_NEXT_PROPERTY.intValue() ? 0 : (short) ((record.getNextProp() & 0xFFFF_0000_0000L) >> 32);
cursor.putShort( prevModifier );
cursor.putInt( (int) record.getPrevProp() );
cursor.putShort( nextModifier );
cursor.putInt( (int) record.getNextProp() );
//just stuff 3 bytes
cursor.putBytes(new byte[]{0,0,0});
}
else
{
Reference.encode( toRelative( record.getPrevProp(), recordId ), cursor );
Reference.encode( toRelative( record.getNextProp(), recordId ), cursor );
}
for ( PropertyBlock block : record )
{
for ( long propertyBlock : block.getValueBlocks() )
Expand Down Expand Up @@ -126,4 +168,10 @@ public long getNextRecordReference( PropertyRecord record )
{
return record.getNextProp();
}

private boolean canUseFixedReferences( PropertyRecord record )
{
return (((record.getNextProp() == NULL_REFERENCE.intValue()) || ((record.getNextProp() & 0xFFFF_FFFC_0000_0000L) == 0)) &&
((record.getPrevProp() == NULL_REFERENCE.intValue()) || ((record.getPrevProp() & 0xFFFF_FFFE_0000_0000L) == 0)));
}
}

0 comments on commit 3f295e1

Please sign in to comment.