Skip to content

Dex.Cap.Inbox: = ANY в захвате отменяет обрыв обхода индекса на PostgreSQL 17+ #233

Description

@mmx003

Суть

Запрос захвата в InboxDataProviderEf передаёт дискриминаторы параметром-массивом (= ANY({0})). На PostgreSQL 17+ это отменяет обрыв упорядоченного обхода индекса по литеральному LIMIT: планировщик не знает значений массива на этапе планирования, поэтому ORDER BY ScheduledStartIndexing LIMIT 25 перестаёт останавливать обход на первых 25 строках.

На целевой для проекта PostgreSQL 16.14 проблемы нет — там IN (a, b) нормализуется в тот же = ANY, планы тождественны. Поэтому на сегодня это не дефект, а отложенное ограничение, которое сработает при апгрейде БД.

Замеры

Схема и индексы из InboxEfExtensions, 196k чужих + 4k своих созревших сообщений.

PostgreSQL 18.4, = ANY(ARRAY[...]) — как в коде сейчас:

Sort (rows=25) Sort Method: quicksort Memory: 283kB
  -> Bitmap Heap Scan (actual rows=3975)   <- материализованы ВСЕ свои созревшие

PostgreSQL 18.4, IN (лит, лит) — на том же индексе:

Index Scan  (30 buffers, 0.23 ms, без сортировки)

Сводка по версиям:

PG 16.14 (целевая) PG 17+ / 18.4
= ANY(ARRAY[...]) — как в коде Index Scan, 200k rows removed Bitmap Heap + Sort 3975 ради 25
IN (лит, лит) тот же план, тот же Filter Index Scan, 30 буферов
Разница нет есть, заметная

Причина расхождения: планировщик 17+ умеет обрывать упорядоченный обход индекса по литеральному LIMIT, а = ANY с параметром-массивом эту способность отменяет. На 16 обрывать нечего, поэтому разницы не видно.

Почему это стоит зафиксировать

Комментарий в коде (InboxDataProviderEf.cs, ~строки 381-384) заявляет: «LIMIT намеренно остаётся литералом: планировщику нужно знать его, чтобы оборвать упорядоченный обход индекса на первых MessagesToProcess строках».

На PG 16 это утверждение неактивно. На PG 17+ оно активно — и = ANY его аннулирует. То есть код содержит намерение, которое сам же и ломает, ровно на тех версиях, где это намерение имеет смысл.

Деградация при апгрейде: 10k своих созревших сообщений (бэклог после инцидента) → каждый захват сортирует 10k строк ради 25. Стоимость растёт линейно с бэклогом, то есть дороже всего именно тогда, когда пропускная способность нужнее. Связать это с апгрейдом БД будет тяжело: код не менялся.

Возможное решение

Раскрывать дискриминаторы в IN (@d0, @d1, ...) — отдельный параметр на каждый, без склейки литералов. Сохраняет и экранирование (ради которого выбран = ANY), и sargability на 17+.

Решать имеет смысл к моменту апгрейда на PG 17+, не раньше: на 16.14 изменение не даст ничего, а горячий путь усложнит.

Контекст

Найдено при ревью #230 (Dex.Cap.Inbox, SUP-1085), тред r3593773403. Находка была снята как DISPUTED — корректно для целевой PG 16.14, но снята слишком широко: для 17+ она верна. Issue заводится, чтобы знание не потерялось к апгрейду.

Связанные: #231 (лимит Content), #232 (retention как окно дедупликации).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions