Как я отучил оракула молоть околесицу про ГОСТы: сказ о doc-rag без единого (почти) заморского слова. cursor.. cursor. doc-rag.. cursor. doc-rag. faiss.. cursor. doc-rag. faiss. fastapi.. cursor. doc-rag. faiss. fastapi. mcp.. cursor. doc-rag. faiss. fastapi. mcp. rag.. cursor. doc-rag. faiss. fastapi. mcp. rag. локальный RAG.. cursor. doc-rag. faiss. fastapi. mcp. rag. локальный RAG. семантический поиск.. cursor. doc-rag. faiss. fastapi. mcp. rag. локальный RAG. семантический поиск. эмбеддинги.. cursor. doc-rag. faiss. fastapi. mcp. rag. локальный RAG. семантический поиск. эмбеддинги. юмор.
Как я отучил оракула молоть околесицу про ГОСТы: сказ о doc-rag без единого (почти) заморского слова - 1

Предуведомление. В этой статье я нарочно изгнал из прозы все заморские словеса. Где обойтись без чужеземщины было невмочь — давал перевод, а рядом, в скобках, истинное имя вещи, чтобы читатель не плутал. Греческое и латинское старьё («программа», «техника», «документ», «схема») я щадил — оно у нас давно прижилось. Все настоящие команды в окошках кода остались как есть: их же по-старорусски не выполнишь.

Также честно скажу сразу: кода у этой снасти пока в общем доступе нет. Я причёсываю настройки и готовлю его к выкладке. Я ниже подробно рассказываю и про устройство, и про грабли, чтобы те, кто захочет что-то похожее сложить у себя в избе — могли это сделать уже сейчас, не дожидаясь меня. Как только выложу — приду в раздел «Слово на прощанье» и положу туда тропу к избе.

1. С чего всё началось: бес в машине

Дело моё — программы писать. А у писаной программы такая повадка: чтобы она дело делала, ей положено сходиться с мастеровой грамотой. Своды технических указаний (СТО), государственные образцы (ГОСТы), руководящие наставления (РД), спецификации, руководства по эксплуатации да всякие пояснительные записки — на этих свитках в наших краях держится промышленная автоматика и сетевое хозяйство электрических подстанций. Сочинять их — не моё ремесло. А вот вычитывать и сверяться — это уже моё, и почти ежедневно. В сумме у меня в закромах болтается несколько тысяч свитков (англ. — files), почти всегда в печатном формате (PDF). Бывают сканы — где-то страница криво снята, где-то перо стоящего рядом мастера прошлось по чертежу.

В какой-то момент я начал просить оракула (так я буду называть в этой статье любого толкователя-вещуна, к которому обращаются с наказами и ждут ответа; в моём случае — это тот, что прижился у меня в писарне, то есть в среде написания кода Cursor):

Скажи, что говорит СТО 34.01-5.1-006 в пункте 7.3.2 про входные испытания?

Оракул отвечал. Уверенно. С раскладом по пунктам. Со ссылками. Беда в одном: ничего из этого не существовало. Это был чистый «бес в машине» — доброжелательная, складная, аргументированная околесица. Иногда там случайно оказывалась правда (видать, в обучающем корпусе вещуна были и наши документы), но проверить, где правда, а где сказка, можно было только сверив каждую цифру со свитком. То есть — выполнив всю работу самому.

Я повозился с двумя-тремя чужими подворьями, что обещают извлечение премудрости (по-заморски такие зовутся RAG-службами): подбрось пачку свитков, задавай вопросы. Часть из них работала. Но:

  • внутренние документы я отдавать в чужие руки не хотел;

  • сама связка ломалась, как только заходила речь о сканах с формулами;

  • никакой возможности встроить это в писарню у меня не было — только пользоваться отдельной горницей через глядалку (англ. — browser).

К тому же мне очень хотелось одного: чтобы оракул, живущий в писарне, сам ходил за ответами в мою библиотеку. Не выдумывал из головы, а доставал из «хранилища премудрости». Если уж он берётся отвечать про СТО — пусть будет добёр, выдаст мне точный кусочек текста с указанием, откуда он взят. А я уж разберусь.

