Из названия не понятно буду ли я писать про странное тестовое задание или про то, как я упустил работу мечты. Дело в том, что я и сам не знаю. Собственно эту дилемму хочу показать вам, а вы меня рассудите. По дороге я покажу, на мой взгляд, интересный способ, как выбрать из очереди еще необработанные элементы с помощью не подходящего индекса.
Диалект SQL от PostgreSQL.
Итак, задание.
Имеем таблицу “msg”,
CREATE TABLE msg ( id numeric(10) PRIMARY KEY ,status numeric(8) NOT NULL ,process_date date NOT NULL ,some_text char(1000) ); CREATE INDEX msg_idx ON msg (process_date); ANALYZE msg;
в которую помещаются сообщения, требующие обработки. Сообщения обрабатываются в порядке возрастания значения поля “process_date“. Каждый обработанный элемент в очереди помечается значением ‘10782572’. Сообщения извлекаются из очереди порциями по 100 записей. Подавляющая часть сообщений в таблице “msg” уже обработана (status = 10782572). Для извлечения очередной порции сообщений используется следующий SQL-запрос:
SELECT id ,process_date ,status FROM msg WHERE status <> '10782572'::numeric ORDER BY process_date FETCH FIRST (100) ROWS ONLY;
Суть задания:
Является ли используемый SQL-запрос оптимальным? Если нет, то почему? Как его можно изменить, чтобы добиться максимального быстродействия?
Поехали.
Должен сказать, что вместе с заданием шел скрипт наполнения таблицы тестовыми данными, который я тут приводить не буду. Он основан на функции random и всё время будет выдавать разный набор.
На что я сразу обратил внимание - это использование бесполезного rows only и fetch вместо limit.
Но спишем все это на попытку запутать. Ведь само задание кажется очень и очень простым.
Решение.
Для начала взглянем на план выполнения без всяких переделок, чтобы ответить на вопрос является ли запрос оптимальным.
Limit (cost=0.42..2937.72 rows=100 width=16) (actual time=3403.035..3406.669 rows=100 loops=1) -> Index Scan using msg_idx on msg (cost=0.42..590396.06 rows=20100 width=16) (actual time=3403.034..3406.659 rows=100 loops=1) Filter: (status <> '10782572'::numeric) Rows Removed by Filter: 962028 Planning Time: 0.222 ms Execution Time: 3406.693 ms
Время выполнения 3.4 секунды. В очереди на кассе это ерунда, а вот под нагрузкой это время может отозваться болью у каждого пользователя. Собственно ожидание больше секунды уже нестерпимая боль.
Слово Filter это вообще красный флаг, оно означает, что скорее всего мы прочитали с диска гораздо больше чем нужно запросу, а затем откинули не нужные строки.
Row Removed нам это и подтверждает. 962028 строк из прочитанных 1000000 было отброшено. Собственно прочитан был весь индекс по process_date. Ну не беда ли?!
Вот так я и ответил. На что мне сказали, что
не нужно было комментировать этот план.
Давайте посмотрим на тестовые данные, которые сгенерировал скрипт из задания. Я приведу урезанную версию, чтобы было понятно.
id |
status |
process_date |
some_text |
1 |
10782572 |
2026-01-01 |
asdf |
2 |
10782572 |
2026-01-02 |
dfghj |
3 |
12 |
2026-01-03 |
;lkj |
4 |
10782572 |
2026-01-04 |
dfgh |
5 |
34 |
2026-01-05 |
gert |
Заметьте что 2026-01-04 обработано, т.к. status = 10782572.
Но постойте, ведь в задании сказано, что
Сообщения обрабатываются в порядке возрастания значения поля “process_date“.
Здесь же получается, что 3-е число еще не обработано, 4-ое обработано, а 5-ое снова не обработано.
Странно все это подумал я и решил, что данный факт просто опишу в решении.
Итак первый алгоритм, который ко мне пришел был следующим.
with recursive cte as ( select max(process_date) as pd from msg union all select distinct process_date from msg fm join cte on (cte.pd - interval '1 day') = fm.process_date where fm.status <> '10782572'::numeric(8) ) SELECT id ,process_date ,status FROM msg WHERE process_date = any(array(select pd from cte)) and status <> '10782572'::numeric(8) ORDER BY process_date FETCH FIRST (100) ROWS ONLY;
Давайте разберем что я тут делаю.
Если вкратце, то я использую только индексный доступ, чтобы определить только необработанные элементы.
Быстро выбираю самую последнюю запись
select max(process_date) as pd from msg
Далее рекурсивно спускаюсь вниз по временной шкале пока не встречу полностью обработанную дату
select distinct process_date from msg fm join cte on (cte.pd - interval '1 day') = fm.process_date where fm.status <> '10782572'::numeric(8)
Т.е. если запрос ничего не вернул, значит этот день полностью обработан и рекурсия останавливается.
После того, как я нашел диапазон необработанных дат остаётся только их выбрать из основной таблицы, чтобы захватить нужные поля, далее отсортировать и взять первые 100 элементов.
SELECT id ,process_date ,status FROM msg WHERE process_date = any(array(select pd from cte)) and status <> '10782572'::numeric(8) ORDER BY process_date FETCH FIRST (100) ROWS ONLY;
План выполнения показывать не буду, чтобы не загромождать, но он поверьте хорош, т.к. там нет столь тяжелого Rows Removed.
Вполне довольный решил переспать с этим алгоритмом. Вдруг чего умнее придет в голову.
И ведь пришло.
with date_range as ( select (select max(process_date) from msg) as date_to, (select process_date from msg where status = '10782572'::numeric(8) order by process_date desc limit 1 ) as date_from ) SELECT id ,process_date ,status FROM msg fm join date_range dr on fm.process_date between dr.date_from and dr.date_to WHERE fm.status <> 10782572::numeric(8) ORDER BY process_date FETCH FIRST (100) ROWS ONLY;
Идея та же самая, но вот рекурсии уже нет. Её я заменил на индексный доступ в обратном порядке
(select process_date from msg where status = '10782572'::numeric(8) order by process_date desc limit 1 ) as date_from
Запрос выше последовательно сканирует индекс по process_date в обратном порядке пока не встретит обработанную запись. А вот как только такая запись находится limit 1 останавливает сканирование и возвращает эту запись. Красота, только есть проблема. Если за последней (мы идем в обратном порядке) обработанной записью есть не обработанные, мы об этом никогда не узнаем.
Итак, задание я посчитал выполненным. Указал в нем на проблему не последовательной обработки элементов и послал на проверку.
Ответ был таков (но я его своими словами опишу).
Первая часть задания нацелена на то, чтобы оптимизатор не использовал мешающий индекс. К тому же мы увидели несостыковки в части логики.
«Мешающий индекс» подумал я. Бывают такие, конечно, среди десятка других, но тут то он один. А задание в том чтобы переписать запрос. В общем, я был обескуражен, взволнован и удивлен одновременно.
Отключим индекс и посмотрим на план выполнения. Только вот данных для наглядности добавим побольше. В сумме, примерно 55 млн.
set enable_indexscan = off; explain(analyze) SELECT id ,process_date ,status FROM msg WHERE status <> 10782572::numeric(8) ORDER BY process_date FETCH FIRST (100) ROWS ONLY;
Limit (cost=772626.06..772637.72 rows=100 width=16) (actual time=3691.510..3701.549 rows=100 loops=1) -> Gather Merge (cost=772626.06..775155.80 rows=21682 width=16) (actual time=3646.649..3656.677 rows=100 loops=1) Workers Planned: 2 Workers Launched: 2 -> Sort (cost=771626.03..771653.14 rows=10841 width=16) (actual time=3623.705..3623.715 rows=67 loops=3) Sort Key: process_date Sort Method: top-N heapsort Memory: 32kB Worker 0: Sort Method: top-N heapsort Memory: 32kB Worker 1: Sort Method: top-N heapsort Memory: 32kB -> Parallel Seq Scan on msg (cost=0.00..771211.70 rows=10841 width=16) (actual time=122.181..3620.000 rows=13319 loops=3) Filter: (status <> '10782572'::numeric(8,0)) Rows Removed by Filter: 18526680 Planning Time: 0.079 ms JIT: Functions: 13 Options: Inlining true, Optimization true, Expressions true, Deforming true Timing: Generation 1.571 ms (Deform 0.690 ms), Inlining 272.073 ms, Optimization 68.598 ms, Emission 52.106 ms, Total 394.348 ms Execution Time: 3702.109 ms
Снова видим Filter и громадный Rows Removed. Время выполнения почти 4 секунды.
В итоге, но уже без надежды на успех постарался на пальцах описать, почему индекс не только не мешает, но и помогает. При этом сделал число 2026-01-04 не обработанным.
Вот план моего варианта.
set enable_indexscan = on; explain(analyze) with date_range as ( select (select max(process_date) from msg) as date_to, (select process_date from msg where status = 10782572::numeric(8) order by process_date desc limit 1 ) as date_from ) SELECT id ,process_date ,status FROM msg fm join date_range dr on fm.process_date between dr.date_from and dr.date_to WHERE fm.status <> 10782572::numeric(8) ORDER BY process_date FETCH FIRST (100) ROWS ONLY;
Limit (cost=1.41..346469.99 rows=100 width=16) (actual time=85.106..85.274 rows=100 loops=1) InitPlan 1 -> Limit (cost=0.44..0.50 rows=1 width=4) (actual time=69.283..69.284 rows=1 loops=1) -> Index Scan Backward using msg_idx on msg (cost=0.44..3149956.72 rows=55729572 width=4) (actual time=69.278..69.278 rows=1 loops=1) Filter: (status = '10782572'::numeric(8,0)) Rows Removed by Filter: 39957 InitPlan 3 -> Result (cost=0.46..0.47 rows=1 width=4) (actual time=0.021..0.021 rows=1 loops=1) InitPlan 2 -> Limit (cost=0.44..0.46 rows=1 width=4) (actual time=0.017..0.017 rows=1 loops=1) -> Index Only Scan Backward using msg_idx on msg msg_1 (cost=0.44..1036483.85 rows=55755590 width=4) (actual time=0.014..0.014 rows=1 loops=1) Heap Fetches: 0 -> Index Scan using msg_idx on msg fm (cost=0.44..450409.61 rows=130 width=16) (actual time=72.283..72.443 rows=100 loops=1) Index Cond: ((process_date >= (InitPlan 1).col1) AND (process_date <= (InitPlan 3).col1)) Filter: (status <> '10782572'::numeric(8,0)) Rows Removed by Filter: 983 Planning Time: 0.367 ms JIT: Functions: 16 Options: Inlining false, Optimization false, Expressions true, Deforming true Timing: Generation 1.312 ms (Deform 0.494 ms), Inlining 0.000 ms, Optimization 0.474 ms, Emission 12.407 ms, Total 14.193 ms Execution Time: 86.705 ms
Rows Removed минимален, как и количество отброшенных записей. К тому же оптимизированный вариант работает намного быстрей.
Ответ был таков.
Мы не можем гарантировать условия последовательной обработки элементов очереди.
Это видимо и были мои несостыковки в логике. Хотя вариант с рекурсивным cte решает эту проблему
Интересоваться, почему в задании сказано, что элементы обрабатываются в порядке возрастания process_date, а гарантировать они это не могут я не стал. Понятное дело не стал. Ведь в задании про это ни слова.
Вообще говоря, для обработки очереди нам даны for update и skip locked.
От себя. Я не жалею, что упустил работу мечта, но мне очень интересно, где я был не прав. Хотя возможно вы тоже видите, что в задании упущены ключевые моменты, без которых, выполнить его сложно.
P.S.
Условие
status <> 10782572::numeric(8)
я писал и так и эдак, но это константа и в плане выглядит одинаково.
Комментарии (33)

