Сбор статистики через триггеры #16

Closed
opened 2026-05-02 16:17:14 +03:00 by aleksey · 2 comments
Owner

Этап 4. Сбор статистики через триггеры (пункт 5 задачи)

Цель: реализовать лёгкий сбор агрегированной статистики без затратных запросов к большим таблицам.

Шаги:

  1. Создание модуля stats_collector (может быть gen_event или отдельный gen_server).
    • При старте подписаться на события: mnesia:subscribe({table, event, simple}), аналогично для booking, review.
    • Реализовать обработчик событий, который при получении {write, Table, New, Old, ...} обновляет счётчики в ETS-таблице stats_ets:
      • Количество созданных событий/бронирований/отзывов за текущий день в разрезе календарей/специалистов.
      • Количество отменённых событий/бронирований.
      • Среднее, максимальное, минимальное количество бронирований на событие и т.п.
    • Периодически (каждые 5 минут) сбрасывать накопленные данные ETS в постоянную таблицу stats (или в лог-файл) и сбрасывать счётчики для следующего интервала.
  2. Интеграция в infra_mnesia:start/0:
    • Запускать stats_collector после инициализации Mnesia.
  3. Оптимизация:
    • Обработка событий должна быть быстрой и не блокировать Mnesia; можно использовать легковесный процесс, только обновляющий счетчики в ETS.
    • Не подписываться на архивные таблицы, так как они не изменяются в реальном времени.
  4. Тестирование:
    • Создать/изменить несколько записей, проверить обновление счётчиков в ETS и корректный сброс в постоянное хранилище.

Результат: работает система сбора статистики, не влияющая на производительность основных операций.

## Этап 4. Сбор статистики через триггеры (пункт 5 задачи) **Цель**: реализовать лёгкий сбор агрегированной статистики без затратных запросов к большим таблицам. **Шаги**: 1. **Создание модуля `stats_collector`** (может быть gen_event или отдельный gen_server). - При старте подписаться на события: `mnesia:subscribe({table, event, simple})`, аналогично для `booking`, `review`. - Реализовать обработчик событий, который при получении `{write, Table, New, Old, ...}` обновляет счётчики в ETS-таблице `stats_ets`: - Количество созданных событий/бронирований/отзывов за текущий день в разрезе календарей/специалистов. - Количество отменённых событий/бронирований. - Среднее, максимальное, минимальное количество бронирований на событие и т.п. - Периодически (каждые 5 минут) сбрасывать накопленные данные ETS в постоянную таблицу `stats` (или в лог-файл) и сбрасывать счётчики для следующего интервала. 2. **Интеграция в `infra_mnesia:start/0`**: - Запускать `stats_collector` после инициализации Mnesia. 3. **Оптимизация**: - Обработка событий должна быть быстрой и не блокировать Mnesia; можно использовать легковесный процесс, только обновляющий счетчики в ETS. - Не подписываться на архивные таблицы, так как они не изменяются в реальном времени. 4. **Тестирование**: - Создать/изменить несколько записей, проверить обновление счётчиков в ETS и корректный сброс в постоянное хранилище. **Результат**: работает система сбора статистики, не влияющая на производительность основных операций.
aleksey added the Future label 2026-05-02 16:17:14 +03:00
Author
Owner

План выполнения Задачи #16 – Сбор статистики через триггеры

1. Анализ и цели

После выполнения задач #12–#15 база данных EventHub стабильна.
Необходимо добавить систему сбора агрегированной статистики без нагрузки на основную БД.

Цели:

  • Создать лёгкий модуль stats_collector, подписанный на изменения таблиц Mnesia (event, booking, review).
  • При каждом изменении обновлять счётчики в ETS-таблице stats_ets.
  • Каждые 5 минут сбрасывать накопленные данные в постоянную таблицу Mnesia stats (или лог) и обнулять счётчики.
  • Интегрировать запуск в infra_mnesia и дерево супервизора.
  • Обеспечить минимальное влияние на производительность (асинхронная обработка, только ETS-операции).

