Skip to content

Commit 3579d32

Browse files
ltamasifacebook-github-bot
authored andcommitted
Add a new interface method SecondaryIndex::NewIterator to enable querying the index (#13257)
Summary: Pull Request resolved: #13257 The patch adds a new API `NewIterator` to `SecondaryIndex`, which should return an iterator that can be used by applications to query the index. This method takes a `ReadOptions` structure, which can be used by applications to provide (implementation-specific) query parameters to the index, and an underlying iterator, which should be an iterator over the index's secondary column family, and is expected to be leveraged by the returned iterator to read the actual secondary index entries. (Providing the underlying iterator this way enables querying the index as of a specific point in time for example.) Querying the index can be performed by calling the returned iterator's `Seek` API with a search target, and then using `Next` (and potentially `Prev`) to iterate through the matching index entries. `SeekToFirst`, `SeekToLast`, and `SeekForPrev` are not expected to be supported by the iterator. The iterator should expose primary keys, that is, the secondary key prefix should be stripped from the index entries. The exact semantics of the returned iterator depend on the index and are implementation-specific. For simple indices, the search target might be a primary column value, and the iterator might return all primary keys that have the given column value. (This behavior can be achieved using the new class `SecondaryIndexIterator`.) However, other semantics are also possible: for vector indices, the search target might be a vector, and the iterator might return similar vectors from the index. (This will be implemented for `FaissIVFIndex` in a subsequent patch.) Reviewed By: jaykorean Differential Revision: D67684777 fbshipit-source-id: 59bc33919405a3e9e316a1fa4790c1708788eb85
1 parent d2db80c commit 3579d32

File tree

5 files changed

+279
-1
lines changed

5 files changed

+279
-1
lines changed

include/rocksdb/utilities/secondary_index.h

+29
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66

77
#pragma once
88

9+
#include <memory>
910
#include <optional>
1011
#include <string>
1112
#include <variant>
1213

14+
#include "rocksdb/iterator.h"
15+
#include "rocksdb/options.h"
1316
#include "rocksdb/rocksdb_namespace.h"
1417
#include "rocksdb/slice.h"
1518
#include "rocksdb/status.h"
@@ -96,6 +99,32 @@ class SecondaryIndex {
9699
const Slice& primary_column_value, const Slice& previous_column_value,
97100
std::optional<std::variant<Slice, std::string>>* secondary_value)
98101
const = 0;
102+
103+
// Create an iterator that can be used by applications to query the index.
104+
// This method takes a ReadOptions structure, which can be used by
105+
// applications to provide (implementation-specific) query parameters to the
106+
// index as well as an underlying iterator over the index's secondary column
107+
// family, which the returned iterator is expected to take ownership of and
108+
// use to read the actual secondary index entries. (Providing the underlying
109+
// iterator this way enables querying the index as of a specific point in time
110+
// for example.)
111+
//
112+
// Querying the index can be performed by calling the returned iterator's
113+
// Seek API with a search target, and then using Next (and potentially
114+
// Prev) to iterate through the matching index entries. SeekToFirst,
115+
// SeekToLast, and SeekForPrev are not expected to be supported by the
116+
// iterator. The iterator should expose primary keys, that is, the secondary
117+
// key prefix should be stripped from the index entries.
118+
//
119+
// The exact semantics of the returned iterator depend on the index and are
120+
// implementation-specific. For simple indices, the search target might be a
121+
// primary column value, and the iterator might return all primary keys that
122+
// have the given column value; however, other semantics are also possible.
123+
// For vector indices, the search target might be a vector, and the iterator
124+
// might return similar vectors from the index.
125+
virtual std::unique_ptr<Iterator> NewIterator(
126+
const ReadOptions& read_options,
127+
std::unique_ptr<Iterator>&& underlying_it) const = 0;
99128
};
100129

101130
} // namespace ROCKSDB_NAMESPACE

