Repository navigation
Conversation
|
#113 needs one fix before it merges: on a production-sized (10-archive) lore DB, every lore run now prints a compaction warning for #113 alone. On a two-archive test DB (io-uring/0 + linux-nvme/0, with the last 6,000 commits of each archive held back and then published 125 per archive per run), the hourly The warning. It reports a compaction skip, not a failed write: A cold build writes the table about once per 1,024-commit batch, which left 993 fragments on the merged build (3,160 on main).
I have left out keeping the skip silent, as main does, because the table would then gain a fragment per archive on every refresh with nothing ever compacting it. Also not fixed. |
compact_lore_tables() skipped the lore table entirely and never touched lore_vectors, so neither ever dropped an old version. Every hourly refresh rewrites the five lore FTS indices in full (the body index alone is over 800 MB) and appends dozens of fragments, and all of it stayed on disk: on one host lore grew from 3.9 GB to 20 GB in two days, 17 GB of it superseded FTS indices. It also wedged FTS updates. optimize_lore_fts_indices() refuses to run once lore.lance/_indices holds more than 100 entries, and with nothing pruned the count only grew, by five per run. After about 14 hourly runs the indices stopped covering new mail for good. Prune old versions of lore from compact_lore_tables(), which only the lore run calls. Its Compact and index optimization phases, which the existing comment blames for running out of memory, stay skipped; prune reads only each manifest's index metadata. optimize_database() still skips lore: with delete_unverified set, a prune from another process could delete files a running lore refresh has not committed. Add lore_vectors to the tables compact_lore_tables() handles. Put the prune in one helper, prune_old_versions(), used by compact_lore_tables(), optimize_database() and create_lore_fts_indices(), which carried its own copy of the same call to clean up after rebuilding the FTS indices. The prune runs before the run's FTS update, so one superseded set of FTS indices remains on disk until the next run. It deletes their files but leaves their directories behind, so count only non-empty entries in the _indices guard.
Each lore parser thread sends a batch once it holds batch_size emails, and flushes whatever it has left when the commit list runs out. On an hourly refresh that adds a few hundred emails per archive, every thread ends up with a short batch, and each of them became its own append to lore and lore_indexed_commits: one host's refreshes added 40 to 80 fragments to each table per run, and the lore_vectors step then made one merge_insert per fragment. A parser only sends a short batch when its input has run out, so when an inserter receives one, keep receiving until it holds at least batch_size emails or every parser has finished. Full batches received on their own are inserted as before, and a merged batch stays under twice batch_size. In a simulated hourly refresh of 50 new emails with 32 parser threads, lore gains one fragment per run instead of 32.
update_lore_vectors() workers send one Vec per lore RecordBatch that still has emails without a vector, and the inserter ran a merge_insert into lore_vectors for each of them. On an incremental run most of those hold only a few vectors, while every merge_insert joins against the whole table: one host's day of new mail (6,228 emails) took about 1,600 merge_inserts and 56 minutes, nearly all of it in those joins, and each hourly run added 40 to 80 fragments to lore_vectors. Buffer vectors in the inserter and write once at least 8192 have accumulated, flushing the remainder when the workers finish. The merge semantics are unchanged. merge_insert inserts every unmatched source row, so keep one entry per message_id in a buffer, as separate merges used to. An hourly-sized run now adds one fragment. The progress bar still advances on every Vec the inserter receives, not at each write, so on an incremental run it does not sit at zero until the final write and then jump to the total. Its count can now run ahead of what has been written, so its message reads "Vectorized N emails" rather than "Inserted N vectors".
A cold lore build appends one lore_indexed_commits fragment per inserted batch: 1,013 for a production-sized set of archives, 3,660 for an upstream build. optimize_single_table() skips compaction above 500 fragments and nothing else brings the count down, so the table stays over the limit for good. Rebuilding with --clear, as the skip message advises, recreates the table over the limit. Exempt this table from the limit and keep the limit for every other table. Databases already over it compact on their next lore run that adds mail. The limit guards against a lance compaction CPU spin seen with thousands of small fragments (61e5493). lore_indexed_commits holds one 40-char SHA per row and no index. On lance 4.0.0 a table of that shape compacts in under 5 s at both 1,013 and 3,660 fragments, with no spin, whether compacted alone, as compact_lore_tables() does, or eight tables at a time, as optimize_database() does. The spin does not reproduce on lance 1.0.1 either, the version 61e5493 saw it on, so its cause is still unknown.
semcode-index printed "Lore table compaction completed successfully" and "FTS indices updated successfully" whatever had happened. The reasons for skipping or failing went to tracing::warn!, which the default SEMCODE_DEBUG level filters out, so the lore FTS indices stopped covering new mail for two days on one host without a line in the hourly unit's log. Print the FTS skip reasons and lore compaction problems to stderr. optimize_lore_fts_indices() and compact_lore_tables() now return whether they did their work, and the success lines are printed only when they did. optimize_single_table() also reports a table it did not compact because it has more than 500 fragments as a partial failure rather than success, since its fragment count only grows from there.
6b14580 to
4d66a45
Compare
|
I pushed an update that clears the permanent The new commit, "lore: compact lore_indexed_commits whatever its fragment count", adds the exemption in I checked first that compacting a table of this shape is safe. On lance 4.0.0 a table with one SHA per row and no index compacts in under 5 s at both 1,013 and 3,660 fragments, with no thread spinning, whether alone or alongside five other tables compacting at the same time. The spin from 61e5493 does not reproduce on lance 1.0.1 either, the version semcode used at that commit, so its cause is still unknown, which is why the limit stays for the other tables. The test builds 600 fragments in one write, which needs lance's The review fixes:
Still not fixed in this PR: |
Hourly
semcode-index --lorerefreshes grew one host's lore database from 3.9 GB to 20 GB in two days, and after about 14 runs the FTS indices stopped covering new mail, without a line in the log. These four commits stop the growth and make the failure visible; the prune they add assumes a single writer, explained below the list.loreorlore_vectors, so every superseded FTS index stayed on disk (17 GB of the growth). The_indicescount also kept rising untiloptimize_lore_fts_indices()refused to run.compact_lore_tables()now prunes both tables, and the_indicesguard counts only non-empty entries.batch_size, so an hourly refresh adds one fragment toloreinstead of one per parser.lore_vectors. The vector inserter ran onemerge_insertper lore RecordBatch, each joining against the whole table: a day of new mail took about 1,600 of them and 56 minutes. It now writes once per 8,192 vectors.tracing::warn!, which the default log level hides, and the success lines printed regardless. They now go to stderr, and the success lines print only when the work ran.The prune passes
delete_unverified, so it assumes no other process writes to the lore tables while a lore run is going: no overlapping lore runs and no separate--vectorsrun, as a systemd oneshot unit guarantees.optimize_database()still skipslorefor the same reason.This PR is independent of #112; they merge cleanly in either order.
Testing: on top of f6ba491, each commit passes
cargo check --all-targets, and the branch passescargo fmt --check,cargo clippy --all-targets -- -D warningsandcargo test(510 passed, 0 failed). The size and fragment numbers come from a host running hourly lore refreshes, and from a simulated refresh of 50 new emails.