Аналіз TSDB у Prometheus 2

Аналіз TSDB у Prometheus 2

База даних тимчасових рядів (TSDB, time series database) у Prometheus 2 – це відмінний приклад інженерного рішення, яке пропонує серйозні покращення порівняно зі сховищем v2 у Prometheus 1 у плані швидкості накопичення даних та виконання запитів, ефективності використання ресурсів. Ми впроваджували Prometheus 2 у Percona Monitoring and Management (PMM), і я мав можливість розібратися з продуктивністю Prometheus 2 TSDB. У цій статті я розповім про результати цих спостережень.

Середнє робоче навантаження Prometheus

Для тих, хто звик мати справу з базами даних основного призначення, звичайне робоче навантаження Prometheus досить цікаве. Швидкість накопичення даних прагне стабільної величини: зазвичай сервіси, які ви моніторите, посилають приблизно однакову кількість метрик, і інфраструктура змінюється відносно повільно.
Запити інформації можуть надходити з різних джерел. Деякі з них, наприклад алерти, також прагнуть стабільної та передбачуваної величини. Інші, такі як запити користувача, можуть викликати сплески, хоча, це не характерно для більшої частини навантаження.

тест навантаження

Під час тестування я сконцентрувався на можливості накопичувати дані. Я розгорнув Prometheus 2.3.2, скомпільований за допомогою Go 1.10.1 (як частина PMM 1.14) на сервісі Linode, використовуючи цей скрипт: StackScript. Для максимально реалістичного генерування навантаження, за допомогою цього StackScript я запустив кілька MySQL-нод із реальним навантаженням (Sysbench TPC-C Test), кожна з яких емулювала 10 нод Linux/MySQL.
Всі нижченаведені тести проводилися на сервері Linode з вісьмома віртуальними ядрами та 32 Гбайт пам'яті, на якому запущено 20 навантажувальних симуляцій моніторингу двохсот інстансів MySQL. Або, в термінах Prometheus, 800 таргетів (targets), 440 зборів (scrapes) за секунду, 380 тисяч записів (samples) за секунду та 1,7 млн ​​активних часових рядів.

Дизайн

Звичайний підхід традиційних баз даних, у тому числі той, що використовував Prometheus 1.x, полягає в ліміть пам'яті. Якщо його недостатньо, щоб витримати навантаження, ви зіткнетеся з великими затримками, і якісь запити не будуть виконані. Використання пам'яті в Prometheus 2 конфігурується через ключ storage.tsdb.min-block-duration, який визначає, як довго записи зберігатимуться у пам'яті перед скиданням на диск (за замовчуванням це 2 години). Кількість необхідної пам'яті залежатиме від кількості часових рядів, ярликів (labels) та інтенсивності збору даних (scrapes) у сумі з чистим потоком. У плані дискового простору Prometheus прагне використовувати по 3 байти на запис (sample). З іншого боку, вимоги до пам'яті значно вищі.

Незважаючи на те, що є можливість конфігурувати розмір блоку, не рекомендується налаштовувати його вручну, тому ви поставлені перед необхідністю дати Prometheus стільки пам'яті скільки він попросить для вашого навантаження.
Якщо пам'яті буде недостатньо, щоб підтримувати вхідний потік метрик, Prometheus впаде з out of memory або до нього дістанеться OOM killer.
Додати swap, щоб відтягнути момент падіння, коли у Prometheus закінчується пам'ять, не особливо допомагає, тому що використання цієї функції викликає вибухове споживання пам'яті. Я думаю, що справа в Go, його garbage collector і в тому, як він працює зі swap.
Іншим цікавим підходом виглядає налаштування скидання head block на диск у певний час замість того, щоб відраховувати його з часу старту процесу.

Аналіз TSDB у Prometheus 2

Як ви можете бачити з графіка, скидання на диск відбуваються кожні дві години. Якщо ви зміните параметр min-block-duration на одну годину, то ці скидання відбуватимуться щогодини, починаючи через півгодини.
Якщо ви хочете використовувати цей та інші графіки у вашій інсталяції Prometheus, можете використовувати цей дашборд. Він був розроблений для PMM, але, з невеликими змінами, підходить до будь-якої інсталяції Prometheus.
У нас є активний блок, який називається head block, який зберігається в пам'яті; блоки з більш старими даними доступні через mmap(). Це прибирає необхідність конфігурувати кеш окремо, але також означає, що вам потрібно залишати достатньо місця для кеша операційної системи, якщо ви хочете робити запити до даних старших за ті, які вміщує head block.
А ще це означає, що споживання Prometheus віртуальної пам'яті буде досить високим, про що не варто турбуватися.

Аналіз TSDB у Prometheus 2

