Tarantool CE/EE Documentation portal logo
Помощь
Обновлена 15 сентября 2026 г. в 08:55

Руководство по устранению проблем

Проблема: при выполнении INSERT/UPDATE-запросов возникает ошибка ER_MEMORY_ISSUE

Возможные причины

  • Недостаток оперативной памяти (значения параметров 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 в режиме горячего резервирования.

Проблема: Tarantool создает высокую нагрузку на CPU

Возможные причины

Поток обработки транзакций нагружает 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, используйте шардинг базы данных.

Проблема: обработка запросов прекращается по времени ожидания

Возможные причины

  1. Быстрые и медленные запросы обрабатываются в рамках одного соединения, поэтому буфер упреждающего чтения (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 МБ.

    • Разделите обработку быстрых и медленных запросов по разным соединениям на уровне бизнес-логики.

  2. Медленные диски.

    Решение Проверьте загрузку дисков (с помощью утилит iostat, iotop или strace обращая внимание на параметр iowait) и разместите .xlog-файлы и снимки состояния на разных дисках, задав разные значения для параметров wal_dir и memtx_dir.

Проблема: параметры репликации lag и idle принимают отрицательные значения

Это параметры box.info.replication.(upstream.)lag и box.info.replication.(upstream.)idle из таблицы replication.

Возможные причины

Часы на машинах не синхронизированы или NTP-сервер работает неправильно.

Решение

Проверьте настройки NTP-сервера.

Если проблем с NTP-сервером нет, ничего предпринимать не нужно. Задержка (lag) репликации вычисляется по системным часам двух машин. Если они рассинхронизированы, часы удалённого мастера могут постоянно отставать от часов локального экземпляра Tarantool.

Проблема: значение параметра idle растет, но в журнале нет связанных сообщений

Это параметр 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>').

Решение

Возможны два способа решения:

После этого перезапустите репликацию, как описано в разделе Перезапуск репликации.

Проблема: Tarantool работает заметно медленнее обычного

Возможные причины

Память занята большим количеством неиспользуемых объектов.

Решение

Запустите сборщик мусора в 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

Описание проблемы

Переключение файберов запрещено в метаметоде __gc, начиная с этого изменения, во избежание неожиданной нехватки памяти в Lua. Однако может потребоваться функция с передачей управления (yield) для закрытия ресурсов, например, сокета.

Ниже приведены примеры правильной реализации такой процедуры.

Решение

Примеры, которые иллюстрируют логику решения:

Все пояснения приведены в комментариях в листинге кода. -- > обозначает вывод в консоль.

Пример 1

Реализация корректного завершающего обработчика для 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)

Пример 2

Реализация корректного завершающего обработчика для пользовательского типа (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)

Пример 3

Важно отметить, что реализации завершающих обработчиков в примерах выше создают дополнительную нагрузку на систему из-за создания нового файбера при каждом вызове __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 do    local task    while true do      task = worker_next_task      if task then break end      -- XXX: Заставить файбер ждать выполнения задачи.      worker_cv:wait()    end    worker_next_task = task.next    task.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 then    worker_next_task = task  else    worker_last_task.next = task  end  worker_last_task = task  worker_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)