Blog
Short notes on compatibility, benchmarks, and the odd things that turn up in real search data.
Releases should not depend on remembering three version files
The gateway has one release version, but it appears in more than one place: Rust, the Helm chart, and the lockfile.
0.1.2: safer defaults for production traffic
Version 0.1.2 is a small operational release.
A missing update is not an upsert
Elasticsearch’s update API does not create a missing document unless the request explicitly asks for upsert behavior. The gateway previously lost that distinction: an update to an absent ID could return “result”: “updated”, even though Qdrant had no point to change. A text-field update could go further and create the document from an empty source.
A missing delete is not a successful delete
Deleting an absent document is intentionally idempotent, but Elasticsearch still tells the client that nothing was removed. The gateway previously returned HTTP 200 with “result”: “deleted” because Qdrant’s point-delete operation succeeds even when the point does not exist.
A timeout is a feature
What happens when Qdrant stops responding?
Oversized requests are not bad JSON
The gateway bounds request bodies with MAX_BODY_BYTES and gives bulk ingestion a separate MAX_BULK_BYTES ceiling. Those limits protect memory, but the rejection previously looked like a generic HTTP 400 compatibility error. A client could not reliably tell whether to fix malformed JSON or split a valid payload into smaller requests.
A missing document is a 404
Elasticsearch’s get-document API distinguishes a missing document at both layers of the response: the JSON body contains “found”: false, and the HTTP status is 404. The gateway previously returned the right body with HTTP 200.
One replica is the safe default
The Helm chart used to request two gateway replicas while also provisioning one ReadWriteOnce volume for the SQLite metadata database. That combination looked highly available but did not supply a highly available control plane.
No phantom indices after creation failures
Creating an index spans two stores: Qdrant owns the collections and payload indexes, while SQLite keeps the gateway’s mappings and aliases. That boundary needs a deliberate publication order.
A slash is part of the document ID
Elasticsearch clients put document IDs in URL path segments. An ID such as order/42 therefore reaches the server as order%2F42; spaces and non-ASCII text are encoded for the same reason. The gateway previously routed on the raw URI path, so it stored the encoded spelling as _id instead of the caller’s original value. A later request produced by a client from order/42 could still find that spelling, but responses exposed order%2F42, breaking identity round trips and the documented arbitrary-ID guarantee.
When bulk errors did not mean errors
Elasticsearch bulk responses have two levels: a top-level errors boolean and an items array whose action objects contain per-document results. The Bulk API contract defines errors as true when one or more operations did not complete successfully. Clients commonly inspect the boolean first and only walk the item details when it is true.
Three nodes, one disappears, and the search keeps moving
A search service is easy to benchmark when everyone is alive. The more useful question is what happens when one of them vanishes in the middle of lunch.
Making the bulk request limit real
The gateway has separate request-size controls for ordinary JSON requests and bulk NDJSON: MAX_BODY_BYTES defaults to 10 MiB, while MAX_BULK_BYTES defaults to 50 MiB. A useful bulk batch is often much larger than a search or single-document request.
Keep the Elasticsearch client, change the engine
Changing search engines can mean changing application code that already works. In a product catalogue, documentation site, jobs board, or support portal, the application may already know how to create an index, bulk documents, search with filters, and render _source through an Elasticsearch client.
Two collections, one stateless gateway
Reading a product’s current source and finding it through search are different operations. Qdrant ES Gateway can store the source separately from its searchable projection, allowing a write to wait for durable source storage while the search projection converges asynchronously.
Pattern queries without a client rewrite
Catalogue search often has two different jobs hiding behind one endpoint. Natural-language search wants token-aware retrieval. Operational search wants to find an SKU prefix, a serial number, or a wildcarded identifier.
Batching searches and paging through ordered results
An application may need several searches to populate a catalogue page, or successive pages from an ordered result set. Qdrant ES Gateway supports _msearch for batching searches and search_after for cursor pagination within its supported search API.
Compatibility includes the boring lifecycle calls
An application can have a perfectly ordinary search query and still fail a migration before it reaches that query. Production clients create and delete indexes, ask whether an index exists, refresh after an import, and sometimes close and reopen an index while applying settings.
Launch video: keep the Elasticsearch client, change the engine
Here is the short launch video for Qdrant ES Gateway:
A dress, a tea bag, and a null
Real catalogues are less like neat databases and more like drawers everyone in the house has used.