docs: сервер эмбеддингов переехал на .1.40 + честные итоги проверок
Прод переключён на новый сервер эмбеддингов (VM 210 «embedding-cpu», 192.168.1.40, хост pve2). Ключевое, чего не было в исходном плане переключения: OLLAMA_URL живёт в Infisical, и deploy.sh генерирует .env из него на каждом запуске — правка .env на сервере откатилась бы первым же деплоем. Совместимость векторов проверена прямым сравнением, а не на слово: косинус 0.9999997, расхождение 1e-4 (округление AVX2 против AVX-512) — переиндексация 93 тыс. векторов не понадобилась. Документация приведена в соответствие с фактами: - ARCHITECTURE/DIAGRAM/README/CREDENTIALS: новый сервер, старый помечен как выведенный; убрано упоминание CUDA — GPU в проекте нет; - DR-HA: эмбеддинги больше не висят на хосте .254, чьё падение 28.08 разом унесло брокер, прокси и секреты; - INGESTION: исправлено собственное враньё про КиберЛенинку — «48 часов на 100 тысяч» опровергнуто практикой, сайт блокирует выкачку после ~130 статей; снапшот OpenAlex вычеркнут как путь к миллионам (там только метаданные). bulk_ingest_pmc.py: снята пометка «не закончен» — бага не было, первый прогон упал уже после успешной вставки, а второй корректно пропустил дубли. Проверено: 100 статей, 183 090 отпечатков. Реальный темп 0.3 ст/с записан честно. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,7 +23,7 @@
|
|||||||
│ RabbitMQ│ Celery задачи
|
│ RabbitMQ│ Celery задачи
|
||||||
┌────────────▼──┐ ┌──▼──────────────┐
|
┌────────────▼──┐ ┌──▼──────────────┐
|
||||||
│ worker-gpu │ │ worker-indexer │
|
│ worker-gpu │ │ worker-indexer │
|
||||||
│ (CUDA/FAISS) │ │ (PDF/DOCX parse) │
|
│ (FAISS, CPU) │ │ (PDF/DOCX parse) │
|
||||||
│ Sem. search │ │ Winnowing/MinHash │
|
│ Sem. search │ │ Winnowing/MinHash │
|
||||||
│ LLM paraphrase│ └──────────────────┘
|
│ LLM paraphrase│ └──────────────────┘
|
||||||
└────────────────┘
|
└────────────────┘
|
||||||
@@ -35,7 +35,8 @@
|
|||||||
|
|
||||||
Инфраструктура:
|
Инфраструктура:
|
||||||
PostgreSQL 16 · Redis 7 · RabbitMQ 3 · Elasticsearch 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
|
||||||
```
|
```
|
||||||
|
|
||||||
## Быстрый старт
|
## Быстрый старт
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
│ worker- │ │ PostgreSQL 1.38 · Redis 1.35 │
|
│ worker- │ │ PostgreSQL 1.38 · Redis 1.35 │
|
||||||
│ notifier │ │ RabbitMQ .82 · MinIO 1.21 │
|
│ notifier │ │ RabbitMQ .82 · MinIO 1.21 │
|
||||||
│ SMTP email │ │ Elasticsearch (local 1.32) │
|
│ SMTP email │ │ Elasticsearch (local 1.32) │
|
||||||
└────────────┘ │ Ollama .109 (bge-m3, эмбед.) │
|
└────────────┘ │ Ollama 1.40 (bge-m3, эмбед.) │
|
||||||
│ OpenRouter/DeepSeek — LLM (опц.)│
|
│ OpenRouter/DeepSeek — LLM (опц.)│
|
||||||
│ [opt] Qdrant · Prometheus/Graf.│
|
│ [opt] Qdrant · Prometheus/Graf.│
|
||||||
└──────────────────────────────┘
|
└──────────────────────────────┘
|
||||||
@@ -168,7 +168,8 @@ MinIO (`corpus-upload/`) → `index.ingest_upload` (извлечь текст
|
|||||||
| Redis 7 | 1.35 (выделенный LXC) | кэш, rate-limits, LSH |
|
| Redis 7 | 1.35 (выделенный LXC) | кэш, rate-limits, LSH |
|
||||||
| RabbitMQ | 192.168.20.82 | брокер Celery |
|
| RabbitMQ | 192.168.20.82 | брокер Celery |
|
||||||
| MinIO (S3) | 1.21 | документы, full-text, бэкапы |
|
| 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` |
|
| OpenRouter (DeepSeek, L4 LLM) | облако | опционально вместо локальной Ollama (`LLM_BACKEND=openrouter`); из РФ доступен только через `singbox-proxy` |
|
||||||
| Infisical (секреты) | 192.168.20.111 «VM111» | self-hosted, `infisical.jze9.ru`; единственный источник правды для `.env` на проде |
|
| Infisical (секреты) | 192.168.20.111 «VM111» | self-hosted, `infisical.jze9.ru`; единственный источник правды для `.env` на проде |
|
||||||
| Qdrant / Prometheus / Grafana | 1.32 | опционально, под compose-профилями |
|
| Qdrant / Prometheus / Grafana | 1.32 | опционально, под compose-профилями |
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ flowchart TB
|
|||||||
PG[("PostgreSQL 1.38<br/>users/tasks/documents/<br/>fingerprints/...")]
|
PG[("PostgreSQL 1.38<br/>users/tasks/documents/<br/>fingerprints/...")]
|
||||||
REDIS[("Redis 1.35<br/>кэш · rate-limit · LSH-индекс")]
|
REDIS[("Redis 1.35<br/>кэш · rate-limit · LSH-индекс")]
|
||||||
MINIO[("MinIO 1.21<br/>документы · full-text · бэкапы")]
|
MINIO[("MinIO 1.21<br/>документы · full-text · бэкапы")]
|
||||||
OLLAMA["Ollama .109 embedding-gpu<br/>bge-m3, эмбеддинги (L3)"]
|
OLLAMA["Ollama 1.40 embedding-cpu<br/>bge-m3, эмбеддинги (L3)"]
|
||||||
OPENROUTER["OpenRouter/DeepSeek<br/>LLM L4 (опц., через singbox-proxy)"]
|
OPENROUTER["OpenRouter/DeepSeek<br/>LLM L4 (опц., через singbox-proxy)"]
|
||||||
INFISICAL["Infisical .111<br/>секреты → .env на каждом деплое"]
|
INFISICAL["Infisical .111<br/>секреты → .env на каждом деплое"]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -55,14 +55,16 @@ Redis у нас — кэш/rate-limits/LSH-индекс (префикс `antipla
|
|||||||
| Векторный индекс | было | `VECTOR_BACKEND=qdrant` снимает (см. README) ✅ |
|
| Векторный индекс | было | `VECTOR_BACKEND=qdrant` снимает (см. README) ✅ |
|
||||||
| RabbitMQ .82 | да | мониторинг ловит падение ✅; кластер — по потребности |
|
| RabbitMQ .82 | да | мониторинг ловит падение ✅; кластер — по потребности |
|
||||||
| app/ES 1.32 | да | воркеры горизонтальны; ES single-node (для BM25 не критично) |
|
| app/ES 1.32 | да | воркеры горизонтальны; ES single-node (для BM25 не критично) |
|
||||||
| Хост Proxmox .254 | **да, широкий** | митигации нет — см. ниже |
|
| Хост Proxmox .254 | **да, широкий** | эмбеддинги вынесены на pve2 ✅; брокер/прокси/секреты — нет, см. ниже |
|
||||||
|
|
||||||
**Гипервизор — самая широкая единая точка отказа.** На хосте `192.168.20.254`
|
**Гипервизор — самая широкая единая точка отказа.** На хосте `192.168.20.254`
|
||||||
одновременно живут RabbitMQ (.82), embedding-gpu (.109), Infisical (.111) и
|
одновременно живут RabbitMQ (.82), Infisical (.111) и CT 102 (.253) — фронтенд
|
||||||
CT 102 (.253) — фронтенд и реверс-прокси. Его падение снимает сразу: приём и
|
и реверс-прокси. С 31.08.2026 эмбеддинги отсюда вынесены: сервер `embedding-cpu`
|
||||||
обработку задач (нет брокера), эмбеддинги (нет Ollama), деплой (нет Infisical и
|
(192.168.1.40) стоит на другом физическом хосте (pve2, .1.37), так что падение
|
||||||
Gitea) и весь публичный доступ (нет прокси) — при том что API, PostgreSQL и
|
.254 больше не уносит с собой L3. Его падение по-прежнему снимает сразу: приём
|
||||||
MinIO продолжают работать. Проверено на практике 2026-08-28: хост перестал
|
и обработку задач (нет брокера), деплой (нет Infisical и Gitea) и весь публичный
|
||||||
|
доступ (нет прокси) — при том что API, PostgreSQL, MinIO и сервер эмбеддингов
|
||||||
|
продолжают работать. Проверено на практике 2026-08-28: хост перестал
|
||||||
отвечать даже на ARP, всё перечисленное отвалилось разом, данные не пострадали.
|
отвечать даже на ARP, всё перечисленное отвалилось разом, данные не пострадали.
|
||||||
Разнести хотя бы прокси/брокер по разным физическим хостам — самая дешёвая
|
Разнести хотя бы прокси/брокер по разным физическим хостам — самая дешёвая
|
||||||
мера; пока её нет, восстановление требует физического доступа к железу.
|
мера; пока её нет, восстановление требует физического доступа к железу.
|
||||||
@@ -77,7 +79,8 @@ MinIO продолжают работать. Проверено на практ
|
|||||||
(`restart: unless-stopped`) поднимает его заново, недоставленное сообщение
|
(`restart: unless-stopped`) поднимает его заново, недоставленное сообщение
|
||||||
передоставляется — и цикл повторяется бесконечно, монополизируя весь пул воркера
|
передоставляется — и цикл повторяется бесконечно, монополизируя весь пул воркера
|
||||||
(было и на `worker-indexer` из-за backoff `scripts/parsers/openalex.py`, и на
|
(было и на `worker-indexer` из-за backoff `scripts/parsers/openalex.py`, и на
|
||||||
`worker-gpu` из-за зависшей Ollama на embedding-gpu).
|
`worker-gpu` из-за зависшей Ollama на прежнем embedding-gpu; на новом сервере
|
||||||
|
эмбеддингов зависание закрыто сторожевым таймером на самой машине).
|
||||||
|
|
||||||
Митигации (2026-08-27, сделано для `worker-indexer`):
|
Митигации (2026-08-27, сделано для `worker-indexer`):
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,27 @@
|
|||||||
# Наполнение корпуса — runbook
|
# Наполнение корпуса — 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` (проверенные пользователями работы, не источник для сравнения
|
`user_submission` (проверенные пользователями работы, не источник для сравнения
|
||||||
сами с собой — см. ARCHITECTURE.md §7) 1.
|
сами с собой — см. ARCHITECTURE.md §7) 1.
|
||||||
- Повторный прогон по уже залитым источникам даёт почти одни дубли (типично
|
- Повторный прогон по уже залитым источникам даёт почти одни дубли (типично
|
||||||
1490 из 1500 на источник): прирост дают только новые публикации. Реальный
|
1490 из 1500 на источник): прирост дают только новые публикации. Реальный
|
||||||
рост корпуса — поднятый `limit` или новые темы, а не повторный запуск.
|
рост корпуса — поднятый `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)
|
### Глубина индексации — чем реально располагает детекция (замер 2026-08-28)
|
||||||
|
|
||||||
@@ -31,25 +42,35 @@
|
|||||||
найдёт, хотя сервис рассчитан именно на русских студентов. Причина не в
|
найдёт, хотя сервис рассчитан именно на русских студентов. Причина не в
|
||||||
алгоритме: search API отдаёт только аннотацию и OCR-фрагмент (~700 символов), а
|
алгоритме: search API отдаёт только аннотацию и OCR-фрагмент (~700 символов), а
|
||||||
`url` ведёт на HTML-страницу — докачка по нему бесполезна (замер: 0 из 8).
|
`url` ведёт на HTML-страницу — докачка по нему бесполезна (замер: 0 из 8).
|
||||||
Лечится `scripts/ops/backfill_cyberleninka_pdf.py`: PDF доступен прямым адресом
|
Частично лечится `scripts/ops/backfill_cyberleninka_pdf.py`: PDF доступен прямым
|
||||||
`{url}/pdf` (проверено: 44 из 50, в среднем 23 тыс. символов, глубина 30 → ~1450
|
адресом `{url}/pdf` (проверено: 44 из 50, в среднем 23 тыс. символов, глубина
|
||||||
отпечатков). Полный прогон — около 48 часов при вежливом 1 req/s и порядка
|
30 → ~1450 отпечатков).
|
||||||
+220 млн строк в `fingerprints` (~22 ГБ; место на сервере БД проверять заранее).
|
|
||||||
|
|
||||||
Покрытие L3 на ту же дату — 93 053 вектора (52.5% корпуса); ещё 60 993 документа
|
**Но массовый прогон упирается в защиту сайта.** Замер 2026-08-28: первые ~130
|
||||||
числились векторизованными ошибочно, отметки сброшены `faiss_reconcile.py`.
|
статей скачались штатно, дальше КиберЛенинка перестала отдавать PDF и начала
|
||||||
- OpenAlex при массовом запуске упирается в лимит вежливого пула (10 req/s
|
возвращать HTML-заглушку ~5.7 КБ — счётчик успехов замер на 122, скрипт работал
|
||||||
на mailto, общий для всех воркеров) — пауза между страницами поднята до 1с.
|
вхолостую. Расчёт «48 часов на 100 тысяч при 1 req/s» этим опровергнут. Чтобы
|
||||||
- Добавлен 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.
|
|
||||||
|
|
||||||
|
Покрытие 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
|
- **Парсер CyberLeninka починен** (`scripts/parsers/cyberleninka.py`): раньше слал GET
|
||||||
@@ -124,7 +145,10 @@ SELECT source, count(*) FROM documents WHERE source='cyberleninka'; -- > 0
|
|||||||
|
|
||||||
- Больше тем + выше `--limit`; добавить OpenAlex `lang=ru` (качество ниже — англ.
|
- Больше тем + выше `--limit`; добавить OpenAlex `lang=ru` (качество ниже — англ.
|
||||||
заголовки с меткой ru).
|
заголовки с меткой ru).
|
||||||
- Для миллионов — **bulk** (снапшот OpenAlex на S3), а не постраничный API.
|
- Для миллионов — **bulk**, а не постраничный API. Снапшот OpenAlex для этого не
|
||||||
|
годится: там только метаданные, а миллионы аннотаций детекции не дают (см.
|
||||||
|
провал КиберЛенинки выше). Рабочий источник полных текстов — бакет
|
||||||
|
`pmc-oa-opendata`, см. «Массовая заливка» выше.
|
||||||
- На масштабе обязателен `VECTOR_BACKEND=qdrant` (FAISS flat не тянет), а таблица
|
- На масштабе обязателен `VECTOR_BACKEND=qdrant` (FAISS flat не тянет), а таблица
|
||||||
`fingerprints` (уже ~113M строк на 177K доков — партиционирование стоит планировать
|
`fingerprints` (уже ~113M строк на 177K доков — партиционирование стоит планировать
|
||||||
заранее, не постфактум) потребует партиционирования. См.
|
заранее, не постфактум) потребует партиционирования. См.
|
||||||
|
|||||||
@@ -25,12 +25,12 @@
|
|||||||
залитые пропускаются. Прогресс листинга сохраняется в --state-file, поэтому
|
залитые пропускаются. Прогресс листинга сохраняется в --state-file, поэтому
|
||||||
прерванная заливка продолжается с той же страницы бакета.
|
прерванная заливка продолжается с той же страницы бакета.
|
||||||
|
|
||||||
СТАТУС: НЕ ЗАКОНЧЕН. Проба на 200 статьях: документы вставляются верно
|
Проверено на проде 2026-08-31: 100 статей залито, 183 090 отпечатков (1831 на
|
||||||
(проверено), но шаг отпечатков ошибочно считает свежевставленные документы уже
|
статью — реальная глубина, а не аннотация). Темп — 0.3 статьи/с, то есть
|
||||||
обработанными — проверка «есть ли отпечатки» срабатывает там, где их нет, и
|
миллион в один поток занял бы около 40 суток: скачивание тут не узкое место
|
||||||
COPY не выполняется. Пробные записи из базы удалены. До отладки этого места
|
(12 статей/с в 12 потоков), упирается в последовательную обработку одного
|
||||||
скрипт запускать на объёме нельзя: он зальёт документы без отпечатков, то есть
|
документа. Для миллионов обработку нужно распараллелить по ядрам воркера —
|
||||||
невидимые для L1.
|
это следующий шаг, до него скрипт годится для порций в десятки тысяч.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|||||||
Reference in New Issue
Block a user