同じデータを2か所に持ってはいけない。コース機能で踏みかけた地雷の話

スタンプラリーに、自分でコースを組める機能を足しました。
最大の敵はプログラムではなく、うっかり複製したくなるデータでした。

スタンプラリーに続いて、コース機能を作りました。
登録されているスポットから好きな場所を選び、立ち寄る順番につないで、自分だけのコースを組める機能です。
公開すれば、ほかの人がそのコースに挑戦することもできます。

リリースしたのは明け方でした。
この記事も、コーヒーを片手にその勢いで書いています。

危なかった設計の話

開発の途中でいちばん危なかったのは、プログラムのバグではありません。
データの持ち方でした。

コースにはスタンプ帳のような画面があって、回ったスポットのマスが埋まっていきます。
最初は素直に「コース用のスタンプ記録テーブル」を作りかけました。
手が止まったのは、ラリー側にもう同じ記録があると気づいたときです。

スタンプラリーのチェックイン記録も、コースの達成記録も、中身は同じ「この場所に行った」という事実。
同じ事実を2か所に書くと、いつか必ず食い違います。
ラリーでは押してあるのにコースでは未達成、なんて状態が生まれたら、直すのは悪夢です。

記録は1か所、答えは計算で出す

出した結論は、チェックインの記録はラリー側の1か所だけに置くこと。
コースが覚えるのは「どのスポットを、どの順番で束ねたか」だけです。

達成率は、画面を見るたびにその場で計算します。
コースに入っているスポットと、あなたがチェックイン済みのスポット。
ふたつの集合の重なりを数えれば、何割回ったかが出ます。

完走した人の数え方(イメージ)

SELECT member_id
  FROM チェックイン記録
 WHERE spot_id IN (コースのスポット一覧)
 GROUP BY member_id
HAVING COUNT(DISTINCT spot_id) >= コースのスポット数

この方式の気持ちいいところは、過去のチェックインも自動で効くことです。
去年行ったキャンプ場が入っているコースを今日作れば、そのマスは最初から埋まっている。
記録を写す処理そのものがないから、写し忘れも起きません。

非公開コースの守り方

コースには「自分専用」と「公開」があります。
公開コースは検索エンジンにも読んでほしいので、サーバー側でページを丸ごと組み立てて返す。
自分専用のコースは逆に、URLを誰かに知られてもサーバーは中身を返しません。
ログインした本人のブラウザだけが、鍵を見せてデータを受け取り、手元で画面を組み立てます。

同じ1枚のページなのに、中身の性格によって作り方が2通り。
書いていて、少し不思議な気分でした。

並べ替えのオチ

立ち寄り順の入れ替えは、順番の数字を書き換えるだけ、のはずでした。
ところが「同じコースに同じスポットは1回だけ」という制約を自分で張っていたせいで、入れ替えの途中に一瞬だけ重複が生まれてエラーになる。
結局、いったん全部消してから並べ直す方式にしました。
横着な設計のツケは、プログラムが正直に取り立てにきます。

コースづくりはスタンプコースのページからどうぞ。
まずは、あなたの思い出の場所を3つ、つないでみてください。

技術ブログ一覧へ戻る