Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Design a leaderboard and top-K: the system design interview walkthrough

2026年10月16日に「Dev.to」が公開したITニュース「Design a leaderboard and top-K: the system design interview walkthrough」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

大規模リーダーボードの設計では、単純なDBのORDER BYでは高頻度更新と大量データに対応できない。Redisのソート済みセット(ZSET)を活用し、更新・ランキング取得を高速化することが重要だ。データの永続化やシャーディングによるスケーラビリティも要点で、これらが面接の鍵となる。

ITニュース解説

リーダーボードとトップKプレイヤーを表示するシステム設計は、一見するとシンプルな課題に見えるかもしれないが、何百万ものプレイヤーが参加し、毎秒数千回のスコア更新が発生し、同時にトッププレイヤーや個人の正確なランクを瞬時に表示する必要がある場合、その複雑さは飛躍的に増大する。もしこれをデータベースのテーブルにスコアを保存し、SELECT ... ORDER BY score DESC LIMIT KのようなSQLクエリでトッププレイヤーを取得しようとすると、すぐに限界に直面するだろう。なぜなら、このようなクエリはデータを読み込むたびに再ソートしたり、インデックスを再スキャンしたりするため、大量の更新が頻繁に発生する環境では、インデックスの維持やロックの競合がボトルネックとなり、システム全体が応答不能になる可能性があるからである。

この問題を解決する上で中心となるのが、「ソート済みセット(Sorted Set)」という特別なデータ構造の活用だ。これは、例えばRedisという高速なデータストアのZSET機能に代表されるもので、データが常にスコア順に保たれるように設計されている。ソート済みセットを使うと、プレイヤーのスコア更新、トップKプレイヤーの取得、特定のプレイヤーのランク取得といった操作が、データ量が増えても処理時間が大きく増加しない「O(log n)」という非常に効率的な方法で実行できる。これは、テーブルを毎回ソートするSQLクエリとは異なり、データが常にソートされた状態を維持するため、読み込み時に再ソートのコストがかからないという大きなメリットをもたらす。

大規模なシステムを設計する際には、通常、体系的なアプローチとして以下の5つのステップで考えることが推奨される。まず、「要件の明確化」では、スコアの登録・更新、トップKプレイヤーの表示、特定のプレイヤーの正確なランク表示、特定のプレイヤー周辺の表示といった機能要件と、高頻度の更新処理、低遅延での読み込み、何億ものプレイヤーに対応できる水平方向のスケーラビリティ、システム障害時にもデータが失われない永続性、常に利用できる高可用性といった非機能要件を明確にする。次に「容量の見積もり」では、想定されるプレイヤー数、スコア更新頻度、読み込み頻度、データサイズなどを概算し、その後の設計判断の根拠とする。この段階で、ソート済みセットがシステムの中心となるべき理由が裏付けられる。続いて「APIとデータモデルの定義」では、外部からシステムにアクセスするための機能(API)と、システムが内部で扱うデータの構造を設計する。リーダーボードでは、スコアを送信するAPI、トップKを取得するAPIなどが考えられ、データはプレイヤーIDとスコアをソート済みセットに、スコアの変更履歴を別の永続的なログとして保持する形が適切だ。その後、「高レベル設計」では、クライアント、スコアサービス、ソート済みセットストア、永続的なイベントログ、クエリAPIといった主要なコンポーネントとその連携を全体像として描く。ここで重要なのは、ソート済みセットは高速な「サービス層」として機能し、すべてのスコア変更を記録する「イベントログ」がデータの真の永続的な源(ソースオブトゥルース)であることを明確にすることだ。最後に、「詳細設計」では、システムの核心となる重要な決定事項、例えばソート済みセットの具体的な実装方法、データ分散の戦略、永続性の確保といった点について深く掘り下げて検討する。