Так появился doc-rag — местная (то есть запускаемая у меня дома и в моей же рабочей приказной избе) снасть для извлечения премудрости. Имя без затей: «свитки + извлекательно-обогащённое прорицание». В этой статье я её и разбираю — по-нашему, без единой заморской сорной травы. Сама же сорная трава для читателя, что не любит угадывать, дана ниже в особом словарике.

2. Словарик: что чем переводится

Чтобы дальше можно было читать без запинок, привожу свой переводной словарик. Им же я и сам пользовался, пока писал — иначе сорвался бы в обычное смешение.

Заморское

По-нашему

оракул, толкователь, вещун (AI, LLM, model)

сами по себе уже по-русски — оставляю

челобитная к оракулу (prompt)

наказ, вопрошание

бесовское враньё, бес в машине (hallucination, bug)

как написано

словесная мера (token)

дробь речения

извлечение из премудрости (retrieval)

сыск по хранилищу

извлекательно-обогащённое прорицание (RAG)

как написано

толковательная стрела (embedding)

вкрапление смыслов

ломоть текста (chunk)

как написано

указатель, перепись (index, FAISS)

как написано

смысловой сыск (semantic search)

как написано

буквенный сыск (lexical search)

как написано

подобие, сродство (similarity)

как написано

свиток (file)

как написано

сундук, лубяной короб (folder, directory)

как написано

разметная грамота (Markdown)

как написано

узловой свиток (JSON)

как написано

ступенчатый свиток (YAML)

как написано

чтец-машинка (OCR)

как написано

печать-256 (SHA256)

как написано

двойник (duplicate)

как написано

сирота (orphan)

как написано

приказная изба, ратник (server)

как написано

проситель, гость (client)

как написано

дворовый ратник (daemon, service)

как написано

пристань (port, endpoint)

как написано

гонец-проводной (HTTP)

как написано

сговорное окошко (API)

как написано

уговор обмена с оракулом (MCP)

как написано

ключ-метка, пропуск (API key)

как написано

тропа, стёжка к избе (URL, link)

как написано

запечатление, вклад в летопись (commit)

как написано

ковка (build)

как написано

перековка (rebuild)

как написано

поглощение, набивка (ingest)

как написано

трубопровод сборочный, желоб (pipeline)

как написано

снасть кузнечная, цепь орудий (toolchain)

как написано

бегунец, гонец (runner)

как написано

развёртывание по весям (deploy)

как написано

сосуд переносный (Docker container)

как написано

образ-болван (Docker image)

как написано

грамота-скоморох (script)

как написано

завязка, потреба (dependency)

как написано

летопись (log)

как написано

истёкший срок (timeout)

как написано

припас, схрон (cache)

как написано

светлица для смерда (Web UI)

как написано

глядалка (browser)

как написано

пуговка (button)

как написано

стяг, хоругвь (banner)

как написано

птичка-метка (checkbox)

как написано

заброс, подкидка (upload)

как написано

истребление (delete)

как написано

стирание подчистую (wipe)

как написано

осаживание потока (backpressure)

как написано

писарня (Cursor — среда написания кода)

как написано

уклад (операционная система, OS)

как написано

Линукс-уклад (Linux)

как написано

окошечный уклад (Windows)

как написано

Линукс-постоялец в окошечном укладе (WSL)

как написано

Питон-змейка (Python — имя само по себе)

оставляю как имя

Дебиановы хутора (Debian)

как написано

Убунтова слобода (Ubuntu)

как написано

Список заведомо неполный — но любой, кто прочтёт до конца, поймёт, что я имею в виду.

3. Снасть в общих чертах

Прежде чем углубляться, нарисую путь свитка от моего короба до ответа, который оракул выдаёт в писарне:

свитки     →  разметная   →  ломти    →  толковательные  →  стрельбище    →  оракул
(PDF/DOCX/  грамота       текста     стрелы             меченое
 DOC/MD/    (Markdown)    (chunks)   (embeddings)       (FAISS-указатель)
 TXT)
Как я отучил оракула молоть околесицу про ГОСТы: сказ о doc-rag без единого (почти) заморского слова - 2

Каждое звено — отдельная служба, и каждое можно перековать (rebuild) отдельно от других. Когда я начинал, у меня всё пеклось в одном горшке. Это казалось проще, пока один из ломтей не пришлось перебирать заново — и оказалось, что приходится перебирать всё. Так я и развёл слои.

