Administrator procedures for monitoring OpenSearch cluster health and increasing the shard count of an existing index on the AgileSec Platform. This guide applies to both On-Prem Linux and Kubernetes installations.
Run the queries in this guide from Advanced Dashboards → Dev Tools as a platform administrator. Replace placeholders such as <index name> and <org-domain> with values from your environment.
Overview
This guide documents administrator procedures for maintaining and tuning OpenSearch on the AgileSec platform.
There are two procedures to follow for administering and tuning OpenSearch:
-
Cluster Monitoring and Health Checks. Use these checks to catch oversized shards, unhealthy indices, and incorrect templates before they become an outage. Catching a shard-size problem in Cluster Monitoring and Health Checks means you should add capacity or schedule a re-sharding maintenance window.
-
Update the Shard Count of an Index. Use the re-sharding procedure when an existing index already needs more primary shards, without losing data or changing the index name. OpenSearch does not allow index shard counts to be updated in-place, so this procedure will require downtime.
Cluster Monitoring and Health Checks
Run these checks on a regular cadence, including after a large ingest, after creating a new index, and as part of routine cluster review. Each subsection lists the query, what to look for in the response, and follow-up action.
Cluster health status
-
Get cluster-wide health:
GET _cluster/health
-
Read the following fields:
|
Field |
What it means |
What to do |
|---|---|---|
|
|
All primary and replica shards are allocated. |
Cluster is healthy. Continue with capacity and template checks. |
|
|
All primaries are allocated, but one or more replicas are not. Searches still work; you do not have full replica redundancy. |
Identify the index with the index-health queries below. Typical causes are a missing data node, disk watermark, or replica count higher than available nodes. |
|
|
At least one primary shard is unassigned. Some data is unavailable. |
Treat as an incident. Identify the red index, then use allocation explain in Troubleshooting. Do not start a split while the cluster is red. |
|
|
Count of shards not yet |
Non-zero during recoveries is expected. If values stay high, see Shards stuck in UNASSIGNED or INITIALIZING. |
Info: Yellow indicates a replica problem, not “the cluster is down.” Red means data is missing until the primary is assigned.
Template correctness
Most deployments this guide targets should be initially created with 4 primary shards. If an index is on track to exceed roughly 50 GB per primary shard, review Shard fill and disk headroom section to plan a larger shard count at index creation or create a re-sharding plan.
-
Inspect composable index templates:
GET _index_template -
In each template applying to AgileSec data indices, confirm:
-
-
template.settings.index.number_of_shardsis4(or the value your organization has standardized on during installation). -
template.settings.index.number_of_replicasmatches the intended replica count for the cluster (typically1when you have at least two data nodes). -
Index patterns still match how new indices are named (organization domain with dots replaced by underscores).
-
Note: The template correctness check is a proactive counterpart to re-sharding. Fixing the template prevents the next index from being created too small. Fixing the template does not change shards on an already-existing index. Existing indexes still requires the split procedure.
Shard fill and disk headroom
Each primary shard should stay at or below 50 GB.
-
Check disk per node, then size per shard:
GET _cat/allocation?v&h=node,shards,disk.indices,disk.used,disk.avail,disk.total,disk.percent GET _cat/shards/agilesec.*.v3.event-*,agilesec.*.v3.alert-*?v&h=index,shard,prirep,state,store,node -
What to look for:
-
Shard
storeapproaching 50 GB: Schedule a re-sharding window using Update the Shard Count of an Index. Do not wait until shards exceed this size, as large shards degrade cluster performance and recovery times. -
Disk percent high on any node: Add disk capacity before a watermark forces the index to read-only. Note an index split requires roughly 2× the primary store size free because the original and temporary index exist together until cleanup.
-
-
Confirm watermark thresholds. Defaults vary by version; high watermark stops allocating to the node, flood stage marks indices as read-only:
GET _cluster/settings?include_defaults=true&filter_path=**.cluster.routing.allocation.disk.* -
If an index is unexpectedly read-only, check
cluster.routing.allocation.disk.watermark.flood_stageand free space on every data node before clearingindex.blocks.read_only_allow_delete.
System and node health
-
List node-level CPU, memory, heap, and disk:
GET _cat/nodes?v&h=name,heap.percent,ram.percent,cpu,load_1m,disk.used_percent
-
Observe if
heap.percentstays high (for example above 75–80%) and note ifcpu/load_1mdoes not come down after ingest. Obtain JVM heap and GC pressure with the following command:GET _nodes/stats/jvm?filter_path=nodes.*.name,nodes.*.jvm.mem.heap_used_percent,nodes.*.jvm.mem.heap_used_in_bytes,nodes.*.jvm.gc
Info - Heap Pressure: Rising collection_count and long collection_time_in_millis on old-generation collectors indicate heap pressure. Heap pressure is a capacity or query problem, not a reason to split an index by itself.
-
Write and search thread pools. Rejections mean the cluster is dropping work:
GET _cat/thread_pool?v&h=node_name,name,active,queue,rejected&s=rejected:desc -
Focus on
write,search, andsearch_throttledresults. A growingrejectedcount needs fewer concurrent heavy queries, more nodes, or a pause in ingest, not a template change.
Index health
-
List only unhealthy indices:
GET _cat/indices?v&health=yellow GET _cat/indices?v&health=redAn empty table means no indices are in that state.
-
When a row appears, note
index,pri,rep,docs.count, andstore.size, then collect per-index stats:GET <index name>/_stats -
Use
_statsto confirm document count, store size, and indexing/search rates for each index. -
If the index is yellow or red because of allocation, run allocation explain to determine why the shards are unassigned:
GET /_cluster/allocation/explain -
If the index is green but shards are larger than 50 GB, plan capacity or re-sharding. Do not try to “fix” size at this point. The proper re-sharding process must be followed to avoid data corruption, data loss, or other consequences.
Additional checks
These are not shard-sizing checks, but they catch cluster problems indicating “OpenSearch is slow” during ingest or recovery.
-
Run the following checks:
GET _cluster/pending_tasks GET _cluster/allocation/explain GET _cat/recovery?v&active_only=true GET _cluster/stats?filter_path=indices.count,indices.shards,indices.store,nodes.count,status
-
Note the following from the check results:
-
-
_cluster/pending_tasksshould normally be empty. A long queue means cluster-manager work is backed up, often mapping updates or reroutes. -
_cluster/allocation/explainwith no body explains the first unassigned shard. Use it whenever health is yellow or red. -
_cat/recovery?active_only=trueshows in-flight recoveries. Progressingbytes_percentis healthy; a frozen percentage is not.
-
Update the Shard Count of an Index (Re-sharding)
OpenSearch does not support changing index.number_of_shards on an existing index in place. To increase shard count while keeping the original name, split the index into a temporary index with the target shard count, then clone the temporary index back to the original name.
Important: Follow all prerequisite and procedure steps as written to avoid potential data corruption, data loss, or other consequences.
Prerequisites
The following prerequisite steps confirm which indexes to re-shard and determine the target shard counts. Ensure all prerequisites are complete before proceeding with updating shard counts.
Confirm cluster health
-
Check overall cluster health:
GET _cluster/healthThis is cluster-wide health, not the health of a single index.
-
Confirm
statusisgreenandunassigned_shardsis0. Do not start updating shard counts on a yellow or red cluster.
Confirm disk headroom
-
The split step briefly requires enough free space to hold both the original index and the new temporary index at the same time (the original is not deleted until after the split completes and is verified). Check available capacity per node before proceeding:
GET _cat/allocation?v&h=node,shards,disk.indices,disk.used,disk.avail,disk.total,disk.percent -
Add disk space if any node is close to capacity. You need about 2 × primary store size. (Use the shard query in the next step to get current primary shard count sizes.)
Identify candidate indexes
-
List indexes for your organization, sorted by primary shard count:
GET _cat/indices/agilesec.*.v3.event-*,agilesec.*.v3.alert-*?v&h=index,pri,rep,docs.count,store.size&s=pri -
Review the index list generated in the previous step and identify any index where the average shard size (
store.sizedivided bypri) is approaching or exceeds 50 GB. -
These large indexes are candidates for re-sharding. The remaining prerequisites and procedure steps apply to the results when the list command is run.
Note: Indices are created on demand. When re-tuning a system, re-run the list command and proceed with tuning the new list rather than working from a previous set.
Determine the target shard count
-
Each shard should hold no more than 50 GB. Use the following formula as a starting point, then round up to a convenient valid count:
Target shard count = (store × 2) / 50
-
storeis the size of the primary index. Get current shard sizes:GET _cat/shards/<index name>?v&h=index,shard,prirep,state,store,node
Review target shard count limitations
Info: number_of_routing_shards is fixed when the index is created and caps how far an index can ever be split. If number_of_routing_shards was not set, a target shard count picked after index creation may be rejected.
Split API requirement. The target shard count must be an integer multiple of the current shard count (for example, 4 → 8, 12, or 16, not 4 → 6). If the calculated target is not a multiple, round up to the next valid value.
-
The target shard count must also be reachable for this index. Check shard index limits:
GET <index name>/_settings?include_defaults=true
-
Review
index.number_of_routing_shards. This value is fixed when the index is created and limits how far the index can ever be split, so a target satisfying the multiple rule above can still be rejected. -
Confirm your target is reachable before planning the maintenance window.
Backup candidate indexes
-
Important: back up candidate indexes before proceeding with the next procedure.
Procedure: Update Index’s Shard Count / Re-sharding
Repeat the following steps for each index identified in the prerequisites. Substitute the actual index name for <index name> and the value from the sizing formula for <target shard count>.
Step 1: Block writes on the source index
-
Run the following command to block writes on the source index:
PUT <index name>/_settings { "settings": { "index.blocks.write": true } }
Step 2: Resolve shard size mismatches (only if needed)
-
Run
forcemergeto compress segments and reduce the index store size:POST <index name>/_forcemerge?only_expunge_deletes=false&max_num_segments=1&wait_for_completion=false -
forcemergecan take a while on large shards. Runforcemergeasynchronously and poll for completion for better efficiency:GET _tasks?actions=*forcemerge*&detailed=true -
An empty result (no matching tasks) means the merge has finished. Re-check shard sizes with a shard-layout query. All shards should be roughly equal once complete.
GET _cat/shards/<index name>?v
Step 3: Split into a temporary index with the target shard count
-
Split the target index into a temporary index with the target shard count with the split API:
POST <index name>/_split/reshard-<index name>-tmp { "settings": { "index.number_of_shards": <target shard count>, "index.number_of_replicas": 0 } }
Step 4: Monitor split progress
-
Poll recovery status until every shard shows
stage: done:GET _cat/recovery/reshard-<index name>-tmp?v&h=index,shard,stage,files_percent,bytes_percent,time -
Confirm cluster health for the temporary index is green:
GET _cluster/health/reshard-<index name>-tmp?wait_for_status=green&timeout=60s
Note: Replica shards may briefly show as UNASSIGNED or INITIALIZING with reason INDEX_CREATED while they wait for a recovery slot. This behavior indicates normal queuing, not a failure. See Troubleshooting if progress stalls for an extended period.
Step 5: Verify document counts match
-
Verify the target and temporary document counts match:
GET <index name>/_count GET reshard-<index name>-tmp/_count -
Counts must match exactly before you continue.
Step 6: Verify shard layout and distribution
-
Verify shard layout and distribution:
GET _cat/shards/reshard-<index name>-tmp?v
-
Confirm the expected number of primary and replica shards are all in
STARTEDstate.
-
While reviewing shard layout, also check for size mismatches between shards that hold similar document counts. Size mismatches usually indicates unmerged segments rather than data loss.
Important: If the source index’s primary shards were concentrated on one node, after splitting, the new primary shards can inherit the same concentrated placement. If this concentration is a concern, use the optional rebalancing steps below before you delete the original index.
Step 7: Delete the original index
Important: Write and search outage on this index name. The original index name is unavailable from this delete until step 8, the clone step, completes successfully. Do not proceed unless document counts matched in Step 5 and the temporary index is green.
-
Delete the original index for replacement:
DELETE <index name>
Step 8: Clone the temporary index back to the original name
-
Clone the temporary index to the original index’s name:
POST reshard-<index name>-tmp/_clone/<index name>
Step 9: Confirm the final index is healthy with the correct shard count
-
Confirm the final, cloned index is healthy and get the shard count:
GET _cluster/health/<index name>?wait_for_status=green&timeout=60s
GET <index name>/_settings?pretty
-
Confirm
index.number_of_shardsequals the target count.
Step 10: Verify document count one more time
-
Re-confirm the document count:
GET <index name>/_count
Step 11: Clean up / delete the temporary index
-
Clean up the temporary index with a delete command:
DELETE reshard-<index name>-tmp
Step 12: Clear write block
-
Clear the write block carried over from cloning:
PUT <index name>/_settings { "settings": { "index.blocks.write": false } } -
Confirm if the write block is cleared. :
GET <index name>/_settings?pretty -
index.blocks.writeshould now befalse.
Troubleshooting
Use the following steps for troubleshooting common errors.
Shards stuck in UNASSIGNED or INITIALIZING
-
Get the allocation decision for a specific shard (this is the most direct way to find the blocker):
GET _cluster/allocation/explain { "index": "reshard-<index name>-tmp", "shard": <n>, "primary": false } -
The response
reasonanddecidersfields explain why the shard has not been allocated. The most common cause during re-sharding is recovery throttling, not a hard error:-
cluster.routing.allocation.node_concurrent_recoverieslimits how many shards can recover at once on a given node. Extra shards queue asUNASSIGNED(INDEX_CREATED) until a slot frees up. -
cluster.routing.allocation.node_initial_replicas_recoverieslimits concurrent outgoing replica recoveries from the node holding the primaries.
-
-
Both
node_concurrent_recoveriesandnode_initial_replicas_recoveriesresolve as in-flight recoveries finish. To speed things up temporarily, increasenode_initial_replicas_recoveries:PUT _cluster/settings { "transient": { "cluster.routing.allocation.node_initial_replicas_recoveries": 8 } } -
Revert
node_initial_replicas_recoveriesonce the split finishes. This setting is cluster-wide and affects later recoveries and rebalances:PUT _cluster/settings { "transient": { "cluster.routing.allocation.node_initial_replicas_recoveries": null } }
Recovery looks stalled
-
Check byte-level progress rather than relying on shard state alone. A shard reported as
INITIALIZINGis still progressing as long as its recovery percentage is climbing:GET reshard-<index name>-tmp/_recovery?detailed=true -
If the percentage is frozen across repeated polls (not just slow), check recovery bandwidth throttling and node-level I/O:
GET _nodes/stats/indices/recovery?filter_path=**.total.throttle_time_in_millis GET _cluster/settings?include_defaults=true&filter_path=**.indices.recovery.*
Appendix: Diagnostic query reference
Quick reference for the diagnostic queries used in this guide, plus a few additional ones useful for general cluster health checks.
Disk and capacity
|
Query |
Purpose |
|---|---|
|
|
Disk usage and shard count per node. |
|
|
Size and shard count per index, sorted. |
|
|
Size per individual shard. |
|
|
Disk watermark thresholds that can force an index read-only. |
Shard state and recovery
|
Query |
Purpose |
|---|---|
|
|
Current state of every shard (primary and replica) for an index. |
|
|
Recovery progress per shard. |
|
|
Full byte- and file-level recovery detail. |
|
|
Explains why a specific shard is or is not allocated. |
|
|
Blocks until the index reaches green status or the timeout elapses. |
Merging and tasks
|
Query |
Purpose |
|---|---|
|
|
Force-merge a shard’s segments asynchronously. |
|
|
List running force-merge tasks. |
|
|
Active and total merge counts per node. |
Health and templates
|
Query |
Purpose |
|---|---|
|
|
Cluster status (green / yellow / red) and unassigned shard counts. |
|
|
Filter to problem indices only. |
|
|
Per-node CPU, heap, RAM, and disk. |
|
|
Write/search rejection and queue depth. |
|
|
Confirm new indices are created with 16 primary shards. |
|
|
Confirm a newly created index received the template defaults. |