2. Общая архитектура

Компонент Назначение
stats_collector gen_server, подписан на события таблиц Mnesia. Обновляет ETS-счётчики.
stats_ets ETS-таблица типа set с ключом {MetricType, EntityId}. Текущие значения за интервал.
stats Постоянная таблица Mnesia для агрегированной статистики (опционально).

3. Детальный план реализации

Шаг 3.1. Модуль stats_collector

Файл: src/infra/stats_collector.erl

Тип: gen_server

Состояние: #{} (пустая map, все данные в ETS).

API:

  • start_link() -> {ok, Pid}
  • get_stats(Metric) -> [{Key, Value}] (для отладки/API)

Логика:

  1. При инициализации:

    • Создать ETS-таблицу stats_ets (если не существует).
    • Подписаться: mnesia:subscribe({table, event, simple}), аналогично для booking, review.
    • Установить таймер на 5 минут для сброса.
  2. Обработка событий (handle_info):

    • При {write, Table, New, Old, _ActivityId}:
      • Определить метрику (например, event_created, booking_cancelled).
      • Вызвать ets:update_counter для соответствующего ключа.
    • При {flush}:
      • Прочитать все счётчики из stats_ets.
      • Записать в таблицу Mnesia stats (или лог).
      • Сбросить счётчики (удалить ключи или установить в 0).
      • Установить следующий таймер.

Список метрик (ключи ETS):

  • {events_created, calendar_id} – количество новых событий.
  • {events_cancelled, calendar_id} – количество отменённых событий.
  • {bookings_created, calendar_id} – количество новых бронирований.
  • {bookings_confirmed, calendar_id} – количество подтверждённых.
  • {bookings_cancelled, calendar_id} – количество отменённых.
  • {reviews_created, calendar_id} – количество новых отзывов.
  • {reviews_avg_rating, calendar_id} – сумма рейтингов и количество.

Шаг 3.2. Интеграция в infra_mnesia.erl

В функции handle_call(init_tables, ...) после create_indices() добавить:

ok = start_stats_collector(),

И реализовать:

start_stats_collector() ->
    {ok, _} = stats_collector:start_link(),
    ok.

Шаг 3.3. Обновление infra_sup.erl

Добавить в список детей супервизора:

#{id       => stats_collector,
  start    => {stats_collector, start_link, []},
  restart  => permanent,
  shutdown => 5000,
  type     => worker,
  modules  => [stats_collector]}

Шаг 3.4. Оптимизация

  • Обработчик handle_info выполняет только ets:update_counter или ets:insert.
  • Не подписываться на архивные таблицы (они не изменяются в реальном времени).
  • Периодический сброс – асинхронно через erlang:send_after.

4. Тестирование

  1. Создать/изменить записи event, booking, review.
  2. Проверить счётчики через stats_collector:get_stats(...).
  3. Дождаться интервала сброса (или вызвать принудительный flush).
  4. Убедиться, что данные появились в таблице Mnesia stats или в логе.
  5. Проверить обнуление счётчиков после сброса.

5. Документация

Добавить в README.md раздел «Сбор статистики» с описанием:

  • Какие метрики собираются.
  • Как получить доступ к статистике (API или прямой запрос к ETS).
  • Как настроить интервал сброса.
