Postgres, партиции и пропавшие посты

Симптом

В контент фабрику добавили шесть новых веб-парсеров, задеплоили, привязали источники к двум каналам публикации. Каналы настроены, парсеры работают, данные сохраняются, в пайплайн обработки не попадают. Ни одной ошибки в логе сервиса парсинга.

Контекст

Прод, древний сервис автопубликации новостного контента: парсеры складывают посты в таблицу, оттуда они расходятся по подписанным каналам через таблицу-очередь, дальше идут в AI-обработку и публикацию.

Далее выяснилось, что из веб-парсеров не было опубликовано ничего уже девять месяцев, на фоне другой массы источников и небольшого количества связанных с ними каналов никто этого не замечал.

Девять месяцев назад было включено партиционирование для разгрузки postgres в силу больших обьемов данных.

Корневая причина

Пайплайн обработки для результата веб-парсеров запускался, только если после сохранения контента было: if cursor.rowcount > 0. Партиционированние было реализовано через BEFORE INSERT ROW. Итог - при вставке в таблицу результатов парсинга PostgreSQL честно рапортует клиенту INSERT 0 0 - в ту таблицу, которую назвали, действительно ноль строк, cursor.corcount всегда 0, потому что посты сохранились в партицию.

Ошибочное предположение

Ошибочное предположение - количество строк, которое вернул INSERT, говорит о том, появилась ли запись в базе. На самом деле это ответ про конкретную таблицу, а не про базу. Как только под таблицу подложили перенаправление, счётчик стал правдиво отвечать на другой вопрос, чем тот, который ему задавали.

Обнаружение

Алерт на разрыв между притоком и очередью: доля строк parse, у которых нет ни одной строки в postsprocessing, в разрезе источников, у которых есть хотя бы один подписанный канал. При здоровой системе доля близка к нулю, здесь она стала 100% и держалась девять месяцев.

В проекте уже есть health-checker, но он смотрит на очередь целиком, а трафик из другого семейства парсеров её наполнял, поэтому веб-ветка молчала незаметно. Метрика обязана быть в разрезе источника.

Исправление

Разделил вставку и фактическую проверку существования данных для пайплайна.

Проверка

Шесть юнит-тестов пайплайна на фейковом курсоре, у которого rowcount жёстко равен 0 — так ведёт себя реальная таблица под триггером.

Проверка на осмысленность: против кода до фикса падают четыре из шести.

Общий вывод

Возвращаемое значение операции описывает механизм, а не намерение. Как только под механизм подкладывают перенаправление - партиционирование, прокси, шардинг, кэш, - все производные от него сигналы продолжают быть технически правдивыми и перестают отвечать на вопрос вызывающего. Ошибки не будет, будет ноль.

Отсюда практическое правило: при смене механизма хранения или доставки надо пересчитать не только вызовы, но и читателей побочных сигналов - счётчиков строк, кодов возврата, командных тегов, заголовков. Компилятор и тесты их не найдут, потому что типы и значения остаются валидными.

И второе: отсутствие жалоб измеряет число тех, кто смотрит, а не работоспособность. Прежде чем считать молчание сигналом здоровья, надо убедиться, что у пути есть хоть один живой потребитель.