Elido
2分で読了エンジニアリング

URL短縮サービスの構築方法: アーキテクチャとコード

本番環境に耐えうる URL 短縮サービスの構築方法: ショートコード生成、リダイレクトパス、キャッシュ、クリック追跡、不正利用対策、および運用上の考慮事項を解説します。

Marius Voß
DevRel · edge infra
ショートコードをエンコードする書き込みパスとキャッシュからリダイレクトを解決する読み取りパスを示す URL 短縮サービスのアーキテクチャ図

URL 短縮サービスを構築するには4つのものが必要です: ショートコードからリンク先 URL へのマッピングを保存する場所、各新規リンクに一意のコードを生成する方法、コードを検索して HTTP リダイレクトを返すリダイレクトハンドラー、そして読み取りが書き込みを大きく上回るためルックアップの前に置くキャッシュです。これがコア全体であり、午後一つで立ち上げることができます。

罠は、午後版が製品だと思うことです。ラップトップで動作するリダイレクトと、見知らぬ人がマルウェアに向け、トラフィックでハンマーをかけ、フォーナインの稼働率を期待する中で生き残る URL 短縮サービスは、異なるエンジニアリング問題です。前者はアルゴリズムです。後者は運用上のコミットメントです。

このウォークスルーではコアを誠実に構築し、システム設計チュートリアルが省略するパート - リダイレクトが動作した後にまだ構築しなければならないもの - に大半の時間を費やします。最初に概念的な入門書が必要な場合は、URL 短縮サービスの仕組みでコードなしでメカニズムを説明しています。

URL 短縮サービスの2つのパス: 書き込みパスは一意の ID をショートコードにエンコードして保存し、読み取りパスはクリックをキャッシュを通じてリダイレクトに解決する

要約: URL短縮サービスが実際に行うこと#

URL 短縮サービスは、HTTP リダイレクトを着用したキーバリュールックアップです。キーはショートコード、値は長い URL であり、仕事全体は example.com/aB3x9 を元のアドレスを指す 302 に変換することです。

データモデルは1つのテーブルです。

CREATE TABLE links (
    id          BIGSERIAL PRIMARY KEY,
    short_code  TEXT NOT NULL UNIQUE,
    long_url    TEXT NOT NULL,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE UNIQUE INDEX idx_links_short_code ON links (short_code);

そこには2つのパスがあります。書き込みパスは長い URL を受け取り、ショートコードを生成し、行を挿入します。読み取りパスはショートコードを受け取り、行を検索し、リダイレクトを返します。読み取りは一般的に1000対1程度の比率で書き込みを上回るため、エンジニアリングの注意のほぼすべてをルックアップを高速かつ低コストにすることに向けるべきです。short_code のユニークインデックスが、ルックアップをスキャンではなくインデックスシークにする要素です。それがコア全体です。

ショートコードの生成: Base62、ランダム、またはハッシュ#

ショートコードは興味深い決断が存在する場所です。3つの現実的な戦略があり、それぞれ長さ、予測可能性、衝突の処理難易度でトレードオフがあります。

一意の ID の Base62 がクラシックな手法です。自動インクリメントの行 ID を base62、つまり a-zA-Z0-9 の62文字でエンコードします。コードは短く、各 ID が一意のため衝突しません。ボリュームが約62倍になるごとに1文字長くなります。欠点はコードが連番で推測可能なため、誰でも名前空間を走査できることです。

const alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

// encode turns a positive integer ID into a base62 short code.
func encode(id uint64) string {
	if id == 0 {
		return string(alphabet[0])
	}
	var b []byte
	for id > 0 {
		b = append(b, alphabet[id%62])
		id /= 62
	}
	// reverse, since we built the digits least-significant first
	for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
		b[i], b[j] = b[j], b[i]
	}
	return string(b)
}

ランダム文字列は推測可能性の問題を解決します。nanoid のようなライブラリを使用して短いランダムコードを生成し、保存前にユニークインデックスに対して確認します。base62 の7文字で数兆通りの可能性があるため衝突はまれですが、稀に一意性制約で失敗するインサートが発生した場合は新しいコードで再試行する必要があります。

