diff --git a/mysql-test/suite/rocksdb/r/index_merge_rocksdb.result b/mysql-test/suite/rocksdb/r/index_merge_rocksdb.result index b1744850648d..31563983db1d 100644 --- a/mysql-test/suite/rocksdb/r/index_merge_rocksdb.result +++ b/mysql-test/suite/rocksdb/r/index_merge_rocksdb.result @@ -34,12 +34,12 @@ Table Op Msg_type Msg_text test.t1 analyze status OK explain select * from t1 where key1 = 1; id select_type table partitions type possible_keys key key_len ref rows filtered Extra -1 SIMPLE t1 NULL ref key1 key1 5 # 1 100.00 NULL +1 SIMPLE t1 NULL ref key1 key1 5 # 3 100.00 NULL Warnings: Note 1003 /* select#1 */ select `test`.`t1`.`key1` AS `key1`,`test`.`t1`.`key2` AS `key2`,`test`.`t1`.`key3` AS `key3` from `test`.`t1` where (`test`.`t1`.`key1` = 1) explain select key1,key2 from t1 where key1 = 1 or key2 = 1; id select_type table partitions type possible_keys key key_len ref rows filtered Extra -1 SIMPLE t1 NULL index_merge key1,key2 key1,key2 5,5 # 2 100.00 Using union(key1,key2); Using where +1 SIMPLE t1 NULL index_merge key1,key2 key1,key2 5,5 # 4 100.00 Using union(key1,key2); Using where Warnings: Note 1003 /* select#1 */ select `test`.`t1`.`key1` AS `key1`,`test`.`t1`.`key2` AS `key2` from `test`.`t1` where ((`test`.`t1`.`key1` = 1) or (`test`.`t1`.`key2` = 1)) select * from t1 where key1 = 1; diff --git a/mysql-test/suite/rocksdb/r/records_in_range_dive.result b/mysql-test/suite/rocksdb/r/records_in_range_dive.result new file mode 100644 index 000000000000..f18c6ae694a5 --- /dev/null +++ b/mysql-test/suite/rocksdb/r/records_in_range_dive.result @@ -0,0 +1,108 @@ +CREATE TABLE t1 ( +i INT PRIMARY KEY AUTO_INCREMENT, +a INT NOT NULL, +b INT NOT NULL, +KEY ka(a), +KEY kb(b) comment 'cfname=rev:cf1' +) ENGINE = rocksdb; +SET @save_dive = @@session.rocksdb_records_in_range_dive_threshold; +SET SESSION rocksdb_records_in_range_dive_threshold = 10000; +# +# Memtable-only: the dive must count the narrow range exactly (200), +# instead of the pre-fix estimate of 1. +# +explain select * from t1 where a = 5000; +id select_type table partitions type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 NULL ref ka ka 4 const 200 100.00 NULL +Warnings: +Note 1003 /* select#1 */ select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where (`test`.`t1`.`a` = 5000) +explain select * from t1 where b = 5000; +id select_type table partitions type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 NULL ref kb kb 4 const 200 100.00 NULL +Warnings: +Note 1003 /* select#1 */ select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where (`test`.`t1`.`b` = 5000) +explain select * from t1 where a > 4999 and a < 5001; +id select_type table partitions type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 NULL range ka ka 4 NULL 200 100.00 Using index condition +Warnings: +Note 1003 /* select#1 */ select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where ((`test`.`t1`.`a` > 4999) and (`test`.`t1`.`a` < 5001)) +# +# SST path: flush the memtable; the dive still counts the narrow +# range exactly. +# +set global rocksdb_force_flush_memtable_now = true; +analyze table t1; +Table Op Msg_type Msg_text +test.t1 analyze status OK +explain select * from t1 where a = 5000; +id select_type table partitions type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 NULL ref ka ka 4 const 200 100.00 NULL +Warnings: +Note 1003 /* select#1 */ select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where (`test`.`t1`.`a` = 5000) +explain select * from t1 where b = 5000; +id select_type table partitions type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 NULL ref kb kb 4 const 200 100.00 NULL +Warnings: +Note 1003 /* select#1 */ select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where (`test`.`t1`.`b` = 5000) +explain select * from t1 where a > 4999 and a < 5001; +id select_type table partitions type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 NULL range ka ka 4 NULL 200 100.00 Using index condition +Warnings: +Note 1003 /* select#1 */ select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where ((`test`.`t1`.`a` > 4999) and (`test`.`t1`.`a` < 5001)) +# +# An empty range stays at the conventional 1 row. +# +explain select * from t1 where a = 9999; +id select_type table partitions type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 NULL ref ka ka 4 const 1 100.00 NULL +Warnings: +Note 1003 /* select#1 */ select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where (`test`.`t1`.`a` = 9999) +# +# Deterministic single-block scenario: index kc lives in its own +# column family whose whole SST fits in one ~16KB data block, so +# GetApproximateSizes() is guaranteed to return 0 for any range that +# does not extend past the last key: the exact situation from the +# bug report. +# +CREATE TABLE t2 ( +i INT PRIMARY KEY AUTO_INCREMENT, +c INT NOT NULL, +KEY kc(c) comment 'cfname=cf_dive' +) ENGINE = rocksdb; +set global rocksdb_force_flush_memtable_now = true; +analyze table t2; +Table Op Msg_type Msg_text +test.t2 analyze status OK +# Dive enabled: exact count (201 keys with c = 50). +SET SESSION rocksdb_records_in_range_dive_threshold = 10000; +explain select * from t2 where c = 50; +id select_type table partitions type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 NULL ref kc kc 4 const 201 100.00 Using index +Warnings: +Note 1003 /* select#1 */ select `test`.`t2`.`i` AS `i`,`test`.`t2`.`c` AS `c` from `test`.`t2` where (`test`.`t2`.`c` = 50) +# Cap semantics: the dive reads at most threshold keys, so a range +# with more keys than the threshold estimates as the threshold +# (a lower bound). +SET SESSION rocksdb_records_in_range_dive_threshold = 50; +explain select * from t2 where c = 50; +id select_type table partitions type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 NULL ref kc kc 4 const 50 100.00 Using index +Warnings: +Note 1003 /* select#1 */ select `test`.`t2`.`i` AS `i`,`test`.`t2`.`c` AS `c` from `test`.`t2` where (`test`.`t2`.`c` = 50) +SET SESSION rocksdb_records_in_range_dive_threshold = DEFAULT; +explain select * from t2 where c = 50; +id select_type table partitions type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 NULL ref kc kc 4 const 100 100.00 Using index +Warnings: +Note 1003 /* select#1 */ select `test`.`t2`.`i` AS `i`,`test`.`t2`.`c` AS `c` from `test`.`t2` where (`test`.`t2`.`c` = 50) +# 0 disables the dive: legacy behavior, the estimate collapses to +# 1 row (this is the PS-10110 bug). +SET SESSION rocksdb_records_in_range_dive_threshold = 0; +explain select * from t2 where c = 50; +id select_type table partitions type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 NULL ref kc kc 4 const 1 100.00 Using index +Warnings: +Note 1003 /* select#1 */ select `test`.`t2`.`i` AS `i`,`test`.`t2`.`c` AS `c` from `test`.`t2` where (`test`.`t2`.`c` = 50) +SET SESSION rocksdb_records_in_range_dive_threshold = @save_dive; +DROP TABLE t1; +DROP TABLE t2; diff --git a/mysql-test/suite/rocksdb/r/rocksdb.result b/mysql-test/suite/rocksdb/r/rocksdb.result index 2dc16b91006f..e38deba4ba32 100644 --- a/mysql-test/suite/rocksdb/r/rocksdb.result +++ b/mysql-test/suite/rocksdb/r/rocksdb.result @@ -1036,6 +1036,7 @@ rocksdb_rate_limiter_bytes_per_sec 0 rocksdb_read_free_rpl OFF rocksdb_read_free_rpl_tables .* rocksdb_records_in_range 50 +rocksdb_records_in_range_dive_threshold 100 rocksdb_reset_stats OFF rocksdb_rollback_on_timeout OFF rocksdb_rpl_skip_tx_api OFF diff --git a/mysql-test/suite/rocksdb/t/records_in_range_dive.test b/mysql-test/suite/rocksdb/t/records_in_range_dive.test new file mode 100644 index 000000000000..eb4629faa52b --- /dev/null +++ b/mysql-test/suite/rocksdb/t/records_in_range_dive.test @@ -0,0 +1,113 @@ +--source include/have_rocksdb.inc + +# PS-10110: records_in_range() returned 1 row for narrow ranges that fall +# within a single SST data block (or a narrow memtable range), because +# RocksDB size approximations work at data block granularity and return 0. +# The estimate is now refined with a bounded index dive controlled by +# rocksdb_records_in_range_dive_threshold: an exact key count if the range +# has fewer keys than the threshold, a lower bound of the threshold +# otherwise. +# +# Note: no FORCE INDEX in the queries below - with FORCE INDEX the SQL +# layer skips records_in_range() entirely. + +CREATE TABLE t1 ( + i INT PRIMARY KEY AUTO_INCREMENT, + a INT NOT NULL, + b INT NOT NULL, + KEY ka(a), + KEY kb(b) comment 'cfname=rev:cf1' +) ENGINE = rocksdb; + +# 20000 rows spread over a = b = 1..1000 (20 rows each), plus exactly 200 +# rows with a = b = 5000. +--disable_query_log +let $i = 1; +while ($i <= 1000) { + eval INSERT INTO t1(a, b) VALUES + ($i,$i),($i,$i),($i,$i),($i,$i),($i,$i), + ($i,$i),($i,$i),($i,$i),($i,$i),($i,$i), + ($i,$i),($i,$i),($i,$i),($i,$i),($i,$i), + ($i,$i),($i,$i),($i,$i),($i,$i),($i,$i); + inc $i; +} +let $i = 1; +while ($i <= 200) { + INSERT INTO t1(a, b) VALUES (5000, 5000); + inc $i; +} +--enable_query_log + +SET @save_dive = @@session.rocksdb_records_in_range_dive_threshold; +SET SESSION rocksdb_records_in_range_dive_threshold = 10000; + +--echo # +--echo # Memtable-only: the dive must count the narrow range exactly (200), +--echo # instead of the pre-fix estimate of 1. +--echo # +explain select * from t1 where a = 5000; +explain select * from t1 where b = 5000; +explain select * from t1 where a > 4999 and a < 5001; + +--echo # +--echo # SST path: flush the memtable; the dive still counts the narrow +--echo # range exactly. +--echo # +set global rocksdb_force_flush_memtable_now = true; +analyze table t1; +explain select * from t1 where a = 5000; +explain select * from t1 where b = 5000; +explain select * from t1 where a > 4999 and a < 5001; + +--echo # +--echo # An empty range stays at the conventional 1 row. +--echo # +explain select * from t1 where a = 9999; + +--echo # +--echo # Deterministic single-block scenario: index kc lives in its own +--echo # column family whose whole SST fits in one ~16KB data block, so +--echo # GetApproximateSizes() is guaranteed to return 0 for any range that +--echo # does not extend past the last key: the exact situation from the +--echo # bug report. +--echo # +CREATE TABLE t2 ( + i INT PRIMARY KEY AUTO_INCREMENT, + c INT NOT NULL, + KEY kc(c) comment 'cfname=cf_dive' +) ENGINE = rocksdb; +--disable_query_log +let $i = 1; +while ($i <= 100) { + eval INSERT INTO t2(c) VALUES ($i); + inc $i; +} +let $i = 1; +while ($i <= 200) { + INSERT INTO t2(c) VALUES (50); + inc $i; +} +--enable_query_log +set global rocksdb_force_flush_memtable_now = true; +analyze table t2; + +--echo # Dive enabled: exact count (201 keys with c = 50). +SET SESSION rocksdb_records_in_range_dive_threshold = 10000; +explain select * from t2 where c = 50; + +--echo # Cap semantics: the dive reads at most threshold keys, so a range +--echo # with more keys than the threshold estimates as the threshold +--echo # (a lower bound). +SET SESSION rocksdb_records_in_range_dive_threshold = 50; +explain select * from t2 where c = 50; +SET SESSION rocksdb_records_in_range_dive_threshold = DEFAULT; +explain select * from t2 where c = 50; + +--echo # 0 disables the dive: legacy behavior, the estimate collapses to +--echo # 1 row (this is the PS-10110 bug). +SET SESSION rocksdb_records_in_range_dive_threshold = 0; +explain select * from t2 where c = 50; + +SET SESSION rocksdb_records_in_range_dive_threshold = @save_dive; +DROP TABLE t1; +DROP TABLE t2; diff --git a/mysql-test/suite/rocksdb_sys_vars/r/rocksdb_records_in_range_dive_threshold_basic.result b/mysql-test/suite/rocksdb_sys_vars/r/rocksdb_records_in_range_dive_threshold_basic.result new file mode 100644 index 000000000000..216ba623527a --- /dev/null +++ b/mysql-test/suite/rocksdb_sys_vars/r/rocksdb_records_in_range_dive_threshold_basic.result @@ -0,0 +1,121 @@ +CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam; +INSERT INTO valid_values VALUES(1); +INSERT INTO valid_values VALUES(0); +INSERT INTO valid_values VALUES(100); +INSERT INTO valid_values VALUES(222333); +CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam; +INSERT INTO invalid_values VALUES('\'aaa\''); +INSERT INTO invalid_values VALUES('\'bbb\''); +SET @start_global_value = @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD; +SELECT @start_global_value; +@start_global_value +100 +SET @start_session_value = @@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD; +SELECT @start_session_value; +@start_session_value +100 +'# Setting to valid values in global scope#' +"Trying to set variable @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD to 1" +SET @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD = 1; +SELECT @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD; +@@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD +1 +"Setting the global scope variable back to default" +SET @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD = DEFAULT; +SELECT @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD; +@@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD +100 +"Trying to set variable @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD to 0" +SET @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD = 0; +SELECT @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD; +@@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD +0 +"Setting the global scope variable back to default" +SET @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD = DEFAULT; +SELECT @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD; +@@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD +100 +"Trying to set variable @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD to 100" +SET @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD = 100; +SELECT @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD; +@@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD +100 +"Setting the global scope variable back to default" +SET @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD = DEFAULT; +SELECT @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD; +@@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD +100 +"Trying to set variable @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD to 222333" +SET @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD = 222333; +SELECT @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD; +@@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD +222333 +"Setting the global scope variable back to default" +SET @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD = DEFAULT; +SELECT @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD; +@@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD +100 +'# Setting to valid values in session scope#' +"Trying to set variable @@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD to 1" +SET @@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD = 1; +SELECT @@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD; +@@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD +1 +"Setting the session scope variable back to default" +SET @@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD = DEFAULT; +SELECT @@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD; +@@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD +100 +"Trying to set variable @@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD to 0" +SET @@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD = 0; +SELECT @@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD; +@@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD +0 +"Setting the session scope variable back to default" +SET @@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD = DEFAULT; +SELECT @@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD; +@@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD +100 +"Trying to set variable @@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD to 100" +SET @@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD = 100; +SELECT @@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD; +@@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD +100 +"Setting the session scope variable back to default" +SET @@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD = DEFAULT; +SELECT @@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD; +@@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD +100 +"Trying to set variable @@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD to 222333" +SET @@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD = 222333; +SELECT @@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD; +@@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD +222333 +"Setting the session scope variable back to default" +SET @@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD = DEFAULT; +SELECT @@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD; +@@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD +100 +'# Testing with invalid values in global scope #' +"Trying to set variable @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD to 'aaa'" +SET @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD = 'aaa'; +Got one of the listed errors +SELECT @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD; +@@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD +100 +"Trying to set variable @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD to 'bbb'" +SET @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD = 'bbb'; +Got one of the listed errors +SELECT @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD; +@@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD +100 +SET @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD = @start_global_value; +SELECT @@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD; +@@global.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD +100 +SET @@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD = @start_session_value; +SELECT @@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD; +@@session.ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD +100 +DROP TABLE valid_values; +DROP TABLE invalid_values; diff --git a/mysql-test/suite/rocksdb_sys_vars/t/rocksdb_records_in_range_dive_threshold_basic.test b/mysql-test/suite/rocksdb_sys_vars/t/rocksdb_records_in_range_dive_threshold_basic.test new file mode 100644 index 000000000000..90df990b00b8 --- /dev/null +++ b/mysql-test/suite/rocksdb_sys_vars/t/rocksdb_records_in_range_dive_threshold_basic.test @@ -0,0 +1,20 @@ +--source include/have_rocksdb.inc +--source include/have_myisam.inc + +CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam; +INSERT INTO valid_values VALUES(1); +INSERT INTO valid_values VALUES(0); +INSERT INTO valid_values VALUES(100); +INSERT INTO valid_values VALUES(222333); + +CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam; +INSERT INTO invalid_values VALUES('\'aaa\''); +INSERT INTO invalid_values VALUES('\'bbb\''); + +--let $sys_var=ROCKSDB_RECORDS_IN_RANGE_DIVE_THRESHOLD +--let $read_only=0 +--let $session=1 +--source ../include/rocksdb_sys_var.inc + +DROP TABLE valid_values; +DROP TABLE invalid_values; diff --git a/storage/rocksdb/ha_rocksdb.cc b/storage/rocksdb/ha_rocksdb.cc index 57d6c4d68455..91827e65e4e2 100644 --- a/storage/rocksdb/ha_rocksdb.cc +++ b/storage/rocksdb/ha_rocksdb.cc @@ -2184,6 +2184,17 @@ static MYSQL_THDVAR_UINT(force_index_records_in_range, nullptr, nullptr, 0, /* min */ 0, /* max */ INT_MAX, 0); +static MYSQL_THDVAR_UINT( + records_in_range_dive_threshold, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_HINTUPDATEABLE, + "If the row estimate for records_in_range() is below this value, refine " + "it by scanning the index range, reading at most this many keys. RocksDB " + "size approximations work at data block granularity and may return 0 for " + "narrow ranges that contain many rows. Set to 0 to disable the " + "refinement.", + nullptr, nullptr, 100, + /* min */ 0, /* max */ INT_MAX, 0); + static MYSQL_SYSVAR_UINT( debug_optimizer_n_rows, rocksdb_debug_optimizer_n_rows, PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY | PLUGIN_VAR_NOSYSVAR, @@ -2832,6 +2843,7 @@ static struct SYS_VAR *rocksdb_system_variables[] = { MYSQL_SYSVAR(records_in_range), MYSQL_SYSVAR(force_index_records_in_range), + MYSQL_SYSVAR(records_in_range_dive_threshold), MYSQL_SYSVAR(debug_optimizer_n_rows), MYSQL_SYSVAR(force_compute_memtable_stats), MYSQL_SYSVAR(force_compute_memtable_stats_cachetime), @@ -13279,6 +13291,50 @@ ha_rows ha_rocksdb::records_in_range(uint inx, key_range *const min_key, DBUG_RETURN(ret); } +/* + PS-10110: Count keys in [start, end) (mem-comparable format, bytewise + order) by scanning the index, reading at most max_rows keys. Sets + *capped = true when max_rows was reached, i.e. the range contains at + least that many keys. Returns the number of keys seen. The scan reads + the latest committed state, like the size approximations it refines. +*/ +static uint64_t rdb_index_dive_in_range(const Rdb_key_def &kd, + const rocksdb::Slice &start, + const rocksdb::Slice &end, + uint64_t max_rows, bool *capped) { + rocksdb::ReadOptions read_opts; + // The range may cross prefix-extractor prefixes, so a total-order seek + // is required for correctness. + read_opts.total_order_seek = true; + // Give RocksDB a hard stop at the range end so it does not churn through + // tombstones beyond the range. Bounds are in CF-comparator order: for a + // reverse CF the bytewise range end is the comparator lower bound. + if (kd.m_is_reverse_cf) { + read_opts.iterate_lower_bound = &end; + } else { + read_opts.iterate_upper_bound = &end; + } + + const std::unique_ptr it( + rdb->NewIterator(read_opts, kd.get_cf())); + + *capped = false; + uint64_t count = 0; + rocksdb_smart_seek(kd.m_is_reverse_cf, it.get(), start); + while (is_valid_iterator(it.get())) { + // Keys are raw mem-comparable bytes regardless of CF direction, so a + // plain bytewise compare implements the exclusive upper bound (the + // iterate_lower_bound above is inclusive on its own). + if (it->key().compare(end) >= 0) break; + if (++count >= max_rows) { + *capped = true; + break; + } + rocksdb_smart_next(kd.m_is_reverse_cf, it.get()); + } + return count; +} + void ha_rocksdb::records_in_range_internal(uint inx, key_range *const min_key, key_range *const max_key, int64 disk_size, int64 rows, @@ -13342,6 +13398,30 @@ void ha_rocksdb::records_in_range_internal(uint inx, key_range *const min_key, rdb->GetApproximateMemTableStats(kd.get_cf(), r, &memTableCount, &sz); *row_count += memTableCount; *total_size += sz; + + /* + PS-10110: both estimates above work at coarse granularity + (GetApproximateSizes() at SST data block offsets, + GetApproximateMemTableStats() at skiplist sampling granularity) and can + return 0 for a narrow range that actually contains many rows, which the + caller floors to 1 and feeds to the optimizer, leading to poor join + orders. Refine small estimates with a bounded index dive, similar to + InnoDB: an exact count if the range has fewer keys than the threshold, + a lower bound of the threshold otherwise. For a TTL index the dive + counts not-yet-compacted expired records, just like the approximations + above. Partial indexes are skipped since a raw iterator does not see + unmaterialized rows and would undercount. + */ + const uint64_t dive_threshold = + THDVAR(ha_thd(), records_in_range_dive_threshold); + if (dive_threshold > 0 && *row_count < dive_threshold && + rocksdb_debug_optimizer_n_rows == 0 && !kd.is_partial_index()) { + bool capped = false; + const uint64_t dive_count = + rdb_index_dive_in_range(kd, slice1, slice2, dive_threshold, &capped); + *row_count = capped ? std::max(*row_count, dive_threshold) + : dive_count; + } DBUG_VOID_RETURN; }