Akina
09.04.2026 16:10вместе с заданием шел скрипт наполнения таблицы тестовыми данными, который я тут приводить не буду. Он основан на функции random и всё время будет выдавать разный набор.
Совершенно напрасно. Пусть не воспроизведётся один к одному ваш набор записей. но тенденции останутся практически неизменными. Миллиона записей достаточно, чтобы их выровнять. Опять же всегда можно перегенерировать набор (несколько раз, или расширить его) и повторить - общее останется, а случайные выбросы уйдут. А сейчас - голые слова.
Но постойте, ведь в задании сказано, что
Сообщения обрабатываются в порядке возрастания значения поля “process_date“.
Здесь же получается, что 3-е число еще не обработано, 4-ое обработано, а 5-ое снова не обработано.
Странно все это подумал я и решил, что данный факт просто опишу в решении.
Что вы нашли странного? То, что сообщение взято на обработку, не означает, что оно будет обработано ранее более позднего. Как и не означает, что оно будет обработано вообще. Вполне себе нормальная ситуация, которую вы почему-то решили проигнорировать. так что ответ про "несостыковки по части логики" - совершенно справедливо.
использование бесполезного rows only и fetch вместо limit.
Документация PostgreSQL практически открытым текстом говорит, что LIMIT и FETCH - это разные синтаксические формы одной и той же операции. SQL:2008 ввёл, Postgres взял под козырёк - и не более. В чём вы видите различие в полезности?

