At a glance
This card overlays Elasticsearch’s search thread-pool saturation against your ecommerce traffic, row by row, so you can see the moment the database becomes the bottleneck for shoppers. The search thread pool is a fixed-size queue: each node has a bounded number of search threads plus a bounded queue, and when both fill, new searches are rejected. Saturation is the share of that capacity in use. The danger window is a traffic burst (a sale launch, an email blast, a paid-media spike) when search demand outruns pool capacity: saturation climbs toward 100%, queue depth rises, rejections start, and storefront search begins to fail or stall right when the most shoppers are trying to buy. This is the cross-platform card that connects “the cluster is busy” to “we are losing sales”.
Calculation
The card computes search-pool saturation per node from the thread-pool stats and overlays it on the storefront traffic series for the same minutes:max, not an average), because the search pool is per node: one saturated node rejects searches even while others have headroom, especially with a hot shard. Second, the alert fires on the overlap of high saturation and a traffic burst, not on saturation alone. A saturated pool at 03:00 with no shoppers is a capacity note; the same saturation during a sale launch is revenue at risk. The engine flags the condition critical when saturation exceeds 90% and rejections are non-zero during an identified traffic burst.
Worked example
A platform team runs a 4-node Elasticsearch 8.x cluster behind storefront search for a beauty retailer on BigCommerce. Each node has a search pool of 13 threads and a queue capacity of 1,000 (typical 8.x defaults sized to CPU). A flash sale email goes out at 19:00 on 06 Jun 26. Snapshot of the overlay around the burst:
The card crosses the 90% alert line at 19:02 with rejections starting, exactly as BigCommerce sessions triple off the email. The story is unambiguous: the email landed, shoppers poured in, every one of them hit search, and the search pool on the busiest node filled. From 19:02 to 19:04 roughly 2,320 searches were rejected: those shoppers saw an empty or errored search box during the single highest-intent window of the week.
The decision tree:
- Is the cluster otherwise healthy? Yes, status is green, heap is fine. This is not a fault, it is a capacity shortfall against peak demand. The pool is correctly sized for normal load but not for a 9x burst.
- Why the busiest node first? The
productsindex has mild shard skew, so one node carries the heaviest shard and saturates before the others. Pair with Shard Size Skew % confirms the imbalance. - Immediate vs structural fix? Immediate: shed load (serve a cached “popular products” view to logged-out users so not every session hits live search), and confirm the storefront retries rejected searches gracefully rather than showing an error. Structural: scale out search capacity (more data nodes or a dedicated search tier) ahead of known sale events, and reindex
productsto remove the skew.
- Saturation only means money when it lines up with shoppers. The whole point of this cross-platform card is the overlay. High saturation off-peak is a capacity note; high saturation during a burst is revenue leaking in real time.
- Rejections are the hard floor, watch them, not just saturation. Saturation of 88% is busy but fine; the damage starts when the queue fills and
rejectedincrements. Once rejections are non-zero, shoppers are being turned away. Treat the first non-zero rejection during a burst as the alarm. - The busiest node sets the ceiling. Because the search pool is per node, an average hides the problem. One saturated node (often the hot-shard node) rejects searches while the cluster average still looks comfortable. Always read the max.
Sibling cards platform teams should reference together
Reconciling against the source
Where to look in Elasticsearch’s own tooling:For the traffic side, reconcile against the storefront connector’s own analytics: BigCommerce Analytics, the Shopify admin live view, or Adobe Commerce reporting for sessions/orders per minute in the same window. In managed services the pool stats appear as metrics: Elastic Cloud’s deployment metrics, AWS OpenSearch/Elasticsearch Service’sGET /_nodes/stats/thread_pool/searchfor per-node active threads, queue depth, rejected count and pool size. This is the raw data behind the saturation series.GET /_cat/thread_pool/search?v&h=node_name,active,queue,rejected,sizefor a quick human-readable per-node view.GET /_nodes/stats/jvm,osto confirm CPU and heap headroom during the burst.
ThreadpoolSearchRejected, ThreadpoolSearchQueue and ThreadpoolSearchThreads CloudWatch metrics, and Bonsai’s cluster metrics.
Why our value may legitimately differ from a manual check:
Cross-connector reconciliation:
Known limitations / FAQs
Saturation hit 95% but there were no rejections. Was anything actually wrong? Not yet. Saturation measures how full the pool plus queue is; rejections only start when both are completely full. A 95% reading means you are close to the edge with little headroom left, so the next small increase in demand or one slow query could tip you into rejections. Treat high saturation during a burst as the warning and the first non-zero rejection as the alarm. Why does the card use the busiest node, not the cluster average? Because the search thread pool is per node. A search routed to a saturated node is rejected even if three other nodes are idle, which is exactly what happens with a hot shard: one node carries the heavy shard and saturates first. An average would hide this by diluting the busy node against the quiet ones. The max is the honest ceiling. My cluster is green and heap is fine, so how can search be failing? Cluster status reflects shard allocation, not throughput, and heap reflects memory, not the thread pool. The search pool can be fully saturated and rejecting searches while the cluster is perfectly green with comfortable heap. This card exists precisely to catch that blind spot: a healthy-looking cluster that cannot keep up with burst demand. Indexing was heavy during the burst. Did that fill the search pool? Not directly. Indexing uses the write thread pool, which is separate from the search pool, so heavy indexing does not consume search threads. It does, however, compete for CPU, so a CPU-bound node under heavy indexing can make each search take longer, holding search threads longer and pushing saturation up indirectly. Check CPU viaGET /_nodes/stats/os if both are heavy at once.
Can I just increase the search thread-pool size to stop rejections?
Rarely the right fix. The default search pool is sized to the node’s CPU count (roughly int((cores * 3) / 2) + 1); making it larger than CPU can support means more threads contending for the same cores, which often increases latency rather than reducing rejections. The better levers are: add capacity (more data nodes or a search tier), reduce per-query cost (smaller aggregations, no deep pagination), cache popular searches, and fix shard skew so load spreads evenly.
The rejected count looks huge but the cluster recovered fine. Why?
The rejected field in the thread-pool stats is cumulative since node start, so a raw read shows a lifetime total that can be large even after a brief incident. The card shows the per-minute delta, which is the meaningful rate. If you are reading the native API, take the difference between two snapshots, do not read the absolute number as “rejections right now”.
The traffic series and the saturation series do not line up by a minute. Is the card wrong?
Usually a clock or window-alignment artefact. The overlay aligns Elasticsearch minutes with the storefront connector’s minutes; if the two connectors report on slightly offset windows or time zones, peak rows can appear shifted by a minute. Confirm both connectors use the same display time zone in their Vortex IQ profile. The shapes will match even if a single peak row is off by one.
Does this card work without a connected storefront?
The saturation series works on its own (it is pure Elasticsearch thread-pool data), but the “vs ecom burst” overlay needs a connected Shopify, BigCommerce or Adobe Commerce connector to supply the traffic series. Without it, you get saturation but lose the revenue-at-risk context that makes the alert meaningful. Connect the storefront connector to unlock the overlay.