Web Workers для обработки данных

Визуализация больших геопространственных наборов данных требует разделения ответственности между основным потоком браузера и фоновыми вычислениями. В Kepler.gl вычислительная нагрузка, связанная с парсингом данных, агрегацией, индексацией и подготовкой слоёв визуализации, вынесена в Web Workers, что позволяет сохранить отзывчивость интерфейса даже при работе с миллионами строк.

Kepler.gl строится на принципе разделения pipeline на две части: UI-слой (React + Redux + deck.gl) и вычислительный слой (worker threads). Основная идея заключается в том, что тяжёлые операции никогда не блокируют главный поток, а выполняются в изолированной среде с последующей передачей результата обратно.


Причины использования Web Workers

JavaScript в браузере работает в одном потоке исполнения, и любые синхронные вычисления приводят к блокировке отрисовки интерфейса. При обработке геоданных это становится критичным из-за следующих факторов:

  • большие CSV/JSON файлы (сотни мегабайт);
  • сложные геометрические преобразования (GeoJSON → binary);
  • агрегации по временным и пространственным признакам;
  • построение индексов (например, H3);
  • подготовка данных для WebGL-рендеринга.

Web Workers решают проблему за счёт:

  • изоляции памяти (нет доступа к DOM);
  • параллельного исполнения;
  • передачи данных через message passing;
  • использования transferable objects для минимизации копирования.

Общая схема обработки данных

В Kepler.gl обработка данных проходит через несколько этапов:

  1. Загрузка исходного набора данных.
  2. Передача данных в worker.
  3. Парсинг и нормализация.
  4. Построение внутренних структур хранения.
  5. Генерация слоёв и агрегатов.
  6. Передача результата в основной поток.

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


Worker-процессор и его роль

Worker в Kepler.gl реализует набор функций обработки, которые вызываются через сообщения. Основные задачи worker-а:

  • парсинг CSV, GeoJSON, JSON;
  • преобразование данных в табличный формат;
  • вычисление производных полей;
  • агрегация временных рядов;
  • пространственная индексация;
  • подготовка данных для deck.gl слоёв.

Передача данных осуществляется через postMessage, где payload строго сериализуется.

Пример концептуальной структуры сообщения:

worker.postMessage({
  type: 'PROCESS_FILE',
  payload: {
    data: rawData,
    options: {
      format: 'csv',
      delimiter: ','
    }
  }
});

Изоляция памяти и ограничения

Web Worker не имеет доступа к DOM, window и глобальному состоянию интерфейса. Это накладывает архитектурные ограничения:

  • отсутствие прямого взаимодействия с React-компонентами;
  • невозможность использования браузерных API UI-уровня;
  • необходимость явной сериализации данных.

С другой стороны, это даёт важное преимущество — отсутствие конкуренции за главный поток выполнения.


Передача данных и оптимизация копирования

Одной из ключевых проблем при работе с Web Workers является стоимость копирования данных. При передаче больших массивов возникает значительная нагрузка на память и CPU.

Для оптимизации используются:

Transferable objects

Буферы ArrayBuffer передаются без копирования:

worker.postMessage({
  buffer: arrayBuffer
}, [arrayBuffer]);

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

Structured cloning

Для сложных объектов используется structured clone algorithm, однако он менее эффективен и применяется только там, где невозможно использовать transfer.


Парсинг данных в worker

Обработка входных данных — один из самых тяжёлых этапов pipeline. В Kepler.gl используются оптимизированные парсеры:

  • CSV parser с потоковой обработкой;
  • JSON parser с ленивой десериализацией;
  • GeoJSON transformer;
  • бинарные форматы для ускорения загрузки.

Парсинг CSV часто реализуется построчно, что позволяет избежать загрузки всего файла в память:

function parseCSVStream(chunk) {
  const rows = chunk.split('\n');
  for (let i = 0; i < rows.length; i++) {
    processRow(rows[i]);
  }
}

Преобразование данных в табличную модель

Внутренняя модель Kepler.gl основана на таблицах, где каждая колонка хранится отдельно. Это позволяет:

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

Worker преобразует входные данные в columnar storage:

{
  columns: {
    latitude: Float32Array,
    longitude: Float32Array,
    timestamp: Int32Array
  }
}

Такой формат оптимизирован для GPU-пайплайна deck.gl.


Пространственная индексация и агрегация

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

  • H3 grid indexing;
  • geohash clustering;
  • bounding box partitioning.

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

Пример логики:

function buildSpatialIndex(points) {
  const index = new Map();
  for (const p of points) {
    const cell = h3.latLngToCell(p.lat, p.lng, 7);
    if (!index.has(cell)) index.set(cell, []);
    index.get(cell).push(p);
  }
  return index;
}

Агрегация временных рядов

При работе с временными данными worker выполняет:

  • группировку по временным интервалам;
  • нормализацию timestamp;
  • построение биннинга (hour/day/month);
  • вычисление статистик.

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


Интеграция с Redux и состоянием приложения

Результаты работы worker возвращаются в основной поток и интегрируются в глобальное состояние через Redux.

Схема взаимодействия:

  1. Action запускает обработку.
  2. Middleware отправляет данные в worker.
  3. Worker возвращает результат.
  4. Reducer обновляет store.
  5. React-компоненты перерисовываются.

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


Ошибки и обработка исключений

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

self.postMessage({
  type: 'ERROR',
  error: {
    message: err.message,
    stack: err.stack
  }
});

Основной поток интерпретирует сообщение и обновляет UI состояния ошибки.


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

Основные факторы, влияющие на производительность:

  • стоимость сериализации данных;
  • размер передаваемых сообщений;
  • количество worker-операций;
  • частота пересоздания структур.

Оптимизации включают:

  • переиспользование worker-инстансов;
  • батчинг операций;
  • минимизацию round-trip сообщений;
  • использование бинарных форматов.

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

Для очень больших файлов применяется потоковая модель:

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

Это позволяет работать с файлами, превышающими доступную память.


Связь с deck.gl и GPU-пайплайном

Результаты worker-обработки передаются в deck.gl слои, которые используют WebGL для рендеринга. Подготовленные данные уже находятся в оптимизированной форме:

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

Это снижает нагрузку на GPU и CPU во время отрисовки.


Масштабирование вычислений

Worker-архитектура позволяет масштабировать обработку данных:

  • разделение задач на независимые worker-инстансы;
  • параллельная обработка нескольких датасетов;
  • распределение нагрузки по потокам.

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