# План выполнения Задачи #16 – Сбор статистики через триггеры ## 1. Анализ и цели После выполнения задач #12–#15 база данных EventHub стабильна. Необходимо добавить систему сбора агрегированной статистики **без нагрузки на основную БД**. **Цели:** - Создать лёгкий модуль `stats_collector`, подписанный на изменения таблиц Mnesia (`event`, `booking`, `review`). - При каждом изменении обновлять счётчики в ETS-таблице `stats_ets`. - Каждые 5 минут сбрасывать накопленные данные в постоянную таблицу Mnesia `stats` (или лог) и обнулять счётчики. - Интегрировать запуск в `infra_mnesia` и дерево супервизора. - Обеспечить минимальное влияние на производительность (асинхронная обработка, только ETS-операции). ## 2. Общая архитектура | Компонент | Назначение | |-----------|------------| | `stats_collector` | `gen_server`, подписан на события таблиц Mnesia. Обновляет ETS-счётчики. | | `stats_ets` | ETS-таблица типа `set` с ключом `{MetricType, EntityId}`. Текущие значения за интервал. | | `stats` | Постоянная таблица Mnesia для агрегированной статистики (опционально). | ## 3. Детальный план реализации ### Шаг 3.1. Модуль `stats_collector` **Файл:** `src/infra/stats_collector.erl` **Тип:** `gen_server` **Состояние:** `#{}` (пустая map, все данные в ETS). **API:** - `start_link() -> {ok, Pid}` - `get_stats(Metric) -> [{Key, Value}]` (для отладки/API) **Логика:** 1. При инициализации: - Создать ETS-таблицу `stats_ets` (если не существует). - Подписаться: `mnesia:subscribe({table, event, simple})`, аналогично для `booking`, `review`. - Установить таймер на 5 минут для сброса. 2. Обработка событий (`handle_info`): - При `{write, Table, New, Old, _ActivityId}`: - Определить метрику (например, `event_created`, `booking_cancelled`). - Вызвать `ets:update_counter` для соответствующего ключа. - При `{flush}`: - Прочитать все счётчики из `stats_ets`. - Записать в таблицу Mnesia `stats` (или лог). - Сбросить счётчики (удалить ключи или установить в 0). - Установить следующий таймер. **Список метрик (ключи ETS):** - `{events_created, calendar_id}` – количество новых событий. - `{events_cancelled, calendar_id}` – количество отменённых событий. - `{bookings_created, calendar_id}` – количество новых бронирований. - `{bookings_confirmed, calendar_id}` – количество подтверждённых. - `{bookings_cancelled, calendar_id}` – количество отменённых. - `{reviews_created, calendar_id}` – количество новых отзывов. - `{reviews_avg_rating, calendar_id}` – сумма рейтингов и количество. ### Шаг 3.2. Интеграция в `infra_mnesia.erl` В функции `handle_call(init_tables, ...)` после `create_indices()` добавить: ok = start_stats_collector(), И реализовать: start_stats_collector() -> {ok, _} = stats_collector:start_link(), ok. ### Шаг 3.3. Обновление `infra_sup.erl` Добавить в список детей супервизора: #{id => stats_collector, start => {stats_collector, start_link, []}, restart => permanent, shutdown => 5000, type => worker, modules => [stats_collector]} ### Шаг 3.4. Оптимизация - Обработчик `handle_info` выполняет только `ets:update_counter` или `ets:insert`. - Не подписываться на архивные таблицы (они не изменяются в реальном времени). - Периодический сброс – асинхронно через `erlang:send_after`. ## 4. Тестирование 1. Создать/изменить записи `event`, `booking`, `review`. 2. Проверить счётчики через `stats_collector:get_stats(...)`. 3. Дождаться интервала сброса (или вызвать принудительный `flush`). 4. Убедиться, что данные появились в таблице Mnesia `stats` или в логе. 5. Проверить обнуление счётчиков после сброса. ## 5. Документация Добавить в `README.md` раздел «Сбор статистики» с описанием: - Какие метрики собираются. - Как получить доступ к статистике (API или прямой запрос к ETS). - Как настроить интервал сброса.
Author
Owner

