「労働者が自分の仕事をうまくやりたいなら、まず自分の道具を研ぎ澄まさなければなりません。」 - 孔子、「論語。陸霊公」
表紙 > プログラミング > Fastify と Redis Cache を使用して Web サイトを高速化する

Fastify と Redis Cache を使用して Web サイトを高速化する

2024 年 8 月 27 日に公開
ブラウズ:210

Speeding Up Your Website Using Fastify and Redis Cache

24 時間も前に、Cloudflare キャッシュを使用して Web サイトを高速化する方法についての投稿を書きました。ただし、その後、ロジックの大部分を Redis を使用する Fastify ミドルウェアに移行しました。その理由と、自分で行う方法は次のとおりです。

Cloudflareのキャッシュの問題

Cloudflare キャッシュで 2 つの問題が発生しました:

  • 応答のキャッシュを有効にするとページ ナビゲーションが壊れました。この件については少し前に Remix フォーラムで問題を提起しましたが、これを書いている時点ではまだ未解決です。応答のキャッシュによってページ ナビゲーションが中断される理由は明らかではありませんが、応答が Cloudflare によってキャッシュされている場合にのみ発生します。
  • 元の投稿で説明されているように、Cloudflare で再検証中に古いコンテンツを提供することを実行できませんでした。利用できる機能ではないようです。

他にもいくつか問題が発生しました (パターン マッチングを使用してキャッシュをパージできないなど) が、それらは私の使用例にとって重大なものではありませんでした。

そこで、Redis を使用してロジックを Fastify ミドルウェアに移動することにしました。

[!注記]
画像キャッシュのためにCloudflareキャッシュを残しました。この場合、Cloudflare キャッシュは効果的に CDN として機能します。

Fastify ミドルウェア

以下は、Fastify を使用して応答をキャッシュするために作成したミドルウェアの注釈付きバージョンです。

const isCacheableRequest = (request: FastifyRequest): boolean => {
  // Do not attempt to use cache for authenticated visitors.
  if (request.visitor?.userAccount) {
    return false;
  }

  if (request.method !== 'GET') {
    return false;
  }

  // We only want to cache responses under /supplements/.
  if (!request.url.includes('/supplements/')) {
    return false;
  }

  // We provide a mechanism to bypass the cache.
  // This is necessary for implementing the "Serve Stale Content While Revalidating" feature.
  if (request.headers['cache-control'] === 'no-cache') {
    return false;
  }

  return true;
};

const isCacheableResponse = (reply: FastifyReply): boolean => {
  if (reply.statusCode !== 200) {
    return false;
  }

  // We don't want to cache responses that are served from the cache.
  if (reply.getHeader('x-pillser-cache') === 'HIT') {
    return false;
  }

  // We only want to cache responses that are HTML.
  if (!reply.getHeader('content-type')?.toString().includes('text/html')) {
    return false;
  }

  return true;
};

const generateRequestCacheKey = (request: FastifyRequest): string => {
  // We need to namespace the cache key to allow an easy purging of all the cache entries.
  return 'request:'   generateHash({
    algorithm: 'sha256',
    buffer: stringifyJson({
      method: request.method,
      url: request.url,
      // This is used to cache viewport specific responses.
      viewportWidth: request.viewportWidth,
    }),
    encoding: 'hex',
  });
};

type CachedResponse = {
  body: string;
  headers: Record;
  statusCode: number;
};

const refreshRequestCache = async (request: FastifyRequest) => {
  await got({
    headers: {
      'cache-control': 'no-cache',
      'sec-ch-viewport-width': String(request.viewportWidth),
      'user-agent': request.headers['user-agent'],
    },
    method: 'GET',
    url: pathToAbsoluteUrl(request.originalUrl),
  });
};

app.addHook('onRequest', async (request, reply) => {
  if (!isCacheableRequest(request)) {
    return;
  }

  const cachedResponse = await redis.get(generateRequestCacheKey(request));

  if (!cachedResponse) {
    return;
  }

  reply.header('x-pillser-cache', 'HIT');

  const response: CachedResponse = parseJson(cachedResponse);

  reply.status(response.statusCode);
  reply.headers(response.headers);
  reply.send(response.body);
  reply.hijack();

  setImmediate(() => {
    // After the response is sent, we send a request to refresh the cache in the background.
    // This effectively serves stale content while revalidating.
    // Therefore, this cache does not reduce the number of requests to the origin;
    // The goal is to reduce the response time for the user.
    refreshRequestCache(request);
  });
});

