ホームページ > ウェブフロントエンド > jsチュートリアル > インメモリキャッシュを作成する方法

インメモリキャッシュを作成する方法

Patricia Arquette
リリース: 2024-12-27 02:37:09
オリジナル
667 人が閲覧しました

How to Create an In-Memory Cache

多くのプロジェクトで、キャッシュは便利である一方で、特にクライアント側では見落とされがちであることに気づきました。クライアント側のキャッシュは、待ち時間を短縮し、繰り返されるサーバー要求をオフロードすることでユーザー エクスペリエンスを向上させるために重要です。たとえば、無限スクロールや頻繁に更新されるダッシュボードを備えたアプリケーションでは、以前に取得したデータをキャッシュすることで不必要な API 呼び出しが防止され、よりスムーズな操作とより高速なレンダリング時間が保証されます。

私の最近のプロジェクトの 1 つでは、キャッシュを実装することで API 呼び出し量が 40% 以上削減され、顕著なパフォーマンスの向上とコストの削減につながりました。これは、クライアント側のキャッシュが基本的な最適化戦略とみなされる理由を強調しています。キャッシュは、開発時間の制約やその他の優先事項のため、比較的単純な実装ではパフォーマンスに大きな影響を与えるにもかかわらず、最後に検討される機能の 1 つになる傾向があります。

キャッシュは、静的コンテンツの CDN である Redis を使用したバックエンド キャッシュから、クライアント上のメモリ内キャッシュ、さらには永続性のための localStorage や IndexedDB の使用まで、アーキテクチャのさまざまなレベルで実装できます。理想的には、これらの戦略を組み合わせて、データベースと API の負荷とコスト、さらには、特に以前にフェッチされたデータの場合のクライアント/サーバー リクエストの遅延を軽減する必要があります。

この記事では、JavaScript で TTL (Time-to-Live) サポートを備えた LRU (Least Recent Used) キャッシュを設計および実装し、私の adev-lru と同様のパッケージを作成する方法を検討します。最後には、効果的なキャッシュ ソリューションの中核原則と機能を示す実用的な例が完成します。


LRUキャッシュとは何ですか?

LRU (最も最近使用されていない) キャッシュは、最近アクセスされた項目がメモリ内に残るようにし、容量を超えた場合は最も最近アクセスされた項目を削除します。この戦略は、使用順序を維持することによって機能します。各アクセサリはキャッシュ内のアイテムの位置を更新し、最もアクセスの少ないアイテムが最初に削除されます。

他のキャッシュ戦略と比較して、LRU はシンプルさと効率のバランスが取れており、最近の使用状況が将来のアクセスの信頼できる指標となるシナリオに適しています。たとえば、API 応答、サムネイル、または頻繁にアクセスされるユーザー設定をキャッシュするアプリケーションは、LRU を利用して、エビクション プロセスを過度に複雑にすることなく、冗長なフェッチ操作を削減できます。

アクセス頻度を追跡し追加の簿記を必要とする LFU (最低使用頻度) とは異なり、LRU はこの複雑さを回避しながら、実際の多くのユースケースで優れたパフォーマンスを実現します。同様に、FIFO (先入れ先出し) と MRU (最近使用されたもの) は代替のエビクション ポリシーを提供しますが、最近のアクティビティが重要な使用パターンにはあまり適合しない可能性があります。私の実装では LRU と TTL (Time-to-Live) サポートを組み合わせることで、データの自動有効期限が必要なシナリオも処理し、ライブ ダッシュボードやストリーミング サービスなどの動的な環境での適用性をさらに高めます。最新のデータへのアクセスが重要なアプリケーションで特に役立ちます。

実装

LRUCache クラスは、効率的で、柔軟な構成をサポートし、自動エビクションを処理するように構築されています。以下にいくつかの主要なメソッドを示します:

キャッシュの作成

public static getInstance<T>(capacity: number = 10): LRUCache<T> {
    if (LRUCache.instance == null) {
        LRUCache.instance = new LRUCache<T>(capacity);
    }
    return LRUCache.instance;
}
ログイン後にコピー
ログイン後にコピー

この方法では、アプリケーション内にキャッシュのインスタンスが 1 つだけ存在することが保証され、リソース管理を簡素化する設計上の選択になります。キャッシュをシングルトンとして実装することで、冗長なメモリの使用を回避し、アプリケーション全体で一貫したデータを確保します。これは、追加の調整ロジックを必要とせずに競合を防止し、確実に同期できるため、複数のコンポーネントまたはモジュールが同じキャッシュされたデータにアクセスする必要があるシナリオで特に役立ちます。容量が指定されていない場合、デフォルトは 10 です。

キャッシュへのアイテムの追加

public put(key: string, value: T, ttl: number = 60_000): LRUCache<T> {
    const now = Date.now();
    let node = this.hash.get(key);
    if (node != null) {
        this.evict(node);
    }
    node = this.prepend(key, value, now + ttl);
    this.hash.set(key, node);
    if (this.hash.size > this.capacity) {
        const tailNode = this.pop();
        if (tailNode != null) {
            this.hash.delete(tailNode.key);
        }
    }
    return this;
}
ログイン後にコピー
ログイン後にコピー

