Производительность: профилирование, тюнинг

Рейтинг: 70.1% · 9 голосов
Исчерпывающий курс по Tarantool 3.x: модель данных, движки memtx и vinyl, Lua и файберы, транзакции и MVCC, SQL, конфигурация (box.cfg и декларативная 3.x), репликация и Raft, шардирование vshard, эксплуатация, безопасность. 47 уроков со схемами.
Ответить
Аватара пользователя
denis_tnt
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

Производительность: профилирование, тюнинг

Сообщение denis_tnt »

Оглавление курса (47)
  1. Что такое Tarantool: in-memory СУБД и сервер приложений
  2. Архитектура изнутри: процесс, потоки, event-loop
  3. Установка и первый запуск: tt CLI, пакеты, Docker
  4. Интерактив: консоль, admin-консоль, первые команды
  5. Спейсы и кортежи: форматы, типы данных
  6. Типы индексов и их применимость
  7. Движки хранения: memtx vs vinyl
  8. DDL: схема, создание спейсов и индексов, миграции
  9. DML и выборки: insert/update/upsert, итераторы
  10. Персистентность: WAL, снапшоты, recovery
  11. Внутренности memtx: аллокаторы slab/arena, память
  12. Внутренности vinyl: LSM, компакция, тюнинг
  13. Lua и LuaJIT в Tarantool: box, модули, rocks
  14. Файберы: кооперативная многозадачность, каналы
  15. Транзакции: ACID, изоляция, MVCC
  16. Хранимые процедуры, модули, организация приложения
  17. net.box: удалённые вызовы, async
  18. Пулы соединений, балансировка, реконнект
  19. Ошибки и диагностика: box.error, pcall
  20. Типы и сериализация: MsgPack, decimal, datetime, uuid
  21. SQL в Tarantool: возможности и связь с box
  22. SQL: таблицы, JOIN, подзапросы, представления
  23. SQL: подготовленные выражения, транзакции, Lua-интероп
  24. Классическая конфигурация box.cfg (legacy 1.x/2.x)
  25. Декларативная конфигурация 3.x: config.yaml, иерархия
  26. Роли и приложения в 3.x
  27. Централизованная конфигурация: etcd / config storage
  28. tt CLI глубоко: разработка, сборка, запуск
  29. Cartridge (официальный legacy) и миграция на 3.x
  30. Репликация: replicaset, топологии
  31. Механика репликации: WAL-стриминг, vclock
  32. Синхронная репликация и выборы лидера (Raft)
  33. Жизненный цикл узла: bootstrap, join, rejoin
  34. vshard: router/storage, виртуальные бакеты
  35. Решардинг и rebalancing бакетов
  36. Запросы поверх шардов: map-reduce, crud
  37. Мониторинг: метрики, Prometheus, Grafana
  38. Логирование и аудит
  39. Бэкапы и восстановление
  40. Безопасность: аутентификация, RBAC, TLS
  41. Производительность: профилирование, тюнинг (вы здесь)
  42. Обновления: схема, rolling upgrade
  43. Деплой в продакшен: Docker, топология (официальные паттерны)
  44. Администрирование через официальный TCM (Tarantool Cluster Manager)
  45. Коннекторы: Python, Go, Java
  46. Ключевые модули (rocks): crud, metrics, queue, expirationd
  47. Capstone: шардированный отказоустойчивый кластер
Обзор

Tarantool устроен так, что весь пользовательский код и обработка запросов крутятся в одном потоке - TX (transaction) - на кооперативных файберах. Это убирает блокировки и гонки, но имеет цену: любой кусок кода, который долго не отдаёт управление (yield), задерживает всех остальных. Поэтому профилирование в Tarantool - это в первую очередь поиск ответа на вопрос "кто украл время у TX-потока": Lua-логика, garbage collector LuaJIT, диск (WAL), сеть (iproto) или фрагментация памяти. Инструменты делятся на три слоя: дешёвая интроспекция (fiber.info, fiber.top, box.stat, box.slab.info), Lua/JIT-профайлеры (sysprof, memprof, jit.dump/jit.p) и системные профайлеры (perf, gperftools, pstack/gdb).
Главное правило: сначала локализуй узкое место дешёвыми средствами (где горит CPU, диск или память), и только потом доставай тяжёлый профайлер. Иначе соберёшь гигабайт сэмплов не там.
Механика: где именно теряется время

TX-поток и кооперативность. Один TX-поток исполняет цикл событий (event loop, ev_run на базе libev). Внутри одной итерации выполняются готовые файберы, по очереди, пока каждый сам не вызовет yield (явно через fiber.yield/fiber.sleep или неявно при обращении к диску, сети, при box-операции с репликацией). Пока файбер считает в чистом Lua без yield - event loop стоит. Отсюда первый класс узких мест: CPU-bound Lua без точек переключения.

Как считается CPU файбера. Учёт времени включается лениво. Когда вызван fiber.top_enable(), при каждом переключении контекста (функция clock_set_on_csw в ядре) берётся дельта аппаратного счётчика (на x86 - TSC) и прибавляется и к статистике всего cord (TX-потока), и к статистике уходящего файбера. То есть метрика instant/average - это доля времени потока, потраченная файбером между yield-ами. Важное следствие: учёт привязан к context switch, поэтому файбер, который не делает yield, не "тикает" корректно, а свежесозданный файбер какое-то время показывает nan/inf, пока не прожил полную итерацию. Дополнительно считается cpu_misses - сколько раз TX-поток заметил, что ОС переместила его на другое ядро (повод закрепить процесс через taskset/cpu affinity).

Диск: WAL. Каждая запись (insert/replace/update/delete) после применения в памяти попадает в WAL-поток, который делает fsync согласно wal_mode (write - без fsync на каждую транзакцию, fsync - с fsync, none - без WAL). Запись синхронная для файбера: он засыпает до подтверждения. Если диск медленный, в лог сыплется too long WAL write, и это второй класс узких мест. Порог регулируется too_long_threshold (по умолчанию 0.5 c). Учтите: этот таймер меряет не только сам fsync, но и cbus-планирование и итерацию event loop, поэтому "too long WAL" иногда сигналит на самом деле о перегруженном CPU.

LuaJIT GC и трассы. LuaJIT компилирует горячие циклы в машинные трассы (после hotloop итераций, по умолчанию 56). Инкрементальный mark-and-sweep GC при большом объёме мусора может съедать десятки процентов CPU (в perf это видно как lj_gc_step / gc_onestep). Конструкции, которые JIT не умеет компилировать (NYI - Not Yet Implemented, например pairs/next в некоторых случаях, string.format с рядом спецификаторов), вызывают abort трассы и падение в интерпретатор - третий класс узких мест.

Сеть: iproto. Бинарный протокол обслуживается отдельными network-потоками, которые перекладывают сообщения в TX через cbus. Лимит net_msg_max (по умолчанию 768) ограничивает число одновременных запросов в обработке, защищая TX от перегруза.

Изображение
Узкие места TX-потока и инструменты профилирования

Ключевые команды и код

fiber.top - кто ест CPU прямо сейчас.

Код: Выделить всё

local fiber = require('fiber')
fiber.top_enable()        -- включает учёт времени (есть накладные расходы)
-- подождать пару секунд под нагрузкой
fiber.top()
-- cpu:
--   instant  average  time
--   45.2%    44.8%    12.30   (имя файбера)
fiber.top_disable()       -- выключить, чтобы убрать оверхед
Колонки: instant - доля за последнюю итерацию, average - экспоненциальное скользящее среднее, time - суммарные секунды CPU за время, пока top включён.

fiber.info - стеки и переключения. Поле csw (context switches) показывает, как часто файбер уступает управление; backtrace - в каких C-функциях он сейчас. Маленький csw при большом CPU - признак не-yield-ящего цикла.

Код: Выделить всё

require('fiber').info()
box.stat и box.slab.info - запросы и память.

Код: Выделить всё

box.stat()              -- rps по REPLACE/SELECT/CALL и т.д.
box.stat.net()          -- сетевой трафик, число соединений
box.slab.info()         -- arena_used_ratio, quota_used_ratio, items_used_ratio
Если arena_used_ratio близок к 1 - скоро упрётесь в memtx_memory. Большой разрыв между items_used и arena_used - фрагментация.

