12.10.2026 Технический разбор RU

ICT: неделя анимации карт за минуту, карта станций на OpenStreetMap и что мешало браузеру

Анимация карт ионосферы за неделю строилась 18 минут и обрывалась на прокси. Теперь дни загружаются, а кадры рисуются в параллельных процессах в фоне — та же неделя за 62 секунды вместо 284. Почему потоки сделали только хуже, сколько стоят потоки BLAS, как карта станций переехала на OpenStreetMap и что в браузере ломали политика безопасности и кэш.

Никита Конкин

Проект Репозиторий (оркестратор)
Языковые версии: RU EN

После разбора оркестратора в «ИоноПоток» за неделю, с 3 по 8 октября, влились семь изменений. Главное из них касается модуля карт ионосферы, описанного в разборе методики IonMaps: анимация за неделю перестала обрываться и строится в несколько раз быстрее. Остальные изменения — карта станций на подложке OpenStreetMap, исправления в политике безопасности и кэше, из-за которых браузер не показывал уже готовое, и две правки в конвертерах.

Анимация, которая не дожидалась ответа

Неделя кадров с шагом 15 минут — это 668 кадров по 19 станциям с кригингом. На сервере она строилась 18 минут: 12 минут дни по очереди загружались, ещё 6 минут рисовались кадры, и всё это на одном ядре. Браузер и прокси университета ждут ответа не больше 15 минут, поэтому запрос заканчивался страницей ошибки 504 от прокси, а готовая анимация оставалась никому не нужной.

Процессы, а не потоки

Теперь дни загружаются в параллельных процессах-работниках, а кадры рисуются ещё в нескольких процессах; число тех и других задаётся настройками. Каждый процесс, загружающий день, держит в памяти около 1,3 ГБ для 19 станций, поэтому число одновременно загружаемых дней ограничивает в первую очередь память, а не процессор.

Потоки не подошли. Семь потоков загружали неделю втрое дольше, чем один, а семь процессов — в 4,6 раза быстрее. Процессы-работники создаются запуском нового интерпретатора, а не копированием текущего: веб-процесс сам работает с потоками, а копия многопоточного процесса может унаследовать блокировку, которую уже некому снять.

Вторая ловушка — потоки линейной алгебры. По умолчанию numpy запускает в библиотеке BLAS по потоку на каждый процессор. Двенадцать работников при ограничении контейнера в 12 процессоров открывали каждый по потоку на процессор машины, потоков становилось намного больше, чем ядер, и работа шла в 7 раз дольше. Теперь каждому процессу положен один поток BLAS и OpenMP — и в образе Docker, и для работников, запущенных вне его.

Две горизонтальные столбчатые диаграммы. Слева: раньше 284 секунды, теперь 62 секунды. Справа: один поток — 1, семь потоков — в 3 раза дольше, семь процессов — в 4,6 раза быстрее
Слева — неделя анимации до и после на машине с 12 процессорами. Справа — загрузка недели данных в потоках и в процессах относительно одного потока.

В итоге та же неделя локально, на 12 процессорах, строится за 62 секунды вместо 284. Параллельный и последовательный расчёты дают одинаковый результат: совпадают сводки по дням, а для полного дня — байты GIF-файла.

В фоне и с прогрессом

Даже минута — слишком долго для одного HTTP-запроса, поэтому анимация теперь строится фоновым заданием. Страница IonMaps отправляет те же параметры, что и раньше, получает номер задания и показывает этап и прогресс: «Загрузка данных: день 4 из 7…», «Отрисовка кадров: 200 из 667…». Неверный запрос по-прежнему отклоняется сразу, повторный запрос с теми же параметрами подхватывает уже запущенное задание, у одного пользователя может быть не больше трёх активных заданий, а готовые файлы удаляются через заданное время.

Анимации строятся по одной — фоновые и обычные вместе, — так что все вместе они занимают не больше ядер, чем задано. Контейнер хаба ограничен 12 процессорами, а лимит памяти поднят до 16 ГБ: пик для недели составил 5,1 ГиБ.

Ошибки стали читаемыми. Если ответил сервер — показывается его сообщение. Если вместо ответа пришла HTML-страница ошибки прокси — короткое объяснение и заголовок этой страницы, а не вся страница целиком.

Попутно нашлась ошибка в форме. У полей прозрачности подложки и поля был шаг 0,05, а значение по умолчанию — 0,28. Браузер считал его неверным и не давал запустить построение карты, пока значение не исправят вручную.

Что браузер отказывался показывать

Три ошибки этой недели — про то, что сервер уже отдавал правильно, а браузер не показывал.

  • Видео. Готовую анимацию в MP4 или WebM страница проигрывает по адресу blob:. В политике безопасности контента (CSP) не было правила media-src, поэтому для видео действовало общее default-src 'self', и браузер отказывался его загружать. GIF работал только потому, что blob: был разрешён для изображений. Добавлено media-src 'self' blob:.
  • Контуры стран. Библиотека Plotly для географических карт загружает контуры стран и береговых линий с собственного CDN. Политика безопасности разрешает запросы только к своему серверу, а в сети без выхода в интернет до CDN не добраться в любом случае. Теперь файл контуров скачивается при сборке образа Docker, сверяется по SHA-256 и отдаётся вместе со статикой.
  • Кэш. Статические файлы уходили без заголовка Cache-Control. Браузеры и кэширующие прокси сами угадывали срок свежести по возрасту файла и после выкладки продолжали запускать скрипты прошлой версии: сервер уже отдавал исправленную карту станций, а браузер по-прежнему ходил за контурами на CDN. Теперь статика отдаётся с Cache-Control: no-cache: неизменившийся файл подтверждается ответом 304 и заново не скачивается.

Карта станций на OpenStreetMap

Карта станций показывает проиндексированные станции RINEX и метаданные из заголовков наблюдений. Теперь она рисуется на тайлах OpenStreetMap через карту MapLibre в Plotly: её можно масштабировать, а при загрузке она сама подстраивается под станции. Контурная карта осталась вариантом для работы без интернета, и выбор подложки браузер запоминает.

Страница «Карта станций»: слева фильтры — папка года, папка дня «001 (14 станций)», подложка «OpenStreetMap (нужен интернет)», кнопки загрузки; справа карта Поволжья в тёмной теме с четырнадцатью оранжевыми маркерами ST01–ST14
Карта станций на подложке OpenStreetMap. Станции и их координаты вымышлены: заголовки RINEX сгенерированы для демонстрации.

Чтобы подложка заработала, пришлось пройти по нескольким условиям:

  • Тайлы загружаются с tile.openstreetmap.org, а карта MapLibre использует веб-воркеры из blob: — оба источника добавлены в политику безопасности.
  • Без заголовка Referer сервер тайлов OpenStreetMap отвечает 403 «Access blocked», поэтому страница отправляет его с политикой strict-origin-when-cross-origin — только адрес сайта, без пути.
  • Шрифты подписей с латиницей и кириллицей скачиваются при сборке образа и сверяются по SHA-256 вместо загрузки со стороннего сервера.
  • Для слоя, состоящего только из подписей, Plotly вычислял смещение текста как NaN, и падала вся карта. Слою подписей добавлены невидимые маркеры.
  • Если тайлы не загружаются, Plotly сообщает лишь «Map error.», хотя станции нарисованы. Страница теперь объясняет, что случилось, и предлагает контурную карту, а таблица станций строится раньше карты, так что ошибка карты её больше не скрывает.

Легенда переехала с карты над неё, пояснение под картой переведено, тёмная тема инвертирует тайлы, а масштаб и сдвиг сохраняются при перерисовке.

Конвертеры

В AbsTEC Suite появилась отметка «Очищать входные данные перед absolTEC». С ней программа расчёта абсолютного TEC запускается на копии .dat-файлов станции без строк-заглушек фазы, из-за которых на некоторых станциях расчёт не сходится. Если результат получается неполным, расчёт запускается и на исходных данных, и сохраняется лучший из двух, так что ни одна станция не становится хуже. По умолчанию отметка выключена: так более старый образ запускающего контейнера никогда не получит незнакомый ему параметр.

В конвертере DAT ↔ Parquet исправлена «Перезапись». Конвертер понимает её как «заменять файлы, которые уже есть в папке назначения», но хаб вдобавок подменял папку назначения папкой с исходными данными. С включённой перезаписью файлы Parquet ложились рядом с .dat-файлами, а устаревшие файлы в настоящей папке назначения не обновлялись никогда: после повторных расчётов AbsTEC конвертер пропускал все станции. Теперь назначение — всегда папка Parquet профиля, а подсказка формы объясняет, что делает перезапись. Ни одно прошлое задание не запускалось с перезаписью, поэтому лишних файлов в исходных папках нет.

Выводы

  1. Долгий расчёт не должен жить внутри HTTP-запроса: фоновое задание с этапами и прогрессом переживает любой таймаут прокси.
  2. Для работы, которую выполняет интерпретатор Python, потоки могут оказаться медленнее одного потока — здесь втрое. Параллельность дают процессы.
  3. Число потоков BLAS нужно задавать явно: двенадцать процессов, каждый из которых открывает по потоку на процессор, работали в 7 раз дольше.
  4. Строгая политика безопасности контента ломает всё, что не перечислено явно, — видео из blob:, контуры с CDN, тайлы карты. Каждое такое исключение лучше закрывать собственной копией ресурса, сверенной по хешу.
  5. Без Cache-Control браузер сам решает, сколько хранить статику, и после выкладки показывает прошлую версию. no-cache с ответом 304 почти ничего не стоит.

Доступность

Исходный код оркестратора открыт. Число процессов отрисовки и загрузки задаётся переменными TEC_MAP_WORKERS и TEC_MAP_LOAD_WORKERS, адрес собственного сервера тайлов — TEC_MAP_BASEMAP_TILE_SERVER_URL; все они описаны в docker-compose.yml и .env.example.

Теги

программирование python производительность ионосфера веб безопасность

Другие обновления по проекту

01.10.2026 Технический разбор ICT — платформа обработки данных ионосферного зондирования

ICT: задания, которые переживают перезапуск, Windows XP в контейнере и выкладка без ручных шагов

Полгода работы над оркестратором ICT: события заданий хранятся в базе и переживают перезапуск хаба, унаследованная программа расчёта абсолютного TEC запускается в настоящей Wind...

24.07.2026 Технический разбор ICT — платформа обработки данных ионосферного зондирования

ICT: от наклонного TEC к картам ионосферы — методика IonMaps

Как модуль IonMaps в ICT превращает parquet-выгрузку TEC-suite в региональные карты вертикального электронного содержания: восстановление VTEC из наклонного TEC, интерполяция (л...