vmalyutin Автор
09.04.2026 16:10В задании ничего не сказано про специфику обработки. А я уж поверьте наелся очередей и прекрасно понимаю, что могут быть дырки.
Если читать мою статью внимательно, то у меня есть вариант, который это учитывает.
Про скрипт. Мне не хотелось выкладывать задание полностью. По понятным надеюсь причинам

vmalyutin Автор
09.04.2026 16:10FETCH в продакшене не видел ни разу, потому что есть новый limit

Akina
09.04.2026 16:10Это какой такой "новый"? Тот, который общеизвестен, появился гораздо раньше FETCH, уж в Постгрессе-то точно, так что это по сравнению с ним FETCH - новый.

vmalyutin Автор
09.04.2026 16:10Итак. fetch имеет смысл только with ties, который тут не нужен. Я, конечно, пошел и перечитал. Не уверен я, что даже вы прям все-все детали документации помните. Но суть моей статьи вообще не в этом. Вы б лучше, что-нибудь по делу сказали, чем цепляться за мелочи.

Akina
09.04.2026 16:10Не уверен я, что даже вы прям все-все детали документации помните.
Не поверите, но я, прежде, чем писать, тоже сначала освежил память чтением документации. И даже пробежался по изменениям в версиях.
Вы б лучше, что-нибудь по делу сказали
Вы проигнорировали особенности набора данных. У вас же вся статья построена на "у вас неправильный набор данных, я его поправлю, а потом буду решать". Я ничего не имею против ваших вариантов решения - кроме того, что они решают ДРУГУЮ задачу.
Опять же - вы смело вмешиваетесь в данные: "При этом сделал число 2026-01-04 не обработанным". Сразу возникает вопрос, почему вы столь же смело не вмешались в структуру? Так, как предложил @NeKukSA. На поверхности же плавает..
Нет, я понимаю, что вам обидно. Рассказываете в общем хорошую вещь, а вам тычут в несоблюдение условий. Но ведь оно - имеется, из песни слова не выбросишь.
Т.е. если запрос ничего не вернул, значит этот день полностью обработан и рекурсия останавливается.
А если имеется день без записей? Я не вижу в задании, что это невозможно. Этой возможности ваше первое решение, то, что с рекурсией, не учитывает.
Итак. fetch имеет смысл только with ties, который тут не нужен.
WITH TIES - это дополнительная, и да, весьма небесполезная, фича, но никто не обязывает ей пользоваться. А без неё LIMIT и FETCH - это по факту синонимы в PostgreSQL. И слышать, что решение, имеет или не имеет смысл конкретная операция, принимается на основании её синтаксической формы - несколько странно.

vmalyutin Автор
09.04.2026 16:10Вы проигнорировали особенности набора данных
Я хочу вас спросить. Вы видели запрос с рекурсией? Понимаете, чем он отличается от того для которого потребовалось изменить данные? Понимаете, что эти запросы вернут одно и то же? Я только до сих пор не понимаю, как действует мнимый обработчик. Поэтому варианта основных два.
А если имеется день без записей?
А если кто-то другой загребет ту же запись? Тоже как бы по заданию можно предположить?
А если обработает, а признак не вернёт? Тоже по заданию...
Можно вас попросить проголосовать за неточность ТЗ?
Но ведь оно - имеется, из песни слова не выбросишь.
Там надо ускорить запрос, а не писать реальную очередь.
Не делал дополнительных и супер очевидных индексов только потому что в задании сказано изменить запрос. Изменить САМ запрос, чтобы он стал быстрей. Да и как вы, вероятно уже поняли, индексы нужно было не создавать, а убивать :) И последнее. Работа была 100% такая, что решение ускорить запрос индексом железобетонно не прокатило бы. Слишком уж просто.
Ну и кстати, в решении которое я им послал в пунктах ниже, были и индексы, и инклуды и даже партиции. Согласитесь, с партициями и индексы не нужны. Но все это было проигнорировано.
WITH TIES
Бог с ним. По настоящему это отношение к делу не имеет.

NeKukSA
09.04.2026 16:10А почему не создать подходящий для запроса индекс? Копия уже имеющегося с условием
where status <> '10782572'::numericИндекс всегда будет содержать ссылки только на актуальные для шедулера строки, не будет занимать много памяти и не будет распухать при условии, что строки будут своевременно обрабатываться и автовакуум на базе не выключен)
Я бы начал разговор именно с такого предложения, даже выдумывать ничего не стал бы с запросом. Индексы - такой же инструмент, чего бы им не пользоваться.

vmalyutin Автор
09.04.2026 16:10Вы, конечно совершенно правы! Такой индекс всегда будет "в форме".
Но, в задании написано изменить сам запрос. И я чуть с дуба не рухнул, когда узнал, что изменить запрос у них означает выключить индекс вообще :)

Akina
09.04.2026 16:10То есть правильный ответ, с их точки зрения, это предварительный
SET LOCAL enable_indexscan = off; , что ли? Бре-е-ед..
OlegIct
09.04.2026 16:10да, бред. думаю правильно:
drop index msg_idx;
create index msg_idx on msg (process_date) where status <> '10782572'::numeric;
в запрос добавить for no key update skip locked

poige
09.04.2026 16:10лучше так:
CREATE INDEX msg_unprocessed_idx ON msg (process_date, id) WHERE status <> 10782572;-- для очередей явно задавать порядок

OlegIct
09.04.2026 16:10вряд ли, увеличится размер индекса; более сложно; там всего 100 строк; спросят что вы знаете о BitmapAnd. Про важность unit of order при обработке сообщений в очередях можно просто упомянуть

poige
09.04.2026 16:10вряд ли, увеличится размер индекса
В этом индексе лежат только необработанные строки. А вот батч-выборка ORDER BY process_date, id — стабильная.

OlegIct
09.04.2026 16:10Если на одну дату десятки тысяч строк, тогда да, можно добавить id в order by. Не для спора, а чтобы всё учесть: досортировка не такая трудоемкая, индекс по одному столбцу будет дедуплицирован.

vmalyutin Автор
09.04.2026 16:10Ну да. С таким индексом никаких дополнительных плясок в коде не нужно. Это намного больше похоже на обработку очереди. Только в задании сказано тупо поменять запрос. Ой. Индекс выключить. Ой. Бог знает что сделать с дырками после обработки.

vmalyutin Автор
09.04.2026 16:10Я на самом деле не знаю, что было правильным ответом. Вот честно. Все что они мне сказали я тут привел. Но вот про мешающий индекс точно звучало. А я со стула упал.

Politura
09.04.2026 16:10Угу, есть такая проблема с интервью, когда что-то не говоришь т.к. считаешь, что это очевидно всем и нет нужны проговаривать, а потом узнаешь, что тебя отсеяли т.к. ты якобы не знаешь очевидных вещей. Поэтому надо на интервью все рассуждения, что лезут в голову, учиться озвучивать не стесняясь. Первым делом увидев эту задачу сказать, что надо индекс менять, а не запрос, а потом сказать, что раз по-условию менять именно запрос, буду пытаться что-то с этим сделать, хоть и добиться результатов близких к правильному индексу будет невозможно.

vmalyutin Автор
09.04.2026 16:10Эх, не было у меня интервью. А в текстовом файле с решением были и индексы и партиции и еще много чего. Но все это было проигнорировано.
Отвечали мне в телеге. Все что ответили я описал своими словами

onets
09.04.2026 16:10Я делал подобные штуки для прода. И я вообще вижу кривое задание. То как описано не заработает в проде, будет race condition за необработанные записи, потому что между условным статусом "новый" и "обработано" пройдет некоторое время.
Нужен либо дополнительный статус "в обработке", либо булева колонка "в обработке", а еще лучше lockID и lockExpirationDate. Там надо апдейтить записи в транзакции с блокировкой по строкам и проставлением статуса "в обработке", чтобы параллельный поток не взял в обработку записи, которые уже взял другой поток.
И уже на основе этого построить сначала правильный запрос в зависимости от БД - надо смотреть документацию. Запрос, в том числе, должен исключать записи со статусом "в обработке", а если сделали с колонкой lockExpirationDate - то и фильтр по ней. А потом уже думать над индексами. Ну и нагрузочные тесты.

Politura
09.04.2026 16:10Разные же задачи. Там, где нужна обработка, запрашивается только одна запись, или, точнее, апдейтится статус одной записи с возвратом проапдейченой записи.
А здесь задача вернуть 100 первых необработанных записей. Может для какого-то дашборда, который показывает первые Х записей по каким-то критериям. Хотя обычно страница по умолчанию 20 записей.
Ну и в целом, для обработки лучше не реляционную базу дергать, сжигая ценный ресурс - количество writing транзакций в секунду, а очереди, у которых есть retry, dead letter queue, и прочие вкусности.

Nizametdinov
09.04.2026 16:10SELECTid,process_date ,statusFROMmsgWHEREstatus <> '10782572'::numericORDERBYprocess_dateFETCHFIRST(100)ROWSONLY;А почему не выбирается сразу колонка some_text?
vmalyutin Автор
09.04.2026 16:10Не знаю. Наверно, потому что это просто тестовое задание

Nizametdinov
09.04.2026 16:10Так в этом и было тестовое задание, ты упоролся в оптимизации и не заметил слона в посудной лавке - не?

Murmakil
09.04.2026 16:10Сам уже сталкивался с ситуацией, когда тестовое задание дают исключительно с целью, чтобы появился формальный повод для отказа. В этом случае как раз могут не полностью описать условия задачи или проверять твое решение на совсем других данных или что-нибудь еще. Возможно, и вакансии-то и не было на самом деле: собес провели, потешили свое эго, списали время в таск трекере и выдали тестовое, чтобы потом слить кандидата

Konstantin_4030
09.04.2026 16:10Не люблю тестовые задания ни писать ни делать.
У тебя два пути: придумать абстрактную задачу или выдать фрагмент своего приложения.
В абстрактных задачах я не силен, для меня это выглядит будто нужно напрячься и спрятать в простой задаче непростой подвох. Если плохо спрячешь - задача слишком простая. Если хорошо спрячешь - в половине (скорее 80 процентов) ты констатируешь невнимательность. Круто когда твоя задача нанять суперзвезду, но чаще ты ищешь толковых ребят, которые ещё не всё умеют.
Теперь, значит фрагмент. Найди такой, в утром не слишком много контекстных ограничений (так-то это поле тут не нужно, но мы его потом отдаем в третью систему, забей), в котором мало "исторически сложилось" (система уже работает лет десять, за десять лет подходы пересматривали много раз) и чтобы задача не выглядела глупой (очень длинный идентификатор статуса для отработанной заявки вместо булевых значений) и чтоб код поместился в два-три файла. После этого вырежь половину используемых классов, упрости до базовых типов данных, увидь, что треть кода реально не нужна, но это функции так называемого "ядра". В процессе, пожалуйста, не спрашивай "зачем эти люди сделали именно так" (а там даже фамилии зафиксированы. Часто это твоя фамилия).
После всей этой боли, ты увидишь скучную задачу с очевидным решением. Ты попробуешь немного замаскировать ответ, чтобы хоть какая-то интрига осталась.
После этого придет хороший (без иронии) парень Вячеслав. Наложит на твой текст свою картину мира, что-то предположит в своей голове и предложит своё вырви-мозг решение, которое в общем-то нормальное для абстрактной задачи... Но ты бы не хотел, чтобы у тебя работал человек который помешает фразы "рекурсия" и "10 миллионов строк" в одном предложении и пишет нечитаемый в стрессовой ситуации (то есть несопровождаемый) sql-запрос с cte и сложным условием.
Более того, после всей твоей боли... Хороший человек Вячеслав (без иронии) пойдёт на форум и расскажет о твоей компании свои выводы которые он сделал на примере вычурного тестового задания, которое ты сам считаешь не показательным. Оно не показывает ни как Вячеслав программирует, ни как Вячеслав рассуждает, ни какие у него ценности.
В итоге, два хороших человека неслабо вспотели просто ради того чтобы HR себе галочку поставил.

vmalyutin Автор
09.04.2026 16:10Дак, что да или нет? В целом спич понятен, но нога букав.