Профайлер Lua (sysprof) - семплирующий, видит и Lua, и C-стек.

Код: Выделить всё

misc.sysprof.start({mode = 'C', interval = 10, path = 'sysprof.bin'})
-- ... нагрузка ...
misc.sysprof.stop()
-- разбор в флеймграф:
-- tarantool -e 'require("sysprof")(arg)' - sysprof.bin > tmp
-- perl flamegraph.pl tmp > sysprof.svg
Режимы: D (только счётчики vmstate), L (верхний фрейм), C (полный callchain).

Профайлер памяти (memprof) - давит ли код на GC.

Код: Выделить всё

collectgarbage()                       -- собрать старый мусор
misc.memprof.start('memprof.bin')
-- ... нагрузка ...
misc.memprof.stop()
-- tarantool -e 'require("memprof")(arg)' - memprof.bin
Отчёт показывает аллокации по строкам Lua: ищите строки с десятками тысяч событий.

Частые заблуждения и грабли
  • "fiber.top бесплатен" - нет. Учёт читает таймер на каждом переключении, поэтому держите его включённым только на окно профилирования.
  • "nan/inf в fiber.top - баг" - нет, это нормально сразу после top_enable и для свежих файберов: статистика ещё не накоплена за полную итерацию.
  • "too long WAL = медленный диск" - не всегда. Таймер включает CPU-фазу event loop; перегруженный TX-поток даёт тот же варнинг.
  • "memprof ловит любую память" - только Lua-аллокации. malloc в C, тупл-арена memtx он не видит (для них - valgrind, box.slab.info, perf).
  • "JIT всегда ускоряет" - конструкции из списка NYI обрывают трассу; jit.off() перед memprof рекомендуется, иначе аллокации с трасс маркируются как INTERNAL.
  • "Tarantool отдаёт память после delete" - почти никогда. Память возвращается ОС только после рестарта; внутри арена переиспользуется.
  • "pstack/gdb безопасны на проде" - они замораживают процесс примерно на секунду на каждый вызов; на хайлоаде это удар. perf и gperftools имеют почти нулевой оверхед.
Мини-лаба

Воспроизведите CPU-bound файбер и найдите его через fiber.top. В консоли Tarantool:

Код: Выделить всё

local fiber = require('fiber')
fiber.top_enable()
fiber.create(function()
  fiber.name('hog')
  local x = 0
  for i = 1, 2e9 do x = x + i % 7 end   -- считает без yield
end)
-- сразу же, в этой же консоли:
fiber.top()
Задание: посмотрите, какой instant/CPU показывает файбер hog и каков его csw в fiber.info() (он будет почти нулевым). Затем добавьте внутрь цикла if i % 1e6 == 0 then fiber.yield() end, перезапустите и сравните, как изменились csw и отзывчивость консоли. Объясните разницу через кооперативную модель.

Контрольные вопросы
  • Почему файбер, выполняющий тяжёлый цикл без yield, делает форум неотзывчивым, хотя CPU вроде бы свободен на других ядрах? Что в архитектуре TX-потока это объясняет?
  • В какой момент Tarantool обновляет CPU-метрику файбера и почему свежесозданный файбер может показывать nan в fiber.top?
  • Чем sysprof отличается от memprof по тому, что они измеряют, и какой из них вы возьмёте при подозрении на нагрузку от GC LuaJIT?
  • Варнинг "too long WAL write" не всегда означает медленный диск - назовите хотя бы одну другую причину и инструмент, которым вы это проверите.
👍4 ❤️2 🔥1 😄 🤔
Ответить
← Предыдущая глава
Безопасность: аутентификация, RBAC, TLS
Следующая глава →
Обновления: схема, rolling upgrade

Все главы курса «Tarantool: in-memory СУБД и сервер приложений с нуля до продакшена»

Поделиться темой: ✈ Telegram VK

Вернуться в «Tarantool: СУБД и сервер приложений»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 2 гостя