Skip to content

Commit b89d290

Browse files
anand1976meta-codesync[bot]
authored andcommitted
Add MultiScan statistics (facebook#14248)
Summary: Pull Request resolved: facebook#14248 ### Overview This diff introduces the addition of multi-scan statistics to RocksDB, enhancing the database's ability to monitor and analyze performance during multi-scan operations. ### Key Changes #### Implemented Multi-Scan Statistics The following statistics were implemented to provide deeper insights into multi-scan operations: - **MULTISCAN_PREPARE_MICROS**: Measures the time (in microseconds) spent preparing for multi-scan operations. - **MULTISCAN_BLOCKS_PER_PREPARE**: Tracks the number of blocks processed per multi-scan prepare operation. - **Wasted Prefetch Blocks Count**: Counts the number of prefetched blocks that were not used (i.e., wasted) if the iterator is abandoned before accessing them. - **MULTISCAN_TOTAL_BLOCKS_SCANNED**: Tracks the total number of blocks scanned during all multi-scan operations. - **MULTISCAN_TOTAL_KEYS_SCANNED**: Measures the total number of keys scanned across all multi-scan operations. - **MULTISCAN_TOTAL_MICROS**: Captures the total time (in microseconds) spent in multi-scan operations. - **MULTISCAN_PREFETCHED_BLOCKS**: Counts the number of blocks that were prefetched during multi-scan operations. - **MULTISCAN_USED_PREFETCH_BLOCKS**: Tracks the number of prefetched blocks that were actually used during multi-scan operations. ### Impact This diff provides more fine-grained statistics for multi-scan operations, allowing developers and users to better understand and optimize the performance of their RocksDB instances. Reviewed By: krhancoc Differential Revision: D91053297 fbshipit-source-id: 7158741b9f026c0b5ce8ba1264dbd137e7fe985d
1 parent f84351d commit b89d290

9 files changed

Lines changed: 367 additions & 8 deletions

File tree

db/db_iterator_test.cc

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5027,6 +5027,152 @@ TEST_P(DBMultiScanIteratorTest, AsyncPrefetchWithExternalFileIngestion) {
50275027
ASSERT_EQ(total_keys, 400);
50285028
iter.reset();
50295029
}
5030+
5031+
TEST_P(DBMultiScanIteratorTest, StatisticsTest) {
5032+
// Test that multi scan statistics are properly recorded
5033+
auto options = CurrentOptions();
5034+
options.statistics = CreateDBStatistics();
5035+
// Use small block size to ensure multiple blocks
5036+
BlockBasedTableOptions table_options;
5037+
table_options.block_size = 256;
5038+
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
5039+
DestroyAndReopen(options);
5040+
5041+
// Create data across multiple blocks
5042+
for (int i = 0; i < 100; ++i) {
5043+
std::stringstream ss;
5044+
ss << std::setw(3) << std::setfill('0') << i;
5045+
// Use larger values to ensure multiple blocks
5046+
ASSERT_OK(Put("k" + ss.str(), std::string(100, 'v')));
5047+
}
5048+
ASSERT_OK(Flush());
5049+
5050+
// Reset stats before multi scan
5051+
ASSERT_OK(options.statistics->Reset());
5052+
5053+
// Set up two scan ranges
5054+
std::vector<std::string> key_ranges({"k010", "k030", "k060", "k080"});
5055+
ReadOptions ro;
5056+
ro.fill_cache = GetParam();
5057+
MultiScanArgs scan_options(BytewiseComparator());
5058+
scan_options.insert(key_ranges[0], key_ranges[1]);
5059+
scan_options.insert(key_ranges[2], key_ranges[3]);
5060+
5061+
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
5062+
std::unique_ptr<MultiScan> iter =
5063+
dbfull()->NewMultiScan(ro, cfh, scan_options);
5064+
5065+
// Iterate through all ranges
5066+
int count = 0;
5067+
try {
5068+
for (auto range : *iter) {
5069+
for (auto it : range) {
5070+
(void)it;
5071+
count++;
5072+
}
5073+
}
5074+
} catch (MultiScanException& ex) {
5075+
ASSERT_NOK(ex.status());
5076+
std::cerr << "Iterator returned status " << ex.what();
5077+
abort();
5078+
}
5079+
ASSERT_EQ(count, 40); // 20 keys per range
5080+
iter.reset();
5081+
5082+
// Check statistics
5083+
// MULTISCAN_PREPARE_CALLS should be at least 1
5084+
ASSERT_GE(TestGetTickerCount(options, MULTISCAN_PREPARE_CALLS), 1);
5085+
5086+
// MULTISCAN_PREPARE_ERRORS should be 0
5087+
ASSERT_EQ(TestGetTickerCount(options, MULTISCAN_PREPARE_ERRORS), 0);
5088+
5089+
// MULTISCAN_SEEK_ERRORS should be 0
5090+
ASSERT_EQ(TestGetTickerCount(options, MULTISCAN_SEEK_ERRORS), 0);
5091+
5092+
// Blocks should be prefetched or from cache
5093+
uint64_t blocks_prefetched =
5094+
TestGetTickerCount(options, MULTISCAN_BLOCKS_PREFETCHED);
5095+
uint64_t blocks_from_cache =
5096+
TestGetTickerCount(options, MULTISCAN_BLOCKS_FROM_CACHE);
5097+
ASSERT_GT(blocks_prefetched + blocks_from_cache, 0);
5098+
5099+
// If blocks were prefetched, prefetch bytes and IO requests should be > 0
5100+
if (blocks_prefetched > 0) {
5101+
ASSERT_GT(TestGetTickerCount(options, MULTISCAN_PREFETCH_BYTES), 0);
5102+
uint64_t io_requests = TestGetTickerCount(options, MULTISCAN_IO_REQUESTS);
5103+
ASSERT_GT(io_requests, 0);
5104+
ASSERT_LE(io_requests, blocks_prefetched);
5105+
}
5106+
5107+
// Wasted blocks should be 0 since we iterated through everything
5108+
ASSERT_EQ(TestGetTickerCount(options, MULTISCAN_PREFETCH_BLOCKS_WASTED), 0);
5109+
}
5110+
5111+
TEST_P(DBMultiScanIteratorTest, StatisticsWastedBlocksTest) {
5112+
// Test that wasted blocks are tracked when iteration is abandoned early
5113+
auto options = CurrentOptions();
5114+
options.statistics = CreateDBStatistics();
5115+
// Use small block size to ensure multiple blocks
5116+
BlockBasedTableOptions table_options;
5117+
table_options.block_size = 256;
5118+
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
5119+
DestroyAndReopen(options);
5120+
5121+
// Create data across multiple blocks
5122+
for (int i = 0; i < 100; ++i) {
5123+
std::stringstream ss;
5124+
ss << std::setw(3) << std::setfill('0') << i;
5125+
ASSERT_OK(Put("k" + ss.str(), std::string(100, 'v')));
5126+
}
5127+
ASSERT_OK(Flush());
5128+
5129+
// Reset stats before multi scan
5130+
ASSERT_OK(options.statistics->Reset());
5131+
5132+
// Set up a large scan range
5133+
ReadOptions ro;
5134+
ro.fill_cache = GetParam();
5135+
MultiScanArgs scan_options(BytewiseComparator());
5136+
scan_options.insert("k000", "k099");
5137+
5138+
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
5139+
std::unique_ptr<MultiScan> iter =
5140+
dbfull()->NewMultiScan(ro, cfh, scan_options);
5141+
5142+
// Only iterate through a few keys, then abandon
5143+
int count = 0;
5144+
try {
5145+
for (auto range : *iter) {
5146+
for (auto it : range) {
5147+
(void)it;
5148+
count++;
5149+
if (count >= 5) {
5150+
break; // Abandon iteration early
5151+
}
5152+
}
5153+
if (count >= 5) {
5154+
break;
5155+
}
5156+
}
5157+
} catch (MultiScanException& ex) {
5158+
ASSERT_NOK(ex.status());
5159+
std::cerr << "Iterator returned status " << ex.what();
5160+
abort();
5161+
}
5162+
ASSERT_EQ(count, 5);
5163+
5164+
// Destroy iterator to trigger wasted blocks counting
5165+
iter.reset();
5166+
5167+
uint64_t blocks_prefetched =
5168+
TestGetTickerCount(options, MULTISCAN_BLOCKS_PREFETCHED);
5169+
5170+
// If blocks were prefetched, some should be wasted since we abandoned early
5171+
if (blocks_prefetched > 1) {
5172+
// We only read a few keys, so there should be wasted blocks
5173+
ASSERT_GT(TestGetTickerCount(options, MULTISCAN_PREFETCH_BLOCKS_WASTED), 0);
5174+
}
5175+
}
50305176
} // namespace ROCKSDB_NAMESPACE
50315177

