7 min read
Oct 31st – Tripped Circuit Breaker for Vector Database
Our vector database of choice is the AWS OpenSearch Service. On October 31st, our cluster tripped the k-NN circuit breaker during a bulk ingestion job: one index had eaten almost all of the native memory on a single data node, and indexing stopped until we cleared it.
Background
We rely on OpenSearch’s k-NN plugin (backed by FAISS) for vector search. Each index holds high-dimensional embeddings (~1536 dimensions), and we ingest in bulk batches of 256 documents at a time.
Our setup:
- Engine: OpenSearch 2.19
- Instance type:
or2.large.search - Data nodes: 6, later scaled to 9
- Breaker limit: 60% (default)
- Memory usage pattern: one index (
mab_b2f97...) consistently dominated memory
The Circuit Breaker Event
During a bulk ingestion job, we started seeing errors like this:
"caused_by": {
"type": "knn_circuit_breaker_exception",
"reason": "Parsing the created knn vector fields prior to indexing has failed as the circuit breaker triggered. This indicates that the cluster is low on memory resources and cannot index more documents at the moment. Check _plugins/_knn/stats for the circuit breaker status."
}
Running the diagnostic the error points to:
GET _plugins/_knn/stats?pretty
produced the key result:
"circuit_breaker_triggered": true
Two nodes had excessive graph memory usage:
| Node ID | Graph memory usage | Cache full | Top index | Usage % |
|---|---|---|---|---|
rvr4O1HFRiiZpcO1BctWK61 |
98.27% | True | mab_b2f97... |
85.95% |
knev23f3029vhivh13fjevn |
91.21% | False | mab_b2f97... |
84.24% |
This confirmed that the breaker was triggered by one overloaded index consuming almost all available native memory on a single data node.
Troubleshooting Process
We put together a small Python diagnostic script to confirm the issue programmatically. All requests are signed with SigV4 via requests-aws4auth.
Checking k-NN stats
import json
import boto3
import requests
from requests_aws4auth import AWS4Auth
region = "us-west-2"
service = "es"
endpoint = "https://vpc-main-xxxx.us-west-2.es.amazonaws.com"
creds = boto3.Session().get_credentials()
awsauth = AWS4Auth(creds.access_key, creds.secret_key, region, service, session_token=creds.token)
resp = requests.get(f"{endpoint}/_plugins/_knn/stats?pretty", auth=awsauth)
print(resp.status_code)
print(json.dumps(resp.json(), indent=2))
This returned detailed per-node memory usage and let us pinpoint the culprit.
Verifying the breaker limit
url = f"{endpoint}/_cluster/settings?include_defaults=true&filter_path=**.indices.breaker.request.limit"
resp = requests.get(url, auth=awsauth)
print(json.dumps(resp.json(), indent=2))
Result:
{
"defaults": {
"indices": {
"breaker": {
"request": {
"limit": "60%"
}
}
}
}
}
So we were indeed running against the default 60% threshold.
Checking cluster health
resp = requests.get(f"{endpoint}/_cluster/health?pretty", auth=awsauth)
print(json.dumps(resp.json(), indent=2))
Output:
"status": "green",
"relocating_shards": 2,
"active_shards_percent_as_number": 100.0
Everything was healthy, except for the memory issue.
Diagnosing Shard Distribution
We then checked how the heavy index’s shards were spread across nodes:
resp = requests.get(f"{endpoint}/_cat/shards?v&format=json", auth=awsauth)
shards = [s for s in resp.json() if "mab_b2f97" in s["index"]]
for s in shards:
print(f"Index: {s['index']} | Shard: {s['shard']} | Node: {s['node']}")
Output:
Found 10 shards for index pattern 'mab_b2f97':
Shard 0 → 234rsvdsvde43t36r2b14b686b19523f, b464vwerbwvev393849t8vb97ff1f554
Shard 1 → 234rsvdsvde43t36r2b14b686b19523f, 4289fhf83fb3vb3839c36b17afaf590e
Shard 2 → 234rsvdsvde43t36r2b14b686b19523f, 9b90871abd6e7r3if32iv209vnooino9
...
Observation: one node was hosting four shards of the heavy index. That node was exactly the one showing 98% memory usage.
Fix Attempts
Attempt 1: Scale data nodes
We increased the number of data nodes from 6 to 9. The cluster started redistributing shards (relocating_shards: 2). After waiting ~30 minutes, the breaker was still tripped, because the overloaded node reloaded the same k-NN graphs into memory.
Attempt 2: Reboot the node
We followed the AWS docs and rebooted the data node via the console. The node restarted successfully. The circuit breaker was still tripped.
Rebooting cleared the JVM heap, but as soon as the node rejoined, OpenSearch reloaded the same graphs and hit the memory limit again.
Attempt 3: Close and reopen the index
This one worked. We used the following script:
import time
index_name = "mab_b2f97..."
# Close the index to evict its k-NN graphs from native memory
print(f"Closing index: {index_name}")
resp = requests.post(f"{endpoint}/{index_name}/_close", auth=awsauth)
print("Status:", resp.status_code)
time.sleep(30)
# Reopen it so shards reload with a fresh cache
print(f"Reopening index: {index_name}")
resp = requests.post(f"{endpoint}/{index_name}/_open", auth=awsauth)
print("Status:", resp.status_code)
resp = requests.get(f"{endpoint}/_cluster/health/{index_name}?pretty", auth=awsauth)
print(json.dumps(resp.json(), indent=2))
Result:
Index closed successfully.
Index reopened successfully.
Checking index health...
"status": "yellow" → "green"
Rerunning the stats call:
GET _plugins/_knn/stats?pretty
now showed:
"circuit_breaker_triggered": false
No nodes were above 80% graph memory usage. The breaker reset cleanly and indexing resumed.
Root Cause
- One vector index (
mab_b2f97...) consumed ~85% of native graph memory on a single node. - The k-NN circuit breaker tripped at the 60% limit.
- Adding data nodes redistributed shards but didn’t clear the memory state automatically.
- Rebooting the node reloaded the same graphs.
- Closing and reopening the index flushed the FAISS graphs and rebalanced the k-NN cache.
Outcome
- Circuit breaker cleared
- No nodes above 80% memory usage
- Indexing and search restored
- Cluster status: green
Lessons Learned
- Monitor
_plugins/_knn/statsregularly. It gives visibility into node-level native memory pressure that the usual JVM metrics don’t show. - Circuit breakers protect your cluster. They prevent out-of-memory crashes, but they also pause indexing until you act.
- Closing and reopening an index is a safe, AWS-native reset. It flushes the FAISS graphs from native memory without touching the data.
- Scaling helps, but placement matters. Use
_cat/shardsto confirm that heavy indices are actually spread across nodes. - The default 60% breaker limit is conservative on purpose. It’s safer to scale horizontally than to raise that threshold.
Next Steps
- Monitor k-NN stats and shard placement daily.
- Automate alerts if any node exceeds 80% graph memory usage.
- Tune the ingestion batch size to reduce vector memory bursts.
- Document this process in our internal runbook.
– Siddharth