Сообщество / Базы данных

Датчик прислал шесть строк дважды

M
mike_pg
Junior
Сообщения: 27
Репутация: 9
Дата регистрации:
15.09.2026
АВТОР ТЕМЫ04 нояб. 2026 г., 17:00 (GMT+3)
Цепочка простая: датчик температуры раз в минуту шлёт POST на webhook в n8n, n8n делает INSERT в таблицу readings (id serial, sensor_id, ts, value). Вчера вечером n8n перезапускался, датчик не дождался ответа и, как положено по прошивке, отправил свой буфер заново. Сервис ожил, всё зелёное. Сегодня смотрю:

SELECT sensor_id, ts, count(*)
FROM readings
WHERE ts > now() - interval '1 day'
GROUP BY 1, 2 HAVING count(*) > 1;

sensor_id | ts | count
-----------+------------------------+-------
3 | 2026-11-03 21:14:00+06 | 2
3 | 2026-11-03 21:15:00+06 | 2
...
(6 rows)


Шесть показаний, двенадцать строк. Значения совпадают до последней цифры, отличаются только id. Видимо, первую попытку n8n всё-таки успел записать, но ответ до датчика не дошёл.

Страшного пока ничего: средняя за час съехала чуть-чуть. Но если n8n начнёт падать чаще, график станет врать уже заметно.

Варианты вижу такие: UNIQUE (sensor_id, ts) и INSERT ... ON CONFLICT DO NOTHING; отдельный message_id от датчика, если прошивка его отдаёт; или проверка в самом n8n перед записью. Смущает первый: два честных показания в одну секунду он молча выкинет. Где обычно ловят такие повторы — в базе или до неё?
0
D
dima_radio
Junior
Сообщения: 11
Репутация: 4
Дата регистрации:
26.10.2026
05 нояб. 2026 г., 07:00 (GMT+3)

mike_pg писал:
Смущает первый: два честных показания в одну секунду он молча выкинет.


В радиоклубе в каждой радиограмме есть позывной и порядковый номер. Номер 41 пришёл дважды — это повтор. Пришёл 42 с тем же временем — новое сообщение. Часы у датчика могут поехать, а счётчик в буфере только растёт. Поэтому UNIQUE я бы ставил на (sensor_id, seq), а не на ts. Главное — увеличивать номер. Иначе будешь, как я, с умным видом принимать одну и ту же радиограмму.
0
S
serg_mail
Junior
Сообщения: 26
Репутация: 14
Дата регистрации:
17.09.2026
05 нояб. 2026 г., 09:02 (GMT+3)
У почты эта болезнь известна давно. В логе postfix она выглядит так:

status=deferred (conversation with mx.example.net timed out while sending end of data -- message may be sent more than once)


Письмо ушло, точку после DATA принимающая сторона получила, а «250 OK» до отправителя не дошёл. Отправитель честно повторяет попытку, и получатель видит два одинаковых письма. Отличить их можно только по Message-ID. Время для этого не годится: два разных письма в одну секунду — обычное дело.

dima_radio писал:
Главное — увеличивать номер.


Согласен, но есть оговорка. Номер должен расти и после перезагрузки самого датчика. Если счётчик живёт в RAM, то после пропадания питания он снова начнёт с единицы. Тогда (sensor_id, seq) молча отбросит уже новые показания как повтор старых, и по смыслу это та же ошибка, от которой уходили с ts. На это есть два обычных решения: хранить счётчик во flash или добавить в ключ номер загрузки, то есть (sensor_id, boot_id, seq). Второй вариант проще, если прошивка уже считает перезагрузки.

Если прошивку трогать нельзя, я бы оставил ON CONFLICT DO NOTHING по (sensor_id, ts, value). Совпадение всех трёх полей у двух честных показаний маловероятно. Отброшенные дубли стоит хотя бы считать, чтобы видеть, как часто срабатывает повтор.
0
L
lev_archive
Junior
Сообщения: 16
Репутация: 3
Дата регистрации:
24.10.2026
05 нояб. 2026 г., 10:09 (GMT+3)

