Node.jsとRedis分散キャッシュで構築する高パフォーマンスなURL短縮サービス
このチュートリアルでは、Node.jsとRedisを使用して、スケーラブルなURL短縮サービスを構築する方法を解説します。このサービスは分散キャッシュを活用することで、大量のトラフィックにも効率的に対応し、レイテンシを削減しながらシームレスにスケールできます。一貫性ハッシュ(Consistent Hashing)、キャッシュ無効化戦略、シャーディングといった重要な概念についても詳しく見ていき、高速かつ信頼性の高いシステムを実現します。
このガイドを読み終える頃には、分散キャッシュによってパフォーマンスを最適化した、完全に動作するURL短縮サービスが完成しています。さらに、ユーザーがURLを入力すると、キャッシュヒットやキャッシュミスなどのリアルタイムメトリクスを確認できるインタラクティブなデモも作成します。
この記事で学べること
- Node.jsとRedisを使ったURL短縮サービスの構築方法
- パフォーマンスを最適化するための分散キャッシュの実装方法
- 一貫性ハッシュとキャッシュ無効化戦略の理解
- Dockerを使って複数のRedisインスタンスをシミュレートし、シャーディングとスケーリングを実現する方法
前提条件
始める前に、以下がインストールされていることを確認してください。
- Node.js(v14以上)
- Redis
- Docker
- JavaScript、Node.js、Redisに関する基礎知識
目次
- プロジェクト概要
- ステップ1:プロジェクトのセットアップ
- ステップ2:Redisインスタンスの構築
- ステップ3:URL短縮サービスの実装
- ステップ4:キャッシュ無効化の実装
- ステップ5:キャッシュメトリクスの監視
- ステップ6:アプリケーションのテスト
- まとめ:学んだこと
プロジェクト概要
今回構築するURL短縮サービスは、以下のような仕組みになります。
- ユーザーは長いURLを短縮でき、短縮URLから元のURLへアクセスできます。
- サービスはRedisキャッシュを使用して、短縮URLと元のURLのマッピングを保存します。
- キャッシュは複数のRedisインスタンスに分散され、大量のトラフィックに対応します。
- システムはキャッシュヒットとキャッシュミスをリアルタイムに表示します。
システムアーキテクチャ
スケーラビリティとパフォーマンスを確保するため、サービスを以下のコンポーネントに分割します。
- APIサーバー:URLの短縮・取得リクエストを処理します。
- Redisキャッシュ層:複数のRedisインスタンスを使用した分散キャッシュを提供します。
- Docker:複数のRedisコンテナで分散環境をシミュレートします。
ステップ1:プロジェクトのセットアップ
まず、Node.jsアプリケーションを初期化してプロジェクトをセットアップしましょう。
mkdir scalable-url-shortener
cd scalable-url-shortener
npm init -y
次に、必要な依存パッケージをインストールします。
npm install express redis shortid dotenv
express:軽量なWebサーバーフレームワークです。redis:キャッシュ処理を担当します。shortid:短く一意なIDを生成するために使用します。dotenv:環境変数を管理するために使用します。
プロジェクトのルートに.envファイルを作成します。
PORT=3000
REDIS_HOST_1=localhost
REDIS_PORT_1=6379
REDIS_HOST_2=localhost
REDIS_PORT_2=6380
REDIS_HOST_3=localhost
REDIS_PORT_3=6381
これらの変数は、使用するRedisのホストとポートを定義しています。
ステップ2:Redisインスタンスの構築
Dockerを使用して、複数のRedisインスタンスによる分散環境をシミュレートします。
以下のコマンドを実行して、3つのRedisコンテナを起動しましょう。
docker run -p 6379:6379 --name redis1 -d redis
docker run -p 6380:6379 --name redis2 -d redis
docker run -p 6381:6379 --name redis3 -d redis
これにより、異なるポートで稼働する3つのRedisインスタンスが準備できます。これらのインスタンスを使って、一貫性ハッシュとシャーディングを実装していきます。
ステップ3:URL短縮サービスの実装
それでは、メインのアプリケーションファイルindex.jsを作成しましょう。
require('dotenv').config();
const express = require('express');
const redis = require('redis');
const shortid = require('shortid');
const app = express();
app.use(express.json());
const redisClients = [
redis.createClient({ host: process.env.REDIS_HOST_1, port: process.env.REDIS_PORT_1 }),
redis.createClient({ host: process.env.REDIS_HOST_2, port: process.env.REDIS_PORT_2 }),
redis.createClient({ host: process.env.REDIS_HOST_3, port: process.env.REDIS_PORT_3 })
];
// キーを各Redisクライアントに振り分けるハッシュ関数
function getRedisClient(key) {
const hash = key.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
return redisClients[hash % redisClients.length];
}
// URLを短縮するエンドポイント
app.post('/shorten', async (req, res) => {
const { url } = req.body;
if (!url) return res.status(400).send('URL is required');
const shortId = shortid.generate();
const redisClient = getRedisClient(shortId);
await redisClient.set(shortId, url);
res.json({ shortUrl: `https://localhost:${process.env.PORT}/${shortId}` });
});
// 元のURLを取得するエンドポイント
app.get('/:shortId', async (req, res) => {
const { shortId } = req.params;
const redisClient = getRedisClient(shortId);
redisClient.get(shortId, (err, url) => {
if (err || !url) {
return res.status(404).send('URL not found');
}
res.redirect(url);
});
});
app.listen(process.env.PORT, () => {
console.log(`Server running on port ${process.env.PORT}`);
});
このコードのポイントは以下の通りです。
- 一貫性ハッシュ:
- シンプルなハッシュ関数を使って、キー(短縮URL)を複数のRedisクライアントに分散させています。
- ハッシュ関数により、URLがRedisインスタンス間に均等に分配されることが保証されます。
- URL短縮:
- /shortenエンドポイントが長いURLを受け取り、shortidライブラリで短いIDを生成します。
- 短縮されたURLは、ハッシュ関数によっていずれかのRedisインスタンスに保存されます。
- URLリダイレクト:
- /:shortIdエンドポイントがキャッシュから元のURLを取得し、ユーザーをリダイレクトします。
- キャッシュ内にURLが見つからない場合は、404レスポンスを返します。
ステップ4:キャッシュ無効化の実装
実際のアプリケーションでは、URLが時間の経過とともに期限切れになったり変更されたりすることがあります。これに対応するためには、キャッシュ無効化の仕組みが必要です。
キャッシュされたURLへの有効期限の追加
index.jsを修正して、キャッシュされた各エントリに有効期限を設定できるようにしましょう。
// 有効期限付きでURLを短縮するエンドポイント
app.post('/shorten', async (req, res) => {
const { url, ttl } = req.body; // ttl(有効期間)は任意
if (!url) return res.status(400).send('URL is required');
const shortId = shortid.generate();
const redisClient = getRedisClient(shortId);
await redisClient.set(shortId, url, 'EX', ttl || 3600); // デフォルトTTLは1時間
res.json({ shortUrl: `https://localhost:${process.env.PORT}/${shortId}` });
});
- TTL(Time-To-Live):短縮URLごとにデフォルトで1時間の有効期限を設定しています。必要に応じて、URLごとにTTLをカスタマイズできます。
- キャッシュ無効化:TTLが期限切れになると、該当エントリは自動的にキャッシュから削除されます。
ステップ5:キャッシュメトリクスの監視
キャッシュヒットとキャッシュミスを監視するため、index.jsのエンドポイントにログ出力を追加します。
app.get('/:shortId', async (req, res) => {
const { shortId } = req.params;
const redisClient = getRedisClient(shortId);
redisClient.get(shortId, (err, url) => {
if (err || !url) {
console.log(`Cache miss for key: ${shortId}`);
return res.status(404).send('URL not found');
}
console.log(`Cache hit for key: ${shortId}`);
res.redirect(url);
});
});
このコードの動作は以下の通りです。
- キャッシュヒット:URLがキャッシュ内で見つかった場合です。
- キャッシュミス:URLが見つからなかった場合です。
- このログ出力により、分散キャッシュのパフォーマンスを監視できます。
ステップ6:アプリケーションのテスト
- Redisインスタンスを起動します。
docker start redis1 redis2 redis3
- Node.jsサーバーを起動します。
node index.js
curlやPostmanを使ってエンドポイントをテストします。- URLを短縮する場合:
POST https://localhost:3000/shorten Body: { "url": "https://example.com" }- 短縮URLにアクセスする場合:
GET https://localhost:3000/{shortId}
まとめ:学んだこと
おめでとうございます!Node.jsとRedisを使った、分散キャッシュによるスケーラブルなURL短縮サービスの構築に成功しました。このチュートリアルを通じて、以下のことを学びました。
- 一貫性ハッシュを実装し、キャッシュエントリを複数のRedisインスタンスに分散させる方法
- キャッシュ無効化戦略を活用してデータを最新の状態に保ち、アプリケーションを最適化する方法
- Dockerを使って複数のRedisノードによる分散環境をシミュレートする方法
- キャッシュヒットとミスを監視してパフォーマンスを最適化する方法
次のステップ
- データベースの追加:キャッシュを超えた永続化のために、URLをデータベースに保存しましょう。
- アナリティクスの実装:短縮URLのクリック数やアクセス解析を追跡できるようにしましょう。
- クラウドへのデプロイ:Kubernetesを使用してアプリケーションをデプロイし、オートスケーリングと耐障害性を実現しましょう。
それでは、Happy Coding!
-
Upstash・Redis・QStashで実現するリアルタイム緊急対応システムの構築
本記事では、Upstashを活用して国の避難所マップに関する情報をRedisで安全に保存・アクセスする方法と、QStashを使ってデータベースをリアルタイムに更新する方法について解説します。 はじめに 今日の世界では、自然災害や軍事的な脅威がますます深刻化しており、それに伴い社会サービス分野におけるデジタル化のニーズも高まっています。 AMBERアラートのような緊急放送システムから、COVID-19の追跡アプリ、SOSシステムまで、国に影響を及ぼすあらゆる危機的状況に対して、幅広いテクノロジーが活用されてきました。 本記事では、Redisとサーバーレスワークロードがどのようにリアルタイム緊急対
-
RedisとLuaでスケーラブルな分散レートリミッターを構築する – ステップバイステップ完全ガイド
この包括的なガイドでは、Redis と Lua スクリプトを組み合わせて、高トラフィック環境下におけるユーザーリクエストを制御する「分散レートリミッター」を構築する方法を解説します。レート制限(Rate Limiting)は、不正利用の防止、トラフィックの管理、そしてリソースの保護のために、あらゆるシステムにおいて欠かせない仕組みです。Redis と Lua を活用することで、大量のリクエストを効率的にさばきながら、バックエンドサービスを安全に守れるスケーラブルなレート制限システムを実現できます。さらに本記事では、トラフィックをシミュレートし、レート制限が実際に適用される様子を観察したり、ブロ