В крупном плане у меня всего четыре сущности:

  1. Поглощение (doc-rag ingest) — берёт сырые свитки из сундука sources/incoming/, разбирает их в разметную грамоту, режет ломтями, варит толковательные стрелы и набивает ими указатель.

  2. Сыск (doc_search через уговор обмена с оракулом — MCP) — на вопрошание возвращает ломти, наиболее сродные по смыслу.

  3. Светлица для смерда (страница /ui) — горница в глядалке, через которую можно: подкинуть свитки, посмотреть, что уже усвоено, истребить лишнее, перековать указатель.

  4. Дворовый ратник (служба doc-rag-mcp.service) — запускается при загрузке машины и сидит на пристани 3333, поджидая гостей.

Все четыре сущности живут в одной приказной избе: единая служба, которая с одной стороны разговаривает через гонца-проводного (HTTP), а с другой — через сговорное окошко (API) уговора с оракулом. Расщеплять это на отдельные приказы я не стал: не того масштаба задача, чтобы плодить дворовых ратников.

4. Поглощение свитков

Самый длинный путь — от свитка до толковательной стрелы. На нём же — больше всего грабель.

Разбор. На входе у меня пять видов свитков: печатные (.pdf), ново-вордовы (.docx), старо-вордовы (.doc — двоичный формат, устаревший лет двадцать), разметные (.md) и пустословные текстовые (.txt). Под каждый — свой разбиратель. Печатные я разбираю PyMuPDF (с запасным PyPDF2, если первый не справится); ново-вордовы — через python-docx; старо-вордовы — через antiword (а если его нет, то catdoc), который ставится отдельной командой системного управителя. Разметные и пустословные я просто читаю как есть, без затей.

Если свиток оказался лубочной картинкой (скан), на сцену выходит чтец-машинка (OCR-движок Tesseract). Я наказываю ему разбирать три набора: английскую буквицу, русскую буквицу и формулы. Третий, кстати, не всегда заводится из коробки — пакет tesseract-ocr-equ есть не во всех ветвях Линукс-уклада (на Дебиановых хуторах, в Убунтовой слободе и иже с ними). Если его нет, я не падаю в обморок: просто жалуюсь в летопись.

sudo apt install tesseract-ocr tesseract-ocr-eng tesseract-ocr-rus
sudo apt install tesseract-ocr-equ   # может не быть — это не смертельно

Нарезка. На выходе разбора получается единая разметная грамота: с заголовками, абзацами, иногда с таблицами. Дальше я режу её ломтями. Целевой размер ломтя — 512 словесных мер с перекрытием 64. Перекрытие нужно, чтобы смыслы не оборвались на стыке. Конкретные числа я подобрал на пробу, методом промахов и попаданий: крупнее — оракул хуже сосредотачивается, мельче — теряется связь.

В нарезке есть нюанс: я ловлю двойников (duplicates) дважды. На уровне свитка целиком — по печати-256 (SHA256). Если я приношу тот же самый свиток, что уже сидит в переписи, я его не пускаю и сообщаю просителю на светлице желтым стягом: «двойник, пропускаю». Это спасает от сценария «накидал кучей, поглотил, потом перечитываю и ругаюсь, почему один и тот же документ выдаётся четыре раза подряд». А на уровне ломтей — я ещё проверяю похожесть по двусловесным парам (Jaccard). Это ловит уже не точные двойники, а почти-двойники: тот же текст, оформленный чуть-чуть иначе.

Архивирование. После успешного поглощения свиток уходит из сундука sources/incoming/ в сундук sources/archived/ — это похоже на «рассованные по полкам исходники». Так я знаю, что у меня поглощено и откуда оно. По умолчанию — оригинал хранится; если кого-то это раздражает, я положил выключатель в настройки:

sources:
  archive_after_ingest: true     # переносить ли исходник в архивный сундук
  incremental_ingest: true       # пропускать ли свитки, чья печать уже есть

5. Толковательные стрелы и перепись

Тут самое интересное. Я беру каждый ломоть и прогоняю через толкователь-стрельник (модель эмбеддингов). У меня по умолчанию стоит BAAI/bge-large-en-v1.5. Это плотный толкователь о тысяче двадцати четырёх осях: на каждый ломоть он выдаёт стрелу той самой длины. Стрелы у меня нормированы — длина у всех единичная, чтобы потом сравнивать их по углу.

Стрелы сами по себе — пустой звук, пока их некуда складывать. Для складирования я взял стрельбище меченое (FAISS) — это библиотека от исследовательской группы по-ту-сторону-океана, которая умеет хранить миллионы стрел и быстро находить ближайших соседей. Я использую самый простой указатель IndexFlatIP — точный (не приближённый), без обучения, держит всё в памяти и сравнивает скалярным умножением. Для четырёх с половиной тысяч ломтей этого с лихвой хватает; даже до пятидесяти тысяч я не упрусь.

Поиск устроен буднично:

  1. Беру наказ от оракула («о чём спрашиваешь?»).

  2. Прогоняю его через тот же толкователь-стрельник.

  3. Получаю стрелу-наказ.

  4. Натягиваю её на указатель и стреляю в top_k ближайших соседей.

  5. Возвращаю ломти-победители обратно оракулу — пусть пишет ответ опираясь на них, а не на свою память.

Если же толкователь-стрельник в моей машине не заведён (например, при сборке избы «налегке», без тяжелых завязок), служба не падает: она сводит сыск к буквенному виду. Это грубое, но честное сравнение слов с ломтями: считаются частоты, по ним — оценка близости. Лексическим сыск называется не зря: он не знает, что «дроссель» и «катушка» — одно и то же по смыслу, но если в наказе и в ломте совпало слово «предельный», он на это слово отреагирует.

6. Уговор с оракулом

Главное звено всей затеи — уговор обмена с оракулом, а по-заморски — MCP (это пишется как имя собственное, расшифровка для нас сейчас неважна; ниже я буду говорить «уговор»). Я ввёл в него один-единственный инструмент:

  • doc_search(query, top_k=6) — на наказ возвращает упорядоченный по сродству список ломтей с указанием, откуда они вырваны.

Меньше — лучше. Чем больше инструментов у оракула, тем чаще он бывает сбит с толку. У меня — один сыск, и его задача очевидна.

Уговор живёт на пристани 3333 через гонца-проводного: одна и та же тропа принимает и обычные вопрошания (по-заморски — запрос «POST»), и держит открытым ручей уведомлений (запрос «GET», по-заморски — поток SSE). Это не моё изобретение, это обычная договорённость для «удлинённого» уговора. Подробности по слою уговора (расшифровка сетевых грамоток, поведение под разными просителями) я заведу в отдельный свиток с подробной документацией — он поедет вместе с исходниками, когда я их выложу.

Чтобы оракул из писарни увидел мою избу, надо его натравить (в самой писарне это правится свитком настроек ~/.cursor/mcp.json):

{
  "mcpServers": {
    "doc-rag": {
      "transport": "streamableHttp",
      "url": "http://127.0.0.1:3333/mcp"
    }
  }
}

После этого в беседе оракул, спрашивая про мою документацию, сам дёргает мой doc_search. Я узнаю об этом по тому, что в ответе появляются точные цитаты из моих свитков, а не выдуманные строки.

Развилка. В писарне бывает странность: служба числится включённой, а оракул твердит «у меня нет такого инструмента». Лечится прописыванием уговора в общем настроечном свитке (~/.cursor/mcp.json), а не только в свитке проекта. В исходниках на этот случай лежит грамота-скоморох scripts/print_global_mcp_config.sh, которая печатает готовый кусок, а под окошечным укладом с Линукс-постояльцем (Windows и WSL) — её питоновская сестра scripts/write_global_mcp_config.py, потому что от обыкновенного скомороха в этом окружении бывают мусорные переносы.

7. Светлица для смерда

Как я отучил оракула молоть околесицу про ГОСТы: сказ о doc-rag без единого (почти) заморского слова - 3

Командная строка хороша, пока ты — один разработчик. Но я хотел, чтобы этим могли пользоваться и те, кто не дружит с командной строкой и не писал на грамотах-скоморохах. Поэтому я приладил светлицу: одна страница (/ui), без сборщиков, без отдельной парадной горницы — всё в одном свитке, что отдаёт приказная изба.

