QStashでRedisの状態をデータベースへシームレスに同期する方法
前回の記事では、フルスタックなNext.jsアプリケーションにおいて、ユーザーのクォータ(利用制限)の状態をRedisに保存する方法について解説しました。一般的にRedisはデータベースよりもはるかに高速なため、この手法を採用することでアプリケーションのパフォーマンスを大幅に向上させることができます。
特にUpstash Redisは、このユースケースに最適な選択肢です。完全マネージド型のRedisデータベースであり、HTTP経由でアクセスできるため、Vercelのようなエッジランタイム上でも、ユーザーに近い場所で低レイテンシのコード実行が可能になります。
QStashとは何か?
QStashは、サーバーレスおよびエッジランタイム向けの、HTTPベースのメッセージング&スケジューリングソリューションです。言い換えれば、HTTPリクエストを送信することでCRONジョブを実行できるツールです。
CRONジョブは、特定の時刻にタスクをスケジュール実行するための優れた仕組みです。CRONジョブはバックグラウンドで好きなだけ時間をかけて処理を実行できるため、アプリケーション本体やエンドユーザーのパフォーマンスに影響を与えることはありません。
本記事におけるCRONジョブの目的は、ユーザーのクォータ状態をRedisからデータベースへ同期することです。このタスクを1時間ごとに実行するように設定します。
QStashでCRONジョブのエンドポイントを作成する
まずは前回の記事のおさらいとして、Redisでユーザーのクォータ状態をどのように扱っていたかを確認しましょう。
// Redisに保存されるキー。ユーザーごとにキーが作成され、IDによって一意になる。
const quotaKey = `user:${userId}:quota`;
// INCRコマンドを呼び出して、キーの値を1つ増やす。
const quota = await redis.incr(quotaKey);
// キーの値が1の場合、キーが新規作成されたことを意味する。
// EXPIREコマンドを使って、このキーに24時間の有効期限を設定できる。
if (quota === 1) {
await redis.expire(quotaKey, 60 * 60 * 24);
}
このコードをもとに、QStashを使ったCRONジョブのエンドポイントを作成していきます。エンドポイントは /api/cron/update-usage のURLでトリガーされるようにします。
まず、Next.jsのエンドポイント用に新しいファイルを作成しましょう。
touch pages/api/cron/update-usage.ts
次に重要なのがセキュリティです。このエンドポイントはQStashからのみトリガーできるようにする必要があります。これを実現してくれるのが @upstash/qstash パッケージです。もし認証を行わなければ、誰でもCRONジョブのエンドポイントを呼び出せてしまう可能性があり、深刻なセキュリティ問題につながりかねません。
@upstash/qstash をインストールしましょう。
npm install @upstash/qstash
これで、CRONジョブのエンドポイントのコードを書く準備が整いました。
エンドポイントの処理の流れ
実装内容を簡単にまとめると、以下のようになります。
- Redisの
SCANコマンドを使って、すべてのユーザーのクォータキーを取得する(カーソルベースのページネーションにより高性能)。 SCANで見つかった各キーに対して、ユーザーIDとクォータ値を含むオブジェクトの配列usersを作成する。users配列内の各ユーザーについてトランザクションを作成し、データベース上のクォータを更新する。ここではPrismaを使用する。@upstash/qstash/nextjsのverifySignature関数を使ってエンドポイント関数をエクスポートし、QStash以外からのトリガーを防ぐ。
以下がエンドポイントの完全なコードです。各部分にコメントを付けていますので、処理の流れを把握しやすいはずです。
// /pages/api/cron/update-usage.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { verifySignature } from "@upstash/qstash/nextjs";
import prisma from "@/lib/prisma";
// 環境変数を使ってUpstash Redisインスタンスを生成する。
// .envファイルにこれらの変数が定義されていることを確認しよう。
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
// `@upstash/qstash` に必要な設定。
export const config = { api: { bodyParser: false } };
// RedisからPrismaデータベースへデータを取り込み、全ユーザーの利用状況を更新する。
async function handler(req: NextApiRequest, res: NextApiResponse) {
// `SCAN` で見つかったすべてのキーを格納する配列。
const keys: string[] = [];
// カーソルの現在位置。`SCAN` のたびに更新される。
let cursor = 0;
// `SCAN` コマンドでカーソルベースのページネーションを実行する。
// カーソルがページネーションの終端に達すると、Redisは `0` を返す。
do {
const [nextCursor, newKeys] = await redis.scan(cursor, {
// 取得したいキーのパターンを指定。`*` はワイルドカード。
match: "user:*:quota",
// `SCAN` コマンドが返すキー数の上限。
count: 10,
});
cursor = nextCursor;
keys.push(...newKeys);
} while (cursor !== 0);
// ユーザーIDとクォータを含むオブジェクトの配列。
const users: { id: number; quota: number }[] = [];
// 見つかったキーをもとに、各ユーザーの利用状況をRedisから取得する。
for (const key of keys) {
// キーは `user:${id}:quota` の形式のはず。分割してIDを取り出す。
const id = key.split(":")[1];
// `GET` コマンドでユーザーの現在のクォータを取得する。
const quota = await redis.get(`user:${id}:quota`);
users.push({ id: parseInt(id, 10), quota });
}
// 各ユーザーについてトランザクションを作成し、データベースのクォータを更新する。
const promises = users.map((user) =>
prisma.user.update({
where: { id: user.id },
data: { quota: user.quota },
}),
);
// すべてのトランザクションを実行する。
await Promise.all(promises);
return res.status(200).json({ message: "OK" });
}
// `verifySignature` 関数でハンドラーをラップしてエクスポートすることで、
// QStashのみが認証されてCRONジョブをトリガーできるようにする。
export default verifySignature(handler);
これで最も難しい部分は完了です!
QStashのセットアップ
コードをデプロイする前に、QStashのセットアップを行いましょう。QStashは寛大な無料枠を提供しており、1日あたり最大500リクエストまで利用できます。
Upstashコンソールにログインし、QStash タブをクリックしてください。
続いて、Request Builder を使って、新しいスケジュール済みCRONジョブを作成します。直感的なUIのおかげで非常に簡単です。

