ワダチを作る #15:記録の画面の CPU 時間を 1/5 にした。その途中で、D1 の「読む行の数」の落とし穴に気づいた
前回(#14)のレビューで、記録の画面が、Workers の無料プランの CPU 時間の上限(1 回 10ms)を、大きく超えていると分かりました。今回は、その直しの話です。
- 記録の画面の CPU 時間を、41〜114ms から 8〜13ms にした
- 途中で、1 分ごとの自動の読み直しが、D1 の無料の枠を超えかねないことに気づいた
- 自動の読み直しをやめて、「読み直す」のボタンにした
まず、どこが重いかを測った
当てずっぽうで直さず、先に測りました。
手元で、習慣 10 個・7 年分(約 2 万 3 千件)の記録を持つユーザーを作ります。ビルドしたアプリを wrangler dev で動かし、開発者ツールの仕組み(inspector)につないで、CPU のプロファイルを取りました。20 回の平均です。
直す前の記録の画面は、手元で 1 回 57ms。内訳はこうでした。
- 積み上がりを数えるところ:約 25ms。直近 7 日・30 日・通算を、1 日ずつ回して数えていた
- データベースから 2 万 3 千行を受け取るところ:約 8ms
- タイムゾーンの確かめ(日付の書式の部品を毎回作る):約 3ms
1 回目:数え方を、引き算にした
積み上がりの数え方を、日付を「日の番号」(1970-01-01 から何日目か)にして、引き算で数える形に変えました。
- 期間の日数・やる曜日の数は、1 日ずつ回さず、引き算で出す
- 週○回の習慣の、週ごとの回数は、記録を 1 回なめてまとめる
数え方を変えるとき、いちばん怖いのは、数字がこっそり変わることです。そこで、前の「1 日ずつ数える」形を、テストの中に見本として残しました。でたらめに作った習慣 300 個(毎日・平日・土日・週○回、休止あり、期間の外の記録あり、など)で、新しい数え方と見本の結果が、全部そろうことを確かめています。
数え方の決まりを変えるときは、見本も一緒に変えます。見本は遅いのですが、読めば決まりが分かります。
あわせて、記録の日付を、習慣ごとに 1 行にまとめて受け取るようにしました(SQL の group_concat)。受け取る行は、2 万 3 千行から、習慣の数の 10 行になります。
本番に出すと、41〜114ms が、30ms 前後になりました。ただ、まだ 10ms には届きません。
2 回目:共通の部分と、問い合わせの数
残りを、もう一度測りました。比べるために、使い方のページ(データベースをほとんど読まない)も測ると、約 5ms。どの画面にも共通の部分が、それだけかかっていました。
見えてきたのは、次の 3 つです。
- Drizzle(データベースの部品)と Better Auth(ログインの部品)を、リクエストごとに作り直していた。それぞれ約 0.5ms
- 記録の画面は、データベースに約 10 回、1 つずつ問い合わせていた。1 回ごとに、SQL を組み立てる手間と、送り受けの手間がかかる
- 積み上がり用の日付を、文字(
2026-10-23)で受け取って、1 つずつ数に直していた
そこで、次のように直しました。
- Drizzle と Better Auth は、一度作ったものを使い回す
- 記録の画面の問い合わせ約 10 回を、1 回の
db.batchにまとめる - 日付は、SQL の中で日の番号にしてから受け取る(
juliandayを使う。アプリ側の数え方と同じ値になることは、手元のデータベースで SQL を実際に動かすテストで確かめた)
本番に出したあとの Observability です。
| 直す前 | 1 回目のあと | 2 回目のあと | |
|---|---|---|---|
| 記録の画面の読み直し | 41〜114ms | 30ms 前後 | 8〜13ms |
しばらく間が空いたあとの最初の 1 回だけは、45〜56ms でした。Worker が起動したてで、使い回す部品を一度だけ作るためです。
手元の数は、本番と同じにならない
途中、手元の測定で、おかしな数が出ました。ある直しで、手元では 17ms → 35ms と倍に増えたのに、受け取る文字の量は 1/10 になっていたのです。
調べると、手元のデータベース(D1 のまね)は、同じプロセスの中で動いているため、データベースが SQL を実行する重さまで、手元の CPU 時間に入って見えていました。本番では、D1 は別のところで動きます。手元の数は目安にして、最後は本番の数で確かめる、と決めました。
試してやめたこと:読む行が増えた
その「倍に増えた」直しは、続いている日を、SQL の中で「始めの日+日数」にまとめて受け取る形でした(ウィンドウ関数を使う)。ほぼ毎日付けている習慣なら、受け取る文字は 1/10 になります。
ところが、D1 が読む行の数(rows_read)を見ると、1 回の表示で 4 万 6 千行が、11 万 8 千行に増えていました。D1 の無料プランは、読む行の数に 1 日 500 万行の枠があります。この形は、やめました。
落とし穴:1 分ごとの読み直し
この「読む行の数」を見ていて、もっと大きな問題に気づきました。
少し前に、記録の画面に、自動の読み直しを入れていました。ホーム画面に追加したアプリを開いたままにしていると、翌日になっても、前の日の画面のままだったからです。最初は、「1 分以上離れて戻ってきたとき」と「日付が変わったとき」に読み直す作りでした。
受入のとき、「パソコンでチェックを付けても、開いたままのスマホに出ない」と分かりました。そこで、見えているあいだも、1 分ごとに読み直すようにして、本番に出していました。
ところが、記録の画面は、1 回の表示で、その人の全部の記録を読みます(積み上がりを数えるため)。
- 7 年分の記録なら、1 回で約 4 万 6 千行
- パソコンで記録の画面を開いたまま 8 時間 → 1 分ごとに約 480 回 → 約 2,200 万行
- D1 の無料の枠は、1 日 500 万行。超えると、その日のうちは、全員の読み出しが失敗する
利用者が 1 人でも、開きっぱなしにするだけで、枠を超えかねない作りでした。
作りを変えた:日付が変わったときと、ボタン
直し方を相談すると、こう言われました。
この対応を入れたのは、翌日に開き直さないと、当日の日付にならないことが原因。日付が変わった場合のみ読みに行くのと、再読み込みのボタンを配置して、押した場合のみ読みに行くようにすれば、1 分ごとに読みに行く必要はなくなる。
そのとおりでした。1 分ごとの読み直しは、「見えたままの画面に、ほかの端末の変更を出す」ためだけのもので、ボタンで代わりが利きます。
- 日付が変わったとき:見えているあいだは、1 分ごとに、ブラウザの中で日付だけを見る(通信しない)。変わったら読み直す
- 1 分以上離れて、戻ってきたとき:今までどおり読み直す(アプリを開き直したとき・ロックを外したときなので、回数は限られる)
- 「読み直す」のボタン:ほかの端末で付けた分は、これを押すと出る

日付の見出しの右に「読み直す」のボタン(画面の中身は、架空のデータ)
ボタンの置き場所は、少し迷いました。スマホの幅では、日付・「今日」の札・ボタン 3 つが 1 行に収まりません。案を画面で作って見せてもらい、スマホでは見出しを 2 行(1 行目は日付だけをまん中に、2 行目に札とボタン)、パソコンでは 1 行にしました。
このことは、開発の決まり(CLAUDE.md)にも書きました。時間ごとに読みに行く作りを足すときは、D1 の読む行の数を、先に見積もる。
おわりに
- 直す前に、本番に近いデータで、プロファイルを取って、どこが重いかを測った
- 数え方を変えるときは、前の数え方を見本としてテストに残し、でたらめな入力で結果がそろうことを確かめた
- 手元の数は目安。最後は本番の数で確かめる
- CPU 時間だけでなく、D1 の読む行の数も、無料の枠に効く。時間ごとに読みに行く作りは、先に見積もる
次回は、トップページをメニューの画面にしたことと、見出しのタブ、画面の小さな直しの話です。