Task #6116
openEPIC: Speed up the stats updater daemon (scripts/workflow/update_stats.sh)
79%
Description
Source: sdlc/0-vibes/raw/2026-08-26/slow_ daemon.md — the infinite bash loop in scripts/workflow/update_stats.sh that chains 10 CLI commands, each a separate JVM launch, forever.
Where the time actually goes¶
The round is dominated by IIKO OLAP traffic, and the traffic volume is a product of four independent multipliers, each of which is fixable on its own:
| Multiplier | Where | Factor |
|---|---|---|
| 3 HTTP calls per logical request (auth → olap → logout) | IikoOlapV2RequestsFetchingService.fetchOlapData:35,56 |
×3 |
| one OLAP request per day instead of one per range | IikoStatsGatheringService.getMetricValuesFromIikoDateDesc:117 |
×365 |
| full 365-day window re-downloaded every round, nothing checks what is already stored | same, plus all three metric commands | ×∞ (repeats forever) |
| strictly serial across companies / partners / users |
InitialStatsGatheringCommand, PartnersMetricsGatheringCommand, UsersMetricsGatheringCommand
|
×N |
Order of magnitude for iiko-stats-data-sync alone with N companies:
6 metrics × (1 network + N companies) × 365 days × 3 HTTP calls. At N=200 that is ~1.3M HTTP round-trips per round, all serialised, against an IIKO server that licenses a small number of concurrent API sessions.
On top of that, each of those requests writes its full request+response JSON to iiko_requests twice (INSERT then UPDATE) and printlns the whole payload to nohup.out.
Subtasks¶
Ordered by payoff. The first three are multiplicative — doing all three is what turns a multi-hour round into a short one.
The "IIKO is single-threaded" symptom is mostly self-inflicted: the code opens and closes an IIKO API session for every single request, so the session limit is hit constantly by our own login/logout churn rather than by concurrent work.
Updated by Redmine Admin about 7 hours ago
Ветка speedup/stats-daemon, два коммита: 50a6ebef (реализация) и ce3a1a2b (тесты).
Статус подзадач¶
| Задача | Статус |
|---|---|
| #6117 переиспользование сессии iiko | Resolved |
| #6118 не перекачивать то, что уже в базе | Resolved |
| #6120 releaseToken читал тело дважды | Resolved |
| #6121 сырые запросы в iiko_requests по флагу | Resolved |
| #6122 payload'ы и токен в stdout | Resolved |
| #6124 N+1 и unpaged-выборки | Resolved, частично — детали в задаче |
| #6127 диспатч companies-garbage-collect | Resolved |
| #6123 пул потоков | Feedback — механика есть, по умолчанию 1, нужен замер на сервере |
| #6125 весь оборот в одном процессе | Feedback — команда есть, переключение демона за человеком |
| #6119 спайк по группировке OLAP по датам | Открыт — нужен живой iiko |
Заведена #6128: 66 тестов падают всегда из-за отсутствующих локальных файлов с учётными данными, из-за чего полный прогон не работает как сигнал.
Тесты¶
31 юнит-тест, без базы и без iiko. Оба ключевых набора проверены на то, что они ловят регрессию — временный откат исправления их роняет: IikoAuthTokensReleaseTest 3 из 4 на старом releaseToken, IikoStatsGatheringSkipTest 3 из 6 без пропуска сохранённых интервалов.
Полный прогон: 559 тестов, 66 падений — ровно те же 66, что падали до изменений.
Чего проверка не покрывает¶
Ни одного обращения к живому iiko отсюда сделать нельзя. Не проверено на реальном сервере: что iiko действительно принимает один ключ на серию запросов в течение трёх минут, что текст ответа про протухший ключ совпадает с тем, что ищет looksLikeExpiredToken, и что run-stats-round проходит все десять шагов. Первое выкатывание стоит делать под присмотром.
Что это должно дать¶
Три множителя из таблицы в описании снимаются: ×3 на логин/логаут (#6117), ×365 на посуточный обход в установившемся режиме (#6118), плюс убраны запись каждого тела в iiko_requests и печать payload'ов в nohup.out. Четвёртый, ×N по компаниям, снимается только после замера на сервере (#6123). Оставшийся крупный резерв — #6119: если iiko умеет отдавать год одним запросом с группировкой по дате, посуточный цикл исчезает и на полной перекачке тоже.