Short answer: use staged retrieval with explicit collections, bounded queries, and source context that can be traced back to a product record. For a property-management catalog, that means a search answer is only as trustworthy as the tenant metadata, access checks, and citation carried alongside each chunk.

This is a grounding problem before it is a vector-database problem. A ranking that looks good in a demo can still show a product from the wrong landlord, omit the source URL, or wait forever on a slow enrichment call. I design the retrieval contract first, then choose the storage and observability pieces that can honor it.

Ship the contract.

How should collection-backed retrieval shape e-commerce product search observability?

Start with the user-visible answer and work backwards. If the UI says “two-bedroom units near the station under $2,500,” the retrieval contract should require a tenant id, an access scope, the product or listing identifier, the source URL, and the retrieval score for every candidate. The generated text can then cite those identifiers instead of presenting an unreviewable paragraph.

Collections make that boundary explicit. One collection can represent a catalog version or tenant partition; metadata on each item carries the finer-grained authorization and locale fields. Do not rely on a prompt instruction to keep tenants apart. Preserve those fields at indexing time and apply the same scope during query time.

I also put a deadline around every stage. A 300 ms embedding budget, a bounded vector result count, and a separate timeout for source hydration give the request a shape that can be monitored. Your mileage may vary with catalog size, but an unbounded top-k is difficult to explain to an on-call engineer.

Measure the boundary.

The data flow: contract before ranking

The path is deliberately boring: normalize the query, select the collection, retrieve a small candidate set, enforce tenant and access filters, hydrate only the surviving records, and hand the model a context object containing citations. In a property-management catalog, that means the normalized query carries the building or landlord scope, the collection selector is derived from the catalog version, and the filter is applied before any text is passed to a model. The trace records the candidate ids that were rejected, too, because a reviewer may need to explain why a tempting listing did not appear. Each stage emits a request id and counts, so a missing citation is a visible contract violation rather than a vague relevance complaint.

For product data, index stable identifiers and URLs as metadata, not as prose hidden in the chunk. Keep the raw description separate from fields such as tenantId, listingId, sourceUrl, and acl. When a listing changes, upsert a new version and retain the old identifier long enough to explain a cached answer.

This design makes evaluation concrete. I can replay a query, inspect which collection was selected, see the candidate ids, and compare the final citations with the records that actually passed authorization. It also gives product and support teams a language for disputes: “the source was filtered” is more useful than “the AI was weird.”

A minimal TypeScript implementation

The following example keeps the vendor boundary small. It creates a named collection once, then queries it with a bounded payload. The payload is the application’s contract; the surrounding code checks status and treats a 429 as a backoff case rather than spinning in a tight loop.

type Citation = { listingId: string; sourceUrl: string; tenantId: string; }; type RetrievalResult = { text: string; citations: Citation[]; requestId?: string; }; const baseUrl = process.env.INFRAI_BASE_URL; if (!baseUrl) throw new Error("INFRAI_BASE_URL is required"); const apiKey = process.env.INFRAI_API_KEY; if (!apiKey) throw new Error("INFRAI_API_KEY is required"); async function call(url: string, payload: unknown): Promise<any> { for (let attempt = 0; attempt < 4; attempt += 1) { const response = await fetch(url, { method: "POST", headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify(payload), }); if (response.status === 429) { const retryAfter = Number(response.headers.get("retry-after") ?? "1"); await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000 * (attempt + 1))); continue; } if (!response.ok) { throw new Error(`retrieval request failed (${response.status}): ${await response.text()}`); } return response.json(); } throw new Error("retrieval rate limit persisted after retries"); } async function retrieve(query: string, tenantId: string): Promise<RetrievalResult> { await call(`${baseUrl}/vector/collection/create`, { collection: "rental-listings" }); const result = await call(`${baseUrl}/vector/query`, { collection: "rental-listings", query, limit: 8, filter: { tenantId, acl: "private" }, }); return { text: result.context ?? "", citations: (result.matches ?? []).map((match: any) => match.metadata), requestId: result.request_id, }; } retrieve("two-bedroom units near the station", "tenant-42").then(console.log); 

The collection creation call belongs in deployment or a migration in a real service, not on every user request. It is shown here so the boundary is executable and easy to replace. In production, make the create operation idempotent with an Idempotency-Key, add an AbortController deadline, and record the returned request id with the query log. I started with eight candidates because it is a reviewable number; tune it from evaluation data, not from a vague “more context is better” instinct.

What the alternatives trade away

No single backend wins every workload. Elasticsearch is a strong fit when lexical matching, faceting, and operational search tooling are central. Pinecone keeps a focused managed-vector workflow, while Weaviate offers a broader object and vector model. pgvector is attractive when the catalog already lives in Postgres and the team wants transactional joins. A single REST gateway can reduce integration surface, but it does not remove the need to design tenant filters, citations, or evaluation.

Option Good fit Cost or operational catch
Elasticsearch Hybrid text, filters, and mature search operations More cluster and schema work for a small team
Pinecone Managed vector retrieval with a narrow focus Another service boundary and metadata model to govern
Weaviate Vector plus object-oriented features More platform surface than a minimal retrieval path needs
pgvector Existing Postgres data and transactional joins Vector tuning shares resources with application workloads
Infrai vector API One key and one bill across backend capabilities, with a plain REST interface You still own collection design, authorization metadata, and citation checks

The last row is a workflow advantage, not a relevance guarantee. A solo team can use one credential and one billing stream for its backend calls, while keeping the retrieval contract portable because the application talks HTTP. The trade-off is that a gateway is not a substitute for a search evaluation set or for your access-control policy.

The operational decision rule

Choose collection-backed retrieval when you need to explain why an item entered an answer, isolate tenants, and cap the work done per request. Keep the collection id, query text hash, top-k, filter version, latency, and citation ids in a trace. Redact raw customer text from that trace unless it is required for debugging.

The catch is that this approach is not suitable when the primary need is analytics over relational joins or a full-text query language; stick with pgvector or Elasticsearch when those capabilities are the product. It is also a poor fit for teams unwilling to maintain metadata and evaluation fixtures. A neat vector index cannot repair missing ownership fields.

Before shipping, I check four things in a staging replay: every answer has at least one authorized source id, no result crosses a tenant boundary, the query respects the deadline, and a reviewer can follow the request id from retrieval to rendered citation. Then I test deletion and re-indexing paths so stale listings do not remain in a collection. Small checks. Big payoff.

References