EmDash CMS でページを更新して publish しようとしたところ、APIが以下のレスポンスを返していました。
{
"error": {
"code": "CONTENT_PUBLISH_ERROR",
"message": "Failed to publish content"
}
} この時のCloudflare Workers のログを確認すると、次のエラーが確認できました。
Content publish error:
Error: D1_ERROR: database disk image is malformed: SQLITE_CORRUPT
(extended: SQLITE_CORRUPT_VTAB) 原因
原因は、Cloudflare D1 上にある EmDash の検索用 FTS5 仮想テーブルが破損していたことでした。
EmDash では、検索対象の collection に対して SQLite の FTS5 index が作成されます。今回のサイトでは pages collection にも search support を付けていたためec_pages の更新時に FTS trigger が動く構成になっていました。
publish 処理では、下書き revision の内容を実テーブル ec_pages に反映します。その更新時に _emdash_fts_pages_update trigger が走り、破損していた FTS5 仮想テーブルに触れたことで SQLITE_CORRUPT_VTAB が発生していました。
つまり、publish そのもののバリデーションエラーではなく、publish に伴う検索インデックス更新で落ちていました。
調査したこと
まず、publish API の実装を確認しました。EmDash の publish 処理は大まかに次の流れです。
1. 対象 content を取得
2. 権限チェック
3. draft revision を live revision に昇格
4. revision の内容を content table に同期
5. status を published に更新
6. cache を invalidate
リモート D1 の schema を確認するとpages 用の FTS table と trigger が存在していました。
emdashfts_pages
emdashfts_pages_insert
emdashfts_pages_update
emdashfts_pages_delete このためpages の FTS table / trigger が publish 失敗の原因だと判断しました。
解決方法
今回は、サイトの検索を記事検索用途に絞る方針にしたためpages を検索対象から外しました。
まず、リモート D1 で壊れていた pages の FTS table / trigger を削除しました。
DROP TRIGGER IF EXISTS emdashfts_pages_insert;
DROP TRIGGER IF EXISTS emdashfts_pages_update;
DROP TRIGGER IF EXISTS emdashfts_pages_delete;
DROP TABLE IF EXISTS emdashfts_pages; その後 pages collection の検索設定も無効化しました。
UPDATE emdashcollections
SET search_config = '{"enabled":false}'
WHERE slug = 'pages'; コード側の再発防止
DB だけ直しても、seed 側で pages に search support が残っていると、今後また検索対象に戻る可能性があります。
そのためseed/seed.json も変更しました。
変更前:
{
"slug": "pages",
"label": "Pages",
"labelSingular": "Page",
"supports": ["drafts", "revisions", "search"],
"fields": [
{
"slug": "title",
"label": "Title",
"type": "string",
"required": true,
"searchable": true
},
{
"slug": "content",
"label": "Content",
"type": "portableText",
"searchable": true
}
]
} 変更後:
{
"slug": "pages",
"label": "Pages",
"labelSingular": "Page",
"supports": ["drafts", "revisions"],
"fields": [
{
"slug": "title",
"label": "Title",
"type": "string",
"required": true
},
{
"slug": "content",
"label": "Content",
"type": "portableText"
}
]
} まとめ
今回の CONTENT_PUBLISH_ERROR は、表面的には EmDash の publish 失敗でしたが、実際には Cloudflare D1 上の FTS5 検索インデックス破損が原因でした。
ポイントは次の通りです。
CONTENT_PUBLISH_ERRORだけでは原因は分からない- Cloudflare Workers のログを見ると
SQLITE_CORRUPT_VTABが出ていた - publish 時に
ec_pagesが更新され、FTS trigger が動いていた - 壊れていた
_emdash_fts_pagesを削除して解消した - このサイトでは pages 検索が不要だったため
pagesから search support を外した - 検索対象は posts に限定した
- D1 transaction fallback の互換性も
patch-packageで補正した
EmDash で publish が失敗した場合は、まず管理画面のエラーだけで判断せず、Cloudflare Workers の実ログを見るのが重要です。特に SQLITE_CORRUPT_VTAB が出ている場合は、content 本体ではなく FTS5 の仮想テーブルや trigger を疑うと切り分けが早くなります。
3 Comments