--- title: "Compaction and cleanup" sidebarTitle: "Compaction" description: "Merge small files, prune old versions, and fold new rows into existing indexes." icon: broom --- Every write commits a new table version, and versions accumulate: small files from frequent appends, superseded files from updates and deletes, and rows added since an index was built. `optimize()` addresses all three. It is modelled after `VACUUM` in PostgreSQL — routine housekeeping that a table needs periodically rather than a repair for something broken. ## What it does {#what-it-does} ```python table.optimize() ``` Three operations, in one call: | | | | --- | --- | | **Compaction** | merges small files into larger ones | | **Prune** | removes versions older than the retention window | | **Index** | folds newly added rows into existing indexes | The third is the one people miss. An [index](/indexing) does not automatically cover rows appended after it was built — those rows are still searchable, but by scanning rather than through the index. Running `optimize()` is what brings them under it. ## Retention {#retention} ```python from datetime import timedelta table.optimize(cleanup_older_than=timedelta(days=30)) ``` `cleanup_older_than` defaults to **7 days**. Versions older than that are removed, along with the files only they referenced. The latest version is never removed, whatever you pass — `timedelta(0)` prunes everything except it. Pruning is what makes [time travel](/tables/versioning) finite. A version outside the retention window is gone, and a tag or branch pointing at it no longer resolves. If you rely on reading old versions, set the window to cover the range you need before running this, not after. ## Files from failed transactions {#unverified} A write that fails partway can leave files that look like an operation still in progress. `optimize()` will not remove them until they are at least seven days old, because it cannot tell them apart from a live write. ```python table.optimize(delete_unverified=True) ``` `delete_unverified=True` removes them regardless of age. Only set it when you can guarantee **no other process is writing to the table** — otherwise it can delete files belonging to a write that is genuinely in progress, and corrupt the table. ## Retraining indexes {#retrain} ```python table.optimize(retrain=True) ``` Folding new rows into an index preserves the structure the index was trained on. When the data has shifted enough that the structure itself is a poor fit — a vector index whose clusters no longer reflect the distribution — `retrain=True` rebuilds it rather than extending it. More expensive than a normal optimize, and worth it after a large ingest that changed the shape of the data rather than just its volume. ## When to run it {#when} There is no background compactor: `optimize()` runs when you call it. Frequent small appends are what make it necessary, because each one commits files. A table written in a few large batches needs it rarely; one taking continuous small writes needs it on a schedule. Compaction rewrites files, which conflicts with a concurrent [computed-column refresh](/feature-engineering/add-computed-column) or [view refresh](/feature-engineering/materialized-views) planned against the version it replaces. Sequence them rather than running both at once.