Weaviate: deleting a document never removes its vectors — delete_by_ids passes Dify segment ids to delete_by_id as object UUIDs
- Dominant language
- TypeScript
- Stars
- 156k
- Forks
- 24.6k
- Avg merge
- 22h 9m
- Merged PRs (30d)
- 610
Description
### Self Checks
- [x] I have searched for existing issues, including closed ones.
- [x] I confirm that I am using English to submit this report.
### Dify version
1.16.1 (self-hosted Docker). Also present in 1.13.3 — not a regression from the
VDB workspace refactor (#34900); the same code exists at the old path
`api/core/rag/datasource/vdb/weaviate/weaviate_vector.py`.
### Cloud or Self Hosted
Self Hosted (Docker), `VECTOR_STORE=weaviate`
### Steps to reproduce
1. Create a knowledge base backed by Weaviate and index a few documents.
2. Delete one document through the UI or `DELETE /v1/datasets/{id}/documents/{doc_id}`.
3. `clean_document_task` logs `Cleaned document when document deleted ... succeeded`.
4. Count objects in the Weaviate collection: **none were removed**.
### Actual behaviour
`delete_by_ids` receives Dify **segment ids** (`index_node_id`), which are stored in the
object's `doc_id` property. It passes them to `delete_by_id()`, which expects the
**Weaviate object UUID**. Those UUIDs are `uuid5(URL_NAMESPACE, page_content)` — see
`_get_uuids` in the same file — so the two never coincide.
Every call returns 404, and the 404 is swallowed on purpose:
```python
# api/providers/vdb/vdb-weaviate/src/dify_vdb_weaviate/weaviate_vector.py:378
for uid in ids:
try:
col.data.delete_by_id(uid)
except UnexpectedStatusCodeError as e:
if getattr(e, "status_code", None) != 404:
raise
```
Nothing is deleted, no error surfaces, and the task reports success.
### Expected behaviour
Deleting a document removes its vectors from the collection.
### Impact
Orphan vectors are still returned by the vector search but are dropped afterwards,
because Dify keeps only hits that still have a matching row in Postgres. They silently
consume `top_k` slots, so retrieval quality degrades as deletions accumulate, until a
knowledge base returns nothing at all with no error anywhere.
Measured on one knowledge base: deleting 4 documents left **9165 orphan objects**
(`document_id` no longer present in Postgres). A `dryRun` batch delete with a
`document_id` filter reported the same count, confirming the filter and index are fine —
only the delete path is wrong.
Deleting a whole dataset is unaffected: `clean_dataset_task` drops the entire collection,
a different code path.
### Proposed fix
Delete by filtering on the `doc_id` property, in batches:
```python
batch_size = 100
for start in range(0, len(ids), batch_size):
chunk = ids[start : start + batch_size]
col.data.delete_many(
where=Filter.any_of([Filter.by_property("doc_id").equal(i) for i in chunk])
)
```
`Filter` is already imported in the module. `equal` on a word-tokenised text property
matches the whole token sequence, so distinct UUIDs cannot collide; `contains_any` would
match single hex fragments and over-delete.
Verified on a live instance: 128 segments → 113 objects; after deleting a document with
63 segments, 50 objects remain (113 − 63), zero orphans. Before the change all 113 stayed.
Contributor guide
Research direction
The affected entry point is api/providers/vdb/vdb-weaviate/src/dify_vdb_weaviate/weaviate_vector.py, especially delete_by_ids and _get_uuids. Read those methods first and trace the document cleanup call that supplies segment ids. Done means deleting a document removes its vectors from the Weaviate collection without masking an unrelated failure; dataset deletion is a separate path.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- databases
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100