100行PHP解析LevelDB SSTableファイル
26323 ワード
LevelDBのSSTableファイルを開いて、中のkey-valueデータを解析して印刷して、SSTableフォーマットを理解する必要があるのは見てもいいです.
概略解析手順:末尾のFooter情報を先に読み出し、Footerは48バイトに固定され、Index blockがファイル中で オフセットしていることがFooterから分かる.はIndex Blockを解析し、Index Blockから各Data Blockのファイル内の位置を知ることができる.Index Blockの各項目はkey-value構造であり、valueが格納されているのは対応するData Blockの位置と大きさであり、keyは対応するData Blockの中で最大のkeyであり、まだ少し大きいkeyである.例えば、Data Blockの中で最大のkeyはhelloであり、Index Blockのkeyはhelloa である.各Data Blockを解析し、各key-value対 を得る.
概略解析手順:
$data = file_get_contents('testdb/000009.ldb');
$size = strlen($data);
// footer = meta block handle + index block handle + padding + 8-byte magic number
// footer is fixed 48 byte, parse from this offset
$offset = $size - 48;
// meta block is used for performance, so we can skip parse it
list($metaBlockOffset,$metaBlockSize,$offset) = decodeBlockHandle($offset);
list($idxBlockOffset,$idxBlockSize,$offset) = decodeBlockHandle($offset);
// when parsed one k-v item from index block, this callback function will run
$handleIndexBlockItem = function($key, $seq, $kType, $valBegin, $valLen) {
global $data;
list($datablockOffset,$datablockSize,) = decodeBlockHandle($valBegin);
readBlock($datablockOffset, $datablockSize, function ($key, $seq, $kType, $valBegin, $valLen) {
global $data;
$value = substr($data, $valBegin, $valLen);
echo $key . ' = ' . $value . PHP_EOL;
});
};
readBlock($idxBlockOffset, $idxBlockSize, $handleIndexBlockItem);
function readBlock($offset, $size, $itemCallback) {
global $data;
$blockType = ord($data[$offset]);
$blockChecksum = decodeInt32($offset+1);
$restartNum = decodeInt32($offset+$size-4);
printf("Block: offset=%s, size=%d, type=%d, num_of_restarts=%d, crc32c=%d
",
dechex($offset), $size, $blockType, $restartNum, $blockChecksum);
$cur = $offset;
$currentSharedKey = '';
while ($cur != $offset+$size-4-$restartNum*4) {
// format: shared key length | unshared key length | value length | unshared key | value
$begin = $cur;
list($shareLen, $len) = decodeVarInt($cur);
$cur += $len;
list($unshareLen, $len) = decodeVarInt($cur);
$cur += $len;
list($valLen, $len) = decodeVarInt($cur);
$cur += $len;
$unsharedKey = substr($data, $cur, $unshareLen-8);
if ($shareLen === 0) {
$currentSharedKey = $unsharedKey;
}
// key format: user's binary key | 7-byte sequence number | 1-byte type code
$key = substr($currentSharedKey, 0, $shareLen) . $unsharedKey;
$int = decodeInt64($cur+$unshareLen-8);
$seq = $int >> 8;
$kType = $seq && 0xff; // enum ValueType { kTypeDeletion = 0x0, kTypeValue = 0x1 };
$cur += $unshareLen;
$valBegin = $cur;
if (is_callable($itemCallback)) {
$itemCallback($key, $seq, $kType, $valBegin, $valLen);
}
$cur += $valLen;
}
}
function decodeBlockHandle($offset) {
global $data;
list($blockOffset, $offsetLen) = decodeVarInt($offset);
list($blockSize, $sizeLen) = decodeVarInt($offset+$offsetLen);
return [$blockOffset, $blockSize, $offset+$offsetLen+$sizeLen];
}
function decodeVarInt($offset) {
global $data;
$len = 0;
$result = 0;
while (true) {
$byte = ord($data[$offset+$len]);
$result |= ($byte & 0x7f) << (7 * $len);
$len++;
if (($byte & 0x80) === 0) {
// stop when first bit of byte is 0
break;
}
}
return [$result, $len];
}
function decodeInt32($offset) {
return decodeFixedInt($offset, 32);
}
function decodeInt64($offset) {
return decodeFixedInt($offset, 64);
}
function decodeFixedInt($offset, $bitLen) {
global $data;
$result = 0;
for ($i = 0; $i != $bitLen / 8; $i++) {
$byte = ord($data[$offset+$i]);
$result |= $byte << (8*$i);
}
return $result;
}