URL のハッシュは3番目の選択肢で、通常は最悪の選択肢です。長い URL のハッシュは決定論的で便利に見えますが、それでも切り捨てが必要で、衝突が発生し、同じ URL が同じコードにマッピングされることで情報が漏洩します。ほとんどの本番サービスは内部 ID には base62 を、公開リンクにはランダムコードを選択します。カスタムまたはブランドスラッグ (ユーザーが手で入力するコード) は受け入れ前に同じユニークインデックスに対して検証されます。

リダイレクトパス: 301 vs 302 と分析への影響#

リダイレクトのステータスコードは見た目の選択ではありません。2回目のクリックを受け取れるかどうかを決定します。

301 Moved Permanently はブラウザとプロキシに移動が永続的であることを伝え、キャッシュされます。最初の訪問後、ブラウザはサーバーにアクセスせずに将来のクリックを直接リンク先に送ることができます。生のスピードには優れていますが、分析には致命的です。最もカウントしたいクリックがあなたに届かないものになるからです。HTTP のセマンティクスは RFC 9110 に定義されており、永続的および一時的なリダイレクトの両方が規定されています。

302 Found または 307 Temporary Redirect は毎回再リクエストされます。ブラウザは各クリックでサーバーに問い合わせるため、すべての訪問をカウントでき、古いキャッシュと戦わずに後からリンク先を変更できます。編集可能なリンクとクリックデータが全価値を持つリンク短縮ツールにとって、これが正しいデフォルトです。コストはクリックごとに1回のネットワーク往復ですが、キャッシュヒットによってそれは無視できます。

経験則として: リンクを永続的にキャッシュして変更しない特定の理由がない限り 302 を使用してください。301 vs 302 リダイレクトの投稿でトレードオフを詳しく解説し、リダイレクトの種類では 307 と 308 が重要な場合を含む 3xx ファミリー全体を解説しています。

ストレージとキャッシュ: 1000対1の読み取り/書き込み比率に対応した設計#

読み取りが書き込みを圧倒するため、データベースはボトルネックではなく、キャッシュ戦略がボトルネックです。パターンはリードスルーキャッシュです: クリック時に最初にインメモリキャッシュを確認し、ミス時のみデータベースにフォールバックし、次回のために結果をキャッシュに書き戻します。

func resolve(ctx context.Context, code string) (string, error) {
	if url, ok := cache.Get(code); ok {
		return url, nil // hot path: served from memory
	}
	url, err := db.LookupLongURL(ctx, code)
	if err != nil {
		return "", err
	}
	cache.Set(code, url) // populate for the next click
	return url, nil
}

本番環境では通常2層になります: 最もホットなリンクのための小さなインプロセスキャッシュ、その後ろに Redis などの共有インメモリストアがあり、あるサーバーインスタンスが行ったルックアップをすべてのインスタンスが活用できます。信頼できる情報源であるデータベースは、本当のコールドミス時のみアクセスされます。この層を正しく構築すれば、単一の控えめなサーバーで膨大なクリック量を処理できます。URL リダイレクトのキャッシュ戦略の投稿でエビクションとサイズ決定を詳しく解説し、p95 を15ms以内に抑えるのコーナーストーンでは、負荷下でのチューニングされたリダイレクトパスがどのように見えるかを説明しています。

これを一切運用したくない場合、Elido の API はリダイレクト層、キャッシュ、EU 内リージョン配信を単一の呼び出しでキャッシュヒット時 p95 15ms 以内で提供します。無料で始めて運用を省きましょう。

リダイレクトを遅くせずにクリックをカウントする#

リダイレクトレイテンシを損なう間違いは、リダイレクトハンドラー内でデータベースにクリックを書き込むことです。そうすると、すべての訪問者がリダイレクトを受け取る前に分析書き込みを待つことになります。

