ADR-002: OpenSearch for Practitioner Search¶
Status: Accepted
Context¶
Patients need to find practitioners by combining multiple dimensions simultaneously:
- Geographic proximity — "within 10 km of Paris"
- Specialty — "dentist", "cardiologist"
- Availability — "has a slot this week"
- Other filters — consultation type, language, rating
At 700 searches/second peak, the search backend must return relevant results with sub-200ms P99 latency. The practitioner corpus is 500,000 records, with each record containing location coordinates, specialty tags, and a next_available_slot timestamp that changes frequently.
The options evaluated were:
- Amazon OpenSearch (Elasticsearch-compatible, geo-search, full-text, managed)
- PostgreSQL full-text search (built-in, no additional service)
- Algolia (SaaS search, excellent geo-search, per-request pricing)
- Amazon RDS + PostGIS (relational geo-queries)
Decision¶
Use Amazon OpenSearch with one index per country (practitioners_fr, practitioners_de, …).
A compound OpenSearch query handles all search dimensions in a single round-trip:
{
"query": {
"bool": {
"filter": [
{ "geo_distance": { "distance": "10km", "location": { "lat": 48.85, "lon": 2.35 } } },
{ "term": { "specialty": "dentist" } },
{ "range": { "next_available_slot": { "lte": "now+7d/d" } } }
]
}
}
}
The next_available_slot field is kept current by the Availability Service, which consumes AvailabilityChanged events from Kafka and updates the relevant document.
Consequences¶
Positive:
geo_distancefilter is a first-class, indexed primitive in OpenSearch — no full-table scan.- Per-country indices allow country-specific field mappings (regulatory fields, locale-specific analyzers) and simplify data residency compliance.
- Replica shards provide read scaling without a separate read cluster.
- The index can be fully rebuilt from Kafka event replay if lost — no separate backup/restore procedure needed.
- IRSA + SigV4 authentication means no credentials are embedded in services.
Negative:
- OpenSearch is an eventually consistent read model. A newly created booking may briefly still show the practitioner as available in search results (gap is typically under 1 second, bounded by Kafka lag).
- Index updates are asynchronous — the search index is a derived view, not the source of truth. Correctness on booking is enforced by the RDS unique index, not by search accuracy.
- Operating an OpenSearch cluster (index lifecycle management, mapping migrations) adds operational complexity compared to staying in PostgreSQL.
Trade-off accepted: The brief eventual consistency window in the search index is acceptable because the Booking Service enforces correctness independently. A patient who sees an apparently available slot and attempts to book it will get a clear error if the slot was concurrently taken. PostgreSQL geo-search and full-text search would add significant query complexity and would not scale to 700 RPS without a dedicated read replica pool and query caching anyway.