Marginalia: запросы без ранжирования, systemd, краулинг
- title
- Marginalia: запросы без ранжирования, systemd, краулинг
- type
- summary
- summary
- Marginalia Search переходит с docker compose на systemd, добавляет путь запросов без ранжирования и вдвое сокращает время обхода.
- tags
- search-engines, systemd, operations, crawling
- sources
- marginalia-systemd-migration
- created
- 2026-07-23
- updated
- 2026-07-29
- lang
- ru
- translation_of
- marginalia-systemd-migration
- source_updated
- 2026-07-29
- translated
- 2026-09-01
- translator
- lllm/antigravity/gemini-3.7-flash-medium
Три эксплуатационных изменения в Marginalia Search, описанные автором в июле 2026 года: production переехал с docker compose на чистый systemd, в индексе появился путь выполнения запросов без ранжирования, а краулер разделили, чтобы блог-платформы больше не тормозили всё остальное. Сам автор подытожил результат так: "ушла масса проблем с эксплуатацией, сократилось число тяжёлых запросов, освободив мощности под остальные задачи, а время краулинга сократилось вдвое".
Почему не подошёл docker
Три ограничения развёртывания мешали стандартной работе в контейнерах.
На production-сервере стоят два процессора, у каждого свой банк оперативной памяти. Обращаться к памяти чужого процессора можно, но накладно, а процессы индекса и базы данных как раз упираются в пропускную способность RAM, поэтому размещение с учётом NUMA здесь играет бóльшую роль, чем для обычного сервиса.
Жизненные циклы процессов сильно различаются. Поисковый движок отнюдь не stateless: некоторые процессы тяжёлые и работают неделями, некоторые сервисы долго запускаются, а какие-то приходится часто перезапускать. Одно это уже вынуждает дробить движок на отдельные части - сервисы, хотя автор оговаривается, что это не обязательно микросервисы.
Краулер и соседние процессы работают примерно с дюжины публичных IP-адресов на одном хосте через ipvlan и network namespace'ы. В Linux хватает механизмов, чтобы выделить процессу собственный сетевой стек и связать всё виртуальными свитчами и патч-кордами. Претензия автора в том, что ни один инструмент управления этим хозяйством не годится: они либо слишком низкоуровневые, либо скрывают за абстракциями именно то, что нужно, либо заводят второй источник правды, который со временем расходится с первым.
Docker справляется со всеми тремя задачами, но ценой бесконечных костылей. Его сетевая модель поддерживает ipvlan, но ложится на него криво: при фиксированной подсети нужны запасные свободные IP, чтобы надёжно перезапускать сервисы. Он не даёт вешать правила фаервола на интерфейс ipvlan, поэтому всему, что там крутится, лучше не биндиться к публичному IP. А у процесса внутри контейнера нет вменяемого способа узнать, какой интерфейс публичный, так что остаётся угадывать по диапазонам RFC 1918 и надеяться на лучшее.
Аргумент в пользу systemd прост: под капотом у docker всё равно cgroups и namespace'ы, а systemd делает то же самое без концептуального несовпадения и оставляет конфигурацию в ваших руках, при этом давая и проверки работоспособности, и настраиваемые автоперезапуски. Писать service unit на каждый процесс утомительно, но с drop-in'ами это проще. Заявленный результат: работает стабильнее, чем когда-либо на docker, развёртывание стало быстрее, ушли костыли в service discovery (ведь "контейнеры" сохраняют постоянные внутренний и внешний IP), а сборка сократилась до 2-3 секунд, поскольку JIB выкинули из цепочки сборки. Автор оговаривается: на десктопе systemd переусложнён и мучителен, но для подобных задач такая архитектура полностью оправдана.
Запросы без ранжирования
Обычный пайплайн запросов тяжёл и делает работу, которая многим запросам вовсе не нужна. Поиск обратных ссылок вроде site:foo.com links:bar.com - это чистое пересечение двух списков термов: ранжировать здесь нечего. Тем не менее потоки выделялись под ранжирование, а позиции термов всё равно вычитывались.
Новый путь делает тупое пересечение термов и почти ничего больше. Он работает в один поток примерно за 5 мс - минимум на порядок быстрее полноценного запроса. И однопоточность - важнейшая часть выигрыша: потоки возвращаются в пул исполнения под задачи, которым они действительно нужны. На этот эндпоинт может приходиться до половины всей нагрузки по запросам, хотя доля колеблется, так как заметная часть этого трафика - боты и скраперы, обходящие просмотрщик /site.
Отказ от ранжирования также сделал возможной полную выборку всех результатов. В отличие от ранжированного поиска, неранжированный запрос можно листать постранично до самого конца благодаря курсору, отслеживающему позицию по партициям индекса:
28mbshfptkj.6ijoop7xty.43nivb3bn1.817ucldxy2x.90.12ria2fmp7a.32cydk0dei2t.73wrhi4igjr.53lb1o4iof7
Каждая часть, разделённая точкой, начинается с одного символа идентификатора партиции, за которым следует base-36 идентификатор документа, с которого нужно продолжить на этой партиции. Достаточно компактно, чтобы поместиться в строке запроса или вызове API, а большего, по словам автора, и не требуется.
Разделение краулера
Время обхода росло годами. На каждую партицию уходило почти две недели, а при 8 основных партициях индекса полный краулинг приближался к четырём месяцам - настолько долго, что результаты устаревали ещё до завершения прохода.
Причина в том, что распределение поддоменов подчиняется закону Парето, а вежливость краулера запрещает бомбардировать запросами несколько хостов на одном домене верхнего уровня одновременно. Число известных поддоменов на домен верхнего уровня из статьи: tumblr.com - 8 450 330, blogspot.com - 1 203 425, wordpress.com - 941 330, далее с показателями от 334 941 до 112 578 идут uptodown.com, bandcamp.com, livejournal.com, appstor.io, github.io, substack.com, dreamwidth.org, medium.com и wixsite.com. В частности, Substack быстро начинает возвращать 429, а сжечь IP означает вообще потерять возможность индексировать домен, что хуже любой медлительности.
В итоге основной обход завершался за несколько дней, а затем ещё больше недели еле двигался по substack, medium, wordpress, github.io и neocities. Решением стала отдельная партиция краулера под крупные домены с ограничением по времени, а не до полного завершения: за неделю она обходит столько сайтов, сколько успевает, а затем останавливается, чтобы обновления попали в индекс. Она работает параллельно с основным краулером, который теперь укладывается примерно в пять дней вместо двух недель.
Тот же автор тремя днями позже сформулировал этот принцип в your-harddrive-is-full: задайте жёсткое ограничение, заведомо меньшее имеющихся ресурсов, и оптимизируйте под него, не дожидаясь критической точки. Поисковый движок на одном скромном сервере с краулером, ограниченным по времени вместо цели обойти всё целиком, - это воплощение того самого подхода на практике.