fix(wikipedia): позиция — байтовое смещение, а не номер статьи
All checks were successful
Deploy / test (push) Successful in 3m8s
Deploy / deploy (push) Successful in 30s

Заливка Википедии останавливалась сама собой: позиция хранилась как номер
статьи, и каждый прогон перечитывал дамп с начала. На 20 тысячах это стоило
6 минут из 25 доступных, на 27 тысячах — уже около десяти, а на сотне тысяч
съело бы весь бюджет и заливка встала бы совсем. Ровно это и наблюдалось:
прогон висел с нулём полученных статей, воркер на 100% CPU.

Переход на multistream-вариант дампа: он состоит из независимых bz2-блоков по
~95 статей, к нему прилагается индекс со смещениями. Позиция продолжения стала
байтовым смещением, прогон стартует мгновенно с нужного места.

Проверено на реальных файлах, а не по предположению: формат индекса
(offset:page_id:title), 21 уникальное смещение на 2001 статью, прыжок seek на
смещение из середины файла даёт валидный XML со страницами.

wikipedia_resume_offset.py — разовый пересчёт позиции при переходе: находит по
индексу блок с максимальным залитым page_id (получилось 273 281 821).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
jze9
2026-09-05 23:26:16 +05:00
parent d3106efeee
commit d86bb606c7
3 changed files with 132 additions and 31 deletions

View File

@@ -0,0 +1,79 @@
#!/usr/bin/env python3
"""Вычислить байтовое смещение в дампе Википедии по уже залитым статьям.
Нужен один раз при переходе на multistream-дамп: позиция продолжения сменила
смысл — раньше это был номер статьи (и прогон перечитывал дамп с начала),
теперь байтовое смещение блока.
Как считается: берём максимальный page_id среди залитых статей и находим в
индексе дампа блок, которому он принадлежит. Страницы в дампе идут по
возрастанию page_id, поэтому всё, что дальше этого блока, ещё не залито.
Запуск (в контейнере worker-indexer):
docker compose -f docker-compose.prod.yml exec -T worker-indexer python - \\
< scripts/ops/wikipedia_resume_offset.py # показать
... python - --apply < scripts/ops/wikipedia_resume_offset.py # записать
"""
import argparse
import bz2
import os
DEFAULT_INDEX = "/parsers/ruwiki-index.txt.bz2"
def main() -> None:
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--apply", action="store_true", help="записать смещение в источник")
ap.add_argument("--index", default=DEFAULT_INDEX, help="путь к индексу дампа")
args = ap.parse_args()
from app.db import db_session
from sqlalchemy import text
if not os.path.exists(args.index):
raise SystemExit(f"индекс не найден: {args.index}")
with db_session() as s:
row = s.execute(text("""
SELECT max(split_part(ext_id, ':', 2)::bigint)
FROM documents WHERE source = 'wikipedia_ru'
""")).first()
max_pageid = int(row[0]) if row and row[0] else 0
print(f"максимальный page_id среди залитых: {max_pageid}")
if not max_pageid:
print("залитых статей нет — начинать с нуля")
return
# Индекс отсортирован по смещению, page_id внутри растут: ищем блок,
# в котором лежит наш максимальный, и берём его смещение
offset = 0
found_title = ""
with bz2.open(args.index, "rt", encoding="utf-8") as fh:
for line in fh:
parts = line.split(":", 2)
if len(parts) < 3:
continue
off, pid = int(parts[0]), int(parts[1])
if pid > max_pageid:
break
offset, found_title = off, parts[2].strip()
print(f"блок с этой статьёй начинается на байте {offset} ({found_title[:50]!r})")
if not args.apply:
print("\nзапустите с --apply, чтобы записать смещение в источник")
return
with db_session() as s:
s.execute(text("""
UPDATE parse_sources SET resume_token = :off WHERE source_type = 'wikipedia_ru'
"""), {"off": str(offset)})
s.commit()
print(f"смещение {offset} записано в источник")
if __name__ == "__main__":
main()