Skip to content

Semantic memory's update_feature reads its vector back to rewrite it: concurrent updates lose an embedding, and a missed read fails the update half-applied #1721

Description

@edwinyyyu

Bug

VectorStoreSemanticStorage.update_feature (packages/server/src/memmachine_server/semantic_memory/storage/vector_store_semantic_storage.py, main at d57f5cb36) updates the feature row and commits it, then rewrites the feature's vector record. Since upsert replaces a whole record and the row holds no embedding, it first reads the stored vector back (_get_existing_vector_record: VectorStoreCollection.get(..., return_vector=True)) and upserts it again with fresh properties. That read-modify-write fails in two ways.

1. Concurrent updates lose an embedding, on every backend. Take two updates of one feature: A with a new embedding, B changing only text or metadata. B reads the old vector, A upserts the new one, and then B upserts the old vector over it. The row keeps A's text, but the feature is searched by its old embedding from then on. Nothing serializes the two updates.

2. An update fails half-applied when its read misses. The row is committed before the read. When get does not return the vector record, update_feature raises ResourceNotFoundError("Vector record not found: ...") with the row updated and the vector record not. Whether get sees a recent write is up to the backend. On Milvus, the store's reads run at the collection's consistency level, Session by default, which waits only for the reading process's own writes. An update served by a process other than the one that created or last updated the feature can therefore miss the record, or read an older vector (failure 1 without any concurrency).

Expected

An update of a feature's text or metadata keeps the last embedding written for it. An update never fails, or applies half, because of a read of the vector store.

Fix

Don't read the vector back. Nothing reads the vector record's properties except feature_id: search maps hits to rows through it, and the rows hold every other value. So the record can carry only its vector and feature_id, and update_feature can write the vector store only when it is given a new embedding, writing the whole record then. #1663 (the port of #1598) removes the read, with a vector_uuid column in place of the property, and removes VectorStoreCollection.get, whose only production caller this is.

Activity

  1. added theissue type on Sep 29, 2026
  2. added
    concurrencyRaces, lost updates, unarbitrated read-then-write under concurrent requests or processes
    recoveryA failure or crash leaves durable state nothing repairs: partial writes, lost jobs, no retry
    on Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    concurrencyRaces, lost updates, unarbitrated read-then-write under concurrent requests or processesrecoveryA failure or crash leaves durable state nothing repairs: partial writes, lost jobs, no retry

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions