Как вы организовываете слой доступа к данным?nodejs-103

1. Проблема блокировки потока

Стандартные JSON.parse() и JSON.stringify() являются синхронными операциями и могут блокировать цикл событий при работе с большими данными:

// Проблемный код: блокировка потока
app.post('/api/data', (req, res) => {
  const largeData = JSON.parse(fs.readFileSync('large.json')); // Двойная блокировка!
  const result = processData(largeData);
  res.json(result); // Еще один JSON.stringify
});

2. Асинхронные альтернативы

a) Потоковая обработка

Использование потоков для постепенной обработки:

const { pipeline } = require('stream');
const { parse } = require('JSONStream');

app.post('/stream-parse', (req, res) => {
  const transform = new Transform({
    objectMode: true,
    transform(chunk, enc, cb) {
      // Обработка частей данных
      cb(null, processChunk(chunk));
    }
  });

  pipeline(
    req, // Входящий поток
    parse('*'), // Потоковый парсер
    transform,
    res, // Исходящий поток
    (err) => err && console.error(err)
  );
});

b) Нативные асинхронные методы

В новых версиях Node.js (v21+):

import { parse, stringify } from 'node:json';

app.post('/async-json', async (req, res) => {
  const chunks = [];
  for await (const chunk of req) {
    chunks.push(chunk);
  }
  const data = await parse(Buffer.concat(chunks));
  res.json(await processAsync(data));
});

3. Использование Worker Threads

Для действительно больших JSON:

const { Worker } = require('worker_threads');

function parseInWorker(jsonStr) {
  return new Promise((resolve, reject) => {
    const worker = new Worker(`
      const { parentPort } = require('worker_threads');
      parentPort.postMessage(JSON.parse(${JSON.stringify(jsonStr)}));
    `, { eval: true });

    worker.on('message', resolve);
    worker.on('error', reject);
  });
}

app.post('/worker-parse', async (req, res) => {
  const data = await parseInWorker(req.body);
  res.json(data);
});

4. Альтернативные библиотеки

a) simdjson

Экстремально быстрый парсер:

const simdjson = require('simdjson');

app.post('/simd', (req, res) => {
  const data = simdjson.lazyParse(fs.readFileSync('large.json'));
  res.json(data);
});

b) fast-json-stringify

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

const fastJson = require('fast-json-stringify');
const stringify = fastJson({
  title: 'Example',
  type: 'object',
  properties: {
    id: { type: 'string' },
    name: { type: 'string' }
  }
});

app.get('/fast', (req, res) => {
  res.header('Content-Type', 'application/json');
  res.send(stringify({ id: '1', name: 'Fast' }));
});

5. Оптимизация структуры данных

a) Частичная загрузка

Используйте JSONPath для выборки только нужных данных:

const jsonpath = require('JSONPath');

app.post('/partial', (req, res) => {
  const data = jsonpath.query(largeJson, '$..items[?(@.priority > 3)]');
  res.json(data);
});

b) Binary JSON

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

const BSON = require('bson');

app.post('/bson', (req, res) => {
  const data = BSON.deserialize(req.body);
  const output = BSON.serialize(process(data));
  res.send(output);
});

6. Кэширование результатов

Для часто используемых данных:

const NodeCache = require('node-cache');
const jsonCache = new NodeCache({ stdTTL: 3600 });

app.get('/cached', (req, res) => {
  const cacheKey = req.originalUrl;
  const cached = jsonCache.get(cacheKey);
  if (cached) return res.json(cached);

  const data = processData();
  jsonCache.set(cacheKey, data);
  res.json(data);
});

7. Ограничение размера

Защита от слишком больших запросов:

app.use(express.json({ limit: '100kb' })); // Ограничение тела запроса

app.use((err, req, res, next) => {
  if (err.type === 'entity.too.large') {
    res.status(413).send('Payload too large');
  }
});

Резюмируем:

  1. Для небольших данных: стандартный JSON с лимитами
  2. Для больших данных:
    • Потоковая обработка (JSONStream)
    • Web Workers для CPU-bound операций
  3. Для максимальной производительности:
    • Специализированные библиотеки (simdjson, fast-json-stringify)
    • Бинарные форматы (BSON)
  4. Дополнительные меры:
    • Кэширование результатов
    • Частичная загрузка данных
    • Ограничение размера входящих данных

Главные правила:

  • Никогда не используйте синхронный JSON.parse/stringify для больших данных в основном потоке
  • Для API всегда устанавливайте разумные лимиты размеров
  • Рассмотрите альтернативные форматы данных если JSON становится узким местом