Кэширование данных

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

  • 3D Tiles;
  • Terrain-поверхности;
  • спутниковые снимки;
  • векторные слои;
  • модели glTF;
  • пользовательские данные Entity и Primitive;
  • тайловые сервисы WMTS, TMS и WMS.

При отсутствии кэширования каждая навигация по карте способна приводить к повторной загрузке одинаковых ресурсов, что увеличивает задержки и создает дополнительную нагрузку на серверную инфраструктуру.


Архитектура загрузки данных в CesiumJS

Перед изучением механизмов кэширования необходимо понимать жизненный цикл данных.

Типичная схема работы выглядит следующим образом:

  1. Пользователь перемещает камеру.
  2. Cesium определяет набор необходимых тайлов.
  3. Формируются HTTP-запросы.
  4. Сервер возвращает данные.
  5. Данные декодируются.
  6. Объекты помещаются во внутренние структуры движка.
  7. Выполняется рендеринг.

Во время работы приложения могут существовать одновременно несколько уровней кэша:

Уровень Назначение
Browser Cache Кэширование HTTP-ответов
Memory Cache Хранение данных в оперативной памяти
GPU Cache Размещение ресурсов в видеопамяти
Application Cache Пользовательские механизмы хранения
Service Worker Cache Offline-кэширование ресурсов

Каждый уровень решает собственную задачу и влияет на производительность по-разному.


HTTP-кэширование тайлов

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

Когда сервер возвращает тайл, в ответе могут присутствовать специальные заголовки:

Cache-Control: public, max-age=86400
ETag: "a8d32bc"
Last-Modified: Tue, 21 Jan 2025 12:00:00 GMT

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

При повторном обращении:

const imageryLayer = new Cesium.ImageryLayer(
    new Cesium.UrlTemplateImageryProvider({
        url: '/tiles/{z}/{x}/{y}.png'
    })
);

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

Преимущества:

  • отсутствие дополнительного кода;
  • поддержка всеми современными браузерами;
  • снижение нагрузки на сервер.

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


Кэширование изображений Imagery Layers

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

Например:

const provider = new Cesium.UrlTemplateImageryProvider({
    url: '/satellite/{z}/{x}/{y}.jpg'
});

viewer.imageryLayers.addImageryProvider(provider);

После загрузки изображения Cesium сохраняет текстуры во внутренних структурах.

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

Особенно эффективно это работает при:

  • небольших перемещениях камеры;
  • масштабировании соседних областей;
  • возврате к недавно просмотренным регионам.

Кэширование 3D Tiles

Наборы 3D Tiles часто содержат тысячи или миллионы объектов.

Пример подключения:

const tileset = await Cesium.Cesium3DTileset.fromUrl(
    '/tileset/tileset.json'
);

viewer.scene.primitives.add(tileset);

После загрузки тайлы помещаются во внутренний кэш набора.

Cesium автоматически:

  • отслеживает видимые тайлы;
  • сохраняет недавно использованные данные;
  • удаляет редко используемые элементы.

Внутренний алгоритм работает по принципу ограничения объема памяти.


Ограничение объема кэша 3D Tiles

Для управления потреблением памяти используется свойство:

tileset.cacheBytes = 512 * 1024 * 1024;

В данном случае устанавливается лимит:

512 МБ

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

Дополнительно существует параметр:

tileset.maximumCacheOverflowBytes = 
    256 * 1024 * 1024;

Он позволяет временно превышать установленный лимит.

Пример:

tileset.cacheBytes = 1024 * 1024 * 1024;
tileset.maximumCacheOverflowBytes =
    512 * 1024 * 1024;

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


Политика вытеснения тайлов

Cesium применяет стратегии удаления данных из памяти.

Основная идея:

  • недавно использованные тайлы сохраняются;
  • давно не используемые тайлы удаляются.

Концептуально процесс напоминает алгоритм LRU (Least Recently Used).

Последовательность:

Загрузка тайла
      ↓
Использование
      ↓
Помещение в кэш
      ↓
Превышение лимита
      ↓
Удаление старых тайлов

Благодаря этому память расходуется более эффективно.


Кэширование Terrain-данных

Terrain является одним из самых ресурсоемких типов данных.

Подключение:

const terrain = await Cesium.CesiumTerrainProvider.fromUrl(
    '/terrain'
);

viewer.terrainProvider = terrain;

Высотные тайлы загружаются по мере необходимости и остаются в памяти до момента вытеснения.

Кэширование Terrain обеспечивает:

  • быстрое повторное отображение местности;
  • уменьшение числа запросов;
  • плавное перемещение камеры.

Особенно заметен эффект при полетах над одной и той же территорией.


Кэширование моделей glTF

При загрузке моделей:

const model = await Cesium.Model.fromGltfAsync({
    url: '/models/aircraft.glb'
});

viewer.scene.primitives.add(model);

Cesium сохраняет:

  • геометрию;
  • текстуры;
  • буферы вершин;
  • материалы.

Если одна и та же модель используется несколько раз:

const model1 = await Cesium.Model.fromGltfAsync({
    url: 'tank.glb'
});

const model2 = await Cesium.Model.fromGltfAsync({
    url: 'tank.glb'
});

часть ресурсов может переиспользоваться, что уменьшает потребление памяти и сетевой трафик.


Пользовательский кэш Entity-данных

Иногда данные поступают из внешнего API.

Например:

async function loadAircraft(id) {
    const response = await fetch(
        `/api/aircraft/${id}`
    );

    return response.json();
}

Без кэширования каждый вызов приводит к новому запросу.

Простейший вариант:

const cache = new Map();

async function loadAircraft(id) {

    if (cache.has(id)) {
        return cache.get(id);
    }

    const response = await fetch(
        `/api/aircraft/${id}`
    );

    const data = await response.json();

    cache.set(id, data);

    return data;
}

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


Кэширование с ограничением времени жизни

Часто данные устаревают.

Для этого вводится TTL (Time To Live).

const cache = new Map();

async function getData(id) {

    const item = cache.get(id);

    if (
        item &&
        Date.now() - item.time < 60000
    ) {
        return item.data;
    }

    const response =
        await fetch(`/api/data/${id}`);

    const data =
        await response.json();

    cache.set(id, {
        data,
        time: Date.now()
    });

    return data;
}

В данном примере информация считается актуальной одну минуту.


Кэширование GeoJSON

GeoJSON-файлы могут содержать большое количество объектов.

Пример загрузки:

const dataSource =
    await Cesium.GeoJsonDataSource.load(
        '/data/roads.geojson'
    );

viewer.dataSources.add(dataSource);

Если набор используется повторно, разумно сохранять ссылку:

const geoJsonCache = {};

async function getRoads() {

    if (geoJsonCache.roads) {
        return geoJsonCache.roads;
    }

    geoJsonCache.roads =
        await Cesium.GeoJsonDataSource.load(
            '/data/roads.geojson'
        );

    return geoJsonCache.roads;
}

Такой подход избавляет от повторного парсинга файла.


Кэширование через Service Worker

Service Worker позволяет организовать полноценный локальный слой хранения.

Регистрация:

navigator.serviceWorker.register(
    '/service-worker.js'
);

Пример обработки запросов:

self.addEventListener(
    'fetch',
    event => {

        event.respondWith(
            caches.match(event.request)
                .then(response => {

                    return response ||
                        fetch(event.request);
                })
        );
    }
);

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

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

Offline-кэширование тайлов

Иногда требуется работа без подключения к интернету.

В этом случае тайлы заранее сохраняются:

caches.open('tiles-cache')
    .then(cache => {

        return cache.addAll([
            '/tiles/0/0/0.png',
            '/tiles/1/0/0.png',
            '/tiles/1/0/1.png'
        ]);
    });

После этого данные могут использоваться локально.

Подобный подход применяется в:

  • мобильных GIS;
  • военных системах;
  • навигационных приложениях;
  • инженерных комплексах.

Очистка пользовательского кэша

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

Пример:

for (const [key, value] of cache) {

    if (
        Date.now() - value.time >
        300000
    ) {
        cache.delete(key);
    }
}

Также можно полностью очистить структуру:

cache.clear();

Особенно важно делать это при длительной работе приложения.


Мониторинг использования памяти

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

Для 3D Tiles доступна диагностика:

console.log(
    tileset.totalMemoryUsageInBytes
);

Получение значения:

284563219

означает фактический объем памяти, занимаемый тайлами.

На основе этих данных можно динамически изменять настройки кэширования.


Баланс между памятью и производительностью

Слишком маленький кэш приводит к постоянным повторным загрузкам:

tileset.cacheBytes =
    64 * 1024 * 1024;

Слишком большой кэш способен вызвать нехватку памяти:

tileset.cacheBytes =
    4 * 1024 * 1024 * 1024;

Практика показывает следующие ориентиры:

Тип приложения Размер кэша
Простая карта 128–256 МБ
Городская сцена 512 МБ
Большой цифровой двойник 1–2 ГБ
Инженерные модели 2–4 ГБ

Конкретные значения зависят от объема данных и характеристик оборудования.


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

Для уменьшения задержек можно заранее загружать данные.

Пример:

viewer.camera.flyTo({
    destination: destination,
    complete: async () => {

        await tileset.readyPromise;

        tileset.preloadFlightDestinations = true;
    }
});

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

Преимущества:

  • уменьшение визуальных задержек;
  • более плавные полеты камеры;
  • снижение вероятности появления пустых областей.

Практические рекомендации

Для Imagery Layers

  • настраивать HTTP-кэширование на сервере;
  • использовать CDN;
  • избегать частого обновления тайлов без необходимости.

Для Terrain

  • применять долгоживущие заголовки Cache-Control;
  • минимизировать изменение данных рельефа.

Для 3D Tiles

  • корректно подбирать cacheBytes;
  • контролировать потребление памяти;
  • использовать предварительную загрузку.

Для пользовательских API

  • внедрять TTL-кэширование;
  • очищать устаревшие записи;
  • избегать дублирующих запросов.

Для офлайн-режима

  • использовать Service Worker;
  • сохранять наиболее востребованные тайлы;
  • ограничивать размер локального хранилища.

Грамотно реализованное кэширование позволяет значительно сократить сетевые задержки, повысить плавность навигации, уменьшить нагрузку на серверы и обеспечить стабильную работу сложных геоинформационных приложений на базе CesiumJS даже при обработке больших объемов пространственных данных.