Skip to main content

Compact a bloated ChunkHound DuckDB index by rebuilding it into a fresh file

Project description

ChunkHound Index Compactor

Compact a DuckDB database by rebuilding it into a fresh file. The motivating and supported use case is shrinking a bloated ChunkHound index, whose drop-and-recreate HNSW churn (above its 50-row write-batch threshold) leaves large amounts of orphaned-but-counted blocks. The rebuild pipeline is structurally generic and works on other single-schema DuckDB files, but only ChunkHound-shaped inputs are promised: any shape outside that scope is refused at the front gate (see the Not Supported section below) rather than silently dropped or rebuilt with loss.

That bloat comes back as ChunkHound keeps indexing, so compaction is periodic maintenance rather than a one-time cleanup.

⚡ Quick Start

uvx chunkhound-index-compactor path/to/db.duckdb
# writes path/to/db.duckdb.compacted

uvx chunkhound-index-compactor path/to/db.duckdb --replace
# swaps in the compacted copy and keeps the original at path/to/db.duckdb.bak

uvx chunkhound-index-compactor path/to/.chunkhound --replace
# point at the index directory instead of the .db file inside it

uvx chunkhound-index-compactor path/to/db.duckdb --skip-hnsw
# skips rebuilding vector indexes (RAM-flat, smallest output); restore them later
uvx chunkhound-index-compactor restore path/to/db.duckdb.compacted

uvx fetches a throwaway copy each run. Since compaction is recurring maintenance, install it once with uv tool install chunkhound-index-compactor, then drop the uvx prefix and call chunkhound-index-compactor directly:

uv tool install chunkhound-index-compactor
chunkhound-index-compactor path/to/db.duckdb --replace

The source is opened read-only, but an active writer holds the file lock. Close any process writing to the database before running.

🖥️ CLI Usage

$ chunkhound-index-compactor --help
Usage: chunkhound-index-compactor [OPTIONS] COMMAND [ARGS]...

Options:
  --version  Show the version and exit.

Commands:
  compact  Compact a DuckDB database by rebuilding it into a fresh file. (default)
  restore  Rebuild HNSW vector indexes in a --skip-hnsw artifact, in place.

A bare invocation routes to compact, so chunkhound-index-compactor SOURCE still works:

chunkhound-index-compactor SOURCE [TARGET] [--replace] [--skip-hnsw]
chunkhound-index-compactor restore DATABASE
Argument / Option Meaning
SOURCE Path to a DuckDB file, or a ChunkHound index directory (required)
TARGET Path for the compacted output [default: <source>.compacted]
--replace After success, replace source with the compacted file (original → <source>.bak)
--skip-hnsw Do not rebuild vector indexes; write a recipe table for later restore
--version Print the installed package version and exit (top-level flag)

When SOURCE is a directory, the tool resolves to the single ChunkHound index inside it, so you can pass the index directory directly. Any other directory shape fails, listing the DuckDB files it found (if any) so you can name the exact path.

With --skip-hnsw, the output has no vector index and falls back to a brute-force scan (correct, just unaccelerated) until you run restore. Rebuilding the HNSW is the memory-dominant step, so --skip-hnsw lets you compact on a small machine and restore on a RAM-capable one. See docs/benchmarks.md for peak-RAM numbers and docs/architecture.md §RAM cost asymmetry for why.

🐍 Library Usage

from pathlib import Path
from chunkhound_index_compactor import compact_database, restore_indexes, replace_with_compacted

result = compact_database(Path("big.duckdb"), Path("small.duckdb"))
print(f"{result.source_size} -> {result.target_size} ({result.delta_pct:+.1f}%)")

# Small-RAM path: skip the vector index, restore it later on a bigger machine.
compact_database(Path("big.duckdb"), Path("small.duckdb"), skip_hnsw=True)
restored = restore_indexes(Path("small.duckdb"))
print(f"restored: {restored.restored}")

# Optional: swap in place with .bak backup
backup = replace_with_compacted(result.source, result.target)

compact_database() raises:

  • ValueError: target resolves to the same path as source, the FK graph has a cycle, or the source has a shape refused at the front gate (see the Not Supported section below).
  • FileNotFoundError: source does not exist.
  • FileExistsError: target already exists.
  • RuntimeError: the bundled vss extension binary cannot be located (only reachable if the source contains an HNSW index).

restore_indexes() raises:

  • FileNotFoundError: database does not exist.
  • ValueError: database has no _compactor_hnsw_recipe table (not a --skip-hnsw artifact).
  • RuntimeError: the bundled vss extension binary cannot be located.

replace_with_compacted() raises:

  • FileNotFoundError: source or compacted is missing.
  • FileExistsError: <source>.bak already exists (it refuses to overwrite an existing backup).
  • OSError: the move from compacted to source fails even via the cross-filesystem fallback (shutil.move).

🚫 Not Supported

The tool fails hard rather than silently dropping anything it cannot reproduce.

  • Non-main schemas and views (raise ValueError).
  • User-defined types, generated columns, self-referential foreign keys, and HNSW indexes on non-bare-column expressions (raise ValueError).
  • Foreign-key cycles among tables (raise ValueError).
  • HNSW tuning parameters other than metric (M, M0, ef_construction, ef_search); they are not recoverable from a built index and are rebuilt at the vss defaults.
  • Table and column comments are not carried across the rebuild.

See docs/architecture.md for the reasoning, and docs/out-of-scope.md for approaches considered and not pursued.

🏗️ Development

Setup, local checks, CI, and release process: CONTRIBUTING.md.

⚖️ License

MIT


[!NOTE] Yes, an AI wrote this README. And the code, the docs, the tests, and the .claude/skills it now uses to write the next round. Yes, a human told it to keep the emojis. The human has ADHD, which, as it turns out, means his brain was already doing attention re-routing and context-window thrashing before LLMs made it cool. They call him ... LLMartin. The emojis are a feature.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

chunkhound_index_compactor-0.4.0.tar.gz (60.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

chunkhound_index_compactor-0.4.0-py3-none-any.whl (15.1 kB view details)

Uploaded Python 3

File details

Details for the file chunkhound_index_compactor-0.4.0.tar.gz.

File metadata

File hashes

Hashes for chunkhound_index_compactor-0.4.0.tar.gz
Algorithm Hash digest
SHA256 191c025bceed7ad2eff70db013a3dc392bb145d6dcd20f32550f04ddc8e3d99e
MD5 67c900e7c33e919956e9ee157039e2bc
BLAKE2b-256 a93a2aa1be9126a62bf3d8b584fcac08bfe4626da7100910c9d1a5fe57646657

See more details on using hashes here.

Provenance

The following attestation bundles were made for chunkhound_index_compactor-0.4.0.tar.gz:

Publisher: release.yml on it-bens/chunkhound-index-compactor

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file chunkhound_index_compactor-0.4.0-py3-none-any.whl.

File metadata

File hashes

Hashes for chunkhound_index_compactor-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a27e3f5618ed696b8f0c57392698f731741ae4dd3647712f963b02fd3a4d2215
MD5 ee12a0cab3391224b7947341095fe79d
BLAKE2b-256 e5b8b8aa74554fd1fe9a703f1082b430af66cc9a723c00bc3d43222e9701c94d

See more details on using hashes here.

Provenance

The following attestation bundles were made for chunkhound_index_compactor-0.4.0-py3-none-any.whl:

Publisher: release.yml on it-bens/chunkhound-index-compactor

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page