Chapter 45 · Amazon Opensearch Service
Subchapter 45.16
references/observability.mdMarkdown14 KBView on GitHub
The summary version is in SKILL.md (§ Logs & observability). This file owns the deep playbooks: ISM lifecycle, Splunk/Datadog migration, Trace Analytics, alerting, cost optimization at scale.
Observability features are exposed in OpenSearch UI (the newer dashboards experience), not the older OpenSearch Dashboards.
hot (gp3 EBS, 0–7 days) → UltraWarm (S3-backed, 7–90 days) → Cold (S3, 90–365 days) → delete{
"policy": {
"description": "Hot 7d, warm 83d, cold 275d, delete after 365d",
"default_state": "hot",
"states": [
{
"name": "hot",
"actions": [{ "rollover": { "min_size": "30gb", "min_index_age": "7d" } }],
"transitions": [{ "state_name": "warm", "conditions": { "min_index_age": "7d" } }]
},
{
"name": "warm",
"actions": [{ "warm_migration": {} }],
"transitions": [{ "state_name": "cold", "conditions": { "min_index_age": "90d" } }]
},
{
"name": "cold",
"actions": [{ "cold_migration": {} }],
"transitions": [{ "state_name": "delete", "conditions": { "min_index_age": "365d" } }]
},
{
"name": "delete",
"actions": [{ "cold_delete": {} }]
}
],
"ism_template": [{ "index_patterns": ["logs-*"] }]
}
}warm_migration, cold_migration, cold_delete (idempotent — operations continue past timeout)open and close ops require ES/OS 7.4+; snapshot op requires 7.7+plugins.index_state_management.enabled, .history.enabled, and .rollover_alias are user-tunableism_template.index_patterns apply on index creation; existing indexes need explicit _opendistro/_ism/add/<index> call| Pattern | When |
|---|---|
logs-app-2026-06-01 | Daily rotation; high-volume |
logs-app-2026-06 | Monthly; low-volume |
logs-app-000001 | Rollover alias; let ISM rollover at size/age |
ISM rollover is preferred — it manages the date math for you. Configure with min_size: 30gb (search) or min_size: 50gb (logs) and min_index_age: 1d.
OpenSearch has built-in Trace Analytics:
otel-v1-apm-span-* and otel-v1-apm-service-map-*otel-trace-pipeline:
source:
otel_trace_source: {}
processor:
- otel_trace_raw: {}
- otel_trace_group: {}
sink:
- opensearch:
index_type: "trace-analytics-raw"Native Alerting plugin:
{
"name": "5xx error spike",
"type": "monitor",
"monitor_type": "query_level_monitor",
"schedule": { "period": { "interval": 1, "unit": "MINUTES" } },
"inputs": [{
"search": {
"indices": ["logs-app-*"],
"query": {
"size": 0,
"query": {
"bool": {
"must": [
{ "range": { "@timestamp": { "gte": "now-5m", "lt": "now" } } },
{ "range": { "status": { "gte": 500 } } }
]
}
},
"aggs": { "error_count": { "value_count": { "field": "_id" } } }
}
}
}],
"triggers": [{
"name": "100+ errors in 5min",
"condition": { "script": { "source": "ctx.results[0].aggregations.error_count.value > 100", "lang": "painless" } },
"actions": [{ "destination_id": "<sns-destination>", "subject_template": { "source": "5xx spike", "lang": "mustache" } }]
}]
}PPL is the SQL/Splunk-style query language for logs. Pipe-separated commands.
source=logs-app-2026-06-01 | where status >= 500 | stats count() by service | sort -count() | head 10source=logs-app-* | where @timestamp >= now() - 1h | parse uri "(?<endpoint>/api/[^?]+)" | stats avg(latency_ms), p99(latency_ms) by endpointsource=logs-app-* | eval is_error = if(status >= 500, 1, 0) | stats sum(is_error) as errors, count() as total by service | eval error_rate = errors / total | where error_rate > 0.01PPL operators: where, stats, fields, eval, dedup, sort, head, tail, parse, rename, top.
| Splunk concept | OpenSearch equivalent |
|---|---|
| Index | Index |
| Sourcetype | Field (often service, source) |
| Search head / indexer split | Coordinator / data nodes (mostly transparent on AOS) |
| SPL queries | PPL or DSL — most queries need rewrite |
| Dashboards | OpenSearch Dashboards / OpenSearch UI |
| Saved searches | Saved searches in Dashboards |
| Alerts | Alerting plugin |
| Apps (e.g., Splunk ES) | Security Analytics plugin (subset) |
| Universal Forwarder | Fluent Bit, Fluentd, OTel Collector, Filebeat-OSS |
| Heavy Forwarder | Data Prepper / OpenSearch Ingestion |
| Indexer cluster | OpenSearch domain |
| Search head cluster | Multi-AZ data nodes |
Migration scoping is anchored on detector / dashboard / pipeline count + complexity classification, not on calendar duration. Wall-clock depends on team size, parallelism, and reuse pace — pacing is the customer’s call, not the skill’s.
The streams that decompose any Splunk replatform:
_msearch / aggregations.When responding to a Splunk replatform prompt: NAME the concrete detector / dashboard / pipeline counts the customer gave you and break them down by classification (PPL-translatable vs DSL hand-port; trivial vs complex; correlation searches as their own bucket). Surface the parallelism lever — “can be compressed by splitting across N engineers” — without declaring a wall-clock. Do NOT produce week / month / sprint estimates for coding effort: a dedicated team will deliver much faster than a generic estimate suggests, and the customer’s own staffing decides the calendar.
| Datadog concept | OpenSearch equivalent |
|---|---|
| Logs | OpenSearch logs (PPL queries) |
| APM / traces | Trace Analytics (built-in; less polished than DD) |
| Metrics | Prometheus + AMP/Grafana, or Metric Analytics in OS UI |
| Synthetics | Not built-in — pair with CloudWatch Synthetics or external tool |
| RUM | Not built-in — pair with CloudWatch RUM or external |
| Notebooks | OpenSearch Dashboards Notebooks |
| Watchdog (anomaly detection) | Anomaly Detection plugin |
| CSPM / cloud security | Security Analytics plugin (limited) |
| Workflow Automation | Lambda + Alerting destinations |
Honest assessment:
Kaltura achieved 60% cost reduction vs prior observability setup by moving to Amazon OpenSearch Service with aggressive ISM tiering. Key levers:
For log-analytics workloads, default to OR1 (write-heavy log profile) with UltraWarm tiering for >7-day retention. Full instance family list: sizing.md §Instance family selection. Source of truth: supported-instance-types.html (opens in a new tab).
OR1 trade-offs (observability-specific):
For logs, set refresh_interval: 30s or 60s to reduce CPU overhead from frequent segment refreshes. Default 1s is search-app-tuned.
PUT logs-app-*/_settings
{ "index.refresh_interval": "30s" }3–5 MiB per bulk request for general ingest; 10 MiB for OR1.
Set number_of_replicas: 0 during initial bulk load; raise to target after. Halves storage and indexing cost during reindex.
index.translog.durability:
request (default): fsync per request — durable, slower ingestasync: fsync every sync_interval (default 5s) — bigger throughput, seconds-of-data risk on crashFor non-critical observability indexes, async typically gives 2–5× ingest throughput improvement.
Once an index is rolled over (read-only), force-merge to 1 segment per shard:
POST logs-app-2026-06-01/_forcemerge?max_num_segments=1Reduces segment count → improves search performance and reduces JVM overhead.
Defaults (also valid for OpenSearch):
index.blocks.read_only_allow_delete=true on every indexThis is THE most common “cluster went read-only at 3am” cause. Set up alerting on FreeStorageSpace < 25 GB or storage usage > 80%.
Important license gotcha: the default Logstash distro has a license check that rejects OpenSearch. Two workarounds:
logstash-output-opensearch pluginOr skip Logstash entirely and use OpenSearch Ingestion (managed Data Prepper) or Fluent Bit.
Built-in Anomaly Detection plugin runs Random Cut Forest models on time-series streams. Common observability uses:
PUT _plugins/_anomaly_detection/detectors
{
"name": "5xx-anomaly-detector",
"indices": ["logs-app-*"],
"feature_attributes": [{
"feature_name": "5xx-rate",
"feature_enabled": true,
"aggregation_query": {
"5xx_count": { "value_count": { "field": "_id" } }
}
}],
"filter_query": { "range": { "status": { "gte": 500 } } },
"detection_interval": { "period": { "interval": 1, "unit": "MINUTES" } },
"window_delay": { "period": { "interval": 1, "unit": "MINUTES" } }
}