Что у меня умеет светлица:

  • Подкидывать свитки. Принимаются .pdf, .docx, .doc, .md, .txt. Можно класть пачкой. Если среди принесённого попался двойник — светлица его не пускает, а в верхней части показывает жёлтый стяг со списком двойников: «эти уже сидят в указателе» или «эти уже лежат в очереди на поглощение».

  • Поглотить или перековать. Две пуговки. Поглощение — для новых свитков. Перековка — когда я поменял настройки нарезки или толкователя-стрельника. Оба запускаются в задних рядах (асинхронно); светлица сама опрашивает службу о ходе работы и обновляет стяги.

  • Список усвоенных свитков. Таблица с подписями: имя источника, число ломтей, чем разбирали, доля, прошедшая через чтец-машинку, и когда был поглощён. Можно поглядеть содержимое в виде разметной грамоты, не выходя из светлицы.

  • Истреблять. Около каждой строки — птичка-метка и крестик. Можно истреблять по одному (крестик) или сразу пачкой (галочки + пуговка «истребить выбранных»). Подробности — в следующем разделе.

  • Зона тревожных дел. Внизу — карточка с тремя красными пуговками: «истребить всё», «вычистить сирот» (это когда после неудачной набивки в закромах остались куски, не упомянутые в переписи) и «опустошить сундук-приёмник».

  • Стяг тревоги. Если указателя нет (тяжелее всего), сверху горит красный стяг: «смысловой сыск недоступен — запустите перековку». Рядом — пуговка, чтобы перековку начать одним нажатием. Стяг сам уходит, как только перековка достроится.

Светлица не претендует на красоту: я её рисовал «по-старомосковски» — без сборщиков парадных горниц, без приставок. Простая разметная грамота + маленький встраиваемый кусочек грамоты-скомороха для опросов и пуговок.

8. Истребление и подчистка

Удалять — оказалось интереснее, чем добавлять. На словах: «убрать свиток из переписи». На деле — убрать заодно его ломти, его запись в переписи и его толковательные стрелы из указателя FAISS. С первыми двумя — никакой загвоздки. А вот сам указатель устроен хитро: это плотный массив стрел, и стрелы там не помечаются «удалёнными», а сидят по своим местам.

Можно было бы пойти простым путём: «после каждого истребления — перековывать указатель целиком». Но это плохо. Я уже знаю, сколько длится перековка у меня на машине о восьми работниках, без видеоумельца: три часа и девятнадцать минут на четыре с половиной тысячи ломтей. Каждый раз — три часа? Спасибо, не надо.

Вместо этого я делаю так: читаю старый указатель, через index.reconstruct(i) достаю обратно те самые стрелы (FAISS их хранит и умеет выдавать), отфильтровываю те, чьи ломти приговорены к истреблению, и записываю остаток в новый указатель. Никакой перековки — просто выкладка с прорехами, заклеенными в нужных местах. Десятки секунд вместо трёх часов. На выходе: летопись с описанием, сколько стрел истреблено, сколько сохранено.

Команды у меня доступны и в командной строке, и в светлице:

doc-rag delete <doc_id> [<doc_id>...]   # истребить выборочно
doc-rag wipe --confirm DELETE           # истребить всё начисто
doc-rag clean-orphans                   # вымести сирот
doc-rag clear-incoming                  # опустошить сундук-приёмник

wipe нарочно требует руками написать слово DELETE — иначе он на полпути одумается и откажет. Это — мой собственный засов от тех дней, когда «по привычке нажал не туда».

9. Когда указатель пропал: история про осаживание потока

Как я отучил оракула молоть околесицу про ГОСТы: сказ о doc-rag без единого (почти) заморского слова - 4

Самая ценная для рассказа история произошла, когда я допиливал именно этот раздел кода. Я уже всё описал в предыдущем разделе — ан нет, сначала надо рассказать, как это работало не так.

Изначально, когда я писал смысловой сыск, я добавил такую «удобную» вещь: если в момент вопрошания указателя нет (его, например, перековали в предыдущем подходе и недоделали), служба сама начинает перековку из исходных ломтей прямо в обработчике гонца-проводного. Заодно отвечает просителю буквенным сыском — и продолжает в задних рядах ковать.

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

