This upgrade fixes data isolation, identity, pagination and result correctness. Review existing stored data before deploying it. It does not alter existing collections or database keys automatically.

Namespace contract

For vector CRUD, search and scans, omitted namespace and "" both mean the default namespace. Named namespaces isolate identical logical IDs. delete_all: true deletes only the selected namespace; it does not delete a collection. Use delete_index to remove an entire collection. Index statistics without a namespace continue to describe the whole index.

This is a deliberate correction to older adapters which sometimes searched or deleted across namespaces. Update applications that relied on that behavior to enumerate namespaces explicitly. Qdrant’s _namespace / _vectra_id and Weaviate’s vectraNamespace / vectraId are reserved metadata fields.

Existing Qdrant and Weaviate collections

New arbitrary string IDs use deterministic UUIDs scoped by namespace. The original application ID is stored with the vector and restored on query, fetch and scan. Default-namespace canonical numeric Qdrant IDs and existing UUIDs remain usable. nil and "" map identically.

Older Qdrant string IDs were generated with Ruby’s process-dependent String#hash, and their originals were not stored. The original ID cannot be reconstructed from that hash. Reindex from the authoritative application records into a new collection; do not recalculate old hashes in a new process. A scan of legacy data can export its stored numeric IDs, but cannot recover the original logical IDs. Recover these from application data if identity must be preserved.

For Weaviate, reindex into a collection created with the new reserved properties. Existing UUID objects can still be fetched by UUID, but search requires the reserved schema and stored identity fields. New collections created by Vectra include these properties with field tokenization. Metadata keys must match the collection schema. GraphQL search requires GraphQL enabled on the server; REST CRUD uses class-qualified endpoints. No cloud API or GraphQL deprecation migration is implied by this upgrade.

Back up the source, create a separate target collection, reindex, compare IDs and content, then switch application configuration. Keep the source for rollback. Do not dual-write new deterministic IDs into a legacy collection: that can duplicate old hashed records.

Existing pgvector tables

New tables use PRIMARY KEY (id, namespace). Older tables with PRIMARY KEY (id) continue to accept default-namespace writes, with a guard against overwriting a row in another namespace. They reject named-namespace writes with an actionable configuration error until upgraded.

After a database backup, run the explicit operation during a maintenance window:

client.provider.upgrade_namespace_key!(index: "documents")

This operation locks the table, converts null namespaces to the default, and replaces the primary key inside one transaction. It does not cascade through dependent foreign keys: an incompatible dependency causes rollback and requires a schema-specific migration. Test on a copy of the production schema first. Rolling back to a single-column key is only possible if IDs are globally unique again; restore the backup or migrate application data deliberately.

Production pgvector execution uses a bounded connection pool. Set pool_size and pool_timeout per client and call client.provider.shutdown! on shutdown. Full-text search uses string values in JSONB metadata by default. The provider’s text_column: option selects an existing text column when needed. A GIN index on jsonb_to_tsvector('english', metadata, '["string"]'::jsonb) can accelerate metadata text search.

The alpha endpoints mean pure keyword (0.0) and pure semantic (1.0) retrieval. Intermediate score scales differ between providers; scores are not interchangeable across providers.

Pinecone and Qdrant require an explicit sparse encoder trained/configured identically to ingestion:

client = Vectra.qdrant(
  host: ENV.fetch("QDRANT_HOST"),
  dense_vector_name: "dense",
  sparse_vector_name: "keywords",
  sparse_encoder: ->(text) { MySparseEncoder.encode(text) }
)
# Encoder output: { indices: [7, 42], values: [0.5, 1.2] }
# Stored points must contain dense and corresponding sparse vectors.

Qdrant combines dense and sparse result ranks using weighted reciprocal rank fusion. Pinecone scales dense and sparse query vectors before a single hybrid query, and requires a compatible dot-product index containing both representations. Missing encoders raise UnsupportedFeatureError rather than silently dropping text. Pure semantic retrieval needs no sparse encoder.

Weaviate uses its hybrid operator with the vector inside that operator. pgvector blends vector similarity and text rank without forcing keyword matches on pure-semantic queries. Memory text search is a testing approximation and does not implement hybrid search.

Scanning, streaming and migration

client.each_vector and Streaming#scan_all use actual enumeration: Qdrant scroll, pgvector keyset pages, Weaviate object cursors and Pinecone serverless list/fetch. Pinecone pod indexes without listing support fail explicitly. No similarity-query fallback or 10,000-record truncation is used. scan_all returns the number actually yielded.

query_stream lazily requests increasing top-k windows, rather than pretending similarity search has a cursor. This can repeat work and is subject to provider top-k limits. It terminates or raises when no progress can be made. Use each_vector for export.

Migration copies one selected namespace. Scan failures raise; write failures appear in result[:errors]. Verification compares original IDs, metadata, sparse values and dense values (absolute tolerance 1e-6), plus the target namespace’s record count. It does not claim to be an atomic snapshot. Concurrent writes can change scans and verification. For a consistent migration, stop writes or implement application-level change capture, replay and a coordinated cutover. The library alone does not provide zero-downtime migration.

Rails synchronization

Automatic indexing and deletion run after commit, including configured metadata changes. Clearing an embedding deletes the previous vector. UUID/string primary keys are preserved on result loading. Callback failures are logged through Rails/Vectra’s logger, or warned when no logger exists; a committed database transaction cannot be rolled back by a failed external write.

Callbacks remain synchronous and best-effort. For durable synchronization, use an application-owned job/outbox with retries and reconciliation, and set auto_index: false when managing indexing yourself. reindex_vectors raises BatchError if writes fail instead of reporting attempted rows as successful.

Cache, batches and optional helpers

Query cache keys include result flags and namespace identity. Mutations through CachedClient invalidate its cache even on partial failure, and older in-flight reads cannot populate a current generation. Writes made through a different client/process are not observed: invalidate explicitly or accept TTL staleness. Cached objects are returned by reference; treat them as read-only. Eviction tracks last access without extending TTL.

Batch upsert/delete return success/error summaries. Batch fetch/query now raise BatchError for failed chunks instead of silently returning incomplete or empty results. Progress-callback errors propagate, and worker pools are shut down. Retry, circuit breaker and rate limiter helpers are opt-in; constructing a plain Client does not enable automatic failover. Cache configuration values do not automatically wrap clients; construct CachedClient explicitly.

require "vectra" works with only declared runtime dependencies. ActiveRecord integration loads when Vectra::ActiveRecord is referenced; install ActiveRecord separately for that feature. PostgreSQL requires the optional pg gem.

Validation

The provider-contract workflow runs the full suite with pinned PostgreSQL/pgvector, Qdrant and Weaviate services. Local runs without service URLs explicitly skip live tests. Unit tests and a coverage percentage do not establish compatibility with every server version. Pinecone cloud behavior requires a separately provisioned test index and credentials.