このメソッドは、キャッシュ内の項目を追加または更新します。キーがすでに存在する場合、対応する項目は削除され、キャッシュの先頭に再追加されます。これを行うために、キャッシュは二重リンク リストを使用してデータをノードとして保存し、リストの最後 (末尾) からデータを削除してリストの先頭 (先頭) に移動する機能を維持し、定数 O を保証します。 (1) すべてのノードのデータを読み取り、リストの各ノードへのポインターを保存するためにハッシュ テーブルが使用されます。このプロセスは、最近アクセスしたアイテムが常に優先され、効果的に「最近使用された」アイテムとしてマークされるようにすることで、LRU の原則に沿っています。そうすることで、キャッシュは正確な使用順序を維持します。これは、容量を超えた場合にエビクションの決定を行うために重要です。この動作により、リソースが最適に管理され、頻繁にアクセスされるデータの取得時間が最小限に抑えられます。キーがすでに存在する場合、項目は前面に移動され、最近使用されたものとしてマークされます。

キャッシュからアイテムを取得する

public get(key: string): T | undefined {
    const node = this.hash.get(key);
    const now = Date.now();
    if (node == null || node.ttl < now) {
        return undefined;
    }
    this.evict(node);
    this.prepend(node.key, node.value, node.ttl);
    return node.value;
}
ログイン後にコピー

このメソッドは、保存されているアイテムを取得します。アイテムの有効期限が切れた場合、アイテムはキャッシュから削除されます。

パフォーマンス指標

キャッシュの効率を評価するために、ヒット率、ミス、エビクションなどのパフォーマンス指標を実装しました。

public static getInstance<T>(capacity: number = 10): LRUCache<T> {
    if (LRUCache.instance == null) {
        LRUCache.instance = new LRUCache<T>(capacity);
    }
    return LRUCache.instance;
}
ログイン後にコピー
ログイン後にコピー

キャッシュをクリアする

public put(key: string, value: T, ttl: number = 60_000): LRUCache<T> {
    const now = Date.now();
    let node = this.hash.get(key);
    if (node != null) {
        this.evict(node);
    }
    node = this.prepend(key, value, now + ttl);
    this.hash.set(key, node);
    if (this.hash.size > this.capacity) {
        const tailNode = this.pop();
        if (tailNode != null) {
            this.hash.delete(tailNode.key);
        }
    }
    return this;
}
ログイン後にコピー
ログイン後にコピー

このメソッドはすべてのアイテムをクリアし、キャッシュ状態をリセットします。

私の実装では、T | を返す代わりに getOption のような他のメソッドも追加しました。未定義の場合は、より機能的なアプローチを好む人のために、モナド オプションのインスタンスを返します。また、ロギング目的でキャッシュ上のすべての操作を追跡するための Writer モナドも追加しました。

このアルゴリズムに関連する他のすべてのメソッドは、このリポジトリで非常に詳しくコメントされています: https://github.com/Armando284/adev-lru


キャッシュアルゴリズムの比較

LRU キャッシュが唯一の選択肢ではありません。適切なキャッシュ アルゴリズムの選択は、アプリケーション固有の要件とアクセス パターンに大きく依存します。以下は、LRU と他の一般的に使用されるキャッシュ戦略との比較、およびそれぞれをいつ使用するかに関するガイダンスです:

  • LFU (最低使用頻度): このアルゴリズムは、アクセス回数が最も少ないアイテムを削除します。これは、最近のアクセスよりも長期にわたる使用頻度の方が将来のアクセスを予測するシナリオに最適です。ただし、通常、アクセス数を追跡するために追加の簿記が必要となるため、LRU よりも複雑で計算コストが高くなります。過去の使用パターンが重要なレコメンデーション エンジンや機械学習パイプラインなどのアプリケーションには、LFU を使用します。
  • FIFO (先入れ先出し): このアプローチでは、アクセス頻度に関係なく、最も古いアイテムが削除されます。 FIFO は実装が簡単ですが、使用パターンが考慮されていないため、ほとんどのユースケースには適していない可能性があります。静的構成やプリロードされたアセットのキャッシュなど、固定された予測可能なワークフローを持つアプリケーションで機能します。
  • MRU (最近使用されたもの): MRU は、LRU とは逆に、最近アクセスされたアイテムを削除します。この戦略は、ロールバック システムや特定の種類の元に戻す操作など、古いデータが再利用される可能性が高い状況に最適です。

LRU を使用する場合

LRU は、シンプルさと有効性のバランスをとっており、最近のアクティビティが将来の使用と強く相関するアプリケーションに最適です。例:

  • Web および API キャッシュ: LRU は、特にページ分割されたビュー、無限スクロール、ライブ データの頻繁なポーリングなどのシナリオで、冗長な API 呼び出しを削減するのに適しています。
  • マルチメディア アプリケーション: ビデオや画像など、最近再生または表示したアイテムをキャッシュします。
  • UI 状態管理: 最近アクセスしたコンポーネントの状態を保存して、レンダリング パフォーマンスを向上させます。

対照的に、アクセス パターンが頻度や挿入順序の方が関連性があることを示している場合は、LFU や FIFO などのアルゴリズムがより良い選択となる可能性があります。これらのトレードオフを評価することで、キャッシュ戦略がアプリケーションの目標とリソースの制約に確実に適合するようにします。

  • LFU (最も頻繁に使用されない): 頻度に基づいて最もアクセスの少ないアイテムを削除します。
  • FIFO (先入れ先出し): 最近の使用状況に関係なく、最も古いアイテムを削除します。
  • MRU (最近使用された): LRU とは逆に、最近追加されたアイテムを削除します。

結論

メモリ内キャッシュを実装すると、アプリケーションのパフォーマンスが大幅に向上し、応答時間が短縮され、ユーザー エクスペリエンスが向上します。

完全な LRU キャッシュの動作を確認したい場合は、私の npm パッケージ https://www.npmjs.com/package/adev-lru を使用できます。また、改善を続けるためにフィードバックもお待ちしています。

パッケージを試して感想を共有したり、もっと支援したいと感じたら貢献してみてはいかがでしょうか?!

以上がインメモリキャッシュを作成する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート