Skip to content

Commit

Permalink
Fixes an issue with storing collision values in page cache number array
Browse files Browse the repository at this point in the history
Fixes #11888
  • Loading branch information
tinwelint committed Jul 31, 2018
1 parent 8a393f2 commit 4fd4786
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 25 deletions.
Expand Up @@ -39,7 +39,7 @@ public class PageCachedNumberArrayFactory extends NumberArrayFactory.Adapter
private final PageCache pageCache; private final PageCache pageCache;
private final File storeDir; private final File storeDir;


PageCachedNumberArrayFactory( PageCache pageCache, File storeDir ) public PageCachedNumberArrayFactory( PageCache pageCache, File storeDir )
{ {
Objects.requireNonNull( pageCache ); Objects.requireNonNull( pageCache );
this.pageCache = pageCache; this.pageCache = pageCache;
Expand Down
Expand Up @@ -26,7 +26,7 @@


import static java.lang.Integer.min; import static java.lang.Integer.min;
import static java.lang.Long.max; import static java.lang.Long.max;
import static java.lang.Long.min; import static org.neo4j.io.pagecache.PageCache.PAGE_SIZE;


/** /**
* Stores {@link String strings} in a {@link ByteArray} provided by {@link NumberArrayFactory}. Each string can have different * Stores {@link String strings} in a {@link ByteArray} provided by {@link NumberArrayFactory}. Each string can have different
Expand All @@ -41,7 +41,14 @@ public class StringCollisionValues implements CollisionValues


public StringCollisionValues( NumberArrayFactory factory, long length ) public StringCollisionValues( NumberArrayFactory factory, long length )
{ {
chunkSize = max( length, 10_000 ); // Let's have length (also chunk size) be divisible by PAGE_SIZE, such that our calculations below
// works for all NumberArray implementations.
if ( length % PAGE_SIZE != 0 )
{
length = ((length - 1) / PAGE_SIZE + 1) * PAGE_SIZE;
}

chunkSize = max( length, PAGE_SIZE );
cache = factory.newDynamicByteArray( chunkSize, new byte[1] ); cache = factory.newDynamicByteArray( chunkSize, new byte[1] );
current = cache.at( 0 ); current = cache.at( 0 );
} }
Expand All @@ -57,17 +64,10 @@ public long add( Object id )
throw new IllegalArgumentException( string ); throw new IllegalArgumentException( string );
} }


long bytesLeftInThisChunk = bytesLeftInCurrentChunk();
if ( bytesLeftInThisChunk < Short.BYTES + 1 )
{
// There isn't enough space left in the current chunk to begin writing this value, move over to the next one
offset += chunkSize - (offset % chunkSize);
current = cache.at( offset );
}

long startOffset = offset; long startOffset = offset;
current.setShort( offset, 0, (short) length ); cache.setByte( offset++, 0, (byte) length );
offset += Short.BYTES; cache.setByte( offset++, 0, (byte) (length >>> Byte.SIZE) );
current = cache.at( offset );
for ( int i = 0; i < length; ) for ( int i = 0; i < length; )
{ {
int bytesLeftToWrite = length - i; int bytesLeftToWrite = length - i;
Expand All @@ -87,18 +87,11 @@ public long add( Object id )
return startOffset; return startOffset;
} }


private long bytesLeftInCurrentChunk()
{
long rest = offset % chunkSize;
return rest == 0 ? 0 : chunkSize - rest;
}

@Override @Override
public Object get( long offset ) public Object get( long offset )
{ {
int length = (cache.getByte( offset++, 0 ) & 0xFF) | ((cache.getByte( offset++, 0 ) & 0xFF) << Byte.SIZE);
ByteArray array = cache.at( offset ); ByteArray array = cache.at( offset );
int length = array.getShort( offset, 0 ) & 0xFFFF;
offset += Short.BYTES;
byte[] bytes = new byte[length]; byte[] bytes = new byte[length];
for ( int i = 0; i < length; ) for ( int i = 0; i < length; )
{ {
Expand Down
Expand Up @@ -28,12 +28,17 @@


import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.function.Function;


import org.neo4j.test.Randoms; import org.neo4j.test.Randoms;
import org.neo4j.test.rule.PageCacheAndDependenciesRule;
import org.neo4j.test.rule.RandomRule; import org.neo4j.test.rule.RandomRule;
import org.neo4j.test.rule.fs.DefaultFileSystemRule;
import org.neo4j.unsafe.impl.batchimport.cache.NumberArrayFactory; import org.neo4j.unsafe.impl.batchimport.cache.NumberArrayFactory;
import org.neo4j.unsafe.impl.batchimport.cache.PageCachedNumberArrayFactory;


import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.neo4j.io.pagecache.PageCache.PAGE_SIZE;
import static org.neo4j.unsafe.impl.batchimport.cache.NumberArrayFactory.AUTO_WITHOUT_PAGECACHE; import static org.neo4j.unsafe.impl.batchimport.cache.NumberArrayFactory.AUTO_WITHOUT_PAGECACHE;
import static org.neo4j.unsafe.impl.batchimport.cache.NumberArrayFactory.CHUNKED_FIXED_SIZE; import static org.neo4j.unsafe.impl.batchimport.cache.NumberArrayFactory.CHUNKED_FIXED_SIZE;
import static org.neo4j.unsafe.impl.batchimport.cache.NumberArrayFactory.HEAP; import static org.neo4j.unsafe.impl.batchimport.cache.NumberArrayFactory.HEAP;
Expand All @@ -42,6 +47,8 @@
@RunWith( Parameterized.class ) @RunWith( Parameterized.class )
public class StringCollisionValuesTest public class StringCollisionValuesTest
{ {
@Rule
public final PageCacheAndDependenciesRule storage = new PageCacheAndDependenciesRule( DefaultFileSystemRule::new, getClass() );
@Rule @Rule
public final RandomRule random = new RandomRule().withConfiguration( new Randoms.Default() public final RandomRule random = new RandomRule().withConfiguration( new Randoms.Default()
{ {
Expand All @@ -53,19 +60,24 @@ public int stringMaxLength()
} ); } );


@Parameters @Parameters
public static Collection<NumberArrayFactory> data() public static Collection<Function<PageCacheAndDependenciesRule,NumberArrayFactory>> data()
{ {
return Arrays.asList( HEAP, OFF_HEAP, AUTO_WITHOUT_PAGECACHE, CHUNKED_FIXED_SIZE ); return Arrays.asList(
storage -> HEAP,
storage -> OFF_HEAP,
storage -> AUTO_WITHOUT_PAGECACHE,
storage -> CHUNKED_FIXED_SIZE,
storage -> new PageCachedNumberArrayFactory( storage.pageCache(), storage.directory().directory() ) );
} }


@Parameter( 0 ) @Parameter( 0 )
public NumberArrayFactory factory; public Function<PageCacheAndDependenciesRule,NumberArrayFactory> factory;


@Test @Test
public void shouldStoreAndLoadStrings() public void shouldStoreAndLoadStrings()
{ {
// given // given
try ( StringCollisionValues values = new StringCollisionValues( factory, 10_000 ) ) try ( StringCollisionValues values = new StringCollisionValues( factory.apply( storage ), 10_000 ) )
{ {
// when // when
long[] offsets = new long[100]; long[] offsets = new long[100];
Expand All @@ -84,4 +96,27 @@ public void shouldStoreAndLoadStrings()
} }
} }
} }

@Test
public void shouldMoveOverToNextChunkOnNearEnd()
{
// given
try ( StringCollisionValues values = new StringCollisionValues( factory.apply( storage ), 10_000 ) )
{
char[] chars = new char[PAGE_SIZE - 3];
Arrays.fill( chars, 'a' );

// when
String string = String.valueOf( chars );
long offset = values.add( string );
String secondString = "abcdef";
long secondOffset = values.add( secondString );

// then
String readString = (String) values.get( offset );
assertEquals( string, readString );
String readSecondString = (String) values.get( secondOffset );
assertEquals( secondString, readSecondString );
}
}
} }

0 comments on commit 4fd4786

Please sign in to comment.