Postgres: обрабатывать каждую запись растущей таблицы ровно один раз - PullRequest
1 голос
/ 03 марта 2020

У нас есть растущая база данных событий, каждое из которых имеет первичный ключ uuid. Только INSERTs делается на этой event таблице. Ни одна запись не обновляется и не удаляется.

Эта таблица является частью большой системы и не может быть изменена. Однако мне разрешено добавлять к нему индексы.

create table event (
  id uuid primary key,
  type varchar(50),
  payload jsonb
);

insert into event (uuid, type, payload) values ($1, $2, $3);

События с указанными c types должны агрегироваться постепенно. В прошлом я мог просто сделать большой материализованный взгляд. Но теперь объем такой, что я больше не могу этого делать, и мне придется обрабатывать события одно за другим. Фактически было бы желательно получить значение, близкое к реальному времени.

События, которые необходимо обработать, должны обрабатываться только один раз, в противном случае моя агрегация не работает.

Я рассмотрел следующие варианты:

  1. добавить триггер в мою таблицу event и скопировать события для обработки в другую таблицу, пока они не будут фактически обработаны. Я не хочу этого делать, потому что ошибка в триггере может помешать вставке в таблицу event. Мы не можем проиграть ни одного события.
  2. полагаться на временную метку / серийный номер, чтобы упорядочить записи. Все эти попытки приводили к пропущенным записям из-за других задержек в системе (например, сбои в сети).

Один простой способ достичь sh этой цели «обрабатывать ровно один раз» - хранить UUID каждого обработанного события в таблицу. Затем я мог бы запросить таблицу, чтобы проверить, было ли обработано определенное событие.

create table processed_event (
  id uuid primary key
);

Но тогда это происходит: у меня есть таблица с uuids и индекс с такими же uuids. По сути, я только что продублировал свои данные. И на моем старом снимке (после полного вакуума) требуется значительный объем дискового пространства, около 2 ГБ.

                    objectname                    | objecttype |   entries   |  size   
--------------------------------------------------+------------+-------------+---------
 processed_event                                  | r          | 2.49663e+07 | 1054 MB
 processed_event_pkey                             | i          | 2.49662e+07 | 751 MB

Так что я не уверен, как будет масштабироваться это решение. На данный момент, с производственной базой данных, это будет> 5 ГБ данных только для хранения обработанных событий.

Вопросы:

  1. Есть ли еще какие-либо Как я могу убедиться, что я не обрабатываю дважды одну и ту же запись?
  2. Есть ли шанс, что я смогу избавиться от таблицы processed_event и сохранить только индекс processed_event_pkey?
  3. Любое предложение ?

Решение

Полезны ответы Лоренца и Джеррада, приведенные ниже. Жаль, что я могу проверить только один ...

Вероятно, в итоге мы получим триггер для таблицы event, который вставляет новые идентификаторы событий в таблицу processed_event.

create table processed_event (
  id uuid primary key,
  processed_at timestamp
)

Пока событие не обработано, отметка времени будет нулевой.

Это позволит:

  • быстрый выбор необработанных событий
  • отладка / мониторинг ход обработки
  • повторная обработка событий, если агрегаты изменяются после начальной обработки. Просто установите processed_at на ноль.

Ответы [ 3 ]

1 голос
/ 03 марта 2020

Почему бы вам не добавить новую таблицу

CREATE TABLE materialized_event_count (
   type varchar(50) PRIMARY KEY,
   count bigint NOT NULL
);

(при условии, что интересующий вас агрегат является числом на type)

Затем вы можете добавить триггер если

  • вставляет запись в materialized_event_count или добавляет ее для соответствующего type всякий раз, когда вставленная строка

  • вычитает одну из соответствующей записи, если строка удалена (удалить строку, если счет достигает 0)

  • выполняет оба вышеуказанных действия для обновления, которое меняет type

  • усекает materialized_event_count всякий раз, когда event усекается

1 голос
/ 03 марта 2020

Я думаю, что вы на правильном пути со своим processed_event столом. Вы можете либо отслеживать, когда вы обрабатываете событие (в этом случае ваша таблица будет расти с той же скоростью, что и ваша таблица event), либо вы можете отслеживать, какие события вам еще нужно обработать (и вы можете удалить строки, как они обрабатываются). Лично мне нравится иметь записи о том, что произошло и когда это произошло, а дисковое пространство дешевое, поэтому я выбрал бы go с первым вариантом. Мне нравится идея вставлять строку в processed_event всякий раз, когда строка вставляется в event, а затем обновлять поле date_processed, когда событие действительно обрабатывается. Таким образом, вы можете легко найти список событий, которые еще нужно обработать, выбрав строки, в которых date_processed равно нулю (и если кто-то хочет узнать, когда событие было обработано, вы можете сообщить им).

Если у вас есть доступ к процессу, который вставляет записи в таблицу event, вы можете изменить его, добавив также запись в таблицу processed_event. В противном случае вы могли бы подружиться со спусковым крючком. Вы можете написать триггер так, чтобы исключения обрабатывались, и в случае ошибки строка все еще вставлялась в таблицу. Вот пример: SQLFiddle .

Другой вариант - заполнить таблицу processed_event следующим образом:

insert into processed_event(id)
select id
from event
where id not in (select id from processed_event)

Вы можете запустить ее по расписанию в зависимости от о том, как в режиме реального времени вам нужно обрабатывать событие.

0 голосов
/ 03 марта 2020

Можете ли вы добавить новый атрибут «обработано» в таблицу «событие» и использовать его в качестве указания на то, что запись была обработана (используйте индекс для этого столбца)?

Затем, основываясь на обработанном атрибуте, вы можете перемещать записи в новую таблицу event_backup для увеличения скорости системы, потому что с ростом числа строк система будет работать медленнее и медленнее.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...