Skip to content

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:

  1. Amazon OpenSearch (Elasticsearch-compatible, geo-search, full-text, managed)
  2. PostgreSQL full-text search (built-in, no additional service)
  3. Algolia (SaaS search, excellent geo-search, per-request pricing)
  4. 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_distance filter 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.