const readableToString = (readable: Readable): Promise => {
  const chunks: Uint8Array[] = [];

  return new Promise((resolve, reject) => {
    readable.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
    readable.on('error', (err) => reject(err));
    readable.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
  });
};

app.addHook('onSend', async (request, reply, payload) => {
  if (reply.hasHeader('x-pillser-cache')) {
    return payload;
  }

  if (!isCacheableRequest(request) || !isCacheableResponse(reply) || !(payload instanceof Readable)) {
    // Indicate that the response is not cacheable.
    reply.header('x-pillser-cache', 'DYNAMIC');

    return payload;
  }

  const content = await readableToString(payload);

  const headers = omit(reply.getHeaders(), [
    'content-length',
    'set-cookie',
    'x-pillser-cache',
  ]) as Record;

  reply.header('x-pillser-cache', 'MISS');

  await redis.setex(
    generateRequestCacheKey(request),
    getDuration('1 day', 'seconds'),
    stringifyJson({
      body: content,
      headers,
      statusCode: reply.statusCode,
    } satisfies CachedResponse),
  );

  return content;
});

コメントはコードを詳しく説明していますが、重要なポイントがいくつかあります:

  • キャッシュ基準:
    • リクエスト:
    • 認証されたユーザーの応答をキャッシュしません。
    • GET リクエストのみをキャッシュします。
    • 「/supplements/」を含む URL の応答のみをキャッシュします。
    • リクエストヘッダーにcache-control: no-cacheが含まれている場合はキャッシュをバイパスします。
    • 応答:
    • 成功した応答のみをキャッシュします (statusCode は 200)。
    • すでにキャッシュから提供されている応答をキャッシュしません (x-pillser-cache: HIT)。
    • content-type: text/html の応答のみをキャッシュします。
  • キャッシュキーの生成:
    • リクエスト メソッド、URL、ビューポート幅を含む JSON 表現の SHA-256 ハッシュを使用します。
    • 名前空間の設定と削除を容易にするために、キャッシュ キーの先頭に「request:」を付けます。
  • リクエストの処理:
    • onRequest ライフサイクルにフックして、リクエストにキャッシュされた応答があるかどうかを確認します。
    • 利用可能な場合はキャッシュされた応答を提供し、x-pillser-cache: HIT でマークします。
    • キャッシュされた応答を送信した後にバックグラウンド タスクを開始してキャッシュを更新し、「再検証中に古いコンテンツを提供する」を実装します。
  • 応答処理:
    • onSend ライフサイクルにフックして、応答を処理してキャッシュします。
    • キャッシュを簡素化するために、読み取り可能なストリームを文字列に変換します。
    • 特定のヘッダー (content-length、set-cookie、x-pillser-cache) をキャッシュから除外します。
    • キャッシュ不可能な応答を x-pillser-cache としてマークします: DYNAMIC.
    • 1 日の TTL (Time To Live) で応答をキャッシュし、x-pillser-cache: MISS で新しいエントリをマークします。

結果

いくつかの場所からレイテンシ テストを実行し、各 URL の最も遅い応答時間を取得しました。結果は以下の通りです:

URL オリジン応答時間 Cloudflareのキャッシュされた応答時間 キャッシュされた応答時間の高速化
https://pillser.com/vitamins/vitamin-b1 us-west1 240ミリ秒 16ミリ秒 40ミリ秒
https://pillser.com/vitamins/vitamin-b1 ヨーロッパ西3 320ミリ秒 10ミリ秒 110ミリ秒
https://pillser.com/vitamins/vitamin-b1 オーストラリア南東1 362ミリ秒 16ミリ秒 192ミリ秒
https://pillser.com/supplements/vitamin-b1-3254 us-west1 280ミリ秒 10ミリ秒 38ミリ秒
https://pillser.com/supplements/vitamin-b1-3254 ヨーロッパ西3 340ミリ秒 12ミリ秒 141ミリ秒
https://pillser.com/supplements/vitamin-b1-3254 オーストラリア南東1 362ミリ秒 14ミリ秒 183ミリ秒

