Заливка Википедии останавливалась сама собой: позиция хранилась как номер статьи, и каждый прогон перечитывал дамп с начала. На 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>
80 lines
3.4 KiB
Python
Executable File
80 lines
3.4 KiB
Python
Executable File
#!/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()
|