Кэширование

Роль кэширования в рендеринге карт

MapLibre GL JS опирается на многоуровневую систему кэширования, которая обеспечивает плавную работу интерактивных карт, минимизирует сетевые запросы и снижает нагрузку на тайл-серверы. Основная модель рендеринга построена вокруг векторных тайлов, стилей, шрифтов и растровых ресурсов (спрайтов), каждый из которых может кэшироваться независимо.

Ключевая особенность архитектуры заключается в разделении ответственности между браузером, сетевым стеком и внутренними механизмами MapLibre. В результате кэширование происходит одновременно на нескольких уровнях:

  • HTTP-кэш браузера
  • Внутренний кэш тайлов WebGL-контекста
  • Кэш ресурсов стиля (glyphs, sprites, images)
  • Опциональный пользовательский кэш через transformRequest

HTTP-кэш как основа производительности

Наиболее значимый уровень кэширования в MapLibre GL JS — стандартный HTTP-кэш браузера. Каждый запрос к тайлам, спрайтам или шрифтам сопровождается заголовками, определяющими поведение кэширования:

  • Cache-Control
  • ETag
  • Last-Modified
  • Expires

Векторные тайлы обычно отдаются с долгим временем жизни кэша, поскольку они редко изменяются. Типичный URL тайла выглядит следующим образом:

https://tiles.example.com/14/4823/6160.pbf

Браузер сохраняет такие ответы в дисковом или memory cache, что позволяет повторно использовать их при панорамировании и зуме без повторных запросов.

Особое значение имеет правильная настройка Cache-Control:

Cache-Control: public, max-age=86400, immutable

Использование immutable позволяет браузеру полностью исключить повторную валидацию ресурса в течение срока жизни кэша.


Внутренний кэш тайлов MapLibre

Помимо HTTP-кэша, MapLibre GL JS использует собственный механизм хранения тайлов в памяти. Этот кэш предназначен для ускорения перерисовки карты при:

  • изменении центра карты
  • анимации зума
  • наклонах и вращениях
  • возврате к ранее просмотренным областям

Тайлы сохраняются в памяти в декодированном виде (после парсинга PBF), что позволяет избежать повторной десериализации и ускоряет GPU-пайплайн.

Управление этим кэшем происходит через параметры Map:

const map = new maplibregl.Map({
  container: 'map',
  style: 'https://demotiles.maplibre.org/style.json',
  maxTileCacheSize: 512
});

Параметр maxTileCacheSize определяет количество тайлов, удерживаемых в памяти. При превышении лимита используется стратегия вытеснения (LRU — least recently used), при которой наименее используемые тайлы удаляются первыми.


Кэш стиля и ресурсов

Стиль в MapLibre GL JS представляет собой JSON-документ, который описывает:

  • источники данных (sources)
  • слои (layers)
  • шрифты (glyphs)
  • спрайты (sprites)

После загрузки стиль кешируется в памяти и не пересобирается при каждом изменении состояния карты. Внутри движка происходит разделение на:

  • Style JSON cache
  • Layer evaluation cache
  • Source state cache

Особенно важен кэш спрайтов и глифов, так как они являются отдельными сетевыми ресурсами.


Кэширование спрайтов

Спрайты представляют собой набор иконок, используемых для символических слоёв. MapLibre загружает два файла:

  • sprite.png или sprite@2x.png
  • sprite.json

После загрузки изображения оно кэшируется в WebGL-текстуре, что позволяет использовать иконки без повторной загрузки или декодирования.

Пример ссылки:

https://tiles.example.com/sprites/sprite

Система кэширования спрайтов работает следующим образом:

  1. JSON спрайта загружается и кешируется как объект
  2. PNG спрайт декодируется в текстуру GPU
  3. Текстура сохраняется до пересоздания контекста WebGL

При потере WebGL-контекста спрайты загружаются повторно, но при сохранении контекста повторных загрузок не происходит.


Кэширование шрифтов (glyphs)

Шрифты в MapLibre GL JS представляют собой растровые глифы, разбитые на диапазоны символов. URL обычно выглядит так:

https://tiles.example.com/fonts/{fontstack}/{range}.pbf

Каждый диапазон символов (например, 0–255) загружается отдельно и кэшируется.

Механизм работы:

  • запрос диапазона глифов инициируется при необходимости рендеринга текста
  • результат кэшируется в памяти
  • повторные обращения используют локальный кэш без сетевых запросов

Это критично для языков с большим набором символов, где повторное использование диапазонов существенно снижает нагрузку.


Кэш тайлов и стратегий загрузки

