Skip to content

Commit 768ef1f

Browse files
anand1976facebook-github-bot
authored andcommitted
User defined index reader and iterator (facebook#13727)
Summary: Pull Request resolved: facebook#13727 Add UserDefinedIndexReader and UserDefinedIndexIterator. The BlockBasedTable reads the user defined index meta block during open, verifies the checksum, pins in cache or heap depending on configuration, and allocates a UserDefinedIndexReader object with the contents. Similar to the builder, an IndexReader wrapper is allocated. The wrapper forwards the calls to the native reader and/or user defined index reader as appropriate. A new option, table_index_name, in ReadOptions specifies the index to use when creating a new Iterator. Reviewed By: pdillinger Differential Revision: D76165694 fbshipit-source-id: c30bde4c5ce91ea3dc9ad302e73fe4963c1ed457
1 parent f6841d1 commit 768ef1f

8 files changed

Lines changed: 416 additions & 18 deletions

File tree

include/rocksdb/options.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class Statistics;
5757
class InternalKeyComparator;
5858
class WalFilter;
5959
class FileSystem;
60+
class UserDefinedIndexFactory;
6061

6162
struct Options;
6263
struct DbPath;
@@ -2069,6 +2070,17 @@ struct ReadOptions {
20692070
// Default: false
20702071
bool auto_refresh_iterator_with_snapshot = false;
20712072

2073+
// EXPERIMENTAL
2074+
//
2075+
// Specify an alternate index to use in the SST files instead of the native
2076+
// block based table index. The table_factory used for the column family
2077+
// must support building/reading this index.
2078+
//
2079+
// Currently, only forward scans are supported. For forward scans, only Seek()
2080+
// is supported. SeekToFirst() is not supported. If the caller wishes to scan
2081+
// from start to end, the native index must be used.
2082+
const UserDefinedIndexFactory* table_index_factory = nullptr;
2083+
20722084
// *** END options only relevant to iterators or scans ***
20732085

20742086
// *** BEGIN options for RocksDB internal use only ***

include/rocksdb/user_defined_index.h

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111

1212
#include <string>
1313

14+
#include "rocksdb/advanced_iterator.h"
1415
#include "rocksdb/customizable.h"
16+
#include "rocksdb/options.h"
1517
#include "rocksdb/slice.h"
1618
#include "rocksdb/status.h"
1719

@@ -23,7 +25,8 @@ inline const std::string kUserDefinedIndexPrefix =
2325

2426
// This is a public API for user-defined index builders.
2527
// It allows users to define their own index format and build custom
26-
// indexes during table building.
28+
// indexes during table building. Currently, only a monolithic index
29+
// block is supported (no partitioned index).
2730

2831
// The interface for building user-defined index.
2932
class UserDefinedIndexBuilder {
@@ -52,6 +55,9 @@ class UserDefinedIndexBuilder {
5255
// @last_key_in_current_block: The last key in the current data block
5356
// @first_key_in_next_block: it will be nullptr if the entry being added is
5457
// the last one in the table
58+
// @block_handle: offset/size of the data block referenced by this index
59+
// entry. This should be stored along with the index entry
60+
// key
5561
// @separator_scratch: a scratch buffer to back a computed separator between
5662
// those, as needed. May be modified on each call.
5763
// @return: the key or separator stored in the index, which could be
@@ -76,13 +82,57 @@ class UserDefinedIndexBuilder {
7682
virtual Status Finish(Slice* index_contents) = 0;
7783
};
7884

85+
// The interface for iterating the user defined index. This will be
86+
// instantiated and used by a scan to iterate through the index entries
87+
// covered by the scan.
88+
class UserDefinedIndexIterator {
89+
public:
90+
virtual ~UserDefinedIndexIterator() = default;
91+
92+
// Given the target key, position the index iterator at the index entry
93+
// with the smallest key >= target. The result must be updated with the
94+
// index key, and the bound_check_result. The bound_check_result should
95+
// be set to kOutOfBound if no block satisfies the target key and
96+
// termination criteria, kInbound if the data block is definitely fully
97+
// within bounds, or kUnknown if the data block could be partially
98+
// within bounds.
99+
virtual Status SeekAndGetResult(const Slice& target,
100+
IterateResult* result) = 0;
101+
102+
// Advance to the next index entry. The result must be populated similar
103+
// to SeekAndGetResult.
104+
virtual Status NextAndGetResult(IterateResult* result) = 0;
105+
106+
// Return the BlockHandle in the current index entry
107+
virtual UserDefinedIndexBuilder::BlockHandle value() = 0;
108+
};
109+
110+
// A reader interface for the user defined index
111+
class UserDefinedIndexReader {
112+
public:
113+
virtual ~UserDefinedIndexReader() = default;
114+
115+
// Allocate an iterator that will be used by RocksDB to perform scans
116+
virtual std::unique_ptr<UserDefinedIndexIterator> NewIterator(
117+
const ReadOptions& read_options) = 0;
118+
119+
// The memory usage of the index, including the size of the raw contents and
120+
// any other heap data structures allocated by the reader
121+
virtual size_t ApproximateMemoryUsage() const = 0;
122+
};
123+
79124
// Factory for creating user-defined index builders.
80125
class UserDefinedIndexFactory : public Customizable {
81126
public:
82127
virtual ~UserDefinedIndexFactory() = default;
83128

84129
// Create a new builder for user-defined index.
85130
virtual UserDefinedIndexBuilder* NewBuilder() const = 0;
131+
132+
// Create a new user defined index reader given the contents of the index
133+
// block
134+
virtual std::unique_ptr<UserDefinedIndexReader> NewReader(
135+
Slice& index_block) const = 0;
86136
};
87137

88138
} // namespace ROCKSDB_NAMESPACE

table/block_based/block_based_table_reader.cc

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
#include "table/block_based/hash_index_reader.h"
6060
#include "table/block_based/partitioned_filter_block.h"
6161
#include "table/block_based/partitioned_index_reader.h"
62+
#include "table/block_based/user_defined_index_wrapper.h"
6263
#include "table/block_fetcher.h"
6364
#include "table/format.h"
6465
#include "table/get_context.h"
@@ -114,6 +115,7 @@ INSTANTIATE_BLOCKLIKE_TEMPLATES(Block_kIndex);
114115
INSTANTIATE_BLOCKLIKE_TEMPLATES(Block_kFilterPartitionIndex);
115116
INSTANTIATE_BLOCKLIKE_TEMPLATES(Block_kRangeDeletion);
116117
INSTANTIATE_BLOCKLIKE_TEMPLATES(Block_kMetaIndex);
118+
INSTANTIATE_BLOCKLIKE_TEMPLATES(Block_kUserDefinedIndex);
117119

118120
} // namespace ROCKSDB_NAMESPACE
119121

@@ -1319,6 +1321,34 @@ Status BlockBasedTable::PrefetchIndexAndFilterBlocks(
13191321
if (!s.ok()) {
13201322
return s;
13211323
}
1324+
if (table_options.user_defined_index_factory != nullptr) {
1325+
std::string udi_name(table_options.user_defined_index_factory->Name());
1326+
BlockHandle udi_block_handle;
1327+
1328+
// Should we use FindOptionalMetaBlock here?
1329+
s = FindMetaBlock(meta_iter, kUserDefinedIndexPrefix + udi_name,
1330+
&udi_block_handle);
1331+
if (!s.ok()) {
1332+
return s;
1333+
}
1334+
// Read the block, and allocate on heap or pin in cache. The UDI block is
1335+
// not compressed. RetrieveBlock will verify the checksum.
1336+
s = RetrieveBlock(prefetch_buffer, ro, udi_block_handle,
1337+
rep_->decompressor.get(), &rep_->udi_block,
1338+
/*get_context=*/nullptr, lookup_context,
1339+
/*for_compaction=*/false, use_cache, /*async_read=*/false,
1340+
/*use_block_cache_for_lookup=*/false);
1341+
if (!s.ok()) {
1342+
return s;
1343+
}
1344+
assert(!rep_->udi_block.IsEmpty());
1345+
1346+
std::unique_ptr<UserDefinedIndexReader> udi_reader =
1347+
table_options.user_defined_index_factory->NewReader(
1348+
rep_->udi_block.GetValue()->data);
1349+
index_reader = std::make_unique<UserDefinedIndexReaderWrapper>(
1350+
udi_name, std::move(index_reader), std::move(udi_reader));
1351+
}
13221352

13231353
rep_->index_reader = std::move(index_reader);
13241354

@@ -1766,8 +1796,7 @@ BlockBasedTable::MaybeReadBlockAndLoadToCache(
17661796
ro.fill_cache) {
17671797
Statistics* statistics = rep_->ioptions.stats;
17681798
const bool maybe_compressed =
1769-
TBlocklike::kBlockType != BlockType::kFilter &&
1770-
TBlocklike::kBlockType != BlockType::kCompressionDictionary &&
1799+
BlockTypeMaybeCompressed(TBlocklike::kBlockType) &&
17711800
rep_->decompressor;
17721801
// This flag, if true, tells BlockFetcher to return the uncompressed
17731802
// block when ReadBlockContents() is called.
@@ -1911,6 +1940,7 @@ BlockBasedTable::SaveLookupContextOrTraceRecord(
19111940
trace_block_type = TraceType::kBlockTraceRangeDeletionBlock;
19121941
break;
19131942
case BlockType::kIndex:
1943+
case BlockType::kUserDefinedIndex:
19141944
trace_block_type = TraceType::kBlockTraceIndexBlock;
19151945
break;
19161946
default:
@@ -2003,9 +2033,7 @@ WithBlocklikeCheck<Status, TBlocklike> BlockBasedTable::RetrieveBlock(
20032033
}
20042034

20052035
const bool maybe_compressed =
2006-
TBlocklike::kBlockType != BlockType::kFilter &&
2007-
TBlocklike::kBlockType != BlockType::kCompressionDictionary &&
2008-
rep_->decompressor;
2036+
BlockTypeMaybeCompressed(TBlocklike::kBlockType) && rep_->decompressor;
20092037
std::unique_ptr<TBlocklike> block;
20102038

20112039
{

table/block_based/block_based_table_reader.h

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -228,11 +228,15 @@ class BlockBasedTable : public TableReader {
228228

229229
// Create an iterator for index access. If iter is null, then a new object
230230
// is created on the heap, and the callee will have the ownership.
231-
// If a non-null iter is passed in, it will be used, and the returned value
232-
// is either the same as iter or a new on-heap object that
233-
// wraps the passed iter. In the latter case the return value points
234-
// to a different object then iter, and the callee has the ownership of the
235-
// returned object.
231+
// If a non-null iter is passed in, it may be used, and the returned value
232+
// is either the same as iter or a new on-heap object.
233+
// In the latter case the return value points to a different object then
234+
// iter, and the callee has the ownership of the returned object.
235+
//
236+
// Under all circumstances, the caller MUST use the returned iterator
237+
// for further operations. If the returned iterator != iter, then the
238+
// caller MUST ensure that iter stays in scope until the returned
239+
// iterator is destroyed.
236240
virtual InternalIteratorBase<IndexValue>* NewIterator(
237241
const ReadOptions& read_options, bool disable_prefix_seek,
238242
IndexBlockIter* iter, GetContext* get_context,
@@ -544,6 +548,12 @@ class BlockBasedTable : public TableReader {
544548

545549
bool TimestampMayMatch(const ReadOptions& read_options) const;
546550

551+
bool BlockTypeMaybeCompressed(BlockType type) const {
552+
return type != BlockType::kFilter &&
553+
type != BlockType::kCompressionDictionary &&
554+
type != BlockType::kUserDefinedIndex;
555+
}
556+
547557
// A cumulative data block file read in MultiGet lower than this size will
548558
// use a stack buffer
549559
static constexpr size_t kMultiGetReadStackBufSize = 8192;
@@ -689,6 +699,8 @@ struct BlockBasedTable::Rep {
689699
std::unique_ptr<CacheReservationManager::CacheReservationHandle>
690700
table_reader_cache_res_handle = nullptr;
691701

702+
CachableEntry<Block_kUserDefinedIndex> udi_block;
703+
692704
SequenceNumber get_global_seqno(BlockType block_type) const {
693705
return (block_type == BlockType::kFilterPartitionIndex ||
694706
block_type == BlockType::kCompressionDictionary)

table/block_based/block_cache.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ void BlockCreateContext::Create(std::unique_ptr<Block_kMetaIndex>* parsed_out,
4646
protection_bytes_per_key);
4747
}
4848

49+
void BlockCreateContext::Create(
50+
std::unique_ptr<Block_kUserDefinedIndex>* parsed_out,
51+
BlockContents&& block) {
52+
parsed_out->reset(new Block_kUserDefinedIndex(std::move(block)));
53+
}
54+
4955
void BlockCreateContext::Create(
5056
std::unique_ptr<ParsedFullFilterBlock>* parsed_out, BlockContents&& block) {
5157
parsed_out->reset(new ParsedFullFilterBlock(

table/block_based/block_cache.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@ class Block_kMetaIndex : public Block {
6767
static constexpr BlockType kBlockType = BlockType::kMetaIndex;
6868
};
6969

70+
class Block_kUserDefinedIndex : public BlockContents {
71+
public:
72+
static constexpr CacheEntryRole kCacheEntryRole = CacheEntryRole::kIndexBlock;
73+
static constexpr BlockType kBlockType = BlockType::kUserDefinedIndex;
74+
75+
explicit Block_kUserDefinedIndex(BlockContents&& other)
76+
: BlockContents(std::move(other)) {}
77+
const Slice& ContentSlice() const { return data; }
78+
};
79+
7080
struct BlockCreateContext : public Cache::CreateContext {
7181
BlockCreateContext() {}
7282
BlockCreateContext(const BlockBasedTableOptions* _table_options,
@@ -126,6 +136,8 @@ struct BlockCreateContext : public Cache::CreateContext {
126136
BlockContents&& block);
127137
void Create(std::unique_ptr<Block_kMetaIndex>* parsed_out,
128138
BlockContents&& block);
139+
void Create(std::unique_ptr<Block_kUserDefinedIndex>* parsed_out,
140+
BlockContents&& block);
129141
void Create(std::unique_ptr<ParsedFullFilterBlock>* parsed_out,
130142
BlockContents&& block);
131143
void Create(std::unique_ptr<DecompressorDict>* parsed_out,

0 commit comments

Comments
 (0)