Konstantin_4030
09.04.2026 16:10Я думаю, вы увлеклись математической задачей и потеряли из виду физический смысл. Из-за этого ваше решение отличное но совершенно неприменимо в реальной жизни.
Тут второй вопрос вынуждает вас ответить "нет нужного индекса, потому он работает медленно". А третий вопрос "как его изменить для максимальной производительности" в существующем контексте разрешает вам создать индекс и заменить бесящий вас fetch на limit.
Вы же испугали работодателя отсутствием лени: вы согласились вынести себе мозг делая глупую и трудоёмкую работу потому что, вами показалось, что текст задания от вас это требует.
Не понимаю почему вы подумали, что на базу нельзя влиять: в своей жизни я видел ситуации, когда ты не можешь изменить текст запроса, и всего один раз я был в ситуации, когда нельзя оказывать влияние на постгрес (добавить вьюху, например), но даже там не контролировалось наличие и отсутствие индексов.
Тест на адекватность (просто термин, я считаю вас адекватным) заключается в необходимости сказать "вы знаете, можно многократно усложнить этот запрос и добиться прироста производительности без изменения БД, но это будет сложно сопровождать. Вы уверены что надо менять запрос, может лучше бахнем индекс?". Это нормально, ведь задания тоже пишут люди, а люди могут ошибаться или быть неаккуратными в формулировках. Это так же показывает, что вы работаете в команде, а не "выполняете поставленную задачу".
Если так посмотреть, получается, вы сами запороли собеседование

vmalyutin Автор
09.04.2026 16:10Вот теперь я вас понял. Спасибо за ваше мнение.
Теперь я вас удивлю и надеюсь все встанет на свои места и будет понятно, почему я написал эту статью.
Итак. В файле, который содержал решение были еще несколько вариантов. Там были еще парочка индексов, партиционирование (о нем, кстати, тут никто не подумал) и другие предложения. Т.е. вместо этих всех рекурсией и т.д. был тупой индекс, который содержал только не обработанные элементы очереди. Партиции тут вообще лучше всего подходят, т.к. не требуют индексов. Но все это было проигнорировано, а мои решения были названы сумбурными.
Что я делал дак то, что в отсутствии четкого задания я накидывал разные варианты.

Konstantin_4030
09.04.2026 16:10К сожалению, все ещё не понятно почему вы написали эту статью)
Для меня это выглядит будто, вы предложили много решений, по которым получили общий комментарий. А нам рассказали не самое оптимальное решение и ответ компании. Но Бог с ним.
Я больше хотел обсудить партиции, для меня это какая-то больная тема, к которой мне, похоже, скоро придётся возвращаться.
Как вы тут придумали партиции? В голову приходит только разбиение по статусам.
При апдейтах мы (могу ошибаться) вынуждаем систему переносить запись из одной партиции в другую, что сопровождается обновлением двух (грубо говоря) таблиц и индексов под первичными ключами двух партиций. Как вы правильно сказали, на кассе это не проблема, а на нагруженной системе мы потребляем IO.
В то же время частичный индекс бы просто уменьшился бы да и все. Партиции даже в таком контексте выглядят более проигрышной стратегией.
Плюс, если системе понадобится искать что-нибудь по ServiceDate, вы прозреете насколько партиции все испортят.
Решение с частичным индексом вы тоже предложили, я так понял. Это хорошо.

vmalyutin Автор
09.04.2026 16:10Честно говоря я уже запутался что Вам понятно, а что нет.
Коротко.
Мне дали запрос и предложили его изменить. Изменить ТОЛЬКО запрос. Поэтому я накрутил логики и заодно предложил, как добиться производительности другими способами.
На что получил, как я думаю, не адекватный ответ, про мешающий индекс.
Все мои способы были с комментариями. С плюсами и минусами. Так, например, глобального PK PostgreSQL делать не умеет.
Но это все было просто проигнорировано.
Плюс, если системе понадобится искать что-нибудь по ServiceDate, вы прозреете насколько партиции все испортят.
Не знаю про какой вы ServiceDate, но есть partition pruning. Да и задачи такой не было.
Я так понял вы рассматриваете мой код применительно к продакшену, не понятно к какому. Но делать этого не нужно, потому что это было странное тестовое задание не понятно что проверяющие.
Oeaoo
Однажды я поступил в универ на бюджет (математическая специальность), как потом выяснилось, решив задачу неправильно! После экзамена перерешал и понял где закралась ошибка. Сразу расстроился, думая что не поступил, т.к. без той задачи я уже не проходил по баллам. Но случилось странное как мне казалось тогда чудо - проверяющие почти не срезали за него баллы, т. к. в принципе правильных рассуждений в нём было достаточно много. Мораль, думаю, понятна.