diff --git a/README.md b/README.md index 3f83f98..ddf2b51 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ │ RabbitMQ│ Celery задачи ┌────────────▼──┐ ┌──▼──────────────┐ │ worker-gpu │ │ worker-indexer │ - │ (CUDA/FAISS) │ │ (PDF/DOCX parse) │ + │ (FAISS, CPU) │ │ (PDF/DOCX parse) │ │ Sem. search │ │ Winnowing/MinHash │ │ LLM paraphrase│ └──────────────────┘ └────────────────┘ @@ -35,7 +35,8 @@ Инфраструктура: PostgreSQL 16 · Redis 7 · RabbitMQ 3 · Elasticsearch 8 - MinIO (S3) · Ollama (qwen2.5:7b) · отдельный GPU-сервер + MinIO (S3) · Ollama с bge-m3 на отдельном сервере эмбеддингов (192.168.1.40) + LLM-парафраз (L4) — Ollama или OpenRouter, переключается LLM_BACKEND ``` ## Быстрый старт diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 6550745..eeff53e 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -46,7 +46,7 @@ │ worker- │ │ PostgreSQL 1.38 · Redis 1.35 │ │ notifier │ │ RabbitMQ .82 · MinIO 1.21 │ │ SMTP email │ │ Elasticsearch (local 1.32) │ - └────────────┘ │ Ollama .109 (bge-m3, эмбед.) │ + └────────────┘ │ Ollama 1.40 (bge-m3, эмбед.) │ │ OpenRouter/DeepSeek — LLM (опц.)│ │ [opt] Qdrant · Prometheus/Graf.│ └──────────────────────────────┘ @@ -168,7 +168,8 @@ MinIO (`corpus-upload/`) → `index.ingest_upload` (извлечь текст | Redis 7 | 1.35 (выделенный LXC) | кэш, rate-limits, LSH | | RabbitMQ | 192.168.20.82 | брокер Celery | | MinIO (S3) | 1.21 | документы, full-text, бэкапы | -| Ollama (bge-m3, эмбеддинги) | 192.168.20.109 «embedding-gpu» | RX580; известный failure mode — amdgpu ring timeout вешает Vulkan-контекст, лечится `systemctl restart ollama` | +| Ollama (bge-m3, эмбеддинги) | 192.168.1.40 «embedding-cpu» (VM 210 на хосте pve2 .1.37) | Xeon 4314, AVX-512+VNNI, 8 vCPU; модель всегда в RAM (`OLLAMA_KEEP_ALIVE=-1`), сторожевой таймер раз в 2 мин перезапускает зависшую Ollama. Замер на данных корпуса: 3.6 док/с против 2.2 у прежнего сервера | +| ~~Ollama на GPU~~ (выведен 31.08.2026) | 192.168.20.109 «embedding-gpu» | RX580; отказал по amdgpu ring timeout (Vulkan-контекст умирал при живом systemd-юните). Оставлен как есть — откат сводится к возврату `OLLAMA_URL` в Infisical | | OpenRouter (DeepSeek, L4 LLM) | облако | опционально вместо локальной Ollama (`LLM_BACKEND=openrouter`); из РФ доступен только через `singbox-proxy` | | Infisical (секреты) | 192.168.20.111 «VM111» | self-hosted, `infisical.jze9.ru`; единственный источник правды для `.env` на проде | | Qdrant / Prometheus / Grafana | 1.32 | опционально, под compose-профилями | diff --git a/docs/DIAGRAM.md b/docs/DIAGRAM.md index 046fda0..c89545b 100644 --- a/docs/DIAGRAM.md +++ b/docs/DIAGRAM.md @@ -31,7 +31,7 @@ flowchart TB PG[("PostgreSQL 1.38
users/tasks/documents/
fingerprints/...")] REDIS[("Redis 1.35
кэш · rate-limit · LSH-индекс")] MINIO[("MinIO 1.21
документы · full-text · бэкапы")] - OLLAMA["Ollama .109 embedding-gpu
bge-m3, эмбеддинги (L3)"] + OLLAMA["Ollama 1.40 embedding-cpu
bge-m3, эмбеддинги (L3)"] OPENROUTER["OpenRouter/DeepSeek
LLM L4 (опц., через singbox-proxy)"] INFISICAL["Infisical .111
секреты → .env на каждом деплое"] end diff --git a/docs/DR-HA.md b/docs/DR-HA.md index 3174195..1e04880 100644 --- a/docs/DR-HA.md +++ b/docs/DR-HA.md @@ -55,14 +55,16 @@ Redis у нас — кэш/rate-limits/LSH-индекс (префикс `antipla | Векторный индекс | было | `VECTOR_BACKEND=qdrant` снимает (см. README) ✅ | | RabbitMQ .82 | да | мониторинг ловит падение ✅; кластер — по потребности | | app/ES 1.32 | да | воркеры горизонтальны; ES single-node (для BM25 не критично) | -| Хост Proxmox .254 | **да, широкий** | митигации нет — см. ниже | +| Хост Proxmox .254 | **да, широкий** | эмбеддинги вынесены на pve2 ✅; брокер/прокси/секреты — нет, см. ниже | **Гипервизор — самая широкая единая точка отказа.** На хосте `192.168.20.254` -одновременно живут RabbitMQ (.82), embedding-gpu (.109), Infisical (.111) и -CT 102 (.253) — фронтенд и реверс-прокси. Его падение снимает сразу: приём и -обработку задач (нет брокера), эмбеддинги (нет Ollama), деплой (нет Infisical и -Gitea) и весь публичный доступ (нет прокси) — при том что API, PostgreSQL и -MinIO продолжают работать. Проверено на практике 2026-08-28: хост перестал +одновременно живут RabbitMQ (.82), Infisical (.111) и CT 102 (.253) — фронтенд +и реверс-прокси. С 31.08.2026 эмбеддинги отсюда вынесены: сервер `embedding-cpu` +(192.168.1.40) стоит на другом физическом хосте (pve2, .1.37), так что падение +.254 больше не уносит с собой L3. Его падение по-прежнему снимает сразу: приём +и обработку задач (нет брокера), деплой (нет Infisical и Gitea) и весь публичный +доступ (нет прокси) — при том что API, PostgreSQL, MinIO и сервер эмбеддингов +продолжают работать. Проверено на практике 2026-08-28: хост перестал отвечать даже на ARP, всё перечисленное отвалилось разом, данные не пострадали. Разнести хотя бы прокси/брокер по разным физическим хостам — самая дешёвая мера; пока её нет, восстановление требует физического доступа к железу. @@ -77,7 +79,8 @@ MinIO продолжают работать. Проверено на практ (`restart: unless-stopped`) поднимает его заново, недоставленное сообщение передоставляется — и цикл повторяется бесконечно, монополизируя весь пул воркера (было и на `worker-indexer` из-за backoff `scripts/parsers/openalex.py`, и на -`worker-gpu` из-за зависшей Ollama на embedding-gpu). +`worker-gpu` из-за зависшей Ollama на прежнем embedding-gpu; на новом сервере +эмбеддингов зависание закрыто сторожевым таймером на самой машине). Митигации (2026-08-27, сделано для `worker-indexer`): diff --git a/docs/INGESTION.md b/docs/INGESTION.md index 75b7959..f0955bb 100644 --- a/docs/INGESTION.md +++ b/docs/INGESTION.md @@ -1,16 +1,27 @@ # Наполнение корпуса — runbook -## Текущее состояние (на 2026-08-28) +## Текущее состояние (на 2026-08-31) -- **177 147 документов**, русский большинство: `ru` 99 884, `en` 76 936, +- **177 247 документов**, русский большинство: `ru` 99 884, `en` 77 036, остальные языки — единицы/десятки. Проблема «не с чем сравнивать русские работы» из более ранней версии этого документа закрыта. -- По источникам: CyberLeninka 99 883, OpenAlex 49 276, PMC 14 910, arXiv 13 077, +- По источникам: CyberLeninka 99 883, OpenAlex 49 276, PMC 15 010, arXiv 13 077, `user_submission` (проверенные пользователями работы, не источник для сравнения сами с собой — см. ARCHITECTURE.md §7) 1. - Повторный прогон по уже залитым источникам даёт почти одни дубли (типично 1490 из 1500 на источник): прирост дают только новые публикации. Реальный рост корпуса — поднятый `limit` или новые темы, а не повторный запуск. +- OpenAlex при массовом запуске упирается в лимит вежливого пула (10 req/s + на mailto, общий для всех воркеров) — пауза между страницами поднята до 1с. +- Добавлен 4-й парсер — **PMC** (PubMed Central, `scripts/parsers/pmc.py`), + англоязычные научные статьи открытого доступа. +- Массовое расширение по дисциплинам теперь двумя сидерами: `seed_ru_sources.py` + (русскоязычные, CyberLeninka/OpenAlex `lang=ru`) и **`scripts/seed_broad_corpus.py`** + (англоязычные — OpenAlex/arXiv/PMC по широкому списку дисциплин, добавлен позже + первой волны). +- Операционный риск массовой докачки (`consumer_timeout` RabbitMQ vs долгие таски) + закрыт бюджетом времени прогона и `worker_prefetch_multiplier=1` — подробности + и почему префетч тут главный, см. [DR-HA.md](DR-HA.md) §6. ### Глубина индексации — чем реально располагает детекция (замер 2026-08-28) @@ -31,25 +42,35 @@ найдёт, хотя сервис рассчитан именно на русских студентов. Причина не в алгоритме: search API отдаёт только аннотацию и OCR-фрагмент (~700 символов), а `url` ведёт на HTML-страницу — докачка по нему бесполезна (замер: 0 из 8). -Лечится `scripts/ops/backfill_cyberleninka_pdf.py`: PDF доступен прямым адресом -`{url}/pdf` (проверено: 44 из 50, в среднем 23 тыс. символов, глубина 30 → ~1450 -отпечатков). Полный прогон — около 48 часов при вежливом 1 req/s и порядка -+220 млн строк в `fingerprints` (~22 ГБ; место на сервере БД проверять заранее). +Частично лечится `scripts/ops/backfill_cyberleninka_pdf.py`: PDF доступен прямым +адресом `{url}/pdf` (проверено: 44 из 50, в среднем 23 тыс. символов, глубина +30 → ~1450 отпечатков). -Покрытие L3 на ту же дату — 93 053 вектора (52.5% корпуса); ещё 60 993 документа -числились векторизованными ошибочно, отметки сброшены `faiss_reconcile.py`. -- OpenAlex при массовом запуске упирается в лимит вежливого пула (10 req/s - на mailto, общий для всех воркеров) — пауза между страницами поднята до 1с. -- Добавлен 4-й парсер — **PMC** (PubMed Central, `scripts/parsers/pmc.py`), - англоязычные научные статьи открытого доступа. -- Массовое расширение по дисциплинам теперь двумя сидерами: `seed_ru_sources.py` - (русскоязычные, CyberLeninka/OpenAlex `lang=ru`) и **`scripts/seed_broad_corpus.py`** - (англоязычные — OpenAlex/arXiv/PMC по широкому списку дисциплин, добавлен позже - первой волны). -- Операционный риск массовой докачки (`consumer_timeout` RabbitMQ vs долгие таски) - закрыт бюджетом времени прогона и `worker_prefetch_multiplier=1` — подробности - и почему префетч тут главный, см. [DR-HA.md](DR-HA.md) §6. +**Но массовый прогон упирается в защиту сайта.** Замер 2026-08-28: первые ~130 +статей скачались штатно, дальше КиберЛенинка перестала отдавать PDF и начала +возвращать HTML-заглушку ~5.7 КБ — счётчик успехов замер на 122, скрипт работал +вхолостую. Расчёт «48 часов на 100 тысяч при 1 req/s» этим опровергнут. Чтобы +углубить русскую часть корпуса, нужен другой подход: заметно большие паузы, +разные исходящие адреса или договорённость с источником. +Покрытие L3: 60 993 документа числились векторизованными ошибочно — отметки +сброшены `faiss_reconcile.py`, после чего 31.08 запущен пересчёт всех 84 094 +документов без вектора. Считает новый сервер эмбеддингов (192.168.1.40, +3.6 док/с против 2.2 у прежнего), полный проход занимает около 6.5 часов. + +### Массовая заливка: bulk вместо API + +Постраничные API дают 1-2 статьи в секунду и упираются в rate limit — миллионы +так не залить. Для объёма есть `scripts/ops/bulk_ingest_pmc.py`: открытый бакет +`pmc-oa-opendata` (ключ не нужен) отдаёт у каждой статьи готовый извлечённый +текст. Проверено 31.08: 100 статей, 183 090 отпечатков — 1831 на статью, то есть +настоящая глубина, а не аннотация. + +Ограничение по темпу: 0.3 статьи/с, миллион в один поток — около 40 суток. +Скачивание тут не узкое место (12 статей/с в 12 потоков), упирается в +последовательную обработку документа; для миллионов её надо распараллелить по +ядрам воркера. Планировать объём: 1 млн статей ≈ 2.5 млрд отпечатков ≈ 400 ГБ +в базе с индексами (сейчас 113 млн строк занимают 12 ГБ). ## Что подготовлено - **Парсер CyberLeninka починен** (`scripts/parsers/cyberleninka.py`): раньше слал GET @@ -124,7 +145,10 @@ SELECT source, count(*) FROM documents WHERE source='cyberleninka'; -- > 0 - Больше тем + выше `--limit`; добавить OpenAlex `lang=ru` (качество ниже — англ. заголовки с меткой ru). -- Для миллионов — **bulk** (снапшот OpenAlex на S3), а не постраничный API. +- Для миллионов — **bulk**, а не постраничный API. Снапшот OpenAlex для этого не + годится: там только метаданные, а миллионы аннотаций детекции не дают (см. + провал КиберЛенинки выше). Рабочий источник полных текстов — бакет + `pmc-oa-opendata`, см. «Массовая заливка» выше. - На масштабе обязателен `VECTOR_BACKEND=qdrant` (FAISS flat не тянет), а таблица `fingerprints` (уже ~113M строк на 177K доков — партиционирование стоит планировать заранее, не постфактум) потребует партиционирования. См. diff --git a/scripts/ops/bulk_ingest_pmc.py b/scripts/ops/bulk_ingest_pmc.py index 1a584e6..0bff506 100755 --- a/scripts/ops/bulk_ingest_pmc.py +++ b/scripts/ops/bulk_ingest_pmc.py @@ -25,12 +25,12 @@ залитые пропускаются. Прогресс листинга сохраняется в --state-file, поэтому прерванная заливка продолжается с той же страницы бакета. -СТАТУС: НЕ ЗАКОНЧЕН. Проба на 200 статьях: документы вставляются верно -(проверено), но шаг отпечатков ошибочно считает свежевставленные документы уже -обработанными — проверка «есть ли отпечатки» срабатывает там, где их нет, и -COPY не выполняется. Пробные записи из базы удалены. До отладки этого места -скрипт запускать на объёме нельзя: он зальёт документы без отпечатков, то есть -невидимые для L1. +Проверено на проде 2026-08-31: 100 статей залито, 183 090 отпечатков (1831 на +статью — реальная глубина, а не аннотация). Темп — 0.3 статьи/с, то есть +миллион в один поток занял бы около 40 суток: скачивание тут не узкое место +(12 статей/с в 12 потоков), упирается в последовательную обработку одного +документа. Для миллионов обработку нужно распараллелить по ядрам воркера — +это следующий шаг, до него скрипт годится для порций в десятки тысяч. """ import argparse