Тестирование модуля stats_collector (задача #16)

Шаг 1. Подготовка

Запустите приложение make shell и загрузите определения записей:

rr("include/records.hrl").

Шаг 2. Создание тестовых записей

Календарь

CId1 = <<"cal1">>.
mnesia:dirty_write(#calendar{id = CId1, owner_id = <<"u1">>, title = <<"Test">>, type = personal, status = active, created_at = calendar:universal_time(), updated_at = calendar:universal_time()}).

Событие (создаём и отменяем)

EvId1 = <<"ev1">>.
mnesia:dirty_write(#event{id = EvId1, calendar_id = CId1, start_time = calendar:universal_time(), duration = 60, event_type = single, title = <<"E1">>, status = active, created_at = calendar:universal_time(), updated_at = calendar:universal_time()}).
[E1] = mnesia:dirty_read({event, EvId1}),
mnesia:dirty_write(E1#event{status = cancelled}).

Бронирование

BId = <<"b1">>.
mnesia:dirty_write(#booking{id = BId, event_id = EvId1, user_id = <<"u1">>, status = confirmed, created_at = calendar:universal_time(), updated_at = calendar:universal_time()}).

Отзыв

RevId = <<"rev1">>.
mnesia:dirty_write(#review{id = RevId, user_id = <<"u1">>, target_type = event, target_id = EvId1, rating = 4, comment = <<"Good">>, status = visible, created_at = calendar:universal_time(), updated_at = calendar:universal_time()}).

Шаг 3. Проверка счетчиков в ETS

ets:tab2list(stats_ets).

Ожидаемые ключи: {events_created, CId1}, {events_cancelled, CId1}, {bookings_created, CId1}, {bookings_confirmed, CId1}, {reviews_created, CId1} и т.д.

Шаг 4. Принудительный сброс статистики

stats_collector ! flush.

Шаг 5. Проверка сохранённой статистики в таблице stats

mnesia:dirty_match_object(#stats{_ = '_'}).

Убедитесь, что записи появились, а ETS-таблица очищена:

ets:tab2list(stats_ets).  % должно быть []
# Тестирование модуля stats_collector (задача #16) ## Шаг 1. Подготовка Запустите приложение `make shell` и загрузите определения записей: rr("include/records.hrl"). ## Шаг 2. Создание тестовых записей ### Календарь CId1 = <<"cal1">>. mnesia:dirty_write(#calendar{id = CId1, owner_id = <<"u1">>, title = <<"Test">>, type = personal, status = active, created_at = calendar:universal_time(), updated_at = calendar:universal_time()}). ### Событие (создаём и отменяем) EvId1 = <<"ev1">>. mnesia:dirty_write(#event{id = EvId1, calendar_id = CId1, start_time = calendar:universal_time(), duration = 60, event_type = single, title = <<"E1">>, status = active, created_at = calendar:universal_time(), updated_at = calendar:universal_time()}). [E1] = mnesia:dirty_read({event, EvId1}), mnesia:dirty_write(E1#event{status = cancelled}). ### Бронирование BId = <<"b1">>. mnesia:dirty_write(#booking{id = BId, event_id = EvId1, user_id = <<"u1">>, status = confirmed, created_at = calendar:universal_time(), updated_at = calendar:universal_time()}). ### Отзыв RevId = <<"rev1">>. mnesia:dirty_write(#review{id = RevId, user_id = <<"u1">>, target_type = event, target_id = EvId1, rating = 4, comment = <<"Good">>, status = visible, created_at = calendar:universal_time(), updated_at = calendar:universal_time()}). ## Шаг 3. Проверка счетчиков в ETS ets:tab2list(stats_ets). Ожидаемые ключи: `{events_created, CId1}`, `{events_cancelled, CId1}`, `{bookings_created, CId1}`, `{bookings_confirmed, CId1}`, `{reviews_created, CId1}` и т.д. ## Шаг 4. Принудительный сброс статистики stats_collector ! flush. ## Шаг 5. Проверка сохранённой статистики в таблице stats mnesia:dirty_match_object(#stats{_ = '_'}). Убедитесь, что записи появились, а ETS-таблица очищена: ets:tab2list(stats_ets). % должно быть []
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Reference: EventHub/EventHubBack#16