Schedule ボタンをクリックした後、下にスクロールすると、Scheduled Jobs セクションに作成したCRONジョブが表示されます。

最後のステップとして、Next.jsアプリケーションに2つの環境変数を設定する必要があります。これらの変数は、Next.jsアプリがホストされている場所、今回はVercelに追加します。
Request Builder セクションには、コピー可能な2つの環境変数が用意されています。
QSTASH_CURRENT_SIGNING_KEYQSTASH_NEXT_SIGNING_KEY
これらは受信メッセージの認証に必要不可欠です。設定しなければ、誰でもあなたのエンドポイントを呼び出せてしまうことになります。

環境変数を取得したら、Vercelプロジェクトに追加しましょう。
Vercelプロジェクトのページに移動し、Settings をクリックしてから Environment Variables を選択します。

2つの環境変数を追加したら、コードをプッシュする準備は完了です。
以降、QStashが定期的にCRONジョブのエンドポイントをトリガーし、1時間ごとに全ユーザーの利用状況を自動的に更新してくれるようになります。
まとめ
Upstashはサーバーレス環境向けに多くの便利なツールを提供しています。QStashとRedisを組み合わせれば、さまざまなユースケースに対応した、高性能かつスケーラブルなアプリケーションを構築できます。
実際に動いているシステムを見たい方は、スプレッドシートをAPI化するツール「fastsheet」で同じ仕組みが実装されているので、ぜひ参考にしてみてください。
-
Next.jsとMetaMaskで実現するDApp向けユーザー設定ストレージの構築
DAOやDAppといったWeb3アプリケーションは、年々人気が高まっています。Web3の理念において、これらのプラットフォームは、ユーザーのIDを他者から、ときには本人自身からさえ隠したまま、よりパーソナルでカスタマイズされた体験を提供することが求められます。 本記事では、そうしたケースにおいてユーザー体験をいかに向上させるかを探っていきます。 ウォレットによる認証・ID確認 多くのアプリケーションでは、ETHなどの暗号資産を管理するためにMetaMaskのようなブロックチェーンウォレットが利用されています。こうしたウォレットを使うと、ユーザーはさまざまなチェーン上でアカウントを作成できます。
-
Vercel Edge FunctionsとUpstash Redisで実現する、安全で低遅延なフィルタリングによるデータ漏洩防止
インターネット上では、データ漏洩が深刻な問題となっています。Statistaの調査によると、2022年だけで4億人以上がデータ漏洩の影響を受けました。自分のデータがネット上に流出することを望む人は誰もいません。そのため、ユーザーのプライバシーを尊重するセキュアなアプリケーションの構築は、多くの業界において極めて重要な課題です。 データ漏洩に対抗する有効な手段の一つが、問題のあるデータを他のサービスやユーザーへ配信する前にフィルタリングすることです。この手法には、データの見逃しを防ぐための常に最新のフィルターと、フィルタリングによるパフォーマンスへの影響を最小限に抑えるための低遅延インフラという