「サーバーに無い」を、ずっと1つの意味で読んでいた — 他人のメモを送り直し続けた端末と、1000件で黙って止まる取得 — 制作日誌 #36
制作日誌

「サーバーに無い」を、ずっと1つの意味で読んでいた — 他人のメモを送り直し続けた端末と、1000件で黙って止まる取得 — 制作日誌 #36

共有しているタスクアプリで、ある利用者の画面に「保存できていません」が出続けた。523件は保存できていたのに、1件だけが通らない。残る1件を追ったら、同期の仕組みが「サーバーに無い」をたった1つの意味でしか読んでいなかったことと、取得が1000件で黙って打ち切られていることが、同じ日に見つかった。

KIYODO
#個人開発#アプリ開発#データ同期#失敗の記録#制作日誌

郵便受けに、届くはずの手紙が来ていないとする。考えられる理由はいくつかある。まだ差し出していない。宛先の人が転居した。郵便受けが小さくて、途中からあふれて入りきらなかった。どれも「無い」という同じ見た目になる。

私の作っているタスクアプリの同期は、この「無い」を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)

まだコメントはありません。最初の一言を残しませんか?