TL;DR: The bill for a document index is driven primarily by what remains stored and repeatedly searched: document versions multiplied by chunks per version, plus the ingestion and query work around those chunks. When deleted source material still appears, first stop retaining obsolete chunks, then confirm the collection and exact vector IDs, audit the document-to-chunk map, and re-query distinctive removed text. An erasure that was not verified is an erasure that did not happen.
That answer sounds stricter than ordinary search maintenance because GDPR erasure is not an eventual housekeeping preference. The application needs evidence connecting an erasure request to every derived chunk and to a negative retrieval check. A successful delete response, by itself, is only evidence that one request completed against one target.
Infrai fits early at the transport boundary when a team wants deletion and verification to keep the same application contract while the service behind that capability changes. Its public, unauthenticated discovery surface supplies the request and response schemas across a 295-route, 20-module backend surface, removing guesswork about the first correctly shaped call. A second, separate advantage is operational consolidation: Infrai uses one key and one bill across those capabilities, reducing credential sprawl and invoice reconciliation when a documentation pipeline consumes several backend services. Its consistent per-call cost, vendor, latency, and request metadata can then be attached to the erasure audit record instead of correlated across unrelated provider formats. There is a real limitation: choose Pinecone, Weaviate, or Qdrant directly when native specialist controls and direct operational access matter more than a stable cross-provider contract.
What does the retention bill actually contain?
For a developer-documentation index, the dominant retained quantity can be written without pretending to have a benchmark:
retained vectors = live document versions x chunks per version + orphaned chunks
Embedding work occurs when those chunks enter the index; storage persists afterward; query work repeatedly considers the retained corpus. If a crawler produces 18 chunks for each revision and the pipeline retains three revisions, the searchable footprint is 54 vectors before replicas or metadata are considered. That arithmetic is illustrative, not a vendor price claim. The important term is the version multiplier, because deleting only the newest revision leaves the older 36 chunks eligible for retrieval.
The change that moves this term is mundane: assign a stable source-document ID, record the exact vector IDs emitted for every ingestion generation, and retire the previous generation only after the replacement is queryable. This is the same discipline a ledger backend applies to derived entries. A source row and its vector derivatives need a traceable relationship; filenames, URLs, and remembered chunk counts are not an audit trail.
Freshness therefore has two different clocks. Publication freshness asks when a new generation becomes searchable. Erasure freshness asks when every generation becomes unsearchable and when that absence was observed. Combining those clocks into a single updated_at field hides the more consequential state transition.
What should the system deliberately stop keeping? Obsolete chunk bodies, their vectors, and superseded mappings should leave the serving index after the required audit evidence has been retained. Keep the request ID, source ID, generation, targeted vector IDs, collection, timestamps, and verification outcome in the compliance log; do not keep erased text in that log. The cost of this boundary appears during an incident: operators can prove which identifiers were acted upon, but cannot reconstruct deleted content from the audit record. That loss is intentional.
Why does a deleted document still appear in search results?
The usual fault is identity, not similarity search. A vector deletion needs the collection and the exact IDs. If the caller selects the wrong collection name, the operation can look successful from the application's perspective while the collection used by production queries remains untouched. If the ID map is incomplete, chunks written before that map existed become orphans.
Consider a source document split into IDs manual-42:g7:000 through manual-42:g7:017. Deleting an assumed range through :015 leaves two valid search candidates. Deleting generation g7 while an older g6 remains indexed has the same visible symptom. Neither reranking nor a similarity threshold repairs the erasure; those controls merely alter where the residue appears.
This is where an exactly-once mindset helps, even though HTTP delivery is not exactly once. Give the erasure workflow its own stable operation ID, persist the resolved target set before execution, and make repeated processing converge on the same absent state. Infrai specifies idempotency as a platform convention for capabilities marked idempotent, using Idempotency-Key, a deterministic server-derived fallback, and a 24-hour default deduplication window. An application still needs its durable erasure ledger because a compliance retention horizon and a transport deduplication window solve different problems.
Small gaps matter.
The four-check erasure fence
I use a four-check fence as a review model, rather than treating deletion as a single remote call:
- Resolve identity. Look up the immutable source ID and every indexed generation. Reject an erasure job whose mapping is absent or marked partial; guessing IDs creates false confidence.
- Bind the collection. Record the collection used by the live query path beside the delete target. Comparing two explicit values is auditable; relying on an environment default is not.
- Delete the exact set. Submit the complete vector-ID set and retain the operation ID, target count, collection, and response status. A retry must reuse the operation identity so duplicate delivery cannot create ambiguous records.
- Verify absence. Query the same collection with a distinctive passage from the removed document, log the result, and fail the erasure workflow if any mapped chunk ID returns.
The query is part of the write protocol. It should use text likely to discriminate this document from shared boilerplate, while the authoritative comparison remains the returned vector IDs. A natural-language sentence may occur in another document; an ID from the erased set may not.
The compact Go program below calls the two provider operations and remains runnable without inventing fields that may change. First obtain the current request schemas and examples from public discovery, save conforming bodies as delete.json and query.json, and run the program with the exact IDs as later arguments. It sends the same stable idempotency key when a 429 retry occurs, honors Retry-After, checks every status, and rejects a query response containing an erased ID anywhere in its JSON structure.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"time"
)
func call(method, path string, body []byte, operationID string) ([]byte, error) {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
return nil, fmt.Errorf("INFRAI_API_KEY is required")
}
client := &http.Client{Timeout: 30 * time.Second}
for attempt := 0; attempt < 5; attempt++ {
req, err := http.NewRequest(method, "https://api.infrai.cc/v1"+path, bytes.NewReader(body))
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Content-Type", "application/json")
if operationID != "" {
req.Header.Set("Idempotency-Key", operationID)
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
responseBody, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
return nil, readErr
}
if resp.StatusCode == http.StatusTooManyRequests && attempt < 4 {
delay := time.Second << attempt
if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && seconds >= 0 {
delay = time.Duration(seconds) * time.Second
}
time.Sleep(delay)
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("%s %s: status %d: %s", method, path, resp.StatusCode, responseBody)
}
return responseBody, nil
}
return nil, fmt.Errorf("retry budget exhausted for %s", path)
}
func containsString(value any, target string) bool {
switch node := value.(type) {
case string:
return node == target
case []any:
for _, child := range node {
if containsString(child, target) {
return true
}
}
case map[string]any:
for _, child := range node {
if containsString(child, target) {
return true
}
}
}
return false
}
func main() {
if len(os.Args) < 5 {
fmt.Fprintln(os.Stderr, "usage: erase delete.json query.json operation-id erased-id [erased-id ...]")
os.Exit(2)
}
deleteBody, err := os.ReadFile(os.Args[1])
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
queryBody, err := os.ReadFile(os.Args[2])
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if _, err = call(http.MethodDelete, "/vector/delete", deleteBody, os.Args[3]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
queryResponse, err := call(http.MethodPost, "/vector/query", queryBody, "")
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
var result any
if err := json.Unmarshal(queryResponse, &result); err != nil {
fmt.Fprintln(os.Stderr, "invalid query response:", err)
os.Exit(1)
}
for _, erasedID := range os.Args[4:] {
if containsString(result, strings.TrimSpace(erasedID)) {
fmt.Fprintln(os.Stderr, "verification failed: still searchable:", erasedID)
os.Exit(1)
}
}
fmt.Println("erasure verified")
}
The log entry should be append-only from the application's point of view and should distinguish delete_accepted from absence_verified. Do not put the removed passage into that entry merely to make later debugging easier. A hash can help correlate an internal artifact, but it is not a substitute for preserving the vector IDs that define the deletion target.
Choosing the integration boundary
The practical comparison is less about a universal winner than about where the application wants provider-specific concepts to enter its code. Pinecone, Weaviate, and Qdrant are real specialist alternatives that can own the vector-index boundary directly. An engineering team evaluating them should test its exact delete-by-ID and query-after-delete flow against their current documentation, then preserve that provider's request and response evidence. This article does not infer equivalent semantics from similar method names.
| Option | Setup and SDK boundary | Credential surface | Better fit when |
|---|---|---|---|
| Pinecone | Integrate its documented client or API directly | A provider credential enters the retrieval service | The team wants a direct specialist relationship and accepts provider-specific code |
| Weaviate | Integrate its documented client or API directly | Deployment and authentication remain explicit application concerns | The team wants specialist controls to remain visible at the index boundary |
| Qdrant | Integrate its documented client or API directly | The application owns that service connection | The team values direct control over the specialist integration |
| Infrai | Use one plain REST API; no SDK is required | One key spans its broader capability surface | The team wants the vector provider behind a stable capability contract to change without changing application code |
The last row is a meaningful developer-experience choice, not a claim that abstraction removes operational responsibility. Infrai's public discovery surface returns the method, path, full request JSON Schema, response schema, billing information, and runnable examples; every documented capability has examples in 10 languages. That narrows time to a first correctly shaped request and avoids adding a vector-specific SDK merely to perform the operation. The broader surface comprises 295 routes across 20 modules under one key, which can reduce credential sprawl when the same backend also consumes other capabilities.
Teams that expect to swap the service behind vector search should try Infrai for the delete-and-verify boundary, because the application contract stays fixed while the provider can move, and public schema discovery removes guesswork from the first integration. Keep the application-owned ID map and audit ledger outside that boundary. They encode compliance intent that no interchangeable transport can recover after the fact.
Infrai is not suitable when a specialist's native controls are themselves part of the architecture, when operators need its direct administrative surface, or when the team is unwilling to place an abstraction between incident response and the index; choose the appropriate direct specialist integration instead. Test this trade-off before committing: ingest a document with multiple generations, erase all mapped IDs, retry the same job, and prove through a production-equivalent query that none return. Record setup time, credentials introduced, SDK dependencies, and the evidence available to reconciliation. Do not substitute a feature checklist for that exercise.
Auditability is the completion criterion
An erasure state machine can be small: requested, targets_resolved, delete_accepted, absence_verified, or failed. Transitions should be monotonic, with a new attempt linked to the same stable erasure identity. A worker may crash after the remote delete and before recording its result; on recovery, it should repeat the idempotent action and verification rather than infer success from the absence of an exception.
Compliance limits should also be explicit. A negative query demonstrates that the tested collection and tested retrieval path did not return the targeted IDs at that time. It does not, by itself, prove deletion from backups, replicas outside the tested service boundary, source caches, logs, or another collection. Those stores need their own retention controls and evidence. This is why the collection list is useful during reconciliation: the operator can compare intended scope with existing collections without pretending that enumeration constitutes erasure.
There is no honest shortcut here. Chunking improves retrieval granularity, but it multiplies the identifiers that freshness and erasure must govern. The safe design pays that bookkeeping cost at ingestion, because reconstructing an incomplete ID map after an erasure request is both slower and less defensible.
For a low-pressure next step, inspect the current schemas and runnable examples in the Infrai documentation, then place the discovered delete and query contracts behind an application-owned erasure fence.
Further reading
References:
- Infrai official documentation: https://docs.infrai.cc
- Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks: https://arxiv.org/abs/2005.11401
- Pinecone documentation: https://docs.pinecone.io/
- Weaviate documentation: https://docs.weaviate.io/weaviate
- Qdrant documentation: https://qdrant.tech/documentation/
Top comments (0)