Продовжуємо нашу подорож з AWS OpenSearch Service.
Що ми маємо – це маленький кластер AWS OpenSearch Service, 3 трьома data nodes, використовується в ролі vector store для AWS Bedrock Knowledge Bases.
Попередні частини:
- AWS: знайомство з OpenSearch Service в ролі vector store
- AWS: створення OpenSearch Service cluster та налаштування аутентифікації і авторизації
- Terraform: створення AWS OpenSearch Service cluster та юзерів
Вже мали перший production incident 🙂
Запустили якийсь пошук без фільтрів, і наші t3.small.search
вмерли через CPU.
Тому давайте глянемо що у нас є з моніторингу всього цього щастя.
Зараз зробимо щось базове, просто з метриками CloudWatch, але в плані моніторингу OpenSearch є кілька рішень:
- метрики CloudWatch самого OpenSearchService – дані по CPU, Memory, JVM, які ми можемо збирати до VictoriaMetrics і генерити алерти або використати в Grafana dashboard, див. Monitoring OpenSearch cluster metrics with Amazon CloudWatch
- CloudWatch Events, які генерить OpenSearch Service – див. Monitoring OpenSearch Service events with Amazon EventBridge, можемо їх через SNS відправляти до Opsgenie, а звідти до Slack
- логи в CloudWatch Logs – можемо збирати в VictoriaLogs, і генерити якісь метрики і алерти, але я під час нашого production incent нічого цікавого в логах не побачив, див. Monitoring OpenSearch logs with Amazon CloudWatch Logs
- Monitors самого OpenSearch – вміє в Anomaly Detection та власний Alerting, є навіть окремий Terraform resource
opensearch_monitor
, див. також Configuring alerts in Amazon OpenSearch Service - і є Prometheus Exporter Plugin, який відкриває ендпоінт для збору метрик з Prometheus/VictoriaMetrics (але в AWS OpenSearch Managed його додати не можна, хоча сапорт обіцяє, ще feature request є – може колись додадуть)
Зміст
CloudWatch метрики
Метрик досить багато, але з того, що може бути цікавим нам – враховуючи те, що у нас нема виділених master та coordinator nodes, і ми не використовуємо ultra-warm та cold інстансами.
Cluster metrics:
ClusterStatus
:green
/yellow
/red
– основний показник стану кластеру, контроль активності шардів данихShards
:active
/unassigned
/delayedUnassigned
/activePrimary
/initializing
/relocating
– більш детальна інформація по стану шардів, але тут просто загальна кількість, без деталізації по конкретним індексамNodes
: кількість нод в кластері – знаючи, скільки має бути живих нод – можемо алертити, коли якась нода відвалитьсяSearchableDocuments
: не те щоб саме для нас було дуже цікаво, але можливо буде корисним потім, аби бачити що взагалі твориться в індексахCPUUtilization
: відсоток використання CPU разом на всіх нодах, і це прям must-haveFreeStorageSpace
: теж корисно моніторитиClusterIndexWritesBlocked
: чи все ОК із записами в індексJVMMemoryPressure
таOldGenJVMMemoryPressure
: відсоток використання пам’яті JVM heap – далі окремо копнемо в моніторинг JVM, бо це прям окремий геморойAutomatedSnapshotFailure
: мабуть, good to know, якщо бекап сфейлитьсяCPUCreditBalance
: нам корисно, бо ми наt3
інстансах (але у нас в CloudWatch її нема)2xx
,3xx
, 4xx,
5xx`: дані по HTTP-запитам і помилкам- я тут збираю тільки
5хх
для алертів
- я тут збираю тільки
ThroughputThrottle
іIopsThrottle
: в RDS ми стикались з проблемами доступу до диску, тому варто помоніторити і тут, див. PostgreSQL: AWS RDS Performance and monitoring- тут треба буде дивитись на метрики з EBS volume metrics, але для початку можна просто додати алерти на Throttle взагалі
HighSwapUsage
: аналогічно до попередніх метрик – колись мали біду в RDS, тому краще помоніторити і тут
EBS volume metrics – тут в принципі стандартні метрики EBS, як і для EC2 або RDS:
ReadLatency
таWriteLatency
: затримки читання/запису- іноді бувають спайки, тому можна додати
ReadThroughput
таWriteThroughput
: “пропускна здатність”? загальне навантаження на диск, давайте скажемо такDiskQueueDepth
: черга I/O операцій- у нас в CloudWatch пуста (поки що?), тому скіпаємо
ReadIOPS
таWriteIOPS
: кількість операцій читання/запису на секунду
Instance metrics – тут метрики по кожному OpenSearch інстансу (не серверу, EC2, а самого OpenSearch) на кожній ноді:
FetchLatency
таFetchRate
: як швидко отримуємо дані з шардів (але в CloudWatch теж не знайшов)ThreadCount
: кількість потоків в операційній системі, які були створені JVM (Garbadge Collector threads, search threads, write/index threads, etc)- в CloudWatch значення стабільне, але в Grafana для загальної картини поки можна додати, подивимось, чи буде там щось цікаве
ShardReactivateCount
: як часто шарди зі станів cold/inactive переводяться в активні, що потребує ресурсів операційної системи і CPU та пам’яті; ну… може бути, треба глянути чи воно взагалі у нас має якісь значення- але в CloudWatch теж нічого – “did not match any metrics“
ConcurrentSearchRate
таConcurrentSearchLatency
: кількість і швидкість одночасних запитів на пошук – може бути цікавим, якщо довго висять багато паралельних запитів- але у нас (поки що?) ці значення постійно на нулі, тому скіпаємо
SearchRate
: кількість пошукових запитів на хвилину, корисно для загальної картиниSearchLatency
: швидкість виконання пошукових запитів, мабуть, дуже корисно, можна навіть алерт прикрутитиIndexingRate
таIndexingLatency
: аналогічно, але для індексації нових документівSysMemoryUtilization
: відсоток використання пам’яті на дата-ноді, але це не дасть повноцінної картини, треба дивитись на пам’ять JVMJVMGCYoungCollectionCount
таJVMGCOldCollectionCount
: кількість запусків Garbage Collectors, корисно разом з даними по JVM memory, поговоримо далі детальнішеSearchTaskCancelled
таSearchShardTaskCancelled
: про погані новини 🙂 якщо задачі канселяються – щось явно йде не так (або юзер сам перервав виконання запиту, або HTTP connection reset, або таймаути, чи навантаження на кластер)- але у нас завжди по нулях, навіть коли кластер падав, тому поки сенсу збору цих метрик не бачу
ThreadpoolIndexQueue
таThreadpoolSearchQueue
: кількість задач на індексацію та пошук в черзі, коли їх забагато – маємоThreadpoolIndexRejected
таThreadpoolSearchRejected
ThreadpoolIndexQueue
в CloudWatch нема взагалі, аThreadpoolSearchQueue
є, але теж постійно в нулях, тому поки скіпаємо
ThreadpoolIndexRejected
таThreadpoolSearchRejected
: власне, вище- в CloudWatch картина аналогічна –
ThreadpoolIndexRejected
нема взагалі,ThreadpoolSearchRejected
в нулях
- в CloudWatch картина аналогічна –
ThreadpoolIndexThreads
таThreadpoolSearchThreads
: максимальна кількість потоків операційної системи для індексації та пошуку, якщо всі зайняті – то запити підуть вThreadpoolIndexQueue
/ThreadpoolSearchQueue
- в OpenSearch є кілька типів пулів для потоків – search, index, write і т.д., і для кожного пулу є показник threads (скільки виділено), queue – черга, rejected – відхилено, бо черга переповнена, див. OpenSearch Threadpool
- в Node Stats API (
GET _nodes/stats/thread_pool
) є показник active threads, але в CloudWatch такого не бачу ThreadpoolIndexThreads
у нас в CloudWatch взагалі нема, а ThreadpoolSearchThreads статична, поки, думаю, можна скіпнути їхній моніторинг
PrimaryWriteRejected
: відхилені операції записи в primary-шарди через проблеми в thread pool write або index, чи навантаження на дата-ноді- в CloudWatch поки пусті, але додамо збір і алерт
ReplicaWriteRejected
: відхилені операції записи в replica-шарди – в primary документ додано, але не може записати в репліку- в CloudWatch поки пусті, але додамо збір і алерт
k-NN metrics – нам корисно, бо у нас vector store з k-NN:
KNNCacheCapacityReached
: коли кеш повністю зайнятий (див. далі)KNNEvictionCount
: як часто дані з кешу видаляються – ознака, що пам’яті не вистачаєKNNGraphMemoryUsage
: використання off-heap пам’яті під графи самого векторуKNNGraphQueryErrors
: кількість помилок при пошуку в векторах- в CloudWatch поки пусті, але додамо збір і алерт
KNNGraphQueryRequests
: загальна кількість запитів до k-NN graphsKNNHitCount
таKNNMissCount
: скільки результатів було повернуто з кешу, а скільки довелось зчитувати з дискуKNNTotalLoadTime
: швидкість завантаження з диску в кеш (великі графи або завантажений EBS – буде рости час)
Моніторинг Memory
Давайте подумаємо як нам основнім показники помоніторити, і першим – пам’ять, бо це ж Java.
Що у нас є по пам’яті з метрик?
SysMemoryUtilization
: відсоток використання пам’яті на сервері (дата-ноді) взагаліJVMMemoryPressure
: загальний відсоток використання JVM Heap; JVM Heap по дефолту виділяється в 50% від пам’яті серверу, але не більше 32 гігOldGenJVMMemoryPressure
: див. даліKNNGraphMemoryUsage
: про це говорили в першому пості – AWS: знайомство з OpenSearch Service в ролі vector store- в CloudWatch ще є метрика
KNNGraphMemoryUsagePercentage
– але в документації її нема
- в CloudWatch ще є метрика
kNN Memory usage
Спершу коротенько про пам’ять під k-NN.
Отже, на EC2 у нас виділяється пам’ять під JVM Heap (50% доступної на сервері), і окремо – off-heap для OpenSearch vector store, де він тримає графи та кеш vectore store – див. Approximate k-NN search, плюс під саму операційну систему і її файловий кеш.
Якоїсь метрики типу “KNNGraphMemoryAvailable” у нас нема, але маючи KNNGraphMemoryUsagePercentage
та KNNGraphMemoryUsage
можемо її порахувати:
KNNGraphMemoryUsage
: у нас зараз 662 мегабайтиKNNGraphMemoryUsagePercentage
: 60%
Значить, під k-NN graphs виділяється 1 гігабайт поза JVM Heap memory (це на t3.medium.search
).
З документації k-Nearest Neighbor (k-NN) search in Amazon OpenSearch Service:
OpenSearch Service uses half of an instance’s RAM for the Java heap (up to a heap size of 32 GiB). By default, k-NN uses up to 50% of the remaining half
Знаючи, що у нас зараз t3.medium.search
, на яких видається 4 гігабайти пам’яті – 2 GB йде під JVM Heap, і 1 гігабайт – під k-NN графи.
Основну частину KNNGraphMemory
використовує k-NN cache, тобто частина оперативної пам’яті системи, в якій OpenSearch тримає HNSW-графи з векторних індексів, аби не зчитувати їх кожного разу з диску (див. k-NN clear cache).
Тому корисно мати графіки по EBS IOPS та використанню k-NN cache.
JVM Memory usage
Окей, давайте згадувати що там в Java взагалі відбувається, див. What Is Java Heap Memory?, OpenSearch Heap Size Usage and JVM Garbage Collection та Understanding the JVMMemoryPressure metric changes in Amazon OpenSearch Service.
Якщо дуже спрощено, то:
- Stack Memory: окрім JVM Heap маємо Stack, який виділяється кожному потоку, де він тримає свої змінні, посилання, параметри запуску
- задається через
-Xss
, дефолтне значення від 256 кілобайт до 1 мегабайту, див. Understanding Threads and Locks (не знайшов, як подивитись в OpenSearch Service) - якщо маємо багато threads – буде багато пам’яті під їхні стеки
- очищується, коли thread вмирає
- задається через
- Heap Space:
- використовується для виділення пам’яті, яка доступна всім потокам
- керується Garbage Collectors (GC)
- в контексті OpenSearch у нас тут будуть кеши пошуку і індексацій
В Heap memory у нас є:
- Young Generation: свіженькі дані, усі нові об’єкти
- дані звідси або видаляються зовсім, або переміщаються в Old Generation
- Old Generation: сам код процесу OpenSearch, кеші, індексні структури Lucene, великі масиви
Якщо OldGenJVMMemoryPressure
забитий – значить, Garbage Collector не може його почистити, бо на дані є посилання, і тоді маємо проблему – бо в Heap нема місця для нових даних, і JVM може впасти з помилкою OutOfMemoryError.
Взагалі “heap pressure” – це коли в Young Gen і Old Gen мало вільної пам’яті, і нема де розмістити нові дані, аби відповісти клієнтам.
Це призводить до частого запуску Garbage Collector, що займає час та ресурси системи – замість обробки запитів від клієнтів.
В результаті latency зростає, індексація нових документів гальмує, або взагалі отримуємо ClusterIndexWritesBlocked
– аби уникнути Java OutOfMemoryError, бо при індексації OpenSearch спочатку пише дані в Heap, а потім “скидається” на диск.
Див. Key JVM Metrics to Monitor for Peak Java Application Performance.
Отже – для картини використання пам’яті моніторимо:
SysMemoryUtilization
– для загальної картини по стану EC2- в нашому випадку тут буде стабільно близько 90%, але це ОК
JVMMemoryPressure
– для загальної картини по JVM- має регулярно чиститись з Garbage Collector (GC)
- якщо постійно вище 80-90% – є проблеми з запуском GC
OldGenJVMMemoryPressure
– для даних по Old Generation Heap- має бути на рівні 30-40%, якщо вище і не вичищається – то проблеми або з кодом, або з GC
KNNGraphMemoryUsage
– в нашому випадку треба для загальної картини
І варто додати алерти на HighSwapUsage
– у нас вже відбувався активний swapping, коли запустились на t3.small.search
, і це показник того, що пам’яті недостатньо.
Збір метрик до VictoriaMetrics
Власне, як вибрати метрики?
Спершу шукаємо їх в CloudWatch Metrics, і дивимось чи взагалі метрика є, і чи вона повертає якісь цікаві дані.
Наприклад, SysMemoryUtilization
дає інфу.
Отуто у нас на t3.small.search
був спайк, після якого кластер впав:
А ось метрика HighSwapUsage
– теж до переїзду на t3.medium.search
:
ClusterStatus
є:
Shards
є, але це по всім індексам, і нема можливості фільтрувати по окремим:
Ну і треба мати на увазі, що збір метрик з CloudWatch теж коштує грошей за API-запити, тому все підряд збирати не варто.
Взагалі для збору метрик з CloudWatch ми користуємось YACE (Yet Another CloudWatch Exporter), але він не підтримує OpenSearch Managed cluser – див. Features.
Тому беремо звичайний експортер – CloudWatch Exporter.
У нас він деплоїться з Helm-чарту моніторингу (див. VictoriaMetrics: створення Kubernetes monitoring stack з власним Helm-чартом), додаємо йому новий конфіг:
... prometheus-cloudwatch-exporter: enabled: true serviceAccount: name: "cloudwatch-sa" annotations: eks.amazonaws.com/sts-regional-endpoints: "true" serviceMonitor: enabled: true config: |- region: us-east-1 metrics: - aws_namespace: AWS/ES aws_metric_name: KNNGraphMemoryUsage aws_dimensions: [ClientId, DomainName, NodeId] aws_statistics: [Average] - aws_namespace: AWS/ES aws_metric_name: SysMemoryUtilization aws_dimensions: [ClientId, DomainName, NodeId] aws_statistics: [Average] - aws_namespace: AWS/ES aws_metric_name: JVMMemoryPressure aws_dimensions: [ClientId, DomainName, NodeId] aws_statistics: [Average] - aws_namespace: AWS/ES aws_metric_name: OldGenJVMMemoryPressure aws_dimensions: [ClientId, DomainName, NodeId] aws_statistics: [Average]
Зверніть увагу, що для різних метрик можуть бути різні Dimenstions
– перевіряємо їх в CloudWatch:
Деплоїмо, перевіряємо:
І навіть цифри вийшли такі, як ми рахували в першому пості – маємо ~130000 документів в production index, по формулі num_vectors * 1.1 * (4*1024 + 8*16)
виходить 604032000 байт, або 604.032 мегабайт.
А на графіку маємо 662261 kilobytes – це 662 мегабайти, але по всім індексам разом.
Тепер у VictoriaMetrics у нас є метрики aws_es_knngraph_memory_usage_average
, aws_es_sys_memory_utilization_average
, aws_es_jvmmemory_pressure_average
, aws_es_old_gen_jvmmemory_pressure_average
.
Аналогічно додаємо решту.
Для пошуку того, як саме метрики називаються в VictoriaMetrics/Prometheus – відкриваємо порт до CloudWatch Exporter:
$ kk port-forward svc/atlas-victoriametrics-prometheus-cloudwatch-exporter 9106
І з curl
та grep
шукаємо метрики:
$ curl -s localhost:9106/metrics | grep aws_es # HELP aws_es_cluster_status_green_maximum CloudWatch metric AWS/ES ClusterStatus.green Dimensions: [ClientId, DomainName] Statistic: Maximum Unit: Count # TYPE aws_es_cluster_status_green_maximum gauge aws_es_cluster_status_green_maximum{job="aws_es",instance="",domain_name="atlas-kb-prod-cluster",client_id="492***148",} 1.0 1758014700000 # HELP aws_es_cluster_status_yellow_maximum CloudWatch metric AWS/ES ClusterStatus.yellow Dimensions: [ClientId, DomainName] Statistic: Maximum Unit: Count # TYPE aws_es_cluster_status_yellow_maximum gauge aws_es_cluster_status_yellow_maximum{job="aws_es",instance="",domain_name="atlas-kb-prod-cluster",client_id="492***148",} 0.0 1758014700000 # HELP aws_es_cluster_status_red_maximum CloudWatch metric AWS/ES ClusterStatus.red Dimensions: [ClientId, DomainName] Statistic: Maximum Unit: Count # TYPE aws_es_cluster_status_red_maximum gauge aws_es_cluster_status_red_maximum{job="aws_es",instance="",domain_name="atlas-kb-prod-cluster",client_id="492***148",} 0.0 1758014700000 ...
Створення Grafana dahsboard
ОК, метрики з CloudWatch маємо – їх поки вистачить.
Подумаємо, що ми хочемо бачити в Grafana.
Загальна ідея – така собі “overview” дашборда, де на одній борді будуть відображатись всі головні дані по кластеру.
Які метрики зараз є, і як ми їх можемо використати в Grafana – я їх тут собі виписував, аби не заплутатись, бо їх вийшло багатенько:
-
aws_es_cluster_status_green_maximum
,aws_es_cluster_status_yellow_maximum
,aws_es_cluster_status_red_maximum
: можна зробити одну Stats панель aws_es_nodes_maximum
: теж якусь Stats панель – знаємо, скільки має бути, і будемо робити червоним, коли Data Nodes менше, ніж має бутиaws_es_searchable_documents_maximum
: просто інтересу заради – графіком покажемо кількість документів разом в усіх індексахaws_es_cpuutilization_average
: одним графіком по кожній ноді, і якусь Stats з загальною інформацією і різними кольорамиaws_es_free_storage_space_maximum
: просто Statsaws_es_cluster_index_writes_blocked_maximum
: не став додавати в Grafana, тільки алертaws_es_jvmmemory_pressure_average
: графік і Statsaws_es_old_gen_jvmmemory_pressure_average
: десь поруч, теж графіком + Statsaws_es_automated_snapshot_failure_maximum
: це просто для алертаaws_es_5xx_maximum
: і графік, і Statsaws_es_iops_throttle_maximum
: графік, аби бачити в порівнянні з іншими даними типу CPU/Mem usageaws_es_throughput_throttle_maximum
: графікaws_es_high_swap_usage_maximum
: і графік, і Stats – графік, аби бачити в порівнянні з CPU/дискамиaws_es_read_latency_average
: графікaws_es_write_latency_average
: графікaws_es_read_throughput_average
: не став додавати, бо забагато графіківaws_es_write_throughput_average
: не став додавати, бо забагато графіківaws_es_read_iops_average
: графік, корисно, аби розуміти роботу кешу k-NN – якщо його мало (а ми тестили наt3.small.search
з 2 гігабайтами загальної пам’яті) – то читання з диску буде багатоaws_es_write_iops_average
: аналогічноaws_es_thread_count_average
: не став додавати, бо воно доволі статичне і якось сильно корисної інформації не побачивaws_es_search_rate_average
: теж просто графікaws_es_search_latency_average
: аналогічно, десь поручaws_es_sys_memory_utilization_average
: ну, воно постійно буде десь під 90%, поки прибрав з Grafana, але додав в алертиaws_es_jvmgcyoung_collection_count_average
: графік, бачити як часто викликаєтьсяaws_es_jvmgcold_collection_count_average
: графік, бачити як часто викликаєтьсяaws_es_primary_write_rejected_average
: графік, але поки не став додавати, бо забагато графіків – тільки алертaws_es_replica_write_rejected_average
: графік, але поки не став додавати, бо забагато графіків – тільки алерт- k-NN:
aws_es_knncache_capacity_reached_maximum
: тільки для warning-алертуaws_es_knneviction_count_average
: не став додавати, хоча може бути цікавимaws_es_knngraph_memory_usage_average
: не став додаватиaws_es_knngraph_memory_usage_percentage_maximum
: графік, замістьaws_es_knngraph_memory_usage_average
aws_es_knngraph_query_errors_maximum
: тільки алертaws_es_knngraph_query_requests_sum
: графікaws_es_knnhit_count_maximum
: графікaws_es_knnmiss_count_maximum
: графікaws_es_knntotal_load_time_sum
: було непогано мати графік, але нема місця на борді
VictoriaMetrics/Prometheus sum()
, avg()
та max()
Спершу давайте згадаємо які у нас є функції для агрегації даних.
З CloudWatch для OpenSearch ми будемо отримувати два основні типи – counter та gauge:
$ curl -s localhost:9106/metrics | grep cpuutil # HELP aws_es_cpuutilization_average CloudWatch metric AWS/ES CPUUtilization Dimensions: [ClientId, DomainName, NodeId] Statistic: Average Unit: Percent # TYPE aws_es_cpuutilization_average gauge aws_es_cpuutilization_average{job="aws_es",instance="",domain_name="atlas-kb-prod-cluster",node_id="BzX51PLwSRCJ7GrbgB4VyA",client_id="492***148",} 10.0 1758099600000 ...
Різниця між ними:
- counter: значення може тільки збільшувати значення
- gauge: значення може збільшуватись і зменшуватись
Тут у нас “TYPE aws_es_cpuutilization_average gauge
“, бо використання CPU може і збільшуватись, і зменшуватись.
Див. чудово документацію VictoriaMetrics – Prometheus Metrics Explained: Counters, Gauges, Histograms & Summaries:
Як ми його можемо використати в графіках?
Якщо ми просто подивимось на значення – то у нас тут є набір лейбл, кожна формує власні тайм-серії:
aws_es_cpuutilization_average{node_id="BzX51PLwSRCJ7GrbgB4VyA"}
== 9aws_es_cpuutilization_average{node_id="IIEcajw5SfmWCXe_AZMIpA"}
== 28aws_es_cpuutilization_average{node_id="lrsnwK1CQgumpiXfhGq06g"}
== 8
З sum()
без лейбл ми просто отримаємо суму всіх значень:
Якщо зробимо sum by (node_id)
– то отримаємо значення для конкретної тайм-серії, яка тут буде збігатись з вибіркою без sum by ()
:
(значення міняється, поки пишу і роблю запити)
З max()
без фільтрів – отримаємо просто максимальне значення, вибране з усіх отриманих тайм-серій:
А з avg()
– середнє значення всіх значень, тобто сума всіх значень поділена на кількість тайм-серій:
(41+46+12)/3 33
Власне, чому я про це став писати окремо – бо з sum()
навіть із by (node_id)
іноді можна отримати такі во спайки:
Хоча без sum()
їх нема:
А траплялись вони через те, що в цей момент перестворювався Pod з CloudWatch Exporter:
І в цей момент ми отримували дані зі старого поду, і з нового.
Тому тут варіант або використовувати max()
, або просто avg()
. Хоча max()
все ж, мабуть, краще, бо нам цікаві “найгірші” показники.
Окей – з цим розібрались, погнали робити дашборду.
Cluster status
Тут хочеться на одній Stats панелі бачити всі три значення – Green, Yellow, Red.
Але так як в Grafana у нас нема if/else, то зробимо “костиль”.
Збираємо всі три метрики, і результат кожної множимо на 1, 2, чи 3:
sum(aws_es_cluster_status_green_maximum) by (domain_name) * 1 + sum(aws_es_cluster_status_yellow_maximum) by (domain_name) * 2 + sum(aws_es_cluster_status_red_maximum) by (domain_name) * 3
Відповідно, якщо aws_es_cluster_status_green_maximum
== 1, то 1 * 1 == 1, а aws_es_cluster_status_yellow_maximum
== 0 і aws_es_cluster_status_red_maximum
будуть == 0 – то і множення поверне 0.
А якщо aws_es_cluster_status_green_maximum
стане 0, але aws_es_cluster_status_red_maximum
буде 1 – то 1 * 2 отримаємо 3, і по значенню 3 будемо міняти показник в Stats-панелі
І додаємо Value mappings з текстом і кольорами:
Nodes status
Тут все просто – знаємо потрібну кількість, поточну отримуємо з aws_es_nodes_maximum
:
sum(aws_es_nodes_maximum) by (domain_name)
І знов через Value mappings задаємо значення і кольори:
На випадок, якщо колись збільшимо кількість нод, і забудемо оновити тут значення для “ОК” – то додаємо третій статус, ERR:
CPUUtilization: Stats
Тут зробимо кросивенько – з типом візуалізації Gauge:
avg(aws_es_cpuutilization_average) by (domain_name)
Задаємо Text size та Unit:
І Thresholds:
Description непогано генерить ChatGPT – корисно і девелоперам, і нам самим через півроку, або просто беремо опис з документації AWS:
The percentage of CPU usage for data nodes in the cluster. Maximum shows the node with the highest CPU usage. Average represents all nodes in the cluster.
Додаємо решту Stats:
CPUUtilization: Graph
Тут виведемо графік по CPU кожної ноди – середнє за 5 хвилин:
max(avg_over_time(aws_es_cpuutilization_average[5m])) by (node_id)
І ось теж приклад того, як з sum()
з’являлись спайки, яких не було насправді:
Тому робимо max()
.
Задамо Gradient mode == Opacity, і Unit == percent:
Задаємо Color scheme і Thresholds, включаємо Show thresholds:
В Data links можна задати лінку на сторінку DataNode Health в AWS Console:
https://us-east-1.console.aws.amazon.com/aos/home?region=us-east-1#opensearch/domains/atlas-kb-prod-cluster/data_Node/${__field.labels.node_id}
Всі доступні поля – по Ctrl+Space:
Actions, мабуть, не так давно з’явилось, ще не використовував, але виглядає цікаво – можна щось пушнути:
JVMMemoryPressure: Graph
Тут нам цікаво бачити чи не “залипає” використання пам’яті, і як часто запускається Garbage Collector.
Запит простий – можна зробити max by (node_id)
, але я зробив просто загальну картину по кластеру:
max(aws_es_jvmmemory_pressure_average)
І графік аналогічно попередньому:
В Desription додаємо пояснення “коли хвилюватись”:
Represents the percentage of JVM heap in use (young + old generation).
Values below 75% are normal. Sustained pressure above 80% indicates frequent GC and potential performance degradation.
Values consistently > 85–90% mean heap exhaustion risk and may trigger ClusterIndexWritesBlocked – investigate immediately.
JVMGCYoungCollectionCount and JVMGCOldCollectionCount
Дуже корисний графік, аби бачити як часто зпускаються Garbage Collects.
В запиті використаємо increase[1m]
– побачити як змінилось значення за хвилину:
max(increase(aws_es_jvmgcyoung_collection_count_average[1m])) by (domain_name)
І для Old Gen:
max(increase(aws_es_jvmgcold_collection_count_average[1m])) by (domain_name)
Unit – ops/sec, Decimals задаємо 0, аби мати тільки цілі значення:
KNNHitCount vs KNNMissCount
Тут зробимо дані на секунду – rate()
:
sum(rate(aws_es_knnhit_count_average[5m]))
І для Cache Miss:
sum(rate(aws_es_knnmiss_count_average[5m]))
Unit ops/s, кольори можемо задати через Overrides:
Статистика тут, до речі, дуже так собі – стабільно багато Cache missed, але чому – поки не розібрались.
Фінальний результат
Збираємо всі графіки, і отримуємо щось таке:
t3.small.search
vs t3.medium.search
на графіках
І приклад того, як нестача ресурсів, в першу чергу пам’яті, відображається на графіках: у нас були t3.medium.search
, потім ми повернули t3.small.search
, аби подивитись як воно на перформанс вплине.
t3.small.search
– це лише 2 гігабайти пам’яті і 2 ядра CPU.
З цих 2 гіг пам’яті 1 гіг під JVM Heap, 500 мегабайт під k-NN memory, і 500 залишалось на решту процесів.
Ну і результати, цілком очікувані:
- Garbage Collectors стали запускатись постійно, бо треба було чистити пам’ять, якої не вистачало
- Read IOPS виріс, бо постійно з диска завантажувались дані до JVM Heap Young і k-NN
- Search Latency виріс, бо не всі дані були в кеші, і чекали I/O-операцій з диску
- і CPU utilization підскочив – бо CPU був завантажений і Garbage Collectors, і читанням з диску
Створення Alerts
Ще можна глянути рекомендації від AWS – Recommended CloudWatch alarms for Amazon OpenSearch Service.
OpenSearch ClusterStatus Yellow та OpenSearch ClusterStatus Red: тут просто якщо більше ніж 0:
... - alert: OpenSearch ClusterStatus Yellow expr: sum(aws_es_cluster_status_yellow_maximum) by (domain_name, node_id) > 0 for: 1s labels: severity: warning component: backend environment: prod annotations: summary: 'OpenSearch ClusterStatus Yellow status detected' description: |- The primary shards for all indexes are allocated to nodes in the cluster, but replica shards for at least one index are not *OpenSearch Doman*: `{{ "{{" }} $labels.domain_name }}` grafana_opensearch_overview_url: 'https://{{ .Values.monitoring.root_url }}/d/b2d2dabd-a6b4-4a8a-b795-270b3e200a2e/aws-opensearch-cluster-cloudwatch' - alert: OpenSearch ClusterStatus Red expr: sum(aws_es_cluster_status_red_maximum) by (domain_name, node_id) > 0 for: 1s labels: severity: critical component: backend environment: prod annotations: summary: 'OpenSearch ClusterStatus RED status detected!' description: |- The primary and replica shards for at least one index are not allocated to nodes in the cluster *OpenSearch Doman*: `{{ "{{" }} $labels.domain_name }}` grafana_opensearch_overview_url: 'https://{{ .Values.monitoring.root_url }}/d/b2d2dabd-a6b4-4a8a-b795-270b3e200a2e/aws-opensearch-cluster-cloudwatch' ...
Через labels
у нас реалізований роутинг алертів в Opsgenie до потрібних каналів Slack, а анотація grafana_opensearch_overview_url
використовуються для додавання лінки на Grafana в повідомленні в Slack:
- alert: OpenSearch CPUHigh expr: sum(aws_es_cpuutilization_average) by (domain_name, node_id) > 20 for: 10m ...
OpenSearch Data Node down – якщо нода впала:
- alert: OpenSearch Data Node down expr: sum(aws_es_nodes_maximum) by (domain_name) < 3 for: 1s labels: severity: critical ...
aws_es_free_storage_space_maximum
– нам поки сенсу нема.
... - alert: OpenSearch Blocking Write expr: sum(aws_es_cluster_index_writes_blocked_maximum) by (domain_name) >= 1 for: 1s labels: severity: critical ...
Ну і решта алертів, які я поки що додав:
... - alert: OpenSearch AutomatedSnapshotFailure expr: sum(aws_es_automated_snapshot_failure_maximum) by (domain_name) >= 1 for: 1s labels: severity: critical ... - alert: OpenSearch 5xx Errors expr: sum(aws_es_5xx_maximum) by (domain_name) >= 1 for: 1s labels: severity: critical ... - alert: OpenSearch IopsThrottled expr: sum(aws_es_iops_throttle_maximum) by (domain_name) >= 1 for: 1s labels: severity: warning ... - alert: OpenSearch ThroughputThrottled expr: sum(aws_es_throughput_throttle_maximum) by (domain_name) >= 1 for: 1s labels: severity: warning ... - alert: OpenSearch SysMemoryUtilization High Warning expr: avg(aws_es_sys_memory_utilization_average) by (domain_name) >= 95 for: 5m labels: severity: warning ... - alert: OpenSearch PrimaryWriteRejected High expr: sum(aws_es_primary_write_rejected_maximum) by (domain_name) >= 1 for: 1s labels: severity: critical ... - alert: OpenSearch KNNGraphQueryErrors High expr: sum(aws_es_knngraph_query_errors_maximum) by (domain_name) >= 1 for: 1s labels: severity: critical ... - alert: OpenSearch KNNCacheCapacityReached expr: sum(aws_es_knngraph_query_errors_maximum) by (domain_name) >= 1 for: 1s labels: severity: warning ...
По ходу використання подивимось, що ще можна додати.