utilities/secondary_index/faiss_ivf_index.cc

+7
Original file line numberDiff line numberDiff line change
@@ -210,4 +210,11 @@ Status FaissIVFIndex::GetSecondaryValue(
210210
return Status::OK();
211211
}
212212

213+
std::unique_ptr<Iterator> FaissIVFIndex::NewIterator(
214+
const ReadOptions& /* read_options */,
215+
std::unique_ptr<Iterator>&& /* underlying_it */) const {
216+
// TODO: implement this
217+
return std::unique_ptr<Iterator>(NewErrorIterator(Status::NotSupported()));
218+
}
219+
213220
} // namespace ROCKSDB_NAMESPACE

utilities/secondary_index/faiss_ivf_index.h

+4
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ class FaissIVFIndex : public SecondaryIndex {
4343
std::optional<std::variant<Slice, std::string>>*
4444
secondary_value) const override;
4545

46+
std::unique_ptr<Iterator> NewIterator(
47+
const ReadOptions& read_options,
48+
std::unique_ptr<Iterator>&& underlying_it) const override;
49+
4650
private:
4751
class Adapter;
4852

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Copyright (c) Meta Platforms, Inc. and affiliates.
2+
//
3+
// This source code is licensed under both the GPLv2 (found in the
4+
// COPYING file in the root directory) and Apache 2.0 License
5+
// (found in the LICENSE.Apache file in the root directory).
6+
7+
#pragma once
8+
9+
#include <cassert>
10+
#include <string>
11+
12+
#include "rocksdb/iterator.h"
13+
#include "rocksdb/status.h"
14+
#include "rocksdb/utilities/secondary_index.h"
15+
#include "util/overload.h"
16+
17+
namespace ROCKSDB_NAMESPACE {
18+
19+
// A simple iterator that can be used to query a secondary index (that is, find
20+
// the primary keys for a given search target). Can be used as-is or as a
21+
// building block for more complex iterators.
22+
class SecondaryIndexIterator : public Iterator {
23+
public:
24+
SecondaryIndexIterator(const SecondaryIndex* index,
25+
std::unique_ptr<Iterator>&& underlying_it)
26+
: index_(index), underlying_it_(std::move(underlying_it)) {
27+
assert(index_);
28+
assert(underlying_it_);
29+
}
30+
31+
bool Valid() const override {
32+
return status_.ok() && underlying_it_->Valid() &&
33+
underlying_it_->key().starts_with(prefix_);
34+
}
35+
36+
void SeekToFirst() override {
37+
status_ = Status::NotSupported(
38+
"SeekToFirst is not supported for secondary index iterators");
39+
}
40+
41+
void SeekToLast() override {
42+
status_ = Status::NotSupported(
43+
"SeekToLast is not supported for secondary index iterators");
44+
}
45+
46+
void Seek(const Slice& target) override {
47+
status_ = Status::OK();
48+
49+
std::variant<Slice, std::string> prefix;
50+
51+
const Status s = index_->GetSecondaryKeyPrefix(target, &prefix);
52+
if (!s.ok()) {
53+
status_ = s;
54+
return;
55+
}
56+
57+
prefix_ = std::visit(
58+
overload{
59+
[](const Slice& value) -> std::string { return value.ToString(); },
60+
[](const std::string& value) -> std::string { return value; }},
61+
prefix);
62+
63+
// FIXME: this works for BytewiseComparator but not for all comparators in
64+
// general
65+
underlying_it_->Seek(prefix_);
66+
}
67+
68+
void SeekForPrev(const Slice& /* target */) override {
69+
status_ = Status::NotSupported(
70+
"SeekForPrev is not supported for secondary index iterators");
71+
}
72+
73+
void Next() override {
74+
assert(Valid());
75+
76+
underlying_it_->Next();
77+
}
78+
79+
void Prev() override {
80+
assert(Valid());
81+
82+
underlying_it_->Prev();
83+
}
84+
85+
bool PrepareValue() override {
86+
assert(Valid());
87+
88+
return underlying_it_->PrepareValue();
89+
}
90+
91+
Status status() const override {
92+
if (!status_.ok()) {
93+
return status_;
94+
}
95+
96+
return underlying_it_->status();
97+
}
98+
99+
Slice key() const override {
100+
assert(Valid());
101+
102+
Slice key = underlying_it_->key();
103+
key.remove_prefix(prefix_.size());
104+
105+
return key;
106+
}
107+
108+
Slice value() const override {
109+
assert(Valid());
110+
111+
return underlying_it_->value();
112+
}
113+
114+
const WideColumns& columns() const override {
115+
assert(Valid());
116+
117+
return underlying_it_->columns();
118+
}
119+
120+
Slice timestamp() const override {
121+
assert(Valid());
122+
123+
return underlying_it_->timestamp();
124+
}
125+
126+
Status GetProperty(std::string prop_name, std::string* prop) override {
127+
return underlying_it_->GetProperty(std::move(prop_name), prop);
128+
}
129+
130+
private:
131+
const SecondaryIndex* index_;
132+
std::unique_ptr<Iterator> underlying_it_;
133+
Status status_;
134+
std::string prefix_;
135+
};
136+
137+
} // namespace ROCKSDB_NAMESPACE