Ще один цікавий момент дизайну – використання WAL (write ahead log). Як видно з документації зі сховища, Prometheus використовує WAL для уникнення втрат під час падіння. Конкретні механізми гарантії живучості даних, на жаль, недостатньо документовані. Версія Prometheus 2.3.2 скидає WAL на диск кожні 10 секунд і цей параметр не конфігурується користувачем.

Ущільнення (Compactions)

Prometheus TSDB спроектована за образом LSM-сховища (Log Structured merge — журнально-структуроване дерево зі злиттям): head block періодично скидається на диск, в той же час механізм ущільнення об'єднує кілька блоків разом для уникнення сканування занадто великої кількості блоків при запитах. Тут бачимо кількість блоків, які я спостерігав на тестовій системі після доби навантаження.

Аналіз TSDB у Prometheus 2

Якщо ви хочете дізнатися більше про сховище, ви можете вивчити файл meta.json, в якому є інформація про наявні блоки та про те, як вони з'явилися.

{
       "ulid": "01CPZDPD1D9R019JS87TPV5MPE",
       "minTime": 1536472800000,
       "maxTime": 1536494400000,
       "stats": {
               "numSamples": 8292128378,
               "numSeries": 1673622,
               "numChunks": 69528220
       },
       "compaction": {
               "level": 2,
               "sources": [
                       "01CPYRY9MS465Y5ETM3SXFBV7X",
                       "01CPYZT0WRJ1JB1P0DP80VY5KJ",
                       "01CPZ6NR4Q3PDP3E57HEH760XS"
               ],
               "parents": [
                       {
                               "ulid": "01CPYRY9MS465Y5ETM3SXFBV7X",
                               "minTime": 1536472800000,
                               "maxTime": 1536480000000
                       },
                       {
                               "ulid": "01CPYZT0WRJ1JB1P0DP80VY5KJ",
                               "minTime": 1536480000000,
                               "maxTime": 1536487200000
                       },
                       {
                               "ulid": "01CPZ6NR4Q3PDP3E57HEH760XS",
                               "minTime": 1536487200000,
                               "maxTime": 1536494400000
                       }
               ]
       },
       "version": 1
}

Ущільнення в Prometheus прив'язані до часу скидання head block на диск. У цей момент може проводитись кілька таких операцій.

Аналіз TSDB у Prometheus 2

Зважаючи на все, ущільнення ніяк не обмежені і можуть викликати великі стрибки дискового I/O під час виконання.

Аналіз TSDB у Prometheus 2

Скачки завантаження CPU

Аналіз TSDB у Prometheus 2

Зрозуміло, це досить негативно впливає на швидкість роботи системи, а також є серйозним викликом для LSM-сховищ: як робити ущільнення для підтримки високої швидкості запитів і при цьому не викликати надто сильної верхівки?
Використання пам'яті у процесі ущільнень теж має досить цікавий вигляд.

Аналіз TSDB у Prometheus 2

Ми можемо бачити, як після ущільнення більшість пам'яті змінює стан з Cached на Free: отже, потенційно цінна інформація була звідти прибрана. Цікаво, чи використовується тут fadvice() чи якась ще техніка мінімізації, чи це викликано тим, що кеш було звільнено від блоків, знищених при ущільненні?

Відновлення після збою

Відновлення після збоїв займає час і це обгрунтовано. Для вхідного потоку в мільйон записів в секунду мені довелося чекати близько 25 хвилин, поки проводилося відновлення з урахуванням SSD-диска.

