見出し画像

UpstashでAPIのレスポンスをCacheしてみる【Redis】

こんばんは。みなさん、Upstashをご存知でしょうか?
知らない方のために、まずUpstashの説明をさせていただきます。

1.Upstashとは?

"Serverless Data for Kafka®"
"Serverless Data for Redis®"

とあるように、Upstashは、サーバーレスなデータベースを提供するサービスであり、主にRedisを活用しています。
さらに、Kafkaも利用することができます。
嬉しいことに、東京リージョンも利用でき、REST APIを通じて操作することができます。

Redis自体は、既に経験がある方も多いかもしれません。
今回は、Upstashが提供するサーバーレスなRedisを使用して、APIのレスポンスをキャッシュする実験を行ってみたいと思います。

2.なぜRedisでCacheするのか?

今回ECの新規開発にて、以下のような要件と課題がありました。

● SEO対応が必要なため、商品詳細ページではNext.jsのgetServerSidePropsを使用してサーバーサイドレンダリング(SSR)を行っている。
● 商品詳細ページでは、Shopifyから商品の基本情報や在庫を取得し、さらにMicroCMSから商品詳細情報(商品カタログ)を取得しています。
● 商品詳細ページでは、ShopifyとMicroCMSの両方のAPIをサーバーサイドで呼び出しているため、初期表示に時間がかかり、ページ遷移の体験が悪くなっている。※これはTTFB(Time to First Byte)が長いことが原因です。

この要件と課題を解決するための解決策として、Redisを使用してコンテンツのAPIレスポンスをキャッシュする方法を選択しました。

今回の要件

では早速やっていきます。
Upstashの登録など諸々の説明は省略しますが、Upstashを登録してDBを東京リージョンで作成します。
作成が完了すればREST APIやNodeなど、様々な方法で接続が可能になります。
今回はNext.jsを使用しているので、Node.jsから接続したいと思います。

3.ioredisをインストールする

まずは、redisに接続するためのライブラリであるioredisをインストールします。

$ yarn add -D ioredis

4.Redisの接続情報をenvに設定する

次に、UpstashからRedisに接続するための接続情報を取得し、それを環境変数(env)に設定します。

// .env

REDIS_PASSWORD="" // Upstashから取得
REDIS_PORT="" // Upstashから取得
REDIS_HOST="" // Upstashから取得
X_ENDPOINT_PRODUCTS="" // MicroCMSからデータをFetchするのでついでに追加
X_ENDPOINT_PRODUCTS="" // MicroCMSからデータをFetchするのでついでに追加

5.ioredisを初期化する

ioredisを使いやすくするために、初期化した関数をexportします。以下のように実装できます。

// redis.ts

export const redisClient = new Redis(`rediss://:${process.env.REDIS_PASSWORD ?? ''}@${process.env.REDIS_HOST ?? ''}:${process.env.REDIS_PORT ?? ''}`, {
  tls: { rejectUnauthorized: false }
});

初期化が完了したので、早速レスポンスをキャッシュに保存する処理を行います。
以下に、それぞれのステップを説明します。

● Redisからキーを指定してデータを取得します。 Redisクライアントのgetメソッドを使用して、キャッシュキーを指定してデータを取得します。
● Redisからデータを取得できなければ、MicroCMSから最新のデータを取得します。
● 取得したデータをJSON.stringifyを使用してJSONに変換し、Redisに取得結果をキャッシュとして保存します。
● キャッシュが存在する場合は、そのままJSONをパースしてデータを返します。

// fetch.ts

import { redisClient } from './redis';

//-----------------------------------------------------------------
// 商品の詳細情報を取得する
//-----------------------------------------------------------------
getProductDetail = async (productId: number) => {
  try {
    // RedisにCacheがあるかどうかをチェックする
    const productDetail = await redisClient.get(`product_detail_${productId}`);

    // Cacheが無ければMicroCMSからデータを取得してRedisに保存してからreturnする
    if (!productDetail) {
      const datas = await this.microCMSClient.fetchGetDetail({
        endpoint: process.env.X_ENDPOINT_PRODUCTS ?? '',
        contentId: handle
      });
      await redisClient.set(`product_detail_${productId}`, JSON.stringify(datas), 'EX', 60 * 60 * 24 * 6);
      return datas;
    }

    // CacheがあればJSONをparseしてreturnする
    return JSON.parse(productDetail) as IProductDetail;
  } catch (e) {
    return {
      error: {
        message: e instanceof Error ? e.message : {}
      }
    };
  }
};

このような処理を行うことで、Redisにキャッシュが存在すればMicroCMSからデータを取得せずに直接データを返すことが可能になります。
Redisはデータの取得が高速であるため、TTFB(First Byte Time)を低減することができますね。

6.Cache更新のタイミング

Cacheが存在しない場合はMicroCMSからデータを取得しますが、商品詳細情報が更新された場合にはどうすれば良いでしょうか?
Cacheに保存された情報が古くなってしまっている状況ですね。

結論として、商品詳細情報が更新された際には、MicroCMSのWebhookを使用してRedisのキャッシュをクリアするAPIを呼び出すことが適切な解決策です。

MicroCMSでは、情報が更新された際にWebhookのPOSTを起動することができます。
このWebhookが発火した時に、RedisのキャッシュをクリアするAPIを呼び出すことで、商品情報の更新タイミングでRedisのキャッシュをクリアし、最新の情報を取得する動作を実現できます。

Next.jsのISRなどは、強整合性の必要がないサイトでは有用な場合もありますが、ECサイトなどの強整合性が必要なサイトでは、コンテンツをキャッシュさせるためにこの方法は有効です。

それでは。

この記事が気に入ったらサポートをしてみませんか?