Ускорить формирование отчёта: обратный индекс маппинга, потоковые вложения, тесты прогресса #2

Merged
Raykov-MS merged 2 commits from feat/1-report-perf into migrate_to_opencloud 2026-10-12 17:55:20 +03:00
Owner

Closes #1 (частично — см. «Что осталось»).

Что сделано

  • Обратный индекс маппинга (app/pipeline/mapping.py, pick_rule_values_indexed). Раньше на каждую строку отчёта перебирались ~2 400 кандидатов путей (7 млн вызовов _pick_value на 13 800 строк). Теперь индекс «путь → (правило, кандидат, проба)» строится один раз при импорте, а за строку проходим только по полям записи. Порядок кандидатов и проб сохранён; _pick_value/_pick_structured_value остались как эталонная семантика, с которой индекс сверяет tests/unit/test_mapping_index.py (400 случайных payload + разобранный XML, все правила, все 4 источника).
  • Объединённый lookup переиспользует lookup операции при пустом участнике; пути без [n] минуют regex.
  • Вложения копируются на диск потоково, без второй полной копии через getvalue() (main.py, тест tests/unit/test_main_attachments.py).
  • Прогресс по файлам (уже был в d1daecd) закреплён smoke-тестами: колбэк вызывается ровно раз на XML-файл, включая нечитаемый.
  • scripts/bench_report.py — воспроизводимый замер (--files, --ops, --profile).

Замеры (230 файлов × 20 операций = 13 800 строк, Apple M, Python 3.13)

Состояние мс на строку весь батч
parser (до d1daecd) 1.01 15.3 с
migrate_to_opencloud HEAD 0.42 6.6 с
эта ветка 0.26 4.4 с

На проде умножайте на 8–12: серверное ядро плюс квота 0.25 CPU из raisa_config.yml.

Rulings I made

  • R-1: _pick_value и _pick_structured_value не удалены, а оставлены как эталон для дифференциального теста.
  • R-2: кеш lookup операции между строками одной операции реализован, замерен (0 % выигрыша в чистом прогоне) и убран; тесты-инварианты (test_mapping_lookup_cache.py) оставлены.
  • R-3: GC-настройка (gc.disable/freeze) замерена — выигрыша нет, не применяется.
  • R-4: случайно переформатированные isort'ом без --profile black четыре чужих тестовых файла возвращены к исходному виду; в PR только файлы задачи.

Что осталось (deferred)

  • D-1: нелинейность в проде (100 файлов = 6 мин, 230 = 45 мин) кодом не объясняется; локализовать по новым строкам process.log (размер/операций/разбор/запись на файл) после следующего прогона. Гипотезы: память у потолка 1 ГБ (tmpfs в /tmp, memory.high), разрыв websocket прокси.
  • D-2: профиль ресурсов «Малый» 0.25 CPU — поднять до 1 CPU даёт ×4 без кода.
  • D-3: следующий по весу участок — _build_payload_lookup (5 построений на строку, ~25 % маппинга) и xlsxwriter (~35 % строки).

🤖 Generated with Claude Code

Closes #1 (частично — см. «Что осталось»). ## Что сделано - **Обратный индекс маппинга** (`app/pipeline/mapping.py`, `pick_rule_values_indexed`). Раньше на каждую строку отчёта перебирались ~2 400 кандидатов путей (7 млн вызовов `_pick_value` на 13 800 строк). Теперь индекс «путь → (правило, кандидат, проба)» строится один раз при импорте, а за строку проходим только по полям записи. Порядок кандидатов и проб сохранён; `_pick_value`/`_pick_structured_value` остались как эталонная семантика, с которой индекс сверяет `tests/unit/test_mapping_index.py` (400 случайных payload + разобранный XML, все правила, все 4 источника). - Объединённый lookup переиспользует lookup операции при пустом участнике; пути без `[n]` минуют regex. - **Вложения** копируются на диск потоково, без второй полной копии через `getvalue()` (`main.py`, тест `tests/unit/test_main_attachments.py`). - **Прогресс по файлам** (уже был в d1daecd) закреплён smoke-тестами: колбэк вызывается ровно раз на XML-файл, включая нечитаемый. - `scripts/bench_report.py` — воспроизводимый замер (`--files`, `--ops`, `--profile`). ## Замеры (230 файлов × 20 операций = 13 800 строк, Apple M, Python 3.13) | Состояние | мс на строку | весь батч | |---|---|---| | `parser` (до d1daecd) | 1.01 | 15.3 с | | `migrate_to_opencloud` HEAD | 0.42 | 6.6 с | | эта ветка | 0.26 | 4.4 с | На проде умножайте на 8–12: серверное ядро плюс квота 0.25 CPU из `raisa_config.yml`. ## Rulings I made - R-1: `_pick_value` и `_pick_structured_value` не удалены, а оставлены как эталон для дифференциального теста. - R-2: кеш lookup операции между строками одной операции реализован, замерен (0 % выигрыша в чистом прогоне) и убран; тесты-инварианты (`test_mapping_lookup_cache.py`) оставлены. - R-3: GC-настройка (`gc.disable`/`freeze`) замерена — выигрыша нет, не применяется. - R-4: случайно переформатированные isort'ом без `--profile black` четыре чужих тестовых файла возвращены к исходному виду; в PR только файлы задачи. ## Что осталось (deferred) - D-1: нелинейность в проде (100 файлов = 6 мин, 230 = 45 мин) кодом не объясняется; локализовать по новым строкам `process.log` (размер/операций/разбор/запись на файл) после следующего прогона. Гипотезы: память у потолка 1 ГБ (tmpfs в /tmp, memory.high), разрыв websocket прокси. - D-2: профиль ресурсов «Малый» 0.25 CPU — поднять до 1 CPU даёт ×4 без кода. - D-3: следующий по весу участок — `_build_payload_lookup` (5 построений на строку, ~25 % маппинга) и xlsxwriter (~35 % строки). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
aep added 2 commits 2026-10-09 18:15:47 +03:00
build_fixed_row_by_index evaluated ~2 400 candidate paths per report row
(7 million _pick_value calls per 13 800 rows). A reverse index
"path -> (rule, candidate, probe)" is built once at import; per row we
walk only the record's own fields and keep the best hit per rule, with
the same candidate/probe ordering as _pick_value. Differential test
compares the index with _pick_value on random and parsed payloads.

Also: merged lookup reuses the operation lookup when the participant
payload is empty, and index-free paths skip the regex normalisation.
scripts/bench_report.py reproduces the measurement (0.42 -> 0.26 ms/row).

Refs #1

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
UploadedFile already holds the whole attachment in memory; getvalue()
made a second full copy of each of the 230 files before writing. Copy
through the file interface instead. Smoke tests pin the progress
callback contract: once per XML file, including unreadable ones.

Refs #1

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Raykov-MS approved these changes 2026-10-12 15:30:48 +03:00
Raykov-MS merged commit e74cd69140 into migrate_to_opencloud 2026-10-12 17:55:20 +03:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RSHB/SFM#2
No description provided.