level=info ts=2018-09-13T13:38:14.09650965Z caller=main.go:222 msg="Starting Prometheus" version="(version=2.3.2, branch=v2.3.2, revision=71af5e29e815795e9dd14742ee7725682fa14b7b)"
level=info ts=2018-09-13T13:38:14.096599879Z caller=main.go:223 build_context="(go=go1.10.1, user=Jenkins, date=20180725-08:58:13OURCE)"
level=info ts=2018-09-13T13:38:14.096624109Z caller=main.go:224 host_details="(Linux 4.15.0-32-generic #35-Ubuntu SMP Fri Aug 10 17:58:07 UTC 2018 x86_64 1bee9e9b78cf (none))"
level=info ts=2018-09-13T13:38:14.096641396Z caller=main.go:225 fd_limits="(soft=1048576, hard=1048576)"
level=info ts=2018-09-13T13:38:14.097715256Z caller=web.go:415 component=web msg="Start listening for connections" address=:9090
level=info ts=2018-09-13T13:38:14.097400393Z caller=main.go:533 msg="Starting TSDB ..."
level=info ts=2018-09-13T13:38:14.098718401Z caller=repair.go:39 component=tsdb msg="found healthy block" mint=1536530400000 maxt=1536537600000 ulid=01CQ0FW3ME8Q5W2AN5F9CB7R0R
level=info ts=2018-09-13T13:38:14.100315658Z caller=web.go:467 component=web msg="router prefix" prefix=/prometheus
level=info ts=2018-09-13T13:38:14.101793727Z caller=repair.go:39 component=tsdb msg="found healthy block" mint=1536732000000 maxt=1536753600000 ulid=01CQ78486TNX5QZTBF049PQHSM
level=info ts=2018-09-13T13:38:14.102267346Z caller=repair.go:39 component=tsdb msg="found healthy block" mint=1536537600000 maxt=1536732000000 ulid=01CQ78DE7HSQK0C0F5AZ46YGF0
level=info ts=2018-09-13T13:38:14.102660295Z caller=repair.go:39 component=tsdb msg="found healthy block" mint=1536775200000 maxt=1536782400000 ulid=01CQ7SAT4RM21Y0PT5GNSS146Q
level=info ts=2018-09-13T13:38:14.103075885Z caller=repair.go:39 component=tsdb msg="found healthy block" mint=1536753600000 maxt=1536775200000 ulid=01CQ7SV8WJ3C2W5S3RTAHC2GHB
level=error ts=2018-09-13T14:05:18.208469169Z caller=wal.go:275 component=tsdb msg="WAL corruption detected; truncating" err="unexpected CRC32 checksum d0465484, want 0" file=/opt/prometheus/data/.prom2-data/wal/007357 pos=15504363
level=info ts=2018-09-13T14:05:19.471459777Z caller=main.go:543 msg="TSDB started"
level=info ts=2018-09-13T14:05:19.471604598Z caller=main.go:603 msg="Loading configuration file" filename=/etc/prometheus.yml
level=info ts=2018-09-13T14:05:19.499156711Z caller=main.go:629 msg="Completed loading of configuration file" filename=/etc/prometheus.yml
level=info ts=2018-09-13T14:05:19.499228186Z caller=main.go:502 msg="Server is ready to receive web requests."

Основна проблема процесу відновлення – високе споживання пам'яті. Незважаючи на те, що в нормальній ситуації сервер може стабільно працювати з таким самим обсягом пам'яті, при падінні він може не піднятися через OOM. Єдине рішення, яке я знайшов, це відключити збір даних, підняти сервер, дозволити йому відновитися та перезавантажити вже з увімкненим збором.

розігрів

Ще одна поведінка, яку слід пам'ятати в ході розігріву - співвідношення низької продуктивності і високого споживання ресурсів прямо після старту. У ході деяких, але не всіх стартів я спостерігав серйозне навантаження по CPU та пам'яті.

Аналіз TSDB у Prometheus 2

Аналіз TSDB у Prometheus 2

Провали у використанні пам'яті говорять про те, що Prometheus не може зі старту налаштувати всі збори, і якась інформація виявляється втраченою.
Я не з'ясував точні причини високого навантаження на процесор та пам'ять. Підозрюю, що це пов'язано зі створенням нових часових рядів у head block з високою частотою.

Стрибки навантаження на CPU

Крім ущільнень, що створюють досить високе навантаження по I/O, я помітив серйозні стрибки навантаження на процесор кожні дві хвилини. Сплески довше при високому вхідному потоці і схоже, що вони викликані збирачем сміття Go, принаймні деякі ядра повністю завантажені.

Аналіз TSDB у Prometheus 2

Аналіз TSDB у Prometheus 2

Ці стрибки не такі вже й несуттєві. Схоже, що коли вони виникають, то внутрішня точка входу і метрики Prometheus стають недоступними, що викликає провали в даних в ці ж проміжки часу.

Аналіз TSDB у Prometheus 2

Також можна побачити, що експортер Prometheus затикається на одну секунду.

Аналіз TSDB у Prometheus 2

Ми можемо помітити кореляції із прибиранням сміття (GC).

Аналіз TSDB у Prometheus 2

Висновок

TSDB в Prometheus 2 діє швидко, здатна справлятися з мільйонами часових рядів і водночас із тисячами записів, що здійснюються за секунду, використовуючи досить скромне залізо. Утилізація CPU та дискового I/O теж вражає. Мій приклад показував до 200 000 метриків на секунду на одне використане ядро.

Для планування розширення треба пам'ятати про достатні обсяги пам'яті, і це має бути реальна пам'ять. Обсяг пам'яті, який я спостерігав, становив близько 5 Гбайт на 100 000 записів в секунду вхідного потоку, що давало в сумі з кешем операційної системи близько 8 Гбайт зайнятої пам'яті.

Зрозуміло, ще чекає чимало роботи з приборкання сплесків CPU та дискового I/O, і це не дивно, враховуючи, наскільки ще молода TSDB Prometheus 2 у порівнянні з InnoDB, TokuDB, RocksDB, WiredTiger, але всі вони мали схожі проблеми на початку життєвого циклу.

Джерело: habr.com

Додати коментар або відгук