ソート済みセットの技術的な優位性は、その内部実装にもある。多くのソート済みセットは、「スキップリスト」というデータ構造と「ハッシュテーブル」を組み合わせて実装される。スキップリストは、ソートされたデータに対する検索や範囲取得に優れており、ハッシュテーブルは、特定のプレイヤーのスコアを高速に直接検索するのに役立つ。この組み合わせにより、スコアの更新(ZADD)、トップKの取得(ZREVRANGE)、プレイヤーのランク取得(ZREVRANK)といった操作が、いずれもO(log n)という効率的な計算量で実現される。これに対して、リレーショナルデータベースでスコアを更新した場合、B-treeインデックスの維持にコストがかかり、読み込みのたびにソート処理が必要となるため、大規模な書き込みと読み込みが同時に発生する環境では性能が著しく劣化してしまう。

プレイヤー数が非常に多くなり、単一のノードのメモリや処理能力では対応できなくなった場合、「シャーディング」という手法が必要になる。これは、データを複数のノードに分散させることで、システム全体の処理能力を向上させる方法だ。シャーディングされた環境では、トップKプレイヤーの取得は「散乱・収集(Scatter-Gather)」という比較的容易な方法で実現できる。各シャード(分割されたデータ群を保持するノード)からそれぞれのトップKプレイヤーを取得し、それらをまとめて最終的なトップKを選ぶのだ。しかし、「特定のプレイヤーの正確なグローバルランク」を取得するのは難しくなる。なぜなら、そのプレイヤーよりも上位にいるプレイヤーがどのシャードにいるかをすべて確認し、合計する必要があるため、すべてのシャードに問い合わせる必要があり、非常にコストが高くなるからだ。この問題に対する現実的な解決策としては、すべてのプレイヤーのデータを集約した専用の「ランク計算用レプリカ」を別途用意する、または「おおよそのランク」(例:「トップ2%」など)を高速に提供する、もしくは「正確なランクを定期的に計算してスナップショットとして提供する」といった選択肢が考えられる。どの方法を選ぶかは、システムがどこまでの精度とリアルタイム性を求めるかによる、費用対効果のトレードオフとなる重要な決定事項である。

さらに、インメモリのソート済みセットは、サーバーの再起動などが発生するとデータが失われる可能性があるため、永続性の確保が不可欠だ。ソート済みセットは高速な「サービス層」として扱い、すべてのスコア変更は「永続的なイベントログ」に書き込まれるようにする。このイベントログは、システム再起動時にソート済みセットを再構築するための「真のソース」となり、ログを再生するか、最新のスナップショットとログの差分を適用することで、失われたデータを復旧できる。これにより、一時的にサービスが停止する時間は発生するものの、データが完全に失われることは避けられる。日次、週次、全期間といった異なる期間のリーダーボードを実現するには、それぞれの期間に対応する個別のソート済みセットキーを作成し、同じイベントログからデータを供給する。古い期間のリーダーボードは、一定期間後に自動的に削除されるように「TTL(Time To Live)」を設定することで、メモリを効率的に管理できる。また、スコア更新の際にネットワークの問題などで同じ更新が複数回届いたり、順番が前後したりする可能性がある。これを防ぐためには、各更新に一意のIDを付与し、同じIDの更新は一度だけ適用されるように「冪等性」を確保することが重要だ。加えて、同じスコアのプレイヤーが複数いる場合の順位決定(タイブレーク)には、更新日時などを利用するルールを設定することで、一貫性と正確性を保つことができる。

まとめると、リーダーボードのシステム設計は、単純なソート機能では対応できない大規模な課題であり、Redis ZSETのような「ソート済みセット」を中心に設計し、イベントログによる永続性確保、シャーディングによるスケーラビリティ、そしてグローバルランク計算におけるトレードオフを理解することが不可欠となる。システムエンジニアを目指す上では、こうしたデータ構造の特性や、大規模システムにおける課題解決のアプローチを深く理解することが求められ、なぜその技術を選ぶのか、そのメリットとデメリットは何かを論理的に説明できる能力が非常に重要となる。

関連コンテンツ

関連IT用語