データ同期 (datasync)¶
datasync は、YAMLデータファイルを正(source of truth)として、データベースとの差分確認・反映を行うCLIツールです。Terraformの plan / apply と同じ考え方で動作します。
対象リソース¶
| リソース | YAMLパス | DBテーブル |
|---|---|---|
| 画像 | data/images/*.png |
images |
| 問題 | data/questions/*.yml |
questions, questions_single_choice, questions_single_choice_choices, question_images |
| 問題集 | data/workbooks/*.yml |
workbooks, workbook_questions |
| カテゴリ | data/categories/*.yml |
categories |
使い方¶
差分確認 (plan)¶
cd app && go run ./cmd/datasync -data ../data plan
YAMLとDBの差分をterraform風に表示します。データベースへの変更は行いません。
Images:
(no changes)
Questions:
+ 981 (新しい問題のテキスト...)
~ 42
text: "旧テキスト..." → "新テキスト..."
- 500 (削除される問題のテキスト...)
Workbooks:
(no changes)
Categories:
(no changes)
Plan: 1 to add, 1 to change, 1 to destroy.
+追加(YAMLにあるがDBにない)~変更(YAMLとDBで内容が異なる)-削除(DBにあるがYAMLにない)
差分反映 (apply)¶
cd app && go run ./cmd/datasync -data ../data apply
planと同じ差分を計算し、トランザクション内でDBに反映します。
接続先の切り替え¶
--env フラグで接続先を選択できます。
ローカルDB(デフォルト)¶
go run ./cmd/datasync -data ../data plan
# または明示的に
go run ./cmd/datasync -data ../data -env local plan
localhost:5432 のPostgreSQLに接続します。事前に docker compose up -d postgres でDBを起動してください。
dev環境(Neon)¶
# plan
go run ./cmd/datasync -data ../data -env dev plan
# apply
go run ./cmd/datasync -data ../data -env dev apply
AWS SSM Parameter Store (/rikako/development/database-url) からNeonの接続URLを取得して接続します。
事前に AWS_PROFILE の設定と aws sso login が必要です(AWS CLI セットアップ 参照)。
DATABASE_URL 直接指定¶
DATABASE_URL="postgres://user:pass@host:5432/db?sslmode=require" \
go run ./cmd/datasync -data ../data plan
DATABASE_URL 環境変数が設定されている場合は --env フラグより優先されます。
データ形式¶
問題 (questions)¶
id: 1
type: single_choice
text: 問題文
choices:
- 選択肢A
- 選択肢B
- 選択肢C
correct: 1
explanation: 解説文
images:
- 42
- 43
id: int(ファイル名と一致:1.yml)correct: 0-indexed の正解選択肢番号images: 画像ID(data/images/{id}.pngと対応)
問題集 (workbooks)¶
id: 1
title: 問題集タイトル
description: 説明文
questions:
- 1 # 問題ID
- 2
- 3
questionsの並び順がそのまま出題順序になります
カテゴリ (categories)¶
id: 1
title: カテゴリ名
description: 説明文
workbooks:
- 1 # 問題集ID
- 2
検証(plan / apply の前に弾くもの)¶
問題 YAML は読み込み時に QuestionYAML.Validate() で検証し、1 件でも通らなければ plan / apply ごと失敗する(#390。選択肢が空のまま DB に入った問題が 87 件残っていた再発防止)。
idが正の整数、同じidのファイルが 2 つ無いtextが空でないchoicesが 2 つ以上で、空・空白だけの選択肢が無いcorrectがchoicesの範囲内- 参照が閉じている: 問題集が参照する問題、問題が参照する画像がすべて
data/に存在する(削除の消し漏れ・消しすぎを plan で止める)
注意事項¶
- apply はトランザクション内で実行されるため、途中でエラーが発生した場合はロールバックされます
- 明示的IDでINSERTするため、apply後にシーケンス(auto increment)は自動でリセットされます
choicesが空の問題ではcorrectの差分比較はスキップされます
接続まわりの前提¶
- DBドライバは pgx(stdlib) を simple protocol で駆動する(Issue #291 / #292 で lib/pq から移行)。pgx は SCRAM channel binding に対応しているため、接続文字列に
channel_binding=requireが付いていても構わない。 - datasync は direct エンドポイントに接続する。 pooled endpoint への切替を行う
dbconn.Pooledを呼ぶのはcmd/serverとcmd/adminだけで、datasync は呼ばない。接続方針の一覧は runbook を参照。 - 接続URLは SSM から取得する。dev は
/rikako/development/database-url、prod は/rikako/production/database-url。パラメータ名は Terraform が作る/<project>/<local.environment>/database-urlと一致している。 DATABASE_URL環境変数を直接渡せば-envより優先される。SSM がズレているときの暫定回避に使える。
パラメータは環境ごとに 1 本。 datasync も Lambda も同じものを読む。 Terraform は
lifecycle.ignore_changes = [value]を付けていて初期値を入れるだけなので、 Neon 側でロールパスワードが変わったときの再登録は手作業(out-of-band)になる。aws ssm put-parameter --overwriteで更新してよく、次のterraform applyで巻き戻ることはない。
CI(Sync Content)¶
apply は手元で打たず、CI に任せるのが基本(#391)。
- dev:
data/**が main に入るとsync-content-dev.ymlがdatasync apply→/publish→ CDN invalidate → 問題集Web のデプロイまで自動で行う - prod:
sync-content-prod.ymlを手動 dispatch(production承認 ×2)
詳細は runbook の「データだけ反映する」。以下の datasync -env ... apply を手元で実行するのはデバッグ時や CI が使えないときだけにする。
CI(plan-datasync)¶
.github/workflows/plan-datasync.yml の plan 実行ステップは set -o pipefail + tee で、
datasync が非ゼロ終了したときにステップが失敗するようになっている(2026-06-13 修正済み)。
発火条件は data/** の変更だけに絞っている。この job は PR のソースからビルドした
datasync を dev の AWS 認証情報付きで実行するため、対象を広げると PR 由来のコードを
実行する機会が増えるため。
渡す認証情報も rikako-development-github-actions-datasync-plan 専用ロールに絞って
あり、できるのは SSM から dev の接続URLを読むことだけ。共有の
rikako-development-github-actions(AdministratorAccess)は使わない
(Issue #370)。
datasync バイナリ側の変更を実接続で確かめたいときは、main にマージしてから
workflow_dispatch で実行する。手動実行は job の if で main の内容のみ許可しており、
他の ref を選ぶと AWS の認証情報を取りに行く前に skip される。
tee により datasync の標準出力が public リポジトリの CI ログに出るため、DSN を生のまま
ログや標準出力に出さないこと。datasync は接続先表示のパスワードを url.Redacted() で
マスクしている。