外資系企業の「システムデザイン面接」を突破するために、本気でURL短縮サービスを作ってみた
「URL短縮サービス(Design a URL Shortener)」といえば、GoogleやMetaなど、トップテック企業のシステム設計面接(System Design Interview)で頻出の定番問題です。
「ID生成はどうする?」「ハッシュ衝突の対策は?」「キャッシュ戦略は?」
面接対策としてこれらを机上で議論することはあっても、「実際にコードに落とし込み、正しく動作することを証明した」 経験がある人は意外と少ないのではないでしょうか?
今回は、この定番のお題に対して、「スケーラビリティ」「堅牢性」そして「テスト」 をテーマに、Go言語でフルスクラッチ実装してみました。
特に、結合テストにおいては Testcontainers for Go を採用し、docker-compose に依存しないモダンなテスト環境の構築に挑戦しています。
1. なぜ今、「URL短縮」なのか?
システム設計の勘所を実践形式で学ぶ上で、非常に優れた題材だと感じています。
- Read-Heavyな特性: 読み込みが書き込みより圧倒的に多い(100:1など)ため、キャッシュ戦略が不可欠。
- 一意性の保証: 短縮IDが重複するとサービスとして破綻するため、並行処理への理解が問われる。
- データ整合性: キャッシュとDBの不整合をどう防ぐか。
単に動くものを作るだけでなく、「なぜその技術を選ぶのか」 という設計判断のプロセスを重視して実装しました。
2. アーキテクチャと設計判断
① ID生成:ハッシュ関数 vs Base62エンコーディング
短縮URL(例: abc12)をどう生成するかは最大の論点です。MD5などのハッシュ関数を使う手法もありますが、Base62エンコーディング を採用しました。
- ハッシュ関数 (MD5/SHA): 生成結果が長すぎるため切り詰め(Truncate)が必要。結果として衝突(Collision) が発生する可能性があり、その都度DBへの存在確認が必要になる(書き込み性能の低下)。
- Base62 + Auto Increment: DBのユニークID(数値)を62進数(a-z, A-Z, 0-9)に変換する。数学的に一対一対応(Bijective) するため、衝突チェックが不要で、計算量 O(1) で生成可能。
② HTTPステータス:301 vs 302
リダイレクトには 301 Moved Permanently を使うのが一般的ですが、今回はあえて 302 Found を採用しました。
- 301: ブラウザがキャッシュするためサーバー負荷は下がるが、2回目以降のアクセスがサーバーに届かず、分析(Analytics)ができない。
- 302: 毎回サーバーを経由するため、クリック数やユーザー属性のトラッキングが可能。
URL短縮サービスのビジネス価値は「分析」にあると考え、帯域幅よりもデータの価値を優先しました。
③ キャッシュ戦略:Read-Through Pattern
Read-Heavyなワークロードに耐えるため、Redisを用いた Read-Through パターンを実装しました。

- Redisを確認(Hitすれば即レスポンス)
- なければDBを確認
- DBの結果をRedisに書き込み(TTL付き)
3. Testcontainersによる結合テスト
ここが今回の技術的なハイライトです。
DBを利用するテストを書く際、モック(go-sqlmock)は便利ですが、**「実際のSQLが正しく動くか」「トランザクションが効いているか」**までは保証できません。かといって、docker-compose をCI上で管理するのは面倒です。
そこで、Testcontainers for Go を採用しました。
Testcontainersとは?
GoのテストコードからDockerコンテナをプログラム的に起動・破棄できるライブラリです。
「テスト実行時のみ本物のPostgreSQLとRedisを立ち上げる」ことが可能になります。
実装例: テストヘルパー
以下のように、テスト開始時にコンテナを動的に立ち上げます。
並行処理のテスト
Testcontainersを使えば、本物のDBに対して並行アクセスを行うテストも容易です。以下は、並行書き込み時にID重複が発生しないことを検証するテストです。
これにより、モックでは検出できない**「レースコンディション」や「DB制約の挙動」** までテストすることができます。
まとめ
あくまで実際に運用するわけではなく全て絵空事なわけですが、実際に手を動かしてコードを書いてみると、実務に活かせる知見が多く得られました。
システム設計面接の対策として、あるいはGo言語での実践的なテストパターンの参考として、リポジトリを見ていただければ幸いです。
Discussion