Skip to content

Commit

Permalink
Shrink memory pool addresses by 1 byte.
Browse files Browse the repository at this point in the history
  MemoryPoolAddress is changed from 5 bytes to 4.
  One byte remains for the chunk index,
  but only three bytes are kept for the slot.
  Three bytes is up to 16.77 million slots
  in a single chunk.    For a chunk configured for
  8 byte fixedKeySize, this would imply 550MB + per chunk,
  with 255 total chunks allowed, for over 130GB of
  data in a single segment.  As segments can scale themselves,
  this shrink does not introduce any real impediment to
  the maximum size of a db.

  However, it does save at least 2 bytes per entry.
  One for the table slot, and one for each memory pool slot.
  • Loading branch information
Scott Carey committed Dec 6, 2019
1 parent c3b61ca commit 5dcca23
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 130 deletions.
56 changes: 31 additions & 25 deletions src/main/java/com/oath/halodb/MemoryPoolAddress.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,46 @@
package com.oath.halodb;

/**
* Represents the address of an entry in the memory pool. It will have two components: the index of the chunk which
* contains the entry and the offset within the chunk.
* Represents the address of an entry in the memory pool.
*
* 1 byte -- chunkIndex as an int between 0 and 255, valid chunks are 1 to 255, 0 indicates an empty address
* 3 bytes -- slot as an int between 0 and 2^24-1 (16.77 million).
*
* With slots using 8 byte 'fixedKeyLength', each slot is 33 bytes, and so each chunk in the memory pool
* could hold over 550MB of key data and metadata. There can be 255 slots, so each segment can fit over
* 141GB of data in RAM, and there is typically at least 16 segments.
*/
class MemoryPoolAddress {
interface MemoryPoolAddress {
int ADDRESS_SIZE = 4;
int MAX_NUMBER_OF_SLOTS = (1 << 24) - 1;

static final MemoryPoolAddress empty = new MemoryPoolAddress((byte)0, 0);
int empty = 0;

final int chunkIndex;
final int slot;
static int encode(int chunkIndex, int slot) {
if ((chunkIndex >>> 8) != 0) {
throw new IllegalArgumentException("Invalid chunk index, must be within [0,255], but was: " + chunkIndex);
}
if ((slot & 0xFF00_0000) != 0) {
throw new IllegalArgumentException("Invalid memory pool slot, must be within [0,2^24)" + slot);
}
return chunkIndex << 24 | slot & 0x00FF_FFFF;
}

MemoryPoolAddress(byte chunkIndex, int slot) {
this.chunkIndex = 0xFF & chunkIndex;
if ((slot & 0xFF000000) != 0) throw new IllegalArgumentException();
this.slot = slot;
/** Always between 0 and (2^24 -1) **/
static int slot(int memoryPoolAddress) {
return memoryPoolAddress & 0x00FF_FFFF;
}

final boolean isEmpty() {
return chunkIndex == 0;
/** Always between 0 and 255 **/
static int chunkIndex(int memoryPoolAddress) {
return memoryPoolAddress >>> 24;
}

@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof MemoryPoolAddress)) {
return false;
}
MemoryPoolAddress m = (MemoryPoolAddress) o;
return m.chunkIndex == chunkIndex && m.slot == slot;
static boolean isEmpty(int memoryPoolAddress) {
return memoryPoolAddress == 0;
}

@Override
public int hashCode() {
return 31 * ((31 * chunkIndex) + slot);
static boolean nonEmpty(int memoryPoolAddress) {
return memoryPoolAddress != 0;
}
}
57 changes: 30 additions & 27 deletions src/main/java/com/oath/halodb/MemoryPoolChunk.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@

package com.oath.halodb;

import static com.oath.halodb.MemoryPoolHashEntries.ENTRY_OFF_NEXT_CHUNK_INDEX;
import static com.oath.halodb.MemoryPoolHashEntries.ENTRY_OFF_NEXT_CHUNK_OFFSET;
import static com.oath.halodb.MemoryPoolHashEntries.HEADER_SIZE;

/**
* Memory pool is divided into chunks of configurable sized slots. This represents such a chunk.
*
Expand All @@ -19,7 +15,7 @@
* that is less than or equal to the fixedKeyLength, then the key and data all
* fit in one slot. In this case, the slot is as follows:
*
* 5 bytes -- MemoryPoolAddress pointer (next)
* 4 bytes -- MemoryPoolAddress pointer (next)
* 5 bytes -- HashEntry sizes (key/value length)
* fixedKeyLength bytes -- key data
* 16 bytes -- HashEntry location data (fileId, fileOffset, sequenceId)
Expand All @@ -28,7 +24,7 @@
* slots in the list, chained together. The remainder of the key 'overflows' into
* additional slots structured as follows:
*
* 5 bytes -- MemoryPoolAddress pointer (next)
* 4 bytes -- MemoryPoolAddress pointer (next)
* remaining slot bytes -- key fragment
*
* The number of slots that a key of size K requires is
Expand All @@ -44,7 +40,7 @@
*/
class MemoryPoolChunk<E extends HashEntry> {

private static final int sizesOffset = HEADER_SIZE;
private static final int sizesOffset = MemoryPoolAddress.ADDRESS_SIZE;

private final int chunkId;
private final long address;
Expand All @@ -55,6 +51,7 @@ class MemoryPoolChunk<E extends HashEntry> {
private final int slots;
private final HashEntrySerializer<E> serializer;
private int writeSlot = 0;
private boolean destroyed = false;

private MemoryPoolChunk(long address, int chunkId, int slots, int fixedKeyLength, HashEntrySerializer<E> serializer) {
this.address = address;
Expand All @@ -63,26 +60,31 @@ private MemoryPoolChunk(long address, int chunkId, int slots, int fixedKeyLength
this.fixedKeyLength = fixedKeyLength;
this.fixedKeyOffset = sizesOffset + serializer.sizesSize();
this.locationOffset = fixedKeyOffset + fixedKeyLength;
this.slotSize = MemoryPoolHashEntries.slotSize(fixedKeyLength, serializer);
this.slotSize = MemoryPoolChunk.slotSize(fixedKeyLength, serializer);
this.serializer = serializer;
}

static <E extends HashEntry> MemoryPoolChunk<E> create(int id, int chunkSize, int fixedKeyLength, HashEntrySerializer<E> serializer) {
int fixedSlotSize = MemoryPoolHashEntries.slotSize(fixedKeyLength, serializer);
int slots = chunkSize / fixedSlotSize;
int fixedSlotSize = MemoryPoolChunk.slotSize(fixedKeyLength, serializer);
int slots = Math.min((chunkSize / fixedSlotSize), MemoryPoolAddress.MAX_NUMBER_OF_SLOTS);
if (slots < 1) {
throw new IllegalArgumentException("fixedSlotSize " + fixedSlotSize + " must be smaller than chunkSize " + chunkSize);
throw new IllegalArgumentException("fixedSlotSize " + fixedSlotSize + " must not be larger than chunkSize " + chunkSize);
}
long address = Uns.allocate(slots * fixedSlotSize);
return new MemoryPoolChunk<>(address, id, slots, fixedKeyLength, serializer);
}

void destroy() {
Uns.free(address);
if (!destroyed) {
Uns.free(address);
destroyed = true;
}
}

public int chunkId() {
return chunkId;
@Override
protected void finalize() throws Throwable {
destroy();
super.finalize();
}

Slot slotFor(int slot) {
Expand Down Expand Up @@ -117,6 +119,10 @@ private int slotToOffset(int slot) {
return slot * slotSize;
}

public static int slotSize(int fixedKeySize, HashEntrySerializer<?> serializer) {
return sizesOffset + fixedKeySize + serializer.entrySize();
}

/** Represents a valid Slot within a MemoryPoolChunk **/
class Slot {
private final int slot;
Expand All @@ -126,26 +132,23 @@ private Slot(int slot) {
this.offset = slotToOffset(slot);
}

MemoryPoolAddress toAddress() {
return new MemoryPoolAddress((byte) chunkId, slot);
}

short getKeyLength() {
return serializer.readKeySize(sizeAddress());
}

MemoryPoolAddress getNextAddress() {
byte chunk = Uns.getByte(address, offset + ENTRY_OFF_NEXT_CHUNK_INDEX);
int slot = Uns.getInt(address, offset + ENTRY_OFF_NEXT_CHUNK_OFFSET);
return new MemoryPoolAddress(chunk, slot);
int toAddress() {
return MemoryPoolAddress.encode(chunkId, slot);
}

int getNextAddress() {
return Uns.getInt(address, offset);
}

void setNextAddress(MemoryPoolAddress next) {
Uns.putByte(address, offset + ENTRY_OFF_NEXT_CHUNK_INDEX, (byte) next.chunkIndex);
Uns.putInt(address, offset + ENTRY_OFF_NEXT_CHUNK_OFFSET, next.slot);
void setNextAddress(int nextAddress) {
Uns.putInt(address, offset, nextAddress);
}

void fillSlot(byte[] key, E entry, MemoryPoolAddress nextAddress) {
void fillSlot(byte[] key, E entry, int nextAddress) {
// pointer to next slot
setNextAddress(nextAddress);
// key and value sizes
Expand All @@ -156,7 +159,7 @@ void fillSlot(byte[] key, E entry, MemoryPoolAddress nextAddress) {
entry.serializeLocation(locationAddress());
}

void fillOverflowSlot(byte[] key, int keyoffset, int len, MemoryPoolAddress nextAddress) {
void fillOverflowSlot(byte[] key, int keyoffset, int len, int nextAddress) {
//poiner to next slot
setNextAddress(nextAddress);
// set key data
Expand Down
22 changes: 0 additions & 22 deletions src/main/java/com/oath/halodb/MemoryPoolHashEntries.java

This file was deleted.

Loading

0 comments on commit 5dcca23

Please sign in to comment.