Bug Description
For string-attribute equality SELECTs, the cost-based optimizer switches from SecondaryIndex to a multithreaded full scan once a table grows past a row-count threshold (between 5M and 15M rows on a 32-thread server in our tests) — and the scan cost estimate appears to be blind to row width, so on wide tables the chosen plan is 300–1,000× slower than the SecondaryIndex it declines to use. The SI itself is healthy: forcing it with /*+ SecondaryIndex(attr) */ works and is fast.
Measurements (all RT tables, default settings, Manticore 25.0.0, 32-thread server)
| Table |
rows |
unhinted WHERE sattr='x' LIMIT 3 |
hinted |
CBO choice (SHOW META) |
| narrow (text + 1 string attr) |
5M |
0.014s |
— |
SecondaryIndex (100%) |
| narrow (same schema) |
15M |
0.064s |
— |
scan (no index row) |
| wide (text + 4 string attrs, ~+240B/row) |
15M |
1.52s |
0.005s |
scan |
| production table (~60 attrs incl. JSON/MVA, ~47GB) |
15M |
23–27s (156s for COUNT-style no-LIMIT) |
0.024s |
scan |
So the row-count crossover itself might be defensible on narrow tables (0.064s scan), but because the estimate ignores per-row cost, the same decision on wide tables is catastrophically wrong.
Strong evidence the crossover is the multithreaded-scan preference
OPTION threads=1 (no hint) makes the CBO pick the SecondaryIndex again on both the synthetic wide table and the production table:
SELECT id FROM big_table WHERE sattr='value' LIMIT 3 OPTION threads=1;
SHOW META; -- index: sattr:SecondaryIndex (100%), 0.034s on the production table
Notes: COUNT(*) WHERE sattr='x' stays fast via the Precalc path; numeric-attribute equality keeps choosing SI at all sizes — only string-attr SELECTs are affected, which makes this confusing to diagnose in production.
Repro script
# pymysql against a stock searchd; takes ~5 min
import random, string, pymysql
c = pymysql.connect(host="127.0.0.1", port=9306, autocommit=True).cursor()
c.execute("CREATE TABLE w (title text, sattr string, s2 string, s3 string, s4 string)")
random.seed(9); AB = string.ascii_lowercase
rs = lambda k: "".join(random.choices(AB, k=k))
f2, f3, f4 = rs(80), rs(90), rs(70)
n = 0
while n < 15_000_000:
vals, args = [], []
for j in range(5000):
vals.append("(%s,%s,%s,%s,%s,%s)"); args += [n+j+1, "t", rs(24), f2, f3, f4]
c.execute("INSERT INTO w (id,title,sattr,s2,s3,s4) VALUES " + ",".join(vals), args)
n += 5000
c.execute("FLUSH RAMCHUNK w")
# pick any existing value, then compare:
# SELECT id FROM w WHERE sattr='<v>' LIMIT 3; -- ~1.5s, scan
# SELECT id FROM w WHERE sattr='<v>' LIMIT 3 /*+ SecondaryIndex(sattr) */; -- ~5ms
# SELECT id FROM w WHERE sattr='<v>' LIMIT 3 OPTION threads=1; -- ~50ms, picks SI
Version
Manticore 25.0.0 ce3c27828@26032712 (columnar 13.0.0) (secondary 13.0.0) (knn 13.0.0)
Ubuntu, searchd threads = 32, secondary_indexes = 1, RT tables, row-wise storage
Related: commit d96ec6b ("boost string filter cost in CBO", 6.2.0) suggests string filters were intended to prefer SI. Happy to run further diagnostics.
Bug Description
For string-attribute equality SELECTs, the cost-based optimizer switches from SecondaryIndex to a multithreaded full scan once a table grows past a row-count threshold (between 5M and 15M rows on a 32-thread server in our tests) — and the scan cost estimate appears to be blind to row width, so on wide tables the chosen plan is 300–1,000× slower than the SecondaryIndex it declines to use. The SI itself is healthy: forcing it with
/*+ SecondaryIndex(attr) */works and is fast.Measurements (all RT tables, default settings, Manticore 25.0.0, 32-thread server)
WHERE sattr='x' LIMIT 3SecondaryIndex (100%)indexrow)So the row-count crossover itself might be defensible on narrow tables (0.064s scan), but because the estimate ignores per-row cost, the same decision on wide tables is catastrophically wrong.
Strong evidence the crossover is the multithreaded-scan preference
OPTION threads=1(no hint) makes the CBO pick the SecondaryIndex again on both the synthetic wide table and the production table:Notes:
COUNT(*) WHERE sattr='x'stays fast via thePrecalcpath; numeric-attribute equality keeps choosing SI at all sizes — only string-attr SELECTs are affected, which makes this confusing to diagnose in production.Repro script
Version
Related: commit d96ec6b ("boost string filter cost in CBO", 6.2.0) suggests string filters were intended to prefer SI. Happy to run further diagnostics.