Skip to content

Deleted documents remain in embeddings_queue on 1.5.9 with automatically_purge=true (reproduces closed #3793) #7659

Description

@rutvikbuilds

Summary

#3793"Document deletion leaves plain text and embeddings in db" — was closed as completed on 2025-06-30 with #4884 named as the fix. A commenter reported afterwards that it still reproduced on 1.0.15 and the thread went quiet.

It still reproduces on 1.5.9 (current release), on a store created fresh by 1.5.9, with automatically_purge enabled — which is the default for a new store. embeddings_queue is never trimmed: it grows monotonically and retains the document text and embedding of every deleted record.

Reproduction

chromadb==1.5.9, Python 3.12, macOS arm64, PersistentClient.

import hashlib, os, shutil, sqlite3
import chromadb

DB = os.path.abspath("chroma-persist")
if os.path.exists(DB):
    shutil.rmtree(DB)

def embed(texts):
    return [[(hashlib.sha256(t.encode()).digest()[i % 32] / 255.0) for i in range(32)]
            for t in texts]

def wal(label):
    con = sqlite3.connect(os.path.join(DB, "chroma.sqlite3"))
    n = con.execute("SELECT count(*) FROM embeddings_queue").fetchone()[0]
    cfg = con.execute("SELECT * FROM embeddings_queue_config").fetchall()
    raw = open(os.path.join(DB, "chroma.sqlite3"), "rb").read()
    print(f"{label:26} queue={n:<5} subject on disk: {b'Dana Whitfield' in raw}  {cfg}")

SUBJECT = "Dana Whitfield lives at 42 Alder Row, dana.whitfield@example.com"

client = chromadb.PersistentClient(path=DB)
col = client.create_collection("memories")
col.add(ids=["m1", "m2", "m3"],
        documents=[SUBJECT, "Marcus prefers dark mode", "Kafka runs on 9092"],
        embeddings=embed([SUBJECT, "a", "b"]))
wal("after add 3")

col.delete(ids=["m1", "m2"])
wal("after delete 2")

del col, client
chromadb.api.client.SharedSystemClient.clear_system_cache()
client = chromadb.PersistentClient(path=DB)
col = client.get_collection("memories")
wal("after restart")

docs = [f"Filler record number {i}" for i in range(150)]
col.add(ids=[f"f{i}" for i in range(150)], documents=docs, embeddings=embed(docs))
wal("after 150 more writes")

Actual

after add 3                queue=3     subject on disk: True   [(1, '{"automatically_purge":true,...}')]
after delete 2             queue=5     subject on disk: True   [(1, '{"automatically_purge":true,...}')]
after restart              queue=5     subject on disk: True   [(1, '{"automatically_purge":true,...}')]
after 150 more writes      queue=155   subject on disk: True   [(1, '{"automatically_purge":true,...}')]

The queue only grows. Deleting two records adds two rows rather than removing two.

Expected

With automatically_purge true, entries for records that have been deleted and compacted should not remain in embeddings_queue indefinitely.

What the queue holds

seq=1 op=add     m1   128-byte vector   "Dana Whitfield lives at 42 Alder Row…"
seq=2 op=add     m2   128-byte vector   "Marcus prefers dark mode"
seq=3 op=add     m3   128-byte vector   "Kafka runs on 9092"
seq=4 op=delete  m1   (the add row above is not removed)
seq=5 op=delete  m2   (the add row above is not removed)

The embeddings table is pruned correctly and the API is consistent throughout — count() returns the right number and get() does not return the deleted ids. The residue is only visible below the API.

Other things tried

  • chroma vacuum --path <dir> --force — reclaims some space (16 KiB, 4% on the 155-row store above) but does not purge the queue: embeddings_queue stays at 155 rows and the deleted records' text is still in the file afterwards.
  • No client or collection method in 1.5.9 exposes a purge, compact or truncate operation.
  • Two restarts and 150 further writes, in case a sync threshold gates it. No change.

Why it matters

Unbounded growth. This was the original reporter's complaint on #3793: a delete-then-reinsert workflow grows chroma.sqlite3 forever.

Deleted personal data persists in plaintext. For anyone using Chroma to hold personal data, delete() removes the record from the API while the document text and its embedding stay in the file. That is a data-retention exposure rather than a correctness bug — the API is telling the truth about its own view, and the data is still on disk. Embeddings are also not reliably non-identifying (Morris et al., Text Embeddings Reveal (Almost) As Much As Text), though here the plaintext is present anyway.

Not tested

Ask

Reopen #3793, or treat this as its continuation — happy to move the detail there instead if that is easier to track.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions