ICT: неделя анимации карт за минуту, карта станций на OpenStreetMap и что мешало браузеру
Анимация карт ионосферы за неделю строилась 18 минут и обрывалась на прокси. Теперь дни загружаются, а кадры рисуются в параллельных процессах в фоне — та же неделя за 62 секунды вместо 284. Почему потоки сделали только хуже, сколько стоят потоки BLAS, как карта станций переехала на OpenStreetMap и что в браузере ломали политика безопасности и кэш.
После разбора оркестратора в «ИоноПоток» за неделю, с 3 по 8 октября, влились семь изменений. Главное из них касается модуля карт ионосферы, описанного в разборе методики IonMaps: анимация за неделю перестала обрываться и строится в несколько раз быстрее. Остальные изменения — карта станций на подложке OpenStreetMap, исправления в политике безопасности и кэше, из-за которых браузер не показывал уже готовое, и две правки в конвертерах.
Анимация, которая не дожидалась ответа
Неделя кадров с шагом 15 минут — это 668 кадров по 19 станциям с кригингом. На сервере она строилась 18 минут: 12 минут дни по очереди загружались, ещё 6 минут рисовались кадры, и всё это на одном ядре. Браузер и прокси университета ждут ответа не больше 15 минут, поэтому запрос заканчивался страницей ошибки 504 от прокси, а готовая анимация оставалась никому не нужной.
Процессы, а не потоки
Теперь дни загружаются в параллельных процессах-работниках, а кадры рисуются ещё в нескольких процессах; число тех и других задаётся настройками. Каждый процесс, загружающий день, держит в памяти около 1,3 ГБ для 19 станций, поэтому число одновременно загружаемых дней ограничивает в первую очередь память, а не процессор.
Потоки не подошли. Семь потоков загружали неделю втрое дольше, чем один, а семь процессов — в 4,6 раза быстрее. Процессы-работники создаются запуском нового интерпретатора, а не копированием текущего: веб-процесс сам работает с потоками, а копия многопоточного процесса может унаследовать блокировку, которую уже некому снять.
Вторая ловушка — потоки линейной алгебры. По умолчанию numpy запускает в библиотеке BLAS по потоку на каждый процессор. Двенадцать работников при ограничении контейнера в 12 процессоров открывали каждый по потоку на процессор машины, потоков становилось намного больше, чем ядер, и работа шла в 7 раз дольше. Теперь каждому процессу положен один поток BLAS и OpenMP — и в образе Docker, и для работников, запущенных вне его.

В итоге та же неделя локально, на 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: её можно масштабировать, а при загрузке она сама подстраивается под станции. Контурная карта осталась вариантом для работы без интернета, и выбор подложки браузер запоминает.

Чтобы подложка заработала, пришлось пройти по нескольким условиям:
- Тайлы загружаются с
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 профиля, а подсказка формы объясняет, что делает перезапись. Ни одно прошлое задание не запускалось с перезаписью, поэтому лишних файлов в исходных папках нет.
Выводы
- Долгий расчёт не должен жить внутри HTTP-запроса: фоновое задание с этапами и прогрессом переживает любой таймаут прокси.
- Для работы, которую выполняет интерпретатор Python, потоки могут оказаться медленнее одного потока — здесь втрое. Параллельность дают процессы.
- Число потоков BLAS нужно задавать явно: двенадцать процессов, каждый из которых открывает по потоку на процессор, работали в 7 раз дольше.
- Строгая политика безопасности контента ломает всё, что не перечислено явно, — видео из
blob:, контуры с CDN, тайлы карты. Каждое такое исключение лучше закрывать собственной копией ресурса, сверенной по хешу. - Без
Cache-Controlбраузер сам решает, сколько хранить статику, и после выкладки показывает прошлую версию.no-cacheс ответом 304 почти ничего не стоит.
Доступность
Исходный код оркестратора открыт. Число процессов отрисовки и загрузки задаётся переменными TEC_MAP_WORKERS и TEC_MAP_LOAD_WORKERS, адрес собственного сервера тайлов — TEC_MAP_BASEMAP_TILE_SERVER_URL; все они описаны в docker-compose.yml и .env.example.
- Исходный код: github.com/nikita-konkin/ict-hub
Теги
Другие обновления по проекту
ICT: задания, которые переживают перезапуск, Windows XP в контейнере и выкладка без ручных шагов
Полгода работы над оркестратором ICT: события заданий хранятся в базе и переживают перезапуск хаба, унаследованная программа расчёта абсолютного TEC запускается в настоящей Wind...
ICT: от наклонного TEC к картам ионосферы — методика IonMaps
Как модуль IonMaps в ICT превращает parquet-выгрузку TEC-suite в региональные карты вертикального электронного содержания: восстановление VTEC из наклонного TEC, интерполяция (л...