前回はローカル環境でデータベースをエミュレートできる環境を構築しました。今回からは、手を動かすというよりは、今後の拡張性を考えたデータベースのスキーマ設計を行っていきたいと思います。
前回:D1データベース作成とテスト環境構築の手順 【CFW P2P心理戦ゲーム開発記】 #2

1. リレーショナルデータベースってそもそも何?
リレーショナルデータベースがわからないと、この先の話がいまいち理解できないと思うので、軽く触れておきます。リレーショナルデータベースとは、リレーショナル(関係、関連)なデータベースです。
…と言われても、これだけだと結局何なの?となると思うので、肝となる主キーの設計を具体例を挙げて説明します。
例えば、1クラス、1科目のテストの点数を管理したいとします。 ここで扱いたい情報を羅列してみると以下の通りです。
- 名前
- 出席番号
- 学籍番号
- 点数
もし、「名前」をキーにすると、同姓同名の人がいたらどうなるでしょう。
【成績表(名前をキーにした場合)】
| 名前 | 出席番号 | 学籍番号 | 点数 |
| 田中太郎 | 1 | 2024001 | 80 |
| 鈴木花子 | 2 | 2024002 | 95 |
| 田中太郎 | 3 | 2024003 | 60 |
これだと「”田中太郎”の点数を出して」とシステムにお願いしたとき、80点なのか60点なのか分からなくなってしまいます。
「じゃあ、クラス内で絶対にかぶらない『出席番号』をキーにすればいいのでは?」と思うかもしれません。確かに1クラスだけなら動きます。しかし、システムが拡張され「2クラス分」のデータを管理することになったらどうでしょうか?
【成績表(クラスが増えた場合)】
| クラス | 名前 | 出席番号 | 学籍番号 | 点数 |
| A組 | 田中太郎 | 1 | 2024001 | 80 |
| A組 | 鈴木花子 | 2 | 2024002 | 95 |
| B組 | 佐藤一郎 | 1 | 2024035 | 70 |
A組の1番(田中さん)と、B組の1番(佐藤さん)で「出席番号1」が重複してしまいました。これではまた特定できなくなってしまいます。
そこで主キー(一意に取り出すための絶対的な条件:プライマル)を見直す必要が出てきます。クラスが増えても、学校全体で絶対に重複しない「学籍番号」の出番です。これを主キーに設定すれば、システムがどれだけ拡張されても、誰のデータかを確実に1つに特定できるようになります。
もし、複数科目の点数を管理したくなったら?
ここまでは「1科目だけ」のシンプルな話でした。
もしこれが、国語、数学、英語……と複数科目になったらどうやって管理すればいいでしょうか?
さきほどの表の横に「国語の点数」「数学の点数」と列を増やしていく方法もありますが、それだと新しい科目が追加されるたびに、データベースの構造(表の形)そのものを改造しなければならず、大変です。
| 学籍番号 (主キー) | クラス | 出席番号 | 名前 | 国語 | 数学 | 科目数だけ増える |
|---|---|---|---|---|---|---|
| 2024001 | A組 | 1 | 田中太郎 | 80 | 90 | … |
| 2024035 | B組 | 1 | 佐藤一郎 | 70 | 80 | … |
そこで、「生徒の情報」の表と「科目別のテストの点数」の表を別々に分けて作ります。(これを正規化と言ったりします。)
【生徒テーブル】
| 学籍番号 (主キー) | クラス | 出席番号 | 名前 |
| 2024001 | A組 | 1 | 田中太郎 |
| 2024035 | B組 | 1 | 佐藤一郎 |
【科目別点数テーブル】
| 学籍番号 | 科目 | 点数 |
| 2024001 | 国語 | 80 |
| 2024001 | 数学 | 90 |
| 2024035 | 国語 | 70 |
こうすることで、新しい科目のテストがあっても【科目別点数テーブル】に新しい行を追加していくだけで済みます。「田中太郎くんの数学の点数は?」と調べたいときは、2つの表を「学籍番号」という共通のキーで紐づけて(リレーションさせて)データを取り出します。
さらに、「科目ごとの平均点を出したい」となったときも、点数テーブルだけを見て「科目が『国語』の点数を全部足して人数で割る」といった処理がプログラムで非常に簡単にできるようになります。
まとめ:これがどうして重要なのか?
このように、「データを役割ごとに表(テーブル)に分割し、共通のキーで関係性を持たせて管理する」仕組みこそが、リレーショナルデータベースの正体です。
一見、ただの学校の成績管理の話に見えるかもしれません。しかし、この「データをどう切り分けて、どう主キーで関連付けるか」という設計思考は、今後のゲーム開発やシステム設計において極めて重要になってきます。
例えばゲームなら、「プレイヤーの基本情報(ID、名前、レベル)」のテーブルと、「所持アイテム(プレイヤーID、アイテム名、個数)」のテーブルを分けて管理する、といった設計にそのまま直結するからです。
ここが綺麗に設計できていないと、後から「新しいアイテムを追加したいだけなのに、システム全体がバグを起こしてしまった!」という大惨事になりかねません。
2. 扱いたい情報の洗い出し
ということで、概念が頭に入ったところで、さっそく今回作っていくゲームシステムの実装構想に入りましょう。
今回のシステム(ゲーム)で、私たちは一体どんなデータを保存し、どう管理する必要があるでしょうか? まずは、必要な情報を力技で洗い出すところから始めてみます。
- ユーザの表示名
- ユーザの認証id
- uuid (管理用の学籍番号のような固有id)
- アカウント作成日時
- ユーザ属性(管理人、特別ユーザとか)
- ★レート
- ★勝利数・敗北数
- ★最大連勝数
- 等々….
※★はこのゲームについての情報
チュートリアルスキップなどui上の設定は、ローカルストレージに保存することにします。端末間の連携はできませんが、そこまで問題ないのでRDBの保守性を重視します。
3. スキーマをまとめてみる
羅列した情報をそれぞれの主キーを適切に選びながらテーブルに分けました。
1. users テーブル(全ゲーム共通・基本プロファイル)
ユーザーの「身元」を定義する中心のテーブルです。将来ゲームが10個に増えても、このテーブルは変更されません。
primary : id
| カラム名 | データ型 | 制約 | 説明 |
id | TEXT | PRIMARY KEY | ユーザーの固有ID(UUIDなど) |
display_name | TEXT | NOT NULL | 画面やランキングに表示される名前 |
created_at | TEXT | DEFAULT… | アカウント作成日時 |
2. user_auths テーブル(マルチ認証・ログイン方法の管理)
1人のユーザーが「Google」「GitHub」「メール」など、複数のログイン方法を紐付けられるようにするためのテーブルです(1対多の関係)。
primary : id
| カラム名 | データ型 | 制約 | 説明 |
id | INTEGER | PRIMARY KEY AUTOINCREMENT | 認証データの通し番号 |
user_id | TEXT | REFERENCES users(id) | usersテーブルのどの人と紐付いているか |
provider | TEXT | NOT NULL | 認証元('google', 'github', 'email', 'sms') |
provider_user_id | TEXT | NOT NULL | 各プロバイダー側でのユーザー識別ID(メールアドレスやGoogleのsub IDなど) |
| expires_at | INT | NULL許容 | ワンタイムパスワードのライフタイム。5分以上たったら無効化など |
secret_hash | TEXT | NULL許容 | メールやSMSのワンタイムパスワード等を確認するためのハッシュ値(OAuth時は空) |
- 複合ユニーク制約:
(provider, provider_user_id)をユニークにすることで、「あるGoogleアカウントが別のユーザーに二重登録される」のを防ぎます。
3. buckshot_stats テーブル(バックショット専用データ)
今回のゲーム固有のデータを完全に分離します(1対1の関係)。新ゲーム(例:メモリマージ)を作る時は、これと並列に merge_game_stats テーブルを増やすだけで拡張が完結します。
primary : user_id
| カラム名 | データ型 | 制約 | 説明 |
user_id | TEXT | PRIMARY KEY REFERENCES users(id) | 誰の戦績か |
elo_rating | INTEGER | NOT NULL DEFAULT 1500 | 現在のイロレーティング(初期値1500) |
wins | INTEGER | NOT NULL DEFAULT 0 | 勝ち数 |
losses | INTEGER | NOT NULL DEFAULT 0 | 負け数 |
| winning_streak | INTEGER | NOT NULL DEFAULT 0 | 連勝数 |
最後に
今回は、リレーショナルデータベースとしてD1の中身の設計を行ってみました。今後込み入った認証とか、保有アイテム管理とかをしようとしだすと改変する必要が出てきそうですが、基本的な構成はこれで使いまわせるかな?と考えています。
関連記事
CloudFlare Workersセットアップ
Cloudflare Workersの始め方:Wranglerによるローカル環境構築と世界公開の手順
【第1回】P2Pゲーム構成アイデア
Cloudflare Workers + WebRTCで創るP2P心理戦ゲーム開発記【第1回:構想編】
【第2回】データベースを作る
D1データベース作成とテスト環境構築の手順 【CFW P2P心理戦ゲーム開発記】 #2
もう一つの選択肢:VPSで自由なゲームサーバー構築
本連載ではCloudflare Workersを活用したP2P実装を進めていますが、もし環境の制約がなく、
「もっと使い慣れた言語で自由にゲームサーバーを立てたい」
「WebSocketなどの常駐プロセスをガッツリ回したい」
という場合は、VPS上に独自のシグナリングサーバーを構築するのも強力な正攻法です。
「テキストを読んで知識として知っていること」と、「実際に手元でLinuxサーバー(Ubuntuなど)を叩いて構築した経験」とでは、バックエンドへの理解の深さに大きな差が生まれます。
最近の海外サービスは「最初は無料・格安で普及させ、定着したタイミングで一気に値上げや制限強化に踏み切る」という戦略が多く、個人開発での新規参入や継続運用のハードルが高くなりがちです。
その点、日本の老舗である
さくらのインターネット(さくらのVPS)
は価格面でも運用の面でも圧倒的な安定感があり、個人的にとても信頼して推しています。
「海外サービスの急な仕様変更に振り回されたくない」「自分のインフラ拠点を国内に1つ持っておきたい」という方は、ぜひ一度触ってみてください!
次回予告
現状見落としている項目がある可能性があるので、今後実装しつつ多少拡張したり削減する可能性がありますが、とりあえずこの構成で作っていこうと思います。
次回は、今回設計したデータベースを順次実装していきます。では、次の記事で。 lumenHero
次回:D1データベース実装:ユーザデータ管理 【CFW P2P心理戦ゲーム開発記】 #4
関連記事は、2026年6月12日に公開予定 (あと17時間)