И вот что случилось. Я нажал «перековку» в светлице. Моя свежая аккуратненькая правка, удаляющая устаревший указатель перед перековкой, сработала. Указатель — стёрся. Перековка пошла. И тут случилось то, что у всякого разработчика называется «не вовремя»: я нажал перековку повторно (или промахнулся, или захотел отменить — уже не помню). Старая перековка прервалась. Новая пошла с нуля.

А смысловой сыск, не находя указателя, начал — в каждом гонцовом обращении! — выковывать его заново. Параллельно. С каждым новым гостем, с каждым опросом стяга. Запросы стояли в очереди по минуте. По две. По шесть с половиной. Шестеро из восьми моих работников взмылены до пены, седьмой подключается. Кладовая раздулась до пяти с половиной гигабайт и пухнет дальше. Просители ждут, переспрашивают, посылают новые наказы — те тоже становятся в очередь и тоже начинают перековку.

Кто-то жалуется мне в общей беседе: «у тебя там такие тайм… — ой, прости — таких истёкших сроков набралось, что мне один ответ вернули только через шестьдесят секунд». Я лезу в летопись — а там всё ясно.

Лечится двумя добавками сразу. Во-первых, я выкинул автоковку из обработчика сыска. Если указателя нет — сыск возвращает «не нашёл», а снаружи (поверх него) уже стоит развилка: сводить сыск к буквенному виду или возвращать пустой ответ. Во-вторых, я сделал так, что повторное нажатие на «перековку», пока предыдущая не закончилась, получает в ответ короткое «занят, попробуй позже» (по гонцовым правилам — помер 409). Никакой параллельной ковки в обработчике гонца теперь нет. Любая ковка живёт в задних рядах, и в каждый момент времени её — ровно одна.

И последнее — то, что мне дороже всего: я выставил это наружу. Если хранилище премудрости работает в неполном виде (нет указателя, сыск свёлся к буквенному), служба:

  1. В сговорном окошке для оракула — приклеивает к ответу предупреждающий ломоть текста: «смысловой сыск недоступен, ниже — буквенный сыск, он часто хуже». Оракул это видит вместе с результатами и сам предупреждает просителя.

  2. В светлице — выкидывает наверху красный стяг с пуговкой «перековать»; стяг живой, привязан к опросу состояния — пропадает, как только указатель снова на месте.

Эту двухканальную сигнализацию я считаю самым важным уроком всей затеи. Никаких «тихих понижений качества»: всякий просящий должен знать, что ему сейчас отвечают не в полную силу, и должен видеть, чем это исправить.

10. Развёртывание по весям

У меня doc-rag живёт прямо у меня на дворе: в углу стоит отдельная приказная изба, и в ней сидит дворовый ратник (systemd-служба). Просыпается с первыми петухами (то есть при загрузке избы), отвечает на наказы из писарни по тропе, что за ворота не выходит — наружу ему глядеть незачем. Можно гонять снасть и прямо из исходников — это удобно, когда сам же её и точишь. А кто любит сосуды переносные — в исходниках лежит docker-compose.yml: одна команда, и сосуд встал.

Главный путь, которым я хожу сам — собственный сценарий scripts/install_server_native.sh:

sudo bash scripts/install_server_native.sh

Он:

  • ставит системные потребы (tesseract-ocr*, antiword, python3-venv, build-essential);

  • заводит дворового жителя с именем docrag;

  • переписывает исходники в /opt/doc-rag-mcp/, бережно не трогая существующий сундук build/ (там сидит указатель и перепись — было бы обидно их сшибать при каждом обновлении);

  • запускает грамоту-скомороха scripts/bootstrap.sh без расспросов (опрос по умолчанию: смысловой сыск нужен, чтец-машинка нужен, толкователь-стрельник без видеоумельца);

  • ставит и запускает дворового ратника doc-rag-mcp.service;

  • кладёт настройки в /etc/default/doc-rag (только на первом запуске — чтобы не сшибать местные правки).

Тут легко споткнуться, если у тебя на машине больше одного питона. Поэтому сценарий жёстко гоняет всё внутри подсундука .venv/ (это такое «мнимое житьё» питона в своём углу). А оба сценария установки толкователя стрельника (scripts/install_torch_cpu.sh и ..._gpu.sh) тоже зовут .venv/bin/python напрямую — иначе на свежих Дебиановых хуторах (и в выросших оттуда же новых ветвях) у ворот встаёт строгая стража: «снаружи управляемая среда» (по-заморски — PEP 668) — и ни шагу тебе во двор, ничего не поставишь.

Обновлять — просто:

git pull && sudo bash scripts/install_server_native.sh

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

11. Что я понял по дороге (грабли)

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

  • Не ковать в обработчике гонца. Любая операция дольше нескольких секунд должна жить в задних рядах. Если кто-то стучит в дверь, пока предыдущая ковка идёт, — ответ «занят». Подробности в разделе 9.

  • Понижение качества — наружу, а не в журнале летописи. Двухканальный стяг (в сговорном окошке для оракула и в светлице для смерда) спас меня от целого класса обращений «у вас отвечает мимо». Когда просителю с самого начала сказано, что сейчас не в полную силу, он не приходит с жалобами.

  • Старо-вордовы умеют падать на мелких свитках. antiword на совсем мелком .doc ругается: «text stream too small to handle». Это не моя оплошность — это особенность самого antiword. Я в таких случаях прикрепляю к свитку немного «болванного» текста — и разбор проходит. Знание было выстрадано.

  • Перепись и сундук archived/ иногда разъезжаются. Если я в переписи записал свиток с путём sources/incoming/foo.pdf, а потом служба после поглощения переехала с ним в sources/archived/foo.pdf — на истреблении я могу не найти оригинала по записанному пути. Лечится тем, что истребление пытается тот же свиток и по записанному пути, и в обоих сундуках по короткому имени. Это типичный «дрейф пути», и любому, кто работает с поглощением и архивированием, я советую сразу это учитывать.

  • Перекуй указатель — не перекуй стрелы. Когда я истребляю свиток, мне не нужно гонять снова толкователь-стрельник. Я просто читаю из старого указателя нужные стрелы (index.reconstruct(i)) и складываю остаток в новый. Этим я экономлю те самые часы.

  • Печать-256 — это не только про двойников. По той же печати я определяю, что свиток уже был поглощён в прошлый раз и нет смысла его снова разбирать. Поглощение становится прирастающим: добавил пять — поглотились пять, остальное служба узнала и пропустила.

12. Слово на прощанье

Если бы меня спросили, какую одну вещь я бы хотел донести этим рассказом — это была бы не архитектура и не выкрутасы с перепиской указателя. Скорее так:

Когда что-то у меня в избе работает не в полную силу, я не молчу. Я приклеиваю предупреждение к ответам и вешаю стяг в светлице.

Так живёт у меня не только doc-rag — так живёт теперь всё, что я после этого случая складываю. Если в твоей снасти есть тихая развилка, где она могла бы шепнуть «сейчас отвечу хуже обычного», но молчит — её туда и поправлять первым делом.

Имя снасти — doc-rag. Пока она у меня в личных закромах и ходит только по моим избам. Готовлю её на люди: причёсываю настроечные свитки, выметаю внутренние тропки, дописываю в README указатели на расширенные свитки в сундуке docs/ (установка, командная строка, уговор обмена с оракулом, светлица, развёртывание по весям, бесы и их изгнание). Как выложу — отдельным словом приду сюда же и положу тропу к избе в обновлённую часть статьи. А кто хочет узнать первым — оставляйте весточку в обсуждении или личной грамотой: позову, как снасть выйдет на люди.

А если кому-то этот рассказ показался шуточным сверх меры — на вкус и цвет товарища нет. По мне, про мастеровое дело хорошо иногда писать так, словно сидишь у печки и объясняешь живому соседу, а не читаешь доклад с трибуны. Чужие слова часто заволакивают простую суть. Оттого я и взялся за это переводное упражнение: чтобы за «осаживанием потока» (а оно — про backpressure) разглядеть, что речь идёт об усмирении зарвавшегося потока, а не о городовой осаде. И что половина наших чужестранных слов — про то же самое, только в сложной обёртке.

На этом — кланяюсь.

Автор: trgv

Источник