見本のタスクに「s1」と名前を付けたら、最初に保存した1人が全員分を持っていった — 同期が永久に同じ所で止まり続けた理由 — 制作日誌 #40
アプリに最初から入れていた見本のタスクに、短い固定の名前を付けていた。その名前はサーバーの上では、利用者が違っても「同じ1件」として扱われていた。最初に保存した1人が全員分の名札を持っていき、2人目以降の同期は、毎回同じ所で弾かれ続けていた。
タスク管理アプリの利用記録を眺めていて、同じ失敗が同じ時刻帯に繰り返されているのに気づいた。
1人の利用者の端末が、朝7時ちょうどに6件のタスクをサーバーへ送り、全部弾かれていた。57分後、7時57分にまた同じ6件を送り、また全部弾かれていた。最新版の1.0.22を使っている人で、その時点で失敗の記録は1人から31件たまっていた。
送り直しの仕組み自体は正しく動いている。失敗したものは次の機会にもう一度送る。問題は、何度送っても絶対に通らない理由があったことだ。
31件の失敗は、自分の物ではない行に当たっていた
サーバーが返していた言葉は、要約すると「この行を書き換える権利はあなたに無い」だった。
このアプリのサーバーには「自分の行だけ読み書きできる」という決まりを入れてある。他人のタスクは見ることも、書き換えることもできない。弾かれたということは、この利用者の端末が、他人の行を書き換えに行っていたことになる。
だが、その6件は間違いなく本人の端末で作られたものだった。本人のタスクを送って、なぜ他人の行に当たるのか。
6件の正体は、最初から入っていた見本だった
送られていたタスクの名前を調べると、正体はすぐに分かった。アプリを初めて開いた時に、使い方が分かるようにと置いていた見本のタスクだった。
見本には、プログラムの中で s1、s2、s3 という短い名前を付けていた。どの端末でも、最初に入る見本は同じ中身なので、名前も同じでいいと考えていた。
ここに落とし穴があった。
| 項目 | 私の思い込み | 実際 |
|---|---|---|
| タスクを見分ける鍵 | 「誰の」+「どの名前」の組み合わせ | 名前だけ |
| 利用者Aの s1 と利用者Bの s1 | 別々の2件 | 同じ1件 |
| 先に保存した人 | 自分の見本を保存しただけ | 全員分の s1 の持ち主になる |
| 2人目以降の保存 | 自分の見本を保存 | 他人の行の書き換え → 弾かれる |
例えるなら、マンションの宅配ボックスの番号を、住人ではなく荷物の種類で決めていたようなものだ。「見本1」という荷物は全員がもらうので、全員が同じ1番のボックスを使おうとする。最初に入れた人が鍵をかけ、2人目以降は「そこは他人のボックスです」と断られ続ける。
| 例え | 実物 |
|---|---|
| 宅配ボックスの番号 | サーバー上でタスクを見分ける鍵(名前) |
| 荷物の種類で決めた番号 | 見本に付けた固定の名前 s1〜s3 |
| 最初に鍵をかけた住人 | 最初に見本を編集して保存した1人 |
| 「他人のボックスです」 | 「自分の行だけ」の決まりによる拒否 |
この例えが合わないのは、マンションなら断られた人は管理人に言えば済む点だ。アプリの場合、弾かれた本人には、その見本を他の誰が持っているのか見えない。自分の画面から消しても、サーバーの行は他人の物なので消えない。だから同期のたびに、永久に同じ所で失敗し続ける。
保存しなければ、事故にならなかった
もう1つ厄介だったのは、見本を「眺めるだけ」の人には何も起きないことだ。
送り直しの対象になるのは、端末で変更があったものだけだ。見本を一度でも編集した人、完了の印を付けた人、名前を変えた人がサーバーへ押し上げた時に、初めて名札の取り合いが起きる。そして最初に押し上げた1人だけが成功し、その人は何も困らない。困るのは2人目からで、2人目は自分の見本を触っただけなのに、それ以後ずっと同期が失敗し続ける。
利用記録で1人しか見えなかったのは、見本に触ってから同期した人がそもそも少なかったからだと思う。数が少ないから軽い、とは言えない。当たった人にとっては、いつまでも「保存できていない」状態が続く。
同じ事故を、すでに一度直していた
一番こたえたのは、ここからだった。
タスクを分類するカテゴリの箱にも、以前は l1 という固定の名前を付けていた。そちらで同じ取り合いが起き、端末ごとに違う名前を作るように直してあった。プログラムの中には「固定の名前に戻すな」という注意書きまで残っていた。
それなのに、見本のタスクだけが取り残されていた。
| 初期データ | 固定の名前 | 状態 |
|---|---|---|
| カテゴリの箱 | l1 | 以前の事故で、端末ごとの名前に変更済み |
| 見本のタスク | s1〜s3 | 取り残されていた |
教訓は持っていた。ただ、それを「カテゴリの箱の直し方」として覚えていて、「初期データ全部の作り方」として覚えていなかった。1か所の事故を、その1か所だけで閉じていた。
3つのアプリで、名札を取り合っていた
調べるうちに、被害がこのアプリだけで済まないことも分かった。
同じ土台から作った経路検索のアプリと推し活のアプリも、サーバーを共用している。そして、見本を用意する処理も丸ごと受け継いでいた。つまり経路検索のアプリを使う誰かが先に s1 を保存すれば、タスク管理アプリの利用者の同期が止まる。逆も起きる。1つのアプリだけを直しても、他の2つから名札を取られ続ければ意味がない。
そこで、3つとも直した。
| アプリ | 直した日 |
|---|---|
| タスク管理 | 8月25日(その日のうちに配信) |
| 推し活 | 8月26日(配信) |
| 経路検索 | 8月26日(直しは入れたが、配信は一度見送った) |
経路検索のアプリだけ配信を見送ったのは、同じ作業場所に、別の作業で入れた確かめていない変更が載っていたからだ。直しと一緒に、確かめていない変更まで利用者に届けるわけにはいかなかった。
直し方は、3段に分かれた
直すこと自体は、名前の付け方を変えるだけでは終わらなかった。
- これから作る見本は、端末ごとに違う名前にする。 カテゴリの箱で使っていたのと同じ作り方に揃えた。
- すでに古い名前で保存している端末は、起動時に名前を振り直す。 見本の下に子のタスクを付けている人もいるので、親子のつながりも一緒に付け替える。
- サーバーに残った古い行には、削除の印を付けに行く。
3つ目には、少し意外な性質を使った。削除の印は「自分の行だけ」に絞って付けに行く。他人が持っている s1 なら、該当する行が0件になるだけで、エラーにはならない。書き換えは他人の行に当たると弾かれるが、自分の行に絞った削除は、空振りしても静かに終わる。この非対称のおかげで、「自分の古い見本なら消す、他人のなら何もしない」が、1つの処理で書けた。
結局その日のうちに、見本のタスクそのものをやめた。最初から6件並んでいると一覧が埋まり、自分の1件目を書く場所が見えなくなる、という別の理由からだ。ただ、見本をやめても、すでに古い名前を抱えた端末は残る。振り直しと削除の印は、見本をやめた後も外していない。
初期データは、配った瞬間に全員の物になる
アプリに最初から入れておくデータは、作る側からすると「ただの飾り」に見える。だがサーバーと同期した瞬間、それは利用者のデータと同じ扱いになり、利用者の数だけ複製される。名前が同じなら、複製ではなく取り合いになる。
いまは、新しく初期データを足す時に、その名前が端末ごとに違うかを最初に確かめる。そして同じ種類の初期データが他に残っていないかを棚卸しする。1か所の事故を1か所で閉じない、というのが、2回目でようやく身についたことだ。
なお、この直しは3つのアプリとも、まだ実機で弾かれなくなったところを見届けていない。確かめられているのは、利用記録の上で同じ失敗が出続けていたことと、それを止める処理を入れたことまでだ。
コメント (0)
まだコメントはありません。最初の一言を残しませんか?