## TL;DR
Chroma's persistent client stores data in SQLite, and SQLite allows only one writer at a time. Two processes or threads writing to the same persist directory collide and one gets "database is locked". Serialize your writes through a single process, or switch to Chroma's client/server mode for real concurrency.

## Error
```text
sqlite3.OperationalError: database is locked
```

## Steps

1. Find the concurrent writers. Check for multiple processes, threads, or replicas pointing at the same persist directory:
```bash
lsof [persist-directory]/chroma.sqlite3
```
Expected output: the list of processes holding the file. More than one writer is the problem. (Replace [persist-directory] with your actual path.)

2. As the quick fix, funnel all writes through one process. Queue writes in the app (a single writer thread or a task queue) so the database file sees one writer:
```python
# one shared client, writes funneled through a single thread
client = chromadb.PersistentClient(path="[persist-directory]")
```
Expected output: the lock errors stop because writes no longer overlap. Reads can stay concurrent; only writes need serializing.

3. If you genuinely need concurrent writers (multiple workers, replicas), run Chroma in client/server mode instead of persistent mode:
```bash
chroma run --path [persist-directory]
```
Expected output: a server that serializes writes for you. Point workers at it with an HttpClient instead of PersistentClient.

4. Re-run the workload that failed and confirm clean writes.
Expected output: collections upsert without OperationalError, and the data is visible to all readers.

## When to use
- Persistent Chroma raises "database is locked" on upsert or add
- Multiple threads, processes, or container replicas share one persist directory
- The error is intermittent and correlates with write load

## When not to use
- The error is "unable to open database file" (that is permissions or a bad path)
- You run a single process and still see it (check for a second stray process or a hung writer holding a transaction)
- You already use client/server mode (the lock then lives elsewhere)

## Variant phrasings

### database is locked only under heavy write load
Light load serializes by luck; heavy load overlaps transactions. Same fix: single writer or server mode.

### locked right after a crash
A crashed writer can leave a stale lock or WAL file. Stop all processes, verify no process holds the file, then restart a single writer.

## Why it happens
Persistent mode embeds SQLite, which takes an exclusive lock for each write transaction. Overlapping writes from two threads or processes make the second one wait, then time out with "database is locked". It is a storage-engine limit, not a Chroma bug, which is why the fix is architectural (one writer) rather than a retry loop.

## Edge cases
- Network filesystems (NFS, some FUSE mounts) break SQLite locking entirely; keep the persist directory on local disk.
- A long-running read transaction can block writers; keep transactions short.
- Retrying with backoff masks the problem under light load and fails under real concurrency; fix the writer topology instead.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_xWl9toHBKNRXp2Nun5xuQA
