risingwavelabs/risingwave · error

should be append-only for vector index writer but receive op

Error message

should be append-only for vector index writer but receive op {:?}

What it means

The vector index writer only accepts append-only input; each row in an incoming chunk must have op `Insert`. Any Delete or Update op causes `execute_inner` to return this error with the offending op for diagnosis.

Solutions

  1. Restrict the vector table to INSERT-only writes; model changes as new inserts.
  2. Verify upstream sources are append-only (no upsert CDC configured).
  3. Rebuild the vector index from scratch if historical mutations must be applied.

Example fix

-- before
DELETE FROM embeddings WHERE id = 1;
-- after
-- keep table append-only; filter stale rows at query time or rebuild the index
Defensive patterns

Strategy: validation

Validate before calling

-- application-side check before writing
-- only issue INSERT statements against the vector index input table

Prevention

When it happens

Trigger: A chunk row with `Op::Delete`, `Op::UpdateDelete`, or `Op::UpdateInsert` reaches the vector index writer in `execute_inner`, e.g. an upstream table mutation (UPDATE/DELETE) that must be reflected into the vector index.

Common situations: Updating or deleting embeddings after they were written; CDC/upsert sources feeding a vector index; mistaken use of a non-append-only materialized view as the vector index input.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/60e703dd21711937. Report an issue: GitHub.

Appendix: source

Thrown at src/stream/src/executor/vector/index_writer.rs:86

            .await?;

        while let Some(msg) = input.try_next().await? {
            match msg {
                Message::Barrier(barrier) => {
                    self.vector_writer.flush().await?;
                    self.vector_writer.seal_current_epoch(
                        barrier.epoch.curr,
                        SealCurrentEpochOptions {
                            table_watermarks: None,
                            switch_op_consistency_level: None,
                        },
                    );
                    yield Message::Barrier(barrier);
                }
                Message::Chunk(chunk) => {
                    for (op, row) in chunk.rows() {
                        if op != Op::Insert {
                            return Err(anyhow!(
                                "should be append-only for vector index writer but receive op {:?}",
                                op
                            )
                            .into());
                        }
                        let vector_datum = row.datum_at(0);
                        let Some(vector_datum) = vector_datum else {
                            warn!(
                                ?row,
                                "vector index writer received a row with null vector datum, skipping"
                            );
                            continue;
                        };
                        let vector = vector_datum.into_vector();
                        let info = self
                            .serializer
                            .serialize(row.project(&info_column_indices))
                            .into();

View on GitHub (pinned to 6469eb736d)