「サーバーに無い」を、ずっと1つの意味で読んでいた — 他人のメモを送り直し続けた端末と、1000件で黙って止まる取得 — 制作日誌 #36
共有しているタスクアプリで、ある利用者の画面に「保存できていません」が出続けた。523件は保存できていたのに、1件だけが通らない。残る1件を追ったら、同期の仕組みが「サーバーに無い」をたった1つの意味でしか読んでいなかったことと、取得が1000件で黙って打ち切られていることが、同じ日に見つかった。
郵便受けに、届くはずの手紙が来ていないとする。考えられる理由はいくつかある。まだ差し出していない。宛先の人が転居した。郵便受けが小さくて、途中からあふれて入りきらなかった。どれも「無い」という同じ見た目になる。
私の作っているタスクアプリの同期は、この「無い」を1つの意味でしか読んでいなかった。
| 例え | 実際 |
|---|---|
| 郵便受け | 端末がサーバーから受け取るタスクの一覧 |
| まだ差し出していない手紙 | 端末で作ったが、まだサーバーへ送れていないタスク |
| 宛先が転居した手紙 | 持ち主が共有の外へ移した、他人のタスク |
| 郵便受けからあふれた分 | 取得の上限を超えて届かなかったタスク |
郵便と違うのは、アプリの郵便受けは「あふれました」と教えてくれないことだ。手紙が何通届いたかは数えられても、何通あふれたかは、こちらから聞かないと分からない。
起きたこと
共有の相手がいる利用者の画面に、「保存できていません」という表示が出続けた。何度開き直しても消えない。
その人のタスクのうち523件は、きちんとサーバーに保存されていた。1件だけが、毎回送られては断られていた。
| 項目 | 値 |
|---|---|
| 保存できていたもの | 523件 |
| 送り直しては断られていたもの | 1件(他人が書いたメモ) |
| 断っていたのは | サーバー側の「持ち主以外は書き換えられない」決まり |
1件のために、画面には毎回「保存できていない」が出る。本人から見れば、全部が危ないように見えたはずだ。
1件の正体
その1件は、本人が書いたものではなかった。共有しているカテゴリの中に、相手が書いたメモが入っていた。相手はある日、そのメモを共有のカテゴリから、自分だけが見られる私物の箱へ移した。
移した瞬間から、そのメモは共有の相手にはサーバーから届かなくなる。ここまでは正しい動きだ。
問題は、届かなくなった後の端末の振る舞いだった。端末の同期は、サーバーから届いた一覧と手元の一覧を突き合わせて、こう判断していた。
- サーバーにも手元にもある → 新しい方を残す
- サーバーにだけある → 手元に足す
- 手元にだけある → まだ送れていない自分の分なので、残して送る
最後の行の前提は「手元にだけある行は、自分が作った行だ」。自分のタスクなら、これで正しい。電波の悪い所で作ったタスクが、サーバーに着く前に消されたら困る。
ところが、他人の行は事情が違う。他人の行は、もともとサーバーから届いた写しとしてしか手元に存在しない。自分で作ることは絶対にない。その写しが「手元にだけある」状態になったのなら、意味は「まだ送れていない」ではなく「持ち主が持ち去った」しかありえない。
それを「まだ送れていない」と読んだ端末は、他人のメモを自分の名前で送り直そうとし、サーバーは持ち主ではないからと断る。これを開くたびに繰り返していた。
「無い」の意味を並べ直した
直す前に、「サーバーに無い」が実際に何を意味しうるかを全部書き出した。
| 手元の行 | サーバーに無い理由 | 正しい扱い |
|---|---|---|
| 自分が作った行 | まだ送れていない | 残して送る |
| 他人が作った行 | 持ち主が共有の外へ移した・消した | 手元から外す |
| どちらでも | 取得が途中で切れて、届かなかっただけ | 何もしない(消さない・送らない) |
1行目しか想定していなかったのが、今回の原因だった。2行目を足して、他人の行はサーバーに無ければ手元から外すように直した。
3行目は、直している途中で見つけた。
1000件で黙って止まる
他人の行を手元から外す直しを書いていて、手が止まった。「サーバーに無いから外す」は、サーバーが全部を返してくれている前提に立っている。本当に全部返しているのか。
測ってみると、サーバーへの1回の問い合わせで返ってくるのは、最大1000件だった。それ以上あっても、エラーは出ない。1000件ちょうどで、何事もなかったように返事が終わる。アプリ側はタスクを「全部ください」と頼んでいるだけで、件数の上限を自分では決めていなかった。上限はサーバー側の初期設定にあった。
次に、1000件を超えて持っている人がいるかを数えた。
| 項目 | 値 |
|---|---|
| 1回の取得で返ってくる上限 | 1000件 |
| 1000件を超えて持っていた利用者の件数 | 1338件 |
| 新しい端末で届かないおそれがある分 | 約340件 |
1338件の大半は、9月27日に「引っ越し」機能でまとめて作られたものだった。「引っ越し」は、1件も作らないまま離れていく人を減らすために作った機能だ。その機能で一度にたくさん持ち込んだ人が、知らない上限に先にぶつかっていた。
今の端末では、手元に全部あるので困っていない。危ないのは、機種変更などで新しい端末に入れ直した時だ。最初の1000件しか届かず、残りの約340件は、画面に出ないまま「無い」ことになる。
2つが重なると何が起きるか
ここで、さっきの直しが怖くなった。
「他人の行はサーバーに無ければ外す」を入れた状態で、取得が1000件で切れたとする。切れた先にあった他人の行は「サーバーに無い」に見える。端末はそれを「持ち主が持ち去った」と読んで、手元から外してしまう。消えていない行を、こちらの判断で消すことになる。
そこで、外す処理の手前に条件を1つ置いた。返ってきた件数がちょうど1000件なら、打ち切られた可能性があるので、その回は「無い=消えた」と判断しない。何も外さず、何も送り直さない。
これは応急の歯止めで、根本の直しではない。根本は、1000件ずつ区切って最後まで取りに行く形に変えることだ。そちらは同期の土台に触るので、まだ手を付けていない。
直したことと、残っていること
| 何を | 状態 |
|---|---|
| 他人の行がサーバーに無い時は手元から外す | 直して配信済み |
| 取得がちょうど1000件の回は「無い」を信じない | 直して配信済み |
| 1000件ずつ区切って全件を取る | 未着手 |
| 同じ同期の仕組みを持つ別アプリ | 同じ直しが要る |
「保存できていません」の表示は、直した版が届いた後で消えるはずだ。ただ、本人の端末で消えたことはまだ確かめていない。
振り返り
7月に、自分のタスクを全部消したことがある(制作日誌 #9)。あの時の前提は「サーバーが正しい」で、サーバーが空だった時に破綻した。今回の前提は逆で、「サーバーに無いのは、まだ送っていないから」。どちらも、サーバーとの差を1つの意味でしか読まなかったことが原因だった。
差を見つけた時に、最初に問うべきは「どちらが正しいか」ではなかった。「この差は、何通りの理由で生まれうるか」だった。その数え上げの中に、自分の想定していない持ち主と、自分の知らない上限が入っていた。
コメント (0)
まだコメントはありません。最初の一言を残しませんか?