utilities/transactions/transaction_test.cc

+102-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
#include "util/random.h"
3030
#include "util/string_util.h"
3131
#include "utilities/merge_operators.h"
32-
#include "utilities/merge_operators/string_append/stringappend.h"
32+
#include "utilities/secondary_index/secondary_index_iterator.h"
3333
#include "utilities/transactions/pessimistic_transaction_db.h"
3434

3535
namespace ROCKSDB_NAMESPACE {
@@ -8083,6 +8083,13 @@ TEST_P(TransactionTest, SecondaryIndex) {
80838083
return Status::OK();
80848084
}
80858085

8086+
std::unique_ptr<Iterator> NewIterator(
8087+
const ReadOptions& /* read_options */,
8088+
std::unique_ptr<Iterator>&& underlying_it) const override {
8089+
return std::make_unique<SecondaryIndexIterator>(this,
8090+
std::move(underlying_it));
8091+
}
8092+
80868093
private:
80878094
ColumnFamilyHandle* primary_cfh_{};
80888095
ColumnFamilyHandle* secondary_cfh_{};
@@ -8181,6 +8188,7 @@ TEST_P(TransactionTest, SecondaryIndex) {
81818188
}
81828189

81838190
{
8191+
// Read the raw secondary index entries from CF2
81848192
std::unique_ptr<Iterator> it(db->NewIterator(ReadOptions(), cfh2));
81858193

81868194
it->SeekToFirst();
@@ -8198,6 +8206,58 @@ TEST_P(TransactionTest, SecondaryIndex) {
81988206
ASSERT_OK(it->status());
81998207
}
82008208

8209+
{
8210+
// Query the secondary index
8211+
std::unique_ptr<Iterator> underlying_it(
8212+
db->NewIterator(ReadOptions(), cfh2));
8213+
std::unique_ptr<Iterator> it(
8214+
index->NewIterator(ReadOptions(), std::move(underlying_it)));
8215+
8216+
it->SeekToFirst();
8217+
ASSERT_FALSE(it->Valid());
8218+
ASSERT_TRUE(it->status().IsNotSupported());
8219+
8220+
it->SeekToLast();
8221+
ASSERT_FALSE(it->Valid());
8222+
ASSERT_TRUE(it->status().IsNotSupported());
8223+
8224+
it->SeekForPrev("box");
8225+
ASSERT_FALSE(it->Valid());
8226+
ASSERT_TRUE(it->status().IsNotSupported());
8227+
8228+
it->Seek("box"); // last character used for indexing: x
8229+
ASSERT_TRUE(it->Valid());
8230+
ASSERT_OK(it->status());
8231+
ASSERT_EQ(it->key(), "key3");
8232+
ASSERT_EQ(it->value(), "zab");
8233+
8234+
it->Next();
8235+
ASSERT_TRUE(it->Valid());
8236+
ASSERT_OK(it->status());
8237+
ASSERT_EQ(it->key(), "key4");
8238+
ASSERT_EQ(it->value(), "xuuq");
8239+
8240+
it->Prev();
8241+
ASSERT_TRUE(it->Valid());
8242+
ASSERT_OK(it->status());
8243+
ASSERT_EQ(it->key(), "key3");
8244+
ASSERT_EQ(it->value(), "zab");
8245+
8246+
it->Next();
8247+
ASSERT_TRUE(it->Valid());
8248+
ASSERT_OK(it->status());
8249+
ASSERT_EQ(it->key(), "key4");
8250+
ASSERT_EQ(it->value(), "xuuq");
8251+
8252+
it->Next();
8253+
ASSERT_FALSE(it->Valid());
8254+
ASSERT_OK(it->status());
8255+
8256+
it->Seek("toy"); // last character used for indexing: y
8257+
ASSERT_FALSE(it->Valid());
8258+
ASSERT_OK(it->status());
8259+
}
8260+
82018261
// Make some updates to the key-values indexed above through the database
82028262
// interface (i.e. using implicit transactions)
82038263

@@ -8256,6 +8316,7 @@ TEST_P(TransactionTest, SecondaryIndex) {
82568316
}
82578317

82588318
{
8319+
// Read the raw secondary index entries from CF2
82598320
std::unique_ptr<Iterator> it(db->NewIterator(ReadOptions(), cfh2));
82608321

82618322
it->SeekToFirst();
@@ -8272,6 +8333,46 @@ TEST_P(TransactionTest, SecondaryIndex) {
82728333
ASSERT_FALSE(it->Valid());
82738334
ASSERT_OK(it->status());
82748335
}
8336+
8337+
{
8338+
// Query the secondary index
8339+
std::unique_ptr<Iterator> underlying_it(
8340+
db->NewIterator(ReadOptions(), cfh2));
8341+
std::unique_ptr<Iterator> it(
8342+
index->NewIterator(ReadOptions(), std::move(underlying_it)));
8343+
8344+
it->SeekToFirst();
8345+
ASSERT_FALSE(it->Valid());
8346+
ASSERT_TRUE(it->status().IsNotSupported());
8347+
8348+
it->SeekToLast();
8349+
ASSERT_FALSE(it->Valid());
8350+
ASSERT_TRUE(it->status().IsNotSupported());
8351+
8352+
it->SeekForPrev("bot");
8353+
ASSERT_FALSE(it->Valid());
8354+
ASSERT_TRUE(it->status().IsNotSupported());
8355+
8356+
it->Seek("bot"); // last character used for indexing: t
8357+
ASSERT_TRUE(it->Valid());
8358+
ASSERT_OK(it->status());
8359+
ASSERT_EQ(it->key(), "key1");
8360+
ASSERT_EQ(it->value(), "tluarg");
8361+
8362+
it->Next();
8363+
ASSERT_FALSE(it->Valid());
8364+
ASSERT_OK(it->status());
8365+
8366+
it->Seek("toy"); // last character used for indexing: y
8367+
ASSERT_TRUE(it->Valid());
8368+
ASSERT_OK(it->status());
8369+
ASSERT_EQ(it->key(), "key3");
8370+
ASSERT_EQ(it->value(), "ylprag");
8371+
8372+
it->Next();
8373+
ASSERT_FALSE(it->Valid());
8374+
ASSERT_OK(it->status());
8375+
}
82758376
}
82768377

82778378
TEST_F(TransactionDBTest, CollapseKey) {

0 commit comments

Comments
 (0)