1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cloudflare D1 のローカルDBでマイグレーション漏れを調べて直した

1
Posted at

はじめに

React + Hono + Cloudflare Workers + D1 で作っているアプリに、コメントへのいいね機能を追加しました。

本番には反映できていた一方で、ローカル環境を開くとコメント欄だけが表示されず、「サーバーで問題が発生しました」と出ました。

画面自体は開けていて、投稿一覧も取得できています。つまり、アプリ全体が落ちているわけではなく、コメント取得まわりだけで問題が起きている状態でした。

この記事では、どうやって調べたか、どこを見て原因を特定したか、どう対処したか、最後にどう確認したかをまとめます。

問題

発生していた問題は、コメント一覧APIが 500 Internal Server Error になっていたことです。

画面は表示されますが、コメント欄だけが失敗していました。

原因切り分け

まず、フロントエンドとAPIのコンテナが本当に起動しているかを確認しました。

docker-compose ps

結果として、frontendapi はどちらも起動していました。

kinkyo-note-api-1        Up   0.0.0.0:8787->8787/tcp
kinkyo-note-frontend-1   Up   0.0.0.0:5173->5173/tcp

次に、ブラウザから到達しているかを確認しました。

curl -i http://localhost:5173/g/hanchu/feed
curl -i http://localhost:8787/api/health
curl -i http://localhost:5173/api/health

いずれも 200 OK が返りました。

フロントエンドの画面もAPIのヘルスチェックも動いているため、ポートやDockerの起動失敗ではないと考えました。

そこで、APIログを確認しました。

docker-compose logs --tail=200 api

ログには次のエラーが出ていました。

DrizzleQueryError: Failed query:
select "comment_id", count("id")
from "classmate_comment_likes"
where "classmate_comment_likes"."comment_id" in (?)
group by "classmate_comment_likes"."comment_id"

cause: Error: D1_ERROR: no such table: classmate_comment_likes: SQLITE_ERROR

ここで原因がかなり絞れました。

アプリのコードは classmate_comment_likes テーブルを読みに行っていまが、ローカルD1にはそのテーブルが存在していません。

つまり、コードの問題というより、ローカルDBに新しいマイグレーションが適用されていない状態でした。

今回追加したコメントいいね機能では、次のテーブルを使います。

export const classmateCommentLikes = sqliteTable(
  'classmate_comment_likes',
  {
    id: integer('id').primaryKey({ autoIncrement: true }),
    commentId: integer('comment_id')
      .notNull()
      .references(() => classmateComments.id, { onDelete: 'cascade' }),
    userId: integer('user_id')
      .notNull()
      .references(() => users.id, { onDelete: 'cascade' }),
    createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
  },
)

Drizzle のマイグレーションファイルも存在していました。

drizzle/0008_famous_bloodaxe.sql

中身を見ると、確かに classmate_comment_likes を作るSQLが含まれています。

CREATE TABLE `classmate_comment_likes` (
  `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
  `comment_id` integer NOT NULL,
  `user_id` integer NOT NULL,
  `created_at` integer NOT NULL,
  FOREIGN KEY (`comment_id`) REFERENCES `classmate_comments`(`id`) ON DELETE cascade,
  FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade
);

ここまでで分かったことは、次の通りです。

  • 画面とAPIサーバーは起動している
  • ヘルスチェックも成功している
  • コメント一覧APIだけが500になっている
  • APIログに no such table: classmate_comment_likes が出ている
  • 該当テーブルを作るマイグレーションファイルは存在している
  • したがって、ローカルD1にマイグレーションが未適用である可能性が高い

ポイントは、最初からDBを疑ったわけではないことです。

まず起動状態、次に疎通、最後にAPIログという順で見たことで、問題の範囲を狭められました。

解決方法

ローカルD1に未適用のマイグレーションを当てました。

今回実行したコマンドは次です。

docker-compose exec api npx wrangler d1 migrations apply kinkyo-note-db --local --persist-to .wrangler/state

docker-compose exec api を使っているのは、APIコンテナ内で使っているローカルD1の状態に対して操作したいためです。

--local は、Cloudflare上の本番D1ではなく、ローカルD1へ適用する指定になります。

--persist-to .wrangler/state は、ローカルD1の保存場所を指定しています。

このプロジェクトで、wrangler dev も同じディレクトリを使ってローカルD1を永続化しているので、マイグレーション適用先とAPI起動時のD1保存先がずれると、適用したはずなのにアプリから見るとテーブルがない、という状態になります。

docker-compose.yml でも、API起動時に同じ指定を使っています。

command: >
  sh -c "npm ci &&
  npx wrangler d1 migrations apply kinkyo-note-db --local --persist-to .wrangler/state &&
  npm run dev:api -- --ip 0.0.0.0 --persist-to .wrangler/state"

なぜ今回漏れたのか

コンテナが以前から起動しっぱなしだったためです。

docker-compose up の起動コマンドにはマイグレーション適用が入っています。ただし、すでに起動しているコンテナに対して新しいマイグレーションファイルを追加しても、その起動コマンドが自動でもう一度走るわけではありません。

そのため、コードだけが新しくなり、ローカルD1のテーブルは古いまま残っていました。

対処後、Wrangler は次のように 0008_famous_bloodaxe.sql を適用しました。

Migrations to be applied:
0008_famous_bloodaxe.sql

5 commands executed successfully.
0008_famous_bloodaxe.sql ✅

ローカルDBを完全に作り直したい場合は、Compose の volume ごと削除する方法もあります。

docker-compose down -v
docker-compose up

ただし、この方法はローカルD1のデータも消えます。

今回は既存のローカルデータを残したかったので、未適用のマイグレーションだけを当てる方法にしました。

おわりに

対処後、落ちていたコメントAPIを直接確認しました。

curl -i http://localhost:5173/api/groups/hanchu/classmates/1/comments
curl -i http://localhost:5173/api/groups/hanchu/classmates/9101/comments

どちらも 200 OK になりました。

GET /api/groups/hanchu/classmates/1/comments 200 OK
GET /api/groups/hanchu/classmates/9101/comments 200 OK

レスポンスには、コメント本文だけでなく、コメントいいね用の値も含まれていました。

{
  "comments": [
    {
      "id": 27,
      "classmateId": 1,
      "body": "やっほー",
      "canLike": true,
      "likeCount": 0,
      "likedByMe": false
    }
  ],
  "nextCursor": null
}

最後にブラウザをリロードすると、コメント欄も表示できる状態に戻りました。

今回の学びは、ローカル環境でも「コード」と「DBスキーマ」は別物だということです。

TypeScript のコードや Drizzle の schema を追加しても、D1に実際のテーブルが作られていなければ、実行時には no such table で落ちます。

特に Docker Compose で開発環境を長く起動しっぱなしにしている場合、起動時にマイグレーションを実行する仕組みがあっても、新しく追加した分は反映されていないことがあります。

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?