Slow search
Search latency above expected — diagnose cold starts, oversized `perPage`, complex `filterBy`, missing facets, and upstream search engine pressure.
Healthy median search latency at AACsearch is < 50 ms inside the cluster region, < 150 ms over the public internet. If you are seeing > 500 ms or sporadic timeouts, work through these checks.
Symptom
| What you see | First check |
|---|---|
| First request is slow, subsequent ones fast | Cold start — warm with a no-op query |
| All requests slow, even the second one | Per-request shape (perPage, filterBy) |
| Slow only at peak hours | Per-key concurrency or upstream cluster pressure |
| Random spikes | Network path; check from a second client location |
| Timeouts, 502, 503 | See errors and rate limits for retry strategy |
Decision tree
First request after deploy / restart slow?
└─ yes → cold start, warm with a no-op:
client.search({ q: "*", perPage: 1 })
All requests slow?
├─ perPage > 100 → reduce
├─ filterBy complex → simplify or precompute
├─ facets > 10 fields → reduce or move to per-page
└─ everything minimal → check status page; open ticketCheck 1: cold start
The first request to a freshly-deployed worker can take 200–500 ms longer than steady-state because it has to establish connections and compile the query plan. This is normal.
If your traffic pattern includes long idle periods (e.g., internal dashboard hit twice a day), you will see this on every visit.
Mitigation: warm the worker before the user request:
// At app startup, or on a cron/edge prewarm
await client.search({ q: "*", perPage: 1 });For Vercel / Cloudflare Workers / similar, use a scheduled prewarm call.
Check 2: oversized perPage
perPage directly drives serialization cost. The server scales linearly past ~100; past ~250 it is dominated by JSON encoding.
// ❌ Slow even on a healthy cluster
client.search({ q: "shoes", perPage: 1000 });
// ✅ Use pagination instead
client.search({ q: "shoes", perPage: 50, page: 1 });If you genuinely need every match, use documents/export with an admin key — it streams and is paginated server-side.
Check 3: complex filterBy
Filters with many || (OR) terms or deep parentheses are slow because each branch is evaluated.
// ❌ One big OR over many ids
filterBy: "id:=[id1,id2,...,id500]";
// ✅ Use anyOf which uses the inverted index directly
filterBy: "id:[id1,id2,...,id500]";
// ❌ Nested groups
filterBy: "(brand:=Nike && price:<100) || (brand:=Adidas && price:<150) || ...";
// ✅ Pre-compute a tag field at ingest time
filterBy: "discount_tier:=tier_a";When in doubt, simplify the filter and benchmark with /search?stats=true (response includes searchTimeMs).
Check 4: too many facets
Each facet field requires a sort and bucket aggregation. Past ~10 facets per request, latency rises noticeably.
// ❌
facetBy: "brand,category,subcategory,size,color,material,season,fit,collection,store,warehouse";
// ✅ Show top-level facets only on the first page; load secondary facets lazily
facetBy: "brand,category,price";You can render the rest with separate multi-search calls only when the user opens that filter group.
Check 5: query logging
The response includes a searchTimeMs field if you opt in. Compare it to your end-to-end latency to isolate where the time is going.
const result = await client.search({ q: "shoes", debug: true });
console.log("server:", result.searchTimeMs, "ms");
console.log("end-to-end:", endTime - startTime, "ms");
// Difference > 100 ms suggests network or proxy overhead, not the engineCheck 6: upstream cluster pressure
If searchTimeMs itself is > 500 ms, the issue is upstream. Check:
- https://status.aacsearch.com for ongoing incidents
- Search → Analytics → "p95 latency" graph (last 24h)
- If you self-host: AACSearch instance memory and CPU
If status is green and your own metrics are abnormal, open a ticket — see diagnostics packet below.
Check 7: connection pool exhaustion (server-only)
For server-side high-concurrency callers (10k+ qps from one Node process), the default fetch connection pool can throttle under load. Tune:
import { Agent } from "undici";
const agent = new Agent({
connections: 256, // default is 50
pipelining: 10,
});
const client = new SearchClient({
baseUrl: "...",
apiKey: "...",
indexSlug: "products",
dispatcher: agent,
});For browsers and Workers, this does not apply.
Fix matrix
| Diagnosis | Fix |
|---|---|
| Cold start | Prewarm with q: "*" no-op |
perPage too high | Reduce to ≤ 50, paginate |
| Complex filter | Simplify; precompute tag fields at ingest time |
| Too many facets | Limit to top-level on first paint, lazy-load the rest |
| Server actually slow | Check status page; share searchTimeMs with support |
| Network overhead | Use a region closer to the cluster; reuse keep-alive connections |
| Connection pool | Tune undici Agent for server callers |
Diagnostics packet
| Field | Notes |
|---|---|
| Organization ID | required |
| Index slug | required |
| Time window (UTC) | start and end of the slow period |
searchTimeMs from sample response | required |
| Median end-to-end latency | from your own monitoring |
| Sample request body | minimal failing case |
| Region of caller | datacenter / city / cloud region |
| Concurrency | requests per second from this caller |
If you can reproduce the slowness from a single curl, paste it. Reproducible cases are resolved roughly 4× faster than "it sometimes feels slow."
Related
- Errors and rate limits — for timeouts, 5xx
- Multi-search and querying — efficient query shapes
- Reindexing and zero-downtime — schema change can cause warm-up
- Plans and limits — concurrency caps per plan
Empty results
Search returns `found: 0` for queries that should match — diagnose missing documents, narrow filters, scoped-token filters, and `queryBy` mismatches.
Widget not loading
Widget script tag is on the page but the search UI never appears — diagnose script load failure, missing container, CSP blocks, key issues, and Shadow DOM mounting.