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 — так ведёт себя реальная таблица под триггером.
Проверка на осмысленность: против кода до фикса падают четыре из шести.
Общий вывод
Возвращаемое значение операции описывает механизм, а не намерение. Как только под механизм подкладывают перенаправление - партиционирование, прокси, шардинг, кэш, - все производные от него сигналы продолжают быть технически правдивыми и перестают отвечать на вопрос вызывающего. Ошибки не будет, будет ноль.
Отсюда практическое правило: при смене механизма хранения или доставки надо пересчитать не только вызовы, но и читателей побочных сигналов - счётчиков строк, кодов возврата, командных тегов, заголовков. Компилятор и тесты их не найдут, потому что типы и значения остаются валидными.
И второе: отсутствие жалоб измеряет число тех, кто смотрит, а не работоспособность. Прежде чем считать молчание сигналом здоровья, надо убедиться, что у пути есть хоть один живой потребитель.