Project

General

Profile

Actions

Task #6129

closed

Task #6116: EPIC: Speed up the stats updater daemon (scripts/workflow/update_stats.sh)

Индексы под горячий путь демона: stat_slices вообще без индексов

Added by Redmine Admin about 7 hours ago. Updated about 7 hours ago.

Status:
Resolved
Priority:
Urgent
Assignee:
-
Start date:
08/26/2026
Due date:
% Done:

100%

Estimated time:

Description

Реализовано в ветке speedup/stats-daemon, коммит 4816752b, миграция V135__20260826__add_stats_daemon_indexes.sql. Версия поднята 1.6.5 → 1.6.6.

Проблема

На stat_slices не было ни одного индекса, кроме первичного ключа: таблица создана в V98, а общий проход по индексам был в V89, то есть раньше. При этом это самая большая таблица в схеме — строка на компанию на метрику на день, за год.

Это делало #6118 опасным, а не полезным. Пропуск уже скачанных интервалов добавил запрос

SELECT * FROM stat_slices
WHERE metric_name = ? AND date_start >= ? AND date_end <= ?
ORDER BY date_start

на каждую пару компания-метрика. Без индекса каждый такой запрос — seq scan по всей таблице, и выигрыш от снятых обращений к iiko съедался бы базой. Тот же запрос обслуживает чтение статистики из API, так что он горячий и вне демона.

Что добавлено

  • stat_slices (metric_name, date_start) — равенство по metric_name, диапазон и сортировка по date_start, ровно форма запроса выше. date_end третьей колонкой не нужен: по второму диапазону btree искать не умеет, останется фильтром.
  • companies (user_id) — колонка появилась в V102, после индексного прохода V89, и осталась без индекса. Используется в findAllByUserOrIdIn.
  • iiko_requests (created_at) — индексов не было вовсе, а росла таблица быстрее всех. Нужен для вычистки старых строк.
  • iiko_auth_tokens (is_active, created_at DESC) — под findAllByisActiveOrderByCreatedAtDesc.

Предел btree и защита от него

metric_name — склейка uuid всех компаний партнёра. У btree потолок 2704 байта на кортеж. Замерено на живом postgres, реальные uuid:

компаний длина btree
70 2621 ок
80 2991 index row size 3016 exceeds btree version 4 maximum 2704

Предел — около 72 кофеен на партнёра. Если такое имя уже есть, CREATE INDEX падает вместе со всей миграцией и стартом приложения.

Поэтому миграция смотрит на фактическое max(length(metric_name)) и при превышении порога берёт hash по metric_name плюс btree по date_start. Хуже (равенство без диапазона, дальше BitmapAnd), но лучше seq scan, и деплой не падает.

Порог 2000, а не 2660 — с запасом: проверка видит только сегодняшние имена, а партнёр может обрасти кофейнями завтра, и тогда сломается уже не миграция, а INSERT на проде.

Проверено

  • миграция применена флайвеем к живому postgres в прогоне тестов, без ошибок;
  • все четыре индекса присутствуют в pg_indexes с ожидаемыми определениями;
  • ветка ELSE (hash + btree) проверена отдельно на пробной таблице;
  • прогон: 559 тестов, 66 падений — те же, что и до изменений (#6128).

Чего проверить нельзя: поведение планировщика на боевых объёмах. В тестовой базе в stat_slices 98 строк, там EXPLAIN всё равно выберет seq scan.

При выкатке

Обычный CREATE INDEX берёт ACCESS EXCLUSIVE на время построения — на большом stat_slices это пауза на запись. Если простой недопустим, создайте индексы заранее через CREATE INDEX CONCURRENTLY: все CREATE идут с IF NOT EXISTS, миграция тогда станет пустой.

Отдельно: на сервере рядом с jar лежит VERSION, из которого скрипты берут номер. В репозитории его нет — при выкатке 1.6.6 обновить руками.


Related issues 1 (0 open1 closed)

Related to Task #6118: Fetch only the missing date tail instead of re-downloading 365 days every roundResolved08/26/2026

Actions
Actions #1

Updated by Redmine Admin about 7 hours ago

  • Status changed from New to Resolved
  • % Done changed from 0 to 100
Actions #2

Updated by Redmine Admin about 7 hours ago

  • Related to Task #6118: Fetch only the missing date tail instead of re-downloading 365 days every round added
Actions

Also available in: Atom PDF