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

Библиотека Marked в JavaScript предоставляет мощные возможности для парсинга Markdown в HTML. Одним из важных аспектов оптимизации производительности при работе с большими объёмами Markdown является кэширование результатов преобразования. Кэширование позволяет избежать повторного парсинга одного и того же текста, что особенно важно при динамически обновляемых страницах и серверных приложениях.

Мотивация кэширования

Markdown-документы могут содержать сотни или тысячи строк. Парсинг каждого документа с нуля при каждом рендере может быть затратным по времени. Кэширование сокращает нагрузку на CPU и ускоряет рендеринг, сохраняя уже сгенерированные HTML-фрагменты для повторного использования.

Типы кэша

  1. В памяти Простое кэширование в объекте JavaScript. Подходит для приложений с ограниченным количеством документов или короткой сессией:

    const cache = new Map();
    
    function renderMarkdown(markdown) {
        if (cache.has(markdown)) {
            return cache.get(markdown);
        }
        const html = marked.parse(markdown);
        cache.set(markdown, html);
        return html;
    }

    В этом примере ключом служит сам Markdown-текст, а значением — результат его преобразования в HTML. Такой подход исключает повторный парсинг идентичных строк.

  2. С использованием хэширования Для больших документов прямое хранение текста в качестве ключа может быть неэффективным. Решение — использовать хэш-функции, например SHA-256, чтобы получить компактный ключ:

    import crypto from 'crypto';
    import { marked } from 'marked';
    
    const cache = new Map();
    
    function hashMarkdown(markdown) {
        return crypto.createHash('sha256').update(markdown).digest('hex');
    }
    
    function renderMarkdown(markdown) {
        const key = hashMarkdown(markdown);
        if (cache.has(key)) {
            return cache.get(key);
        }
        const html = marked.parse(markdown);
        cache.set(key, html);
        return html;
    }

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

Стратегии обновления кэша

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

  • TTL (Time to Live) — каждая запись хранится ограниченное время. После истечения TTL результат удаляется из кэша.
  • LRU (Least Recently Used) — удаляются наименее используемые элементы при превышении максимального размера кэша.
  • Комбинированные стратегии — TTL + LRU для оптимального баланса памяти и свежести данных.

Пример реализации простого LRU-кэша:

class LRUCache {
    constructor(maxSize) {
        this.maxSize = maxSize;
        this.cache = new Map();
    }

    get(key) {
        if (!this.cache.has(key)) return null;
        const value = this.cache.get(key);
        this.cache.delete(key);
        this.cache.set(key, value);
        return value;
    }

    set(key, value) {
        if (this.cache.has(key)) {
            this.cache.delete(key);
        } else if (this.cache.size >= this.maxSize) {
            const firstKey = this.cache.keys().next().value;
            this.cache.delete(firstKey);
        }
        this.cache.set(key, value);
    }
}

Использование LRU с Marked:

const cache = new LRUCache(100); // максимум 100 элементов

function renderMarkdown(markdown) {
    const key = hashMarkdown(markdown);
    const cached = cache.get(key);
    if (cached) return cached;
    const html = marked.parse(markdown);
    cache.set(key, html);
    return html;
}

Кэширование на сервере и клиенте

  • На сервере кэширование позволяет снизить нагрузку при множественных запросах к одному Markdown-ресурсу. Это особенно важно для API, генерирующих HTML для фронтенда.
  • На клиенте кэширование ускоряет повторные рендеры на одних и тех же страницах, экономит время при переключении между вкладками и обеспечивает плавный пользовательский опыт.

Взаимодействие с асинхронными функциями

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

const cache = new Map();

async function renderMarkdownAsync(markdown) {
    if (cache.has(markdown)) {
        return cache.get(markdown);
    }
    const htmlPromise = marked.parseAsync(markdown);
    cache.set(markdown, htmlPromise);
    return htmlPromise;
}

Советы по оптимизации

  • Использовать компактные ключи через хэширование для больших текстов.
  • Ограничивать размер кэша и применять стратегии LRU или TTL.
  • В серверных приложениях хранить кэш в Redis или аналогичных системах для масштабируемости.
  • Для динамических Markdown-данных избегать бессмысленного кэширования, проверяя уникальность содержимого.

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