Руководство по устранению проблем
Возможные причины
-
Недостаток оперативной памяти (значения параметров
arena_used_ratioиquota_used_ratioв отчете slab_info приближаются к 100%). Чтобы проверить эти параметры, выполните команды:$ # attaching to a Tarantool instance$ tt connect <instance_name|URI>-- requesting arena_used_ratio valuetarantool> box.slab.info().arena_used_ratio-- requesting quota_used_ratio valuetarantool> box.slab.info().quota_used_ratio
Решение
Варианты действий:
-
Увеличьте значение параметра box.cfg{memtx_memory} в файле экземпляра Tarantool (при наличии доступных ресурсов памяти). В версиях Tarantool до 1.10 изменение параметра требует перезагрузки сервера. При обычной перезагрузке сервер недоступен, пока Tarantool загружается из
.xlog-файлов. Перезагрузка в режиме горячего резервирования обеспечивает практически 100% доступность. -
Очистите базу данных.
-
Проверьте индикаторы фрагментации памяти:
-- requesting quota_used_ratio valuetarantool> box.slab.info().quota_used_ratio-- requesting items_used_ratio valuetarantool> box.slab.info().items_used_ratioПри сильной фрагментации памяти (значение параметра
quota_used_ratioприближается к 100%,items_used_ratioоколо 50%) рекомендуется перезапустить Tarantool в режиме горячего резервирования.
Возможные причины
Поток обработки транзакций нагружает CPU более чем на 60%.
Решение
Подключитесь к экземпляру Tarantool с помощью утилиты tt, проанализируйте статистику запросов с помощью функции box.stat() и определите основной потребитель CPU. Для этого выполните команды:
$ # attaching to a Tarantool instance$ tt connect <instance_name|URI>
-- запрашиваем RPS для вызовов хранимых процедурtarantool> box.stat().CALL.rps
Критическое значение RPS (запросов в секунду) - 75 000, для больших Lua-приложений (более 200 строк кода) - 10 000 - 20 000.
-- значение RPS для запросов указанного типаtarantool> box.stat().<query_type>.rps
Критическое значение RPS для запросов SELECT/INSERT/UPDATE/DELETE - 100 000.
Если основная нагрузка создается SELECT-запросами, добавьте реплику и перенаправьте часть запросов на нее.
Если нагрузка в основном создается запросами INSERT/UPDATE/DELETE, используйте шардинг базы данных.
Возможные причины
-
Быстрые и медленные запросы обрабатываются в рамках одного соединения, поэтому буфер упреждающего чтения (readahead) переполняется медленными запросами.
Решение
Варианты решения:
-
Увеличьте размер буфера упреждающего чтения (параметр box.cfg{readahead}).
Этот параметр можно изменять без перезапуска Tarantool. Подключитесь к экземпляру Tarantool с помощью утилиты tt и вызовите функцию
box.cfg{}с новым значениемreadahead:$ # attaching to a Tarantool instance$ tt connect <instance_name|URI>-- установка нового значения readaheadtarantool> box.cfg{readahead = 10 * 1024 * 1024}Пример расчета: при 1000 RPS, размере одного запроса в 1 КБ и максимальном времени обработки одного запроса 10 секунд минимальный размер readahead-буфера - 10 МБ.
-
Разделите обработку быстрых и медленных запросов по разным соединениям на уровне бизнес-логики.
-
-
Медленные диски.
Решение Проверьте загрузку дисков (с помощью утилит iostat, iotop или strace обращая внимание на параметр
iowait) и разместите .xlog-файлы и снимки состояния на разных дисках, задав разные значения для параметров wal_dir и memtx_dir.
Это параметры box.info.replication.(upstream.)lag и box.info.replication.(upstream.)idle из таблицы
replication.
Возможные причины
Часы на машинах не синхронизированы или NTP-сервер работает неправильно.
Решение
Проверьте настройки NTP-сервера.
Если проблем с NTP-сервером нет, ничего предпринимать не нужно. Задержка (lag) репликации вычисляется по системным часам двух машин. Если они рассинхронизированы, часы удалённого мастера могут постоянно отставать от часов локального экземпляра Tarantool.
Это параметр box.info.replication.(upstream.)idle из таблицы
replication.
Возможные причины
Серверу назначено несколько IP-адресов или один и тот же сервер был указан в box.cfg{} дважды, что создает дублирующее
подключение.
Решение
Обновите Tarantool с версии 1.6 до 1.7, в которой
эта ошибка была исправлена. В этом случае репликация остановится, а в журнале появится ошибка:
'Incorrect value for option ''replication_source'': duplicate connection with the same replica UUID'.
В кластере с одноим мастером и несколькими репликами значения общих параметров из таблицы
replication
(например, box.info.replication.lsn) должны передаваться с мастера и быть одинаковыми на всех репликах. Если они различаются,
это указывает на проблему.
Возможные причины
Сбой репликации.
Решение
Параметр
box.info.replication(.upstream).status
выставлен в stopped.
Возможные причины
В кластере c двумя мастер-серверами один из серверов попытался выполнить действие, уже выполненное другим - например, повторно
вставить кортеж с таким же уникальным ключом (распознается по ошибке вида
'Duplicate key exists in unique index 'primary' in space <space_name>').
Решение
Возможны два способа решения:
- Ручной: пересоздайте данные одного мастера на основе другого, удалив журналы упреждающей записи и снимки состояния.
- Программно: настройте триггер разрешения конфликтов.
После этого перезапустите репликацию, как описано в разделе Перезапуск репликации.
Возможные причины
Память занята большим количеством неиспользуемых объектов.
Решение
Запустите сборщик мусора в Lua с помощью функции collectgarbage(count) и измерьте время ее выполнения с помощью clock.bench() или clock.proc().
Пример кода для подсчета потребляемой памяти:
$ # attaching to a Tarantool instance$ tt connect <instance_name|URI>
-- загрузка модуля clock для работы со временемtarantool> local clock = require 'clock'-- запуск таймераtarantool> local b = clock.proc()-- запуск сборки мусораtarantool> local c = collectgarbage('count')-- остановка таймера по завершении сборки мусораtarantool> return c, clock.proc() - b
- Если
clock.proc()возвращает значение больше 0.001, это может указывать на неэффективное использование памяти. Активное вмешательство не требуется, но рекомендуется оптимизация кода. - Если значение превышает 0.01, необходимо провести подробный анализ кода и оптимизировать потребление памяти.
- Если значение больше 0,01, код приложения однозначно необходимо проанализировать на предмет оптимизации использования памяти.
Переключение файберов запрещено в метаметоде __gc, начиная с
этого изменения, во избежание неожиданной нехватки
памяти в Lua. Однако может потребоваться функция с передачей управления (yield) для закрытия ресурсов, например, сокета.
Ниже приведены примеры правильной реализации такой процедуры.
Примеры, которые иллюстрируют логику решения:
Все пояснения приведены в комментариях в листинге кода. -- > обозначает вывод в консоль.
Реализация корректного завершающего обработчика для FFI-типа (custom_t).
local ffi = require('ffi')local fiber = require('fiber')ffi.cdef('struct custom { int a; };')local function __custom_gc(self)print(("Entered custom GC finalizer for %s... (before yield)"):format(self.a))fiber.yield()print(("Leaving custom GC finalizer for %s... (after yield)"):format(self.a))endlocal custom_t = ffi.metatype('struct custom', {__gc = function(self)-- XXX: Не вызывайте функции с переключением (yielding) в метаметоде __gc.-- Создайте новый файбер для выполнения после выхода из этой процедуры.fiber.new(__custom_gc, self)print(("Finalization is scheduled for %s..."):format(self.a))end})-- Создайте объект cdata типа <custom_t>.local c = custom_t(42)-- Удалите единственную ссылку на объект, чтобы он попал под сборку мусора.c = nil-- Запустите полный цикл GC, чтобы удалить объект без ссылок.collectgarbage('collect')-- > Finalization is scheduled for 42...-- XXX: Закрытие ресурсов не выполняется, пока текущий файбер не уступит управление.-- Запустите этот процесс сейчас.fiber.yield()-- > Entered custom GC finalizer for 42... (before yield)-- > Leaving custom GC finalizer for 42... (after yield)
Реализация корректного завершающего обработчика для пользовательского типа (struct custom).
custom.c
#include <lauxlib.h>#include <lua.h>#include <module.h>#include <stdio.h>struct custom {int a;};const char *CUSTOM_MTNAME = "CUSTOM_MTNAME";/** XXX: Не вызывайте функции с переключением (yielding) в метаметоде __gc.* Создайте новый файбер для выполнения после выхода из этой процедуры.* К сожалению, нельзя передать параметры в функцию, выполняемую созданным* файбером, через <fiber_new_ex>.* Поэтому используется обходной путь: загружается код на Lua (см. ниже),* который создаёт метаметод __gc и передаёт объект для финализации через* стек Lua в порождённый файбер.*/const char *gc_wrapper_constructor = " local fiber = require('fiber') "" print('constructor is initialized') "" return function(__custom_gc) "" print('constructor is called') "" return function(self) "" print('__gc is called') "" fiber.new(__custom_gc, self) "" print('Finalization is scheduled') "" end "" end ";int custom_gc(lua_State *L) {struct custom *self = luaL_checkudata(L, 1, CUSTOM_MTNAME);printf("Entered custom_gc for %d... (before yield)\n", self->a);fiber_sleep(0);printf("Leaving custom_gc for %d... (after yield)\n", self->a);return 0;}int custom_new(lua_State *L) {struct custom *self = lua_newuserdata(L, sizeof(struct custom));luaL_getmetatable(L, CUSTOM_MTNAME);lua_setmetatable(L, -2);self->a = lua_tonumber(L, 1);return 1;}static const struct luaL_Reg libcustom_methods = {{ "new", custom_new },{ NULL, NULL }};int luaopen_custom(lua_State *L) {int rc;/* Создайте метатаблицу для типа struct custom */luaL_newmetatable(L, CUSTOM_MTNAME);/** Запустите инициализатор конструктора завершающего обработчика GC:* - загрузите модуль fiber как апвейл для конструктора завершающего обработчика GC;* - верните конструктор завершающего обработчика GC на вершину стека Lua.*/rc = luaL_dostring(L, gc_wrapper_constructor);/** Проверьте, инициализирован ли конструктор (т.е. не возникло* ни синтаксической, ни ошибки времени выполнения).*/if (rc != LUA_OK)luaL_error(L, "test module loading failed: constructor init");/** Создайте объект GC для функции <custom_gc>, которая будет вызвана* в рамках завершающего обработчика GC, и поместите его на вершину стека* поверх конструктора, возвращённого ранее.*/lua_pushcfunction(L, custom_gc);/** Запустите конструктор с объектом <custom_gc> в качестве единственного аргумента.* В результате завершающий обработчик GC возвращается на вершину стека Lua.*/rc = lua_pcall(L, 1, 1, 0);/** Проверьте, инициализирован ли конструктор (т.е. не возникло* ни синтаксической, ни ошибки времени выполнения).*/if (rc != LUA_OK)luaL_error(L, "test module loading failed: __gc init");/** Назначьте возвращенную функцию метаметодом __gc для метатаблицы пользовательского типа.*/lua_setfield(L, -2, "__gc");/** Инициализируйте таблицу Lua для пользовательского модуля и заполните ее методами.*/lua_newtable(L);luaL_register(L, NULL, libcustom_methods);return 1;}
custom_c.lua
-- Загрузите Lua-расширение на C.local custom = require('custom')-- > constructor is initialized-- > constructor is called-- Создайте пользовательский объект типа <struct custom>.local c = custom.new(9)-- Удалите единственную ссылку на объект, чтобы он попал под сборку мусора.c = nil-- Запустите полный цикл GC для удаления объекта без ссылок.collectgarbage('collect')-- > __gc is called-- > Finalization is scheduled-- XXX: Закрытие ресурсов не выполняется, пока текущий файбер не уступит-- управление. Выполните этот процесс сейчас.require('fiber').yield()-- > Entered custom_gc for 9... (before yield)-- XXX: Завершающий обработчик уступает управление, теперь мы здесь.print('We are here')-- > We are here-- XXX: Этот файбер завершает выполнение, поэтому уступите управление-- оставшемуся файберу, чтобы завершить отложенное закрытие ресурсов.-- > Leaving custom_gc for 9... (after yield)
Важно отметить, что реализации завершающих обработчиков в примерах выше создают дополнительную нагрузку на систему из-за создания
нового файбера при каждом вызове __gc. Чтобы избежать избыточного создания файберов, лучше использовать один файбер-планировщик
с интерфейсом для отложенного выполнения асинхронных действий.
Для этой цели существует модуль sched.lua (см. листинг ниже). Он входит в состав Tarantool и подключается в пользовательском
коде. Пример использования приведён в файле init.lua ниже.
sched.lua
local fiber = require('fiber')local worker_next_task = nillocal worker_last_tasklocal worker_fiberlocal worker_cv = fiber.cond()-- XXX: модуль не готов к перезагрузке, поэтому worker_fiber-- пересоздается при удалении sched.lua из package.loaded.-- Worker — это одиночный файбер для несрочного отложенного выполнения-- функций. Главная цель — запланировать выполнение функции,-- которая вызовет yield, из контекста, где yield не-- допускается. Например, GC-колбэк FFI-объекта.local function worker_f()while true dolocal taskwhile true dotask = worker_next_taskif task then break end-- XXX: Заставить файбер ждать выполнения задачи.worker_cv:wait()endworker_next_task = task.nexttask.f(task.arg)fiber.yield()endendllocal function worker_safe_f()pcall(worker_f)-- Функция <worker_f> никогда не возвращает-- управление. Если – выполнение дошло сюда, вероятно, этот файбер-- отменён и теперь – не может перейти в спящий режим. Создайте новыйworker_fiber = fiber.new(worker_safe_f)endworker_fiber = fiber.new(worker_safe_f)local function worker_schedule_task(f, arg)local task = { f = f, arg = arg }if not worker_next_task thenworker_next_task = taskelseworker_last_task.next = taskendworker_last_task = taskworker_cv:signal()endreturn {postpone = worker_schedule_task}
init.lua
local ffi = require('ffi')local fiber = require('fiber')local sched = require('sched')local function __custom_gc(self)print(("Entered custom GC finalizer for %s... (before yield)"):format(self.a))fiber.yield()print(("Leaving custom GC finalizer for %s... (after yield)"):format(self.a))endffi.cdef('struct custom { int a; };')local custom_t = ffi.metatype('struct custom', {__gc = function(self)-- XXX: Не вызывайте функции с переключением (yielding) в метаметоде __gc.-- Запланируйте вызов __custom_gc через sched.postpone для выполнения-- после выхода из этой процедуры..sched.postpone(__custom_gc, self)print(("Finalization is scheduled for %s..."):format(self.a))end})-- Создайте несколько объектов <custom_t> для последующего закрытия ресурсов.local t = { }for i = 1, 10 do t[i] = custom_t(i) end-- Запустите полный цикл GC для сборки существующего мусора.-- Ничего не будет выведено, поскольку таблица <t> ещё "жива".collectgarbage('collect')-- Удалите ссылку на таблицу и, следовательно, все ссылки на объекты.t = nil-- Запустите полный цикл GC для сборки таблицы и объектов внутри неё.-- В результате все объекты <custom_t> будут запланированы для закрытия ресурсов,-- но сам завершающий обработчик (т.е. функции __custom_gc) вызван не будет.collectgarbage('collect')-- > Finalization is scheduled for 10...-- > Finalization is scheduled for 9...-- > ...-- > Finalization is scheduled for 2...-- > Finalization is scheduled for 1...-- XXX: закрытия ресурсов не произойдет, пока текущий файбер не уступит управление.-- Выполните этот процесс сейчас.fiber.yield()-- > Entered custom GC finalizer for 10... (before yield)-- XXX: Вы находитесь здесь, потому что файбер-планировщик уступил-- управление этому файберу. Проверьте это.print("We're here now. Let's continue the scheduled finalization.")-- > We're here now. Let's continue the finalization-- Подождите секунду, чтобы позволить планировщику очистить оставшийся мусор.fiber.sleep(1)-- > Leaving custom GC finalizer for 10... (after yield)-- > Entered custom GC finalizer for 9... (before yield)-- > Leaving custom GC finalizer for 9... (after yield)-- > ...-- > Entered custom GC finalizer for 1... (before yield)-- > Leaving custom GC finalizer for 1... (after yield)print("Did we finish? I guess so.")-- > Did we finish? I guess so.-- Остановите экземпляр.os.exit(0)