serg_mail писал:
Если прошивку трогать нельзя, я бы оставил ON CONFLICT DO NOTHING по (sensor_id, ts, value).


Меня в этом варианте смущает слово NOTHING. Поясню на своём архиве.

Одну фотографию бабушки у колодца я когда-то отсканировал дважды. Файлы получились одинаковые до байта, и второй я спокойно удалил. Через год выяснилось, что у удалённой копии в имени файла стояла дата с обратной стороны карточки: «авг. 1961». В уцелевшей копии её не было. Сама картинка не пострадала, а подпись пропала. Сравнивал я то, что видно, и считал, что остальное тоже совпадает.

С показаниями выходит похоже. По трём полям строки одинаковые, но сам факт повторной доставки тоже кое-что сообщает. По нему видно, когда n8n падал и сколько раз датчик досылал буфер. DO NOTHING выбрасывает эту запись без следа.

Я бы оставлял след. Например, добавить столбец dup_count и вместо DO NOTHING писать ON CONFLICT (...) DO UPDATE SET dup_count = readings.dup_count + 1. В графике по-прежнему будет одна точка, а в таблице останется отметка, что эту точку присылали дважды. Можно и иначе: складывать отбитые строки в отдельную таблицу с временем приёма. Это чуть больше работы, зато потом есть что сверить.

Для DO UPDATE тоже нужен уникальный индекс по тем же полям, отдельно от него это не работает.
0
L
leo_msk
Junior
Сообщения: 30
Репутация: 15
Дата регистрации:
12.09.2026
05 нояб. 2026 г., 18:00 (GMT+3)
mike_pg, на какой ноде оборвалось то выполнение, которое пометили неудачным? Если Postgres уже отработал, а n8n упал до ответа вебхука, дубль появился между записью и «200 OK». Тогда ответ стоит ставить сразу после INSERT, без хвоста из уведомлений. Окно станет уже, но не исчезнет, так что ключ всё равно нужен. Я однажды полдня ловил такие дубли, а их плодил retry в моей же HTTP-ноде.
0
S
serg_mail
Junior
Сообщения: 26
Репутация: 14
Дата регистрации:
17.09.2026
06 нояб. 2026 г., 09:04 (GMT+3)

leo_msk писал:
Окно станет уже, но не исчезнет


Значит, след нужен. У postfix это строка в логе, здесь dup_count, как у lev_archive. DO NOTHING беру назад.
0
L
lev_archive
Junior
Сообщения: 16
Репутация: 3
Дата регистрации:
24.10.2026
06 нояб. 2026 г., 09:08 (GMT+3)
serg_mail, спасибо, что вернулись к этому. Но у счётчика тоже есть слабое место, я его заметил уже после своего поста.

После той истории с бабушкиной карточкой я стал писать на конвертах карандашом «скан. 2 р.». Прошлой весной разбирал коробку и нашёл конверт с такой пометкой. Что сканировал дважды, понятно. А вот когда и в какой из двух заходов, уже не скажешь. Тогда я сканировал летом на старом сканере и зимой на новом, и от этого зависело, какой файл лучше. Цифра «2» сохранилась, а то, ради чего я её ставил, нет.

dup_count похож на мою пометку. Он покажет, что точку 21:14 прислали дважды. Но mike_pg хотел видеть, когда падал n8n, а это время второго приёма, не время показания. Если в строку добавить received_at и обновлять его в том же DO UPDATE, останется хотя бы последнее повторное поступление. Если важна вся история досылок, лучше отдельная таблица: туда пишется каждая отбитая строка со своим временем приёма. Это то, что я вскользь упоминал в прошлый раз, только теперь понятно зачем.

Для одного датчика раз в минуту, наверное, хватит и received_at. Решать mike_pg, ему этот график потом читать.
0