はじめに
個人開発で水泳の練習記録アプリ「Suimote」を作りました。
公開後にレビューをもらい、練習記録一覧で全レコードを全カラム一括取得している
ことを指摘されました。
問題
const { data } = await supabase
.from("practice_records")
.select("*")
.order("date", { ascending: false });
2つの無駄がありました。
- 全件取得: 画面には最新の数十件しか表示しないのに、毎回全件ダウンロード
-
全カラム取得: 一覧画面で使うのは
id, date, distance, time, stroke, facilityの6つだけなのに、memoやcreated_atも毎回取得
Supabaseの無料枠は帯域5GB/月。レコードが増えるほど無駄に帯域を消費します。
解決方法
ページネーション: .range() で20件ずつ取得
const PAGE_SIZE = 20;
const { data } = await supabase
.from("practice_records")
.select(LIST_COLUMNS)
.order("date", { ascending: false })
.range(0, PAGE_SIZE - 1);
取得件数が PAGE_SIZE 未満になったら最後のページと判定し、「もっと見る」ボタンを非表示にします。
カラム絞り込み: .select("*") をやめる
// 修正前: 全カラム取得
.select("*")
// 修正後: 一覧に必要な6カラムだけ取得
const LIST_COLUMNS = "id, date, distance, time, stroke, facility";
.select(LIST_COLUMNS)
なぜこの方法を選んだか
他にも選択肢はありました。
| 選択肢 | 採用 | 理由 |
|---|---|---|
オフセットベース(.range()) |
✅ | シンプル。練習記録は自分しか追加しないので、データの重複・欠落が起きない |
カーソルベース(.lt("date", lastDate)) |
❌ | 同時書き込みでデータがズレる問題を防げるが、今のSuimoteではその問題が起きない |
| 無限スクロール | ❌ | UXは良いが、「もっと見る」ボタンで十分機能している |
| カラム絞り込み | ✅ | 1行変えるだけでリスクなく効果がある |
「その問題は今の自分のアプリで実際に起きるのか?」 を基準に判断しました。
効果
| 項目 | 修正前 | 修正後 |
|---|---|---|
| 取得件数 | 全件 | 20件ずつ |
| 取得カラム | 全カラム(8つ) | 必要な6カラムのみ |
| 500件ユーザーの帯域 | 約100KB/回 | 約3KB/回 |
学んだこと
-
.select("*")は思考停止のサイン。一覧画面で本当に必要なカラムだけ取得する - ページネーションの方式は複数あるが、今の状況で起きない問題のために実装を複雑にする必要はない
- 「なぜその実装なのか」「他の選択肢は何か」を常に問うことで、レビューで指摘される前に自分で気づけるようになる