50325178
int main(int argc, char** argv) {

include/rocksdb/statistics.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,27 @@ enum Tickers : uint32_t {
552552
// Failure to load the UDI during SST table open
553553
SST_USER_DEFINED_INDEX_LOAD_FAIL_COUNT,
554554

555+
// MultiScan statistics
556+
// # of Prepare() calls
557+
MULTISCAN_PREPARE_CALLS,
558+
// # of Prepare() calls that failed
559+
MULTISCAN_PREPARE_ERRORS,
560+
// # of data blocks prefetched from storage during MultiScan
561+
MULTISCAN_BLOCKS_PREFETCHED,
562+
// # of blocks found already in cache during MultiScan Prepare
563+
MULTISCAN_BLOCKS_FROM_CACHE,
564+
// Total bytes prefetched during MultiScan
565+
MULTISCAN_PREFETCH_BYTES,
566+
// # of prefetched blocks that were never accessed
567+
MULTISCAN_PREFETCH_BLOCKS_WASTED,
568+
// # of actual I/O requests issued during MultiScan
569+
MULTISCAN_IO_REQUESTS,
570+
// # of non-adjacent blocks coalesced into single I/O (within
571+
// io_coalesce_threshold)
572+
MULTISCAN_IO_COALESCED_NONADJACENT,
573+
// # of seeks that failed validation (out of order, etc.)
574+
MULTISCAN_SEEK_ERRORS,
575+
555576
TICKER_ENUM_MAX
556577
};
557578

@@ -695,6 +716,11 @@ enum Histograms : uint32_t {
695716
// MultiScan Prefill iterator Prepare cost
696717
MULTISCAN_PREPARE_ITERATORS,
697718

719+
// Total Prepare() latency for MultiScan
720+
MULTISCAN_PREPARE_MICROS,
721+
// Distribution of blocks prefetched per MultiScan Prepare()
722+
MULTISCAN_BLOCKS_PER_PREPARE,
723+
698724
HISTOGRAM_ENUM_MAX
699725
};
700726

java/rocksjni/portal.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5289,6 +5289,24 @@ class TickerTypeJni {
52895289
return -0x5D;
52905290
case ROCKSDB_NAMESPACE::Tickers::SST_USER_DEFINED_INDEX_LOAD_FAIL_COUNT:
52915291
return -0x5E;
5292+
case ROCKSDB_NAMESPACE::Tickers::MULTISCAN_PREPARE_CALLS:
5293+
return -0x60;
5294+
case ROCKSDB_NAMESPACE::Tickers::MULTISCAN_PREPARE_ERRORS:
5295+
return -0x61;
5296+
case ROCKSDB_NAMESPACE::Tickers::MULTISCAN_BLOCKS_PREFETCHED:
5297+
return -0x62;
5298+
case ROCKSDB_NAMESPACE::Tickers::MULTISCAN_BLOCKS_FROM_CACHE:
5299+
return -0x63;
5300+
case ROCKSDB_NAMESPACE::Tickers::MULTISCAN_PREFETCH_BYTES:
5301+
return -0x64;
5302+
case ROCKSDB_NAMESPACE::Tickers::MULTISCAN_PREFETCH_BLOCKS_WASTED:
5303+
return -0x65;
5304+
case ROCKSDB_NAMESPACE::Tickers::MULTISCAN_IO_REQUESTS:
5305+
return -0x66;
5306+
case ROCKSDB_NAMESPACE::Tickers::MULTISCAN_IO_COALESCED_NONADJACENT:
5307+
return -0x67;
5308+
case ROCKSDB_NAMESPACE::Tickers::MULTISCAN_SEEK_ERRORS:
5309+
return -0x68;
52925310
case ROCKSDB_NAMESPACE::Tickers::TICKER_ENUM_MAX:
52935311
// -0x54 is the max value at this time. Since these values are exposed
52945312
// directly to Java clients, we'll keep the value the same till the next
@@ -5768,6 +5786,24 @@ class TickerTypeJni {
57685786
case -0x5E:
57695787
return ROCKSDB_NAMESPACE::Tickers::
57705788
SST_USER_DEFINED_INDEX_LOAD_FAIL_COUNT;
5789+
case -0x60:
5790+
return ROCKSDB_NAMESPACE::Tickers::MULTISCAN_PREPARE_CALLS;
5791+
case -0x61:
5792+
return ROCKSDB_NAMESPACE::Tickers::MULTISCAN_PREPARE_ERRORS;
5793+
case -0x62:
5794+
return ROCKSDB_NAMESPACE::Tickers::MULTISCAN_BLOCKS_PREFETCHED;
5795+
case -0x63:
5796+
return ROCKSDB_NAMESPACE::Tickers::MULTISCAN_BLOCKS_FROM_CACHE;
5797+
case -0x64:
5798+
return ROCKSDB_NAMESPACE::Tickers::MULTISCAN_PREFETCH_BYTES;
5799+
case -0x65:
5800+
return ROCKSDB_NAMESPACE::Tickers::MULTISCAN_PREFETCH_BLOCKS_WASTED;
5801+
case -0x66:
5802+
return ROCKSDB_NAMESPACE::Tickers::MULTISCAN_IO_REQUESTS;
5803+
case -0x67:
5804+
return ROCKSDB_NAMESPACE::Tickers::MULTISCAN_IO_COALESCED_NONADJACENT;
5805+
case -0x68:
5806+
return ROCKSDB_NAMESPACE::Tickers::MULTISCAN_SEEK_ERRORS;
57715807
case -0x54:
57725808
// -0x54 is the max value at this time. Since these values are exposed
57735809
// directly to Java clients, we'll keep the value the same till the next
@@ -5924,6 +5960,10 @@ class HistogramTypeJni {
59245960
return 0x3D;
59255961
case ROCKSDB_NAMESPACE::Histograms::COMPACTION_PREFETCH_BYTES:
59265962
return 0x3F;
5963+
case ROCKSDB_NAMESPACE::Histograms::MULTISCAN_PREPARE_MICROS:
5964+
return 0x40;
5965+
case ROCKSDB_NAMESPACE::Histograms::MULTISCAN_BLOCKS_PER_PREPARE:
5966+
return 0x41;
59275967
case ROCKSDB_NAMESPACE::Histograms::HISTOGRAM_ENUM_MAX:
59285968
// 0x3E is reserved for backwards compatibility on current minor
59295969
// version.
@@ -6071,6 +6111,10 @@ class HistogramTypeJni {
60716111
TABLE_OPEN_PREFETCH_TAIL_READ_BYTES;
60726112
case 0x3F:
60736113
return ROCKSDB_NAMESPACE::Histograms::COMPACTION_PREFETCH_BYTES;
6114+
case 0x40:
6115+
return ROCKSDB_NAMESPACE::Histograms::MULTISCAN_PREPARE_MICROS;
6116+
case 0x41:
6117+
return ROCKSDB_NAMESPACE::Histograms::MULTISCAN_BLOCKS_PER_PREPARE;
60746118
case 0x3E:
60756119
// 0x3E is reserved for backwards compatibility on current minor
60766120
// version.

java/src/main/java/org/rocksdb/HistogramType.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,20 @@ public enum HistogramType {
212212

213213
COMPACTION_PREFETCH_BYTES((byte) 0x3F),
214214

215+
/**
216+
* MultiScan histogram statistics
217+
*/
218+
219+
/**
220+
* Time spent in Iterator::Prepare() for multi-scan (microseconds)
221+
*/
222+
MULTISCAN_PREPARE_MICROS((byte) 0x40),
223+
224+
/**
225+
* Number of blocks per multi-scan Prepare() call
226+
*/
227+
MULTISCAN_BLOCKS_PER_PREPARE((byte) 0x41),
228+
215229
// 0x3E is reserved for backwards compatibility on current minor version.
216230
HISTOGRAM_ENUM_MAX((byte) 0x3E);
217231

java/src/main/java/org/rocksdb/TickerType.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,55 @@ public enum TickerType {
906906
*/
907907
REMOTE_COMPACT_RESUMED_BYTES((byte) -0x5F),
908908

909+
/**
910+
* MultiScan statistics
911+
*/
912+
913+
/**
914+
* # of calls to Iterator::Prepare() for multi-scan
915+
*/
916+
MULTISCAN_PREPARE_CALLS((byte) -0x60),
917+
918+
/**
919+
* # of errors during Iterator::Prepare() for multi-scan
920+
*/
921+
MULTISCAN_PREPARE_ERRORS((byte) -0x61),
922+
923+
/**
924+
* # of data blocks prefetched during multi-scan Prepare()
925+
*/
926+
MULTISCAN_BLOCKS_PREFETCHED((byte) -0x62),
927+
928+
/**
929+
* # of data blocks found in cache during multi-scan Prepare()
930+
*/
931+
MULTISCAN_BLOCKS_FROM_CACHE((byte) -0x63),
932+
933+
/**
934+
* Total bytes prefetched during multi-scan Prepare()
935+
*/
936+
MULTISCAN_PREFETCH_BYTES((byte) -0x64),
937+
938+
/**
939+
* # of prefetched blocks that were never accessed (wasted)
940+
*/
941+
MULTISCAN_PREFETCH_BLOCKS_WASTED((byte) -0x65),
942+
943+
/**
944+
* # of I/O requests issued during multi-scan Prepare()
945+
*/
946+
MULTISCAN_IO_REQUESTS((byte) -0x66),
947+
948+
/**
949+
* # of non-adjacent blocks coalesced into single I/O request
950+
*/
951+
MULTISCAN_IO_COALESCED_NONADJACENT((byte) -0x67),
952+
953+
/**
954+
* # of seek errors during multi-scan iteration
955+
*/
956+
MULTISCAN_SEEK_ERRORS((byte) -0x68),
957+
909958
TICKER_ENUM_MAX((byte) -0x54);
910959

911960
private final byte value;

monitoring/statistics.cc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,17 @@ const std::vector<std::pair<Tickers, std::string>> TickersNameMap = {
280280
{NUMBER_WBWI_INGEST, "rocksdb.number.wbwi.ingest"},
281281
{SST_USER_DEFINED_INDEX_LOAD_FAIL_COUNT,
282282
"rocksdb.sst.user.defined.index.load.fail.count"},
283+
{MULTISCAN_PREPARE_CALLS, "rocksdb.multiscan.prepare.calls"},
284+
{MULTISCAN_PREPARE_ERRORS, "rocksdb.multiscan.prepare.errors"},
285+
{MULTISCAN_BLOCKS_PREFETCHED, "rocksdb.multiscan.blocks.prefetched"},
286+
{MULTISCAN_BLOCKS_FROM_CACHE, "rocksdb.multiscan.blocks.from.cache"},
287+
{MULTISCAN_PREFETCH_BYTES, "rocksdb.multiscan.prefetch.bytes"},
288+
{MULTISCAN_PREFETCH_BLOCKS_WASTED,
289+
"rocksdb.multiscan.prefetch.blocks.wasted"},
290+
{MULTISCAN_IO_REQUESTS, "rocksdb.multiscan.io.requests"},
291+
{MULTISCAN_IO_COALESCED_NONADJACENT,
292+
"rocksdb.multiscan.io.coalesced.nonadjacent"},
293+
{MULTISCAN_SEEK_ERRORS, "rocksdb.multiscan.seek.errors"},
283294
};
284295

285296
const std::vector<std::pair<Histograms, std::string>> HistogramsNameMap = {
@@ -354,6 +365,8 @@ const std::vector<std::pair<Histograms, std::string>> HistogramsNameMap = {
354365
{NUM_OP_PER_TRANSACTION, "rocksdb.num.op.per.transaction"},
355366
{MULTISCAN_PREPARE_ITERATORS,
356367
"rocksdb.multiscan.op.prepare.iterators.micros"},
368+
{MULTISCAN_PREPARE_MICROS, "rocksdb.multiscan.prepare.micros"},
369+
{MULTISCAN_BLOCKS_PER_PREPARE, "rocksdb.multiscan.blocks.per.prepare"},
357370
};
358371

359372
std::shared_ptr<Statistics> CreateDBStatistics() {

monitoring/stats_history_test.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ TEST_F(StatsHistoryTest, GetStatsHistoryInMemory) {
185185

186186
TEST_F(StatsHistoryTest, InMemoryStatsHistoryPurging) {
187187
constexpr int kPeriodSec = 1;
188-
constexpr int kEstimatedOneSliceSize = 16000;
188+
constexpr int kEstimatedOneSliceSize = 22000;
189189

190190
Options options;
191191
options.create_if_missing = true;
@@ -277,7 +277,7 @@ TEST_F(StatsHistoryTest, InMemoryStatsHistoryPurging) {
277277
// If `slice_count == 0` when new statistics are added, consider increasing
278278
// `kEstimatedOneSliceSize`
279279
ASSERT_EQ(slice_count, 1);
280-
ASSERT_TRUE(stats_history_size_reopen < 16000 &&
280+
ASSERT_TRUE(stats_history_size_reopen < kEstimatedOneSliceSize &&
281281
stats_history_size_reopen > 0);
282282
ASSERT_TRUE(stats_count_reopen < stats_count && stats_count_reopen > 0);
283283
Close();

0 commit comments

Comments
 (0)