MapLibre использует предзагрузку тайлов вокруг текущего viewport. Это создаёт эффект «буфера» данных, который уменьшает задержки при перемещении карты.

Алгоритм:

  1. Определяется текущий bounding box
  2. Вычисляются соседние тайлы (buffer)
  3. Запрашиваются тайлы с учётом zoom level
  4. Сохраняются в tile cache

Важным параметром является renderWorldCopies, который влияет на количество дублируемых тайлов мира. При включённом значении увеличивается объём кэша, но улучшается непрерывность отображения.


Пользовательское управление запросами через transformRequest

Одним из наиболее мощных инструментов контроля кэширования является функция transformRequest. Она позволяет изменять параметры каждого сетевого запроса.

Пример:

const map = new maplibregl.Map({
  container: 'map',
  style: 'https://example.com/style.json',
  transformRequest: (url, resourceType) => {
    if (resourceType === 'Tile') {
      return {
        url: url + '?cacheBust=' + Date.now()
      };
    }
  }
});

Хотя такой подход может отключать кэширование, он используется в сценариях:

  • отладка тайлов
  • динамическая авторизация
  • переключение источников данных
  • A/B тестирование тайл-серверов

При неправильном использовании transformRequest может полностью разрушить эффективность HTTP-кэша.


Кэширование векторных тайлов и декодирование

Векторные тайлы (.pbf) проходят несколько стадий обработки:

  1. загрузка бинарного файла
  2. декодирование protobuf
  3. преобразование в геометрические структуры
  4. подготовка к WebGL рендеру

MapLibre кэширует результат декодирования, а не только сырой ответ. Это ключевое отличие от классического HTTP-кэша.

Такая стратегия позволяет:

  • уменьшить нагрузку на CPU
  • ускорить перерисовку при вращении карты
  • снизить latency при повторном использовании тайла

Поведение кэша при изменении стиля

Изменение стиля через setStyle приводит к частичной или полной очистке кэша:

map.setStyle('https://example.com/new-style.json');

В результате:

  • очищается tile cache, связанный со старым стилем
  • сбрасываются слои и источники
  • сохраняются некоторые глобальные ресурсы WebGL (в зависимости от версии)

Такое поведение связано с тем, что стиль определяет структуру источников данных, и старые тайлы могут быть несовместимы с новой схемой.


Сетевые стратегии и дедупликация запросов

MapLibre GL JS реализует дедупликацию сетевых запросов. Если несколько слоёв запрашивают один и тот же тайл одновременно, создаётся один HTTP-запрос, а результат распределяется между потребителями.

Это достигается через внутренний request scheduler:

  • объединение одинаковых URL
  • контроль очереди запросов
  • приоритетизация видимых тайлов

Такой подход уменьшает количество сетевых соединений и повышает стабильность при медленных сетях.


Оффлайн-кэширование и внешние стратегии

Встроенный MapLibre GL JS не содержит полноценного оффлайн-хранилища, но предоставляет точки расширения для реализации собственных решений:

  • Service Worker caching
  • IndexedDB storage
  • prefetch tile pipelines

Типичная реализация через Service Worker:

self.addEventListener('fetch', event => {
  if (event.request.url.includes('/tiles/')) {
    event.respondWith(
      caches.open('tiles-cache').then(cache => {
        return cache.match(event.request).then(response => {
          return response || fetch(event.request).then(networkResponse => {
            cache.put(event.request, networkResponse.clone());
            return networkResponse;
          });
        });
      })
    );
  }
});

Такая схема позволяет полностью автономно использовать карты без постоянного сетевого доступа.


Инвалидация кэша и контроль актуальности данных

Кэширование требует строгого контроля версионности данных. Основные стратегии инвалидации:

  • versioned URLs (/tiles/v2/{z}/{x}/{y}.pbf)
  • query-based cache busting (?v=123)
  • ETag-based revalidation
  • style hash fingerprinting

Наиболее устойчивой практикой является использование версионированных путей, так как они полностью исключают неоднозначность между старыми и новыми данными.


Производительность и баланс кэширования

Эффективность MapLibre GL JS напрямую зависит от баланса между:

  • размером tile cache
  • количеством предзагружаемых тайлов
  • глубиной зума
  • плотностью слоёв

Слишком агрессивное кэширование приводит к росту потребления памяти, а слишком слабое — к деградации UX из-за повторных запросов и перекодирования.

Оптимальная стратегия обычно включает:

  • умеренный maxTileCacheSize
  • корректные HTTP headers
  • минимизацию перезапросов glyphs и sprites
  • использование CDN с edge caching