Repository navigation
Conversation
|
Thanks for this one, and for #490 and #492 — those two are merged, and the measurements in #492 (the 1/2/4/8 concurrency curve) were good enough to review against directly. This one I'm closing, because the mechanism it describes does not hold on the LanceDB we ship. Details below so the work isn't lost, and so you can reopen on firmer ground if the symptom is still there. The
|
| write | pre-existing files removed | modified | new |
|---|---|---|---|
add() |
0 | 0 | 1 file, 2.1 MB |
merge_insert, all-new keys |
0 | 0 | 1 file, 2.1 MB |
merge_insert, all-existing keys |
0 | 0 | 1 file, 2.1 MB |
The 824 MB is untouched in every case — including the all-existing one, which I expected to rewrite and which does not. There is no "rewrite hundreds of MiB" path here to avoid.
What does cost more than add is finding the matches: 10.1 ms at 100k rows and 25.0 ms at 400k, against a flat 2.6–3.0 ms for add. That is the full outer join the source describes when no usable index exists — a scan of the three key columns, which is why it scales with table size and why it does not produce GiB of resident memory.
(Related, for whoever picks up #477: an indexed join does exist, but only helps a single-column key — 16.9 → 12.8 ms. Three single-column BTrees on our composite key gave nothing: 24.4 → 25.3 ms.)
The bundled default change costs more than the PR saves
Separately from the fast path, this PR sets optimize_rebuild_interval_seconds to 0.0 in both settings.py and default.toml, disabling the periodic rebuild. That sweep is the only application-level bound on active index UUID growth — rebuild_indexes says so in its own docstring, and so does the constant the PR leaves at 12h, which now disagrees with the config in every production path.
Measured with prune running every fifth beat, 2500-row table with FTS + IVF indexes, counting _indices/:
optimize + prune only (this PR) beat 25: 59 files → 50: 109 → 75: 159 → 100: 209 (+2/beat, linear)
optimize + prune + rebuild (main) beat 25: 6 files → 50: 6 → 75: 6 → 100: 6 (flat)
Prune cannot reclaim them; the manifest still references them. At the 60s heartbeat that is roughly 2880 new index files per day per table with nothing collapsing them.
On the symptom
I don't think you imagined it. But the observation that exists is #492's — 8.29 GiB on production replay, with a concurrency curve behind it — and both #490 and #492 are now in main.
A/B on the same 300-file / 3000-entry corpus, real server, RSS sampled once a second:
| drain | peak RSS | |
|---|---|---|
cc86c5b (before #490 and #492) |
244 s | 533 MB |
ec32f4b (after both) |
114 s | 439 MB |
2.1× faster, ~18% lower peak, and neither version comes near GiB. That scale is modest — one table at 3000 rows — so it does not prove the symptom can never appear. It does mean I can't see it, on either side of your two merged fixes.
If you can still reproduce it on current main, please reopen with the numbers — the shape of #492's report (peak against a varied parameter) is exactly what makes a memory claim reviewable, and it is what this PR is missing.
Two smaller things, so they aren't lost: ruff format --check fails on orchestrator.py here (CI would be red), and EVEROS_DISABLE_CASCADE_MAINTENANCE also disables prune, which its name does not suggest — if you want that switch, it would be worth its own PR with a runbook entry and a test.
|
One follow-up, in fairness to the other half of this PR. My comment above took apart the
On this one your direction was right and I was wrong to wave it off. The transient does scale with table size — roughly Where it still doesn't reach the claim: "several GiB" needs on the order of 4 million rows at that slope. If your production store is at that scale, say so and the picture changes — that is exactly the repro condition the PR was missing. For the record, I tried the same change myself (#507, now also closed) and it went badly: a corrupt state byte crashlooped the server, and the skip silently stretched the rebuild interval to 1.95x. So the idea is harder to land safely than it looks, independent of whose PR it is. A separate PR for just the startup-sweep question — persisted clock, failing closed, with the data volume stated — would be welcome. |
问题
在线服务进程与重量级 LanceDB 维护(compact/rebuild 等)运行在同一常驻进程内。每日日志的 upsert 走
merge_insert,即使批次里全部是新键,也会对大 fragment 重写数百 MiB 并驻留 GiB 级 Arrow/Lance 原生内存,多次把常驻服务推过内存水位线。改法
upsert先做轻量主键存在性探测:单列字符串主键且批次内全部为新键时,走 LanceDB 仅追加的add路径;只有确实包含已存在键的批次才支付merge_insert代价(保留上游 PRIMARY_KEY 声明式键不变式,无by参数)。验证
范围
仅含通用修复与测试,不含本地部署配置与环境文件。