それらを切り離しましょう。ハンドラーはすぐにリダイレクトを送信し、その後クリックイベントを耐久性のあるログまたはメッセージキューにファイアアンドフォーゲット作業として送ります。別のコンシューマーがそのストリームを読み取り、自分のスケジュールで分析ストアにイベントを書き込みます。訪問者は待たず、数百万のクリック行をスキャンするレポートクエリがリダイレクトパスのリソースを奪いません。カラムナー分析データベースはこれらの集計クエリをロウストアよりはるかに効率的に処理します。そのためクリックイベントは通常、リンクテーブルとは異なる場所に保存されます。ファイアアンドフォーゲットクリックインジェストの投稿でキュー側を詳しく解説し、クリック分析に Postgres よりカラムナーストアが優れている理由でストレージの選択を説明しています。Elidoの分析機能はこの構造に従っており、リダイレクトにミリ秒を追加せずにクリックを数秒でクエリできます。

動作するリダイレクトの先にある本番バックログ: 不正利用スキャン、レート制限、カスタムドメイン TLS、GDPR に準拠したクリックデータ、高可用性

まだ構築しなければならないもの: 難しい80パーセント#

これがシステム設計のウォークスルーが省略するパートです。動作するリダイレクトは、実際の URL 短縮サービスの約5分の1に過ぎません。残りは、デモをパブリックインターネットに公開できるものに変えるすべてのことです。

  • 不正利用と安全スキャン。パブリック短縮ツールはローンチ後数時間でフィッシングの標的になります。Google Safe Browsing などの脅威フィードに対してリンク先を確認し、再スキャンする必要があります。作成時は安全だった URL が後でマルウェアになる可能性があるからです。URL 短縮サービスセキュリティチェックリストが完全なリストです。
  • レート制限と冪等性。オープンな作成エンドポイントはすぐにスクリプトで叩かれます。再試行されたリクエストが重複リンクを生成しないよう、キーごとの制限と冪等性が必要です。メカニズムはAPI レート制限と冪等性にあります。
  • TLS 付きカスタムドメイン。ブランドリンクは、手動手順なしに、自分が所有していないドメインの証明書をオンデマンドで発行することを意味します。
  • GDPR に準拠したクリックデータ。クリックをログに記録した瞬間から個人データを処理しています。URL 短縮サービスの GDPR 対応が説明するように、IP アドレスの切り捨てと保持期間の文書化は EU ではオプションではありません。
  • 高可用性。あなたのリダイレクトは誰もが共有したすべてのリンクのクリティカルパスになります。ダウンタイムは他の人のコンテンツを壊すため、ほとんどのアプリより稼働率の基準が高くなります。

これらのどれも特別なものではありません。ただ、多くの継続的な作業であり、終わることはありません。ほとんどのチームが MVP で止まり、メンテナンスされているものに手を伸ばす正直な理由です。

構築、購入、またはセルフホスト#

自分で構築することはリダイレクト、エンコーディング、キャッシュを理解する最善の方法であり、クローズドな内部ツールなら MVP がすべて必要なものになるかもしれません。構築してください。週末でどんな面接準備より多くを学べます。

パブリックまたはビジネス向けのものには、メンテナンスを誠実に考慮してください。リダイレクトは無料ですが、不正利用対策、カスタムドメイン TLS、分析パイプライン、オンコールローテーションは無料ではありません。ゼロから書かずにコントロールを望む場合、既存のサービスをセルフホストできます。Elido はそのためにセルフホスティングパスを提供しており、オープンソースの選択肢の投稿でそれらを並べて比較しています。完全にオフロードしたい場合、開発者ソリューションAPI および SDK クイックスタートで上記のバックログなしに本番リダイレクト層を利用できます。

ブログの関連記事#

Elidoを試す

URLを貼り付けて短縮リンクを取得

登録不要。リンクは30日間有効。永久に保存するには登録してください。

Free、登録不要 · 1日あたり2件

Elidoを試す

EUホスティングのURL短縮サービス。カスタムドメイン、詳細な分析、オープンAPI付き。無料プラン - クレジットカード不要。

タグ
build a url shortener
url shortener system design
short code generation
base62 encoding
url redirect
url shortener architecture
link shortener api

続きを読む