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).

🤖 GitHub Action

Run compaction in a workflow without installing uv or managing a Python toolchain. The action creates an isolated environment with the runner's preinstalled Python and runs compact in it.

- uses: it-bens/chunkhound-index-compactor@<commit-sha> # vX.Y.Z
  with:
    index-path: .chunkhound
    replace: "true"

The inputs index-path, target, replace, and skip-hnsw map to the SOURCE, TARGET, --replace, and --skip-hnsw arguments above; only index-path is required. Pin to a commit SHA and let Renovate or Dependabot bump it from the trailing version comment.

The action wraps compact only; run restore separately (see CLI Usage above).

🚫 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.6.0.tar.gz (72.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.6.0-py3-none-any.whl (12.5 kB view details)

Uploaded Python 3

File details

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

File metadata

File hashes

Hashes for chunkhound_index_compactor-0.6.0.tar.gz
Algorithm Hash digest
SHA256 d30fc6399520644ff36a892eb99272fc9fb18d006ddbe8f8026ff6b30e7b9223
MD5 ee8e79e35ad2b34e99ad497929a7a496
BLAKE2b-256 c3b149b696655759286d21fd2826b05086683ba05f692bce01d46672d73de83c

See more details on using hashes here.

Provenance

The following attestation bundles were made for chunkhound_index_compactor-0.6.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.6.0-py3-none-any.whl.

File metadata

File hashes

Hashes for chunkhound_index_compactor-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c5326feee79f6286f0a8c8a2f3d5e671ab87eac25b380e1083e76c8bebe3c790
MD5 69894f1649563e71f8362568294fdd87
BLAKE2b-256 74843a87045a2c55664f508d21532af8680316319948a9063a7ddbea6ee25014

See more details on using hashes here.

Provenance

The following attestation bundles were made for chunkhound_index_compactor-0.6.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