Cloudflare キャッシュと比較すると、Fastify キャッシュは低速です。これは、Cloudflare キャッシュが地域のエッジロケーションから提供されるのに対し、キャッシュされたコンテンツは引き続きオリジンから提供されるためです。ただし、優れたユーザー エクスペリエンスを実現するには、これらの応答時間で十分であることがわかりました。

リリースステートメント この記事は次の場所に転載されています: https://dev.to/lilouartz/speeding-up-your-website-using-fastify-and-redis-cache-4ck6?1 侵害がある場合は、[email protected] までご連絡ください。それを削除するには
最新のチュートリアル もっと>
  • PHP デザイン パターン: アダプター
    PHP デザイン パターン: アダプター
    アダプター デザイン パターンは、互換性のないインターフェイスを持つオブジェクトが連携できるようにする構造パターンです。これは 2 つのオブジェクト間の仲介者 (またはアダプター) として機能し、一方のオブジェクトのインターフェイスを、もう一方のオブジェクトが期待するインターフェイスに変換します。こ...
    プログラミング 2024 年 11 月 6 日に公開
  • PHP の WebSocket を理解する
    PHP の WebSocket を理解する
    WebSocket は、単一の TCP 接続上でリアルタイムの全二重通信チャネルを提供します。クライアントがサーバーにリクエストを送信して応答を待つ HTTP とは異なり、WebSocket を使用すると、複数のリクエストを必要とせずにクライアントとサーバー間の継続的な通信が可能になります。これは、...
    プログラミング 2024 年 11 月 6 日に公開
  • Visual Studio 2012 ではどのような C++11 機能がサポートされていますか?
    Visual Studio 2012 ではどのような C++11 機能がサポートされていますか?
    Visual Studio 2012 の C 11 機能Visual Studio 2012 のプレビュー バージョンが最近リリースされたため、多くの開発者が C 11 機能のサポートに興味を持っています。 Visual Studio 2010 ではすでに部分的な C 11 サポートが提供されていま...
    プログラミング 2024 年 11 月 6 日に公開
  • Windows の起動時に Python スクリプトを自動的に実行するにはどうすればよいですか?
    Windows の起動時に Python スクリプトを自動的に実行するにはどうすればよいですか?
    Windows 起動時に Python スクリプトを実行するWindows を起動するたびに Python スクリプトを実行することは、タスクを自動化したり、重要なプログラムを起動したりするために非常に重要です。いくつかのアプローチで、さまざまなレベルのカスタマイズとユーザー制御が提供されます。スク...
    プログラミング 2024 年 11 月 6 日に公開
  • Astral.CSS の探索: Web デザインに革命をもたらす CSS フレームワーク。
    Astral.CSS の探索: Web デザインに革命をもたらす CSS フレームワーク。
    ペースの速い Web 開発の世界では、フレームワークは、開発者が視覚的に魅力的で機能的な Web サイトを効率的に作成する上で極めて重要な役割を果たします。現在利用可能なさまざまなフレームワークの中で、Astral CSS は、そのユニークな設計哲学と使いやすさの点で際立っています。この記事では、A...
    プログラミング 2024 年 11 月 6 日に公開
  • ESnd アロー関数の包括的なガイド
    ESnd アロー関数の包括的なガイド
    ES6 の概要 ECMAScript 2015 は、ES6 (ECMAScript 6) とも呼ばれ、JavaScript の大幅なアップデートであり、コーディングをより効率的で管理しやすくする新しい構文と機能が導入されています。 JavaScript は Web 開発に使用される...
    プログラミング 2024 年 11 月 6 日に公開
  • アルゴリズムとデータ構造の解明: 効率的なプログラミングの基礎
    アルゴリズムとデータ構造の解明: 効率的なプログラミングの基礎
    この一連の投稿では、学術環境と大手テクノロジー企業の両方で広く議論されている 2 つのトピック、アルゴリズムとデータ構造についての私の学習過程を共有します。これらのトピックは一見すると難しそうに見えるかもしれませんが、特に私のような、他の職業上の課題のためにキャリアを通じてそれらを深く掘り下げる機会...
    プログラミング 2024 年 11 月 6 日に公開
  • pprof を使用して Go プログラム内のゴルーチンの数をプロファイリングするにはどうすればよいですか?
    pprof を使用して Go プログラム内のゴルーチンの数をプロファイリングするにはどうすればよいですか?
    pprof を使用したゴルーチン数のプロファイリングGo プログラム内の潜在的なゴルーチン リークを検出するには、アクティブなゴルーチンの数を長期にわたって監視する必要があります。標準の go ツール pprof コマンドはブロックに関する洞察を提供しますが、ゴルーチンの数に直接対処するものではあり...
    プログラミング 2024 年 11 月 6 日に公開
  • クラスメソッドをコールバックとして渡す方法: メカニズムとテクニックを理解する
    クラスメソッドをコールバックとして渡す方法: メカニズムとテクニックを理解する
    クラス メソッドをコールバックとして渡す方法バックグラウンドシナリオによっては、効率的にクラス メソッドを他の関数へのコールバックとして渡す必要がある場合があります。特定のタスクの実行。この記事では、これを実現するためのさまざまなメカニズムについて説明します。呼び出し可能な構文の使用関数をコールバッ...
    プログラミング 2024 年 11 月 6 日に公開
  • Webスクレイピング - 面白いですね!
    Webスクレイピング - 面白いですね!
    クールな用語: CRON = 指定された間隔でタスクを自動的にスケジュールするプログラミング技術 ウェブって何? プロジェクトなどを調査するとき、私たちは通常、日記、エクセル、ドキュメントなど、さまざまなサイトから情報を書き込みます。 私たちはウェブをスクレイピングし、手動でデータ...
    プログラミング 2024 年 11 月 6 日に公開
  • お客様の声グリッドセクション
    お客様の声グリッドセクション
    ? CSS グリッドを学習しながら、このお客様の声グリッド セクションの作成が完了しました。 ?グリッドは構造化されたレイアウトの作成に最適です。 ?ライブデモ: https://courageous-chebakia-b55f43.netlify.app/ ? GitHub: https://gi...
    プログラミング 2024 年 11 月 6 日に公開
  • REGISTER_GLOBALS が PHP の主要なセキュリティ リスクとみなされるのはなぜですか?
    REGISTER_GLOBALS が PHP の主要なセキュリティ リスクとみなされるのはなぜですか?
    REGISTER_GLOBALS の危険性REGISTER_GLOBALS は、すべての GET 変数と POST 変数を PHP スクリプト内でグローバル変数として使用できるようにする PHP 設定です。この機能は便利に見えるかもしれませんが、潜在的なセキュリティ脆弱性やコーディング方法のため、使...
    プログラミング 2024 年 11 月 6 日に公開
  • Nodemailer の概要: Node.js での簡単な電子メール送信
    Nodemailer の概要: Node.js での簡単な電子メール送信
    Nodemailer は、メールを送信するための Node.js モジュールです。簡単な概要は次のとおりです: トランスポーター: 電子メールの送信方法を定義します (Gmail、カスタム SMTP など経由)。 const transporter = nodemailer.createTra...
    プログラミング 2024 年 11 月 6 日に公開
  • JavaScript での簡単なエラー処理: 安全な代入演算子がコードを簡素化する方法
    JavaScript での簡単なエラー処理: 安全な代入演算子がコードを簡素化する方法
    JavaScript でのエラー処理は面倒になる場合があります。 try/catch ステートメントで大きなコード ブロックをラップすることは機能しますが、プロジェクトが成長するにつれて、デバッグは悪夢のようになります。幸いなことに、もっと良い方法があります。 安全な代入演算子 (?=) を入力しま...
    プログラミング 2024 年 11 月 6 日に公開
  • Javascript は難しい (ESadness あり)
    Javascript は難しい (ESadness あり)
    長文になりますが、もう一度言わせてください。 JAVASCRIPTは難しいです。最後に会ったとき、私は Javascript の世界に足を踏み入れていました。目を輝かせ、希望に満ちたプログラマーが野生のジャングルに足を踏み入れ、「これはどれほど難しいことでしょう?」と言いました。私はどれほど間違っ...
    プログラミング 2024 年 11 月 6 日に公開

免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。

Copyright© 2022 湘ICP备2022001581号-3