Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ENH] CIP: Write-Ahead Log Pruning & Vacuuming #2498

Merged
merged 11 commits into from
Jul 23, 2024
67 changes: 67 additions & 0 deletions docs/cip/CIP-07102024_Write_Ahead_Log_Pruning_Vacuuming.md
codetheweb marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# CIP-07102024: Write-Ahead Log Pruning & Vacuuming

## Status

Current Status: `Under Discussion`

## Motivation

Chroma's SQLite-based write-ahead log grows infinitely over time. When ingesting large amounts of data, it's not uncommon for the SQLite database to grow to many gigabytes in size. Large databases cost more, take longer to back up, and can result in decreased query performance.

There are two separate problems:

- The database, specifically the `embeddings_queue` table, has unbounded growth.
- The SQLite `VACUUM` command, often recommended for such scenarios, is a blocking and potentially slow operation. Read and write operations are both blocked during a `VACUUM`.[^1]

This CIP addresses both issues.

## Proposed Changes

A new configuration parameter will be added, `log:vacuum_threshold`. It defaults to 1GB. Following Postgres' convention, the unit is megabytes. This helps avoid excessive fragmentation—without this parameter, or if it's set to `0`, it is effectively the same as SQLite's full vacuum mode.
codetheweb marked this conversation as resolved.
Show resolved Hide resolved

Two additional things will be done after write transactions:

1. The `embeddings_queue` table will be pruned to remove rows that are no longer needed. Specifically, rows with a sequence ID less than the minimum sequence ID of any active subscriber will be deleted.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WAL is extremely valuable, as more than once, users have corrupted DBs or accidentally deleted their binary index dirs. Let's thread lightly here and make this an opt-in. Furthermore, let's make users aware of the ramifications of WAL pruning and potentially offer ways or processes to back up the WAL, which can help recovery.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel somewhat strongly that WAL is not intended to be the primary backup solution and that the two should not be conflated. We should absolutely offer an easy way to backup and restore databases (though a CLI command?) but imo that's separate from cleaning the WAL. Postgres and MySQL both automatically clean the WAL. Even SQLite itself will automatically truncate the WAL by default when in WAL mode.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not saying to use the WAL as a backup. All I'm saying is that the WAL is currently the only recovery option in Chroma, and users (some) have come to expect that Chroma will automatically rebuild binary indices if they happen to delete them. Enabling auto-pruning by default circumvents an existing behavior and user (again, some) user expectations.

Copy link
Collaborator

@HammadB HammadB Jul 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a valid point on some levels. I view this an instance of https://www.hyrumslaw.com/.

and users (some) have come to expect that Chroma will automatically rebuild binary indices if they happen to delete them.

While this may be true, and people may be in the habit of deleting database files and expecting them to just get rebuilt from the WAL - I would argue this is absolutely pathological behavior that we should not encourage or support. Manually deleting files and expecting the database to recreate them seems ripe for disaster. If the flow we want to support is rebuilding indices, we should productize that and promote it to the top level.

Enabling auto-pruning by default circumvents an existing behavior

Yes there is some undocumented fact that the WAL is infinite, but this should not be relied upon and preserved. I think we shouldn't prevent ourselves from doing the right thing - pruning the wal and preventing unbounded wal growth - in order to preserve users doing the wrong thing - relying on the WAL for backup/restore behavior as opposed to crash recovery.

Copy link
Contributor Author

@codetheweb codetheweb Jul 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe as part of 0.6.0, we can document recommended backup/recovery flows (probably shut down chroma server, make copy of directory--unless we have bandwidth to add a chroma backup command?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tazarov please see updated proposal that addresses this

2. `PRAGMA freelist_count` will be checked to see if the number of free pages multiplied by the page size (`PRAGMA page_size`) is greater than the `log:vacuum_threshold` parameter. If so, `PRAGMA incremental_vacuum` is run to free up pages. This is a non-blocking operation.

Tentatively this will be done after every write transaction, assuming the added latency is minimal. If this is not the case, it will be run on some interval.

## Public Interfaces

In addition to the configuration parameter described above, a new `chroma vacuum` command will be added to the CLI to manually perform a full vacuum of the database. Usage:

```bash
chroma vacuum --path ./chroma_data
```

This automatically runs the pruning operation described above before running `VACUUM`. Prior to any modifications, it checks that there is enough available disk space to complete the vacuum (i.e. the free space on the disk is at least twice the size of the database).[^2]

`chroma vacuum` should be run infrequently; it may increase query performance but the degree to which it does so is currently unknown.
codetheweb marked this conversation as resolved.
Show resolved Hide resolved

We should clearly document that `chroma vacuum` is not intended to be run while the Chroma server is running, maybe in the form of a confirmation prompt.

## Compatibility, Deprecation, and Migration Plan

Incremental vacuuming is not available by default in SQLite, and it's a little more complicated than just flipping a setting:

> However, changing from "none" to "full" or "incremental" can only occur when the database is new (no tables have yet been created) or by running the VACUUM command.[^3]

This means existing installations will not benefit from auto-pruning until they run `chroma vacuum`.

Users should see disk space freed immediately after upgrading and running `chroma vacuum` for the first time. Subsequent runs of `chroma vacuum` will likely free up no or very little disk space as the database will be continuously auto-pruned from that point forward.

## Test Plan

Auto-pruning should be thoroughly tested with property-based testing. We should test `chroma vacuum` with concurrent write operations to confirm it behaves as expected and emits the appropriate error messages.

## Rejected Alternatives

**Only prune when running `chroma vacuum`**: instead of continuously pruning the `embeddings_queue` table, only prune it when running `chroma vacuum` or some other manual command. This alternative was rejected because Chroma should be able to automatically keep its database size in check without manual intervention.

## Resources

- [Excellent overview of different vacuuming strategies](https://blogs.gnome.org/jnelson/2015/01/06/sqlite-vacuum-and-auto_vacuum/)

[^1]: [SQLite Vacuum](https://sqlite.org/lang_vacuum.html)
[^2]: [2.9: Transient Database Used by Vacuum](https://www.sqlite.org/tempfiles.html)
[^3]: https://www.sqlite.org/pragma.html#pragma_auto_vacuum
Loading