🎯 Apple Silicon「挟撃ち」完全攻略MD(Rancher Desktop + Azure SQL Edge + Prisma)
誰でも超やさしく分かる完全版
これは何?
- Apple Silicon (M2) のMacで「Rancher Desktop × Azure SQL Edge × Prisma」を絶対安定で動かすための、単語レベルからの超丁寧ガイドです。
- 会社PCでも“確実に”再現できるように、設計背景→起動→検証→永続化→トラブル対処まで、すべてのコマンドの意味つきで並べました。
- 実機検証により「amd64(x86_64)経路はSEGV」「ARM64ネイティブは安定稼働・永続化OK」を確認済みです。
✅ 先に結論(絶対安定の最重要ポイント)
-
x86_64(amd64)経路を絶対踏まないこと(ここで100%詰む)。
→docker-compose.yml
からplatform: linux/amd64
は削除。Apple SiliconではARM64ネイティブで走らせる。 - 起動直後に、必ず下記を“事実確認”する(超重要):
-
uname -m
→aarch64
(ARM64ネイティブ実行の証拠) -
/proc/sys/vm/legacy_va_layout
→0
(旧式メモリレイアウト依存が表面化していない) -
nc -z localhost 1433
→ OK(ポート疎通)
-
- Rancher DesktopではKubernetesを無効化(余計なVM/Podで環境が乱れるため)。
- Prismaの
updatedAt
は@updatedAt
を推奨(そうでないならシードでupdatedAt: new Date()
を必ず入れる)。
🧠 用語のミニ辞典(単語レベルで理解)
- ARM64(aarch64): Apple SiliconのCPU命令セット。x86_64とは根本的に別。
- x86_64(amd64): Intel/AMDのCPU命令セット。Apple Siliconでは通訳が要る。
- Rancher Desktop: macOS上にLinuxのVM(Lima+QEMU)を立ててコンテナを動かす仕組み。
- Rosetta 2: x86_64のCPU命令をARM64にリアルタイム変換する通訳。OSの性格(法律)は変えられない。
- vm.legacy_va_layout: Linux側のメモリレイアウト切替。1=旧式(レガシー)、0=現代式。
- 挟撃ち: アプリの“旧式にして”という要求(🏹)と、ホスト/仮想化の“できません”という制約(🛡️)が正面衝突して落ちること。
🔥 「挟撃ち」問題を超やさしく
どういうとき起きる?
-
platform: linux/amd64
でx86_64版のSQLサーバー(またはAzure SQL Edgeのamd64経路)をApple Silicon上で動かそうとすると、 - アプリが内部で旧式メモリレイアウト(
vm.legacy_va_layout=1
)を期待し、それをLinux VMに“お願い”します(🏹)。 - しかし、Rancher DesktopのVMはmacOS(XNUカーネル)の管理下。ホストの“法律”が変えられず、その要求は通りません(🛡️)。
- 結果として、**Segmentation fault(SEGV)**で落ちます。
どう回避する?
-
ARM64ネイティブ(Apple Siliconに素直な形)で走らせる。
Azure SQL EdgeのARM64イメージは旧式レイアウトの依存が表面化しにくく、挟撃ちの“片翼(🏹)”を無力化できます。
🧩 ファイルの役割(なぜ必要?)
-
Dockerfile
:mcr.microsoft.com/azure-sql-edge:latest
をベースに、USER mssql
で最小権限実行に切り替える。 -
docker-compose.yml
:-
db
(Azure SQL Edge)とprisma-studio
(DB待機→初期化→Studio起動)を定義。 -
volumes: mssql_data:/var/opt/mssql
により、DBファイルを永続化。 - 重要: Apple Siliconでamd64を強制しない(
platform: linux/amd64
は削除済み)。
-
-
prisma-entrypoint.sh
: Prisma側の初期化フロー(DB待機→npm install→generate→migrate→seed→Studio起動)。起動順の不確実性を吸収。 -
prisma/schema.prisma
: Prismaのモデル定義。updatedAt
は@updatedAt
を推奨(NOT NULL制約の整合が楽になる)。 -
prisma/seed.js
: サンプルデータ投入。Prismaのクライアントはモデル名そのままの小文字複数形(prisma.employees
など)を使う。
🛠️ 会社M2 Mac(Rancher Desktop)でのセットアップ手順
1) Rancher Desktopの前準備
- Kubernetesは必ず無効化。RancherはK8sを自動的に起動しがちで、リソース・ネットワークが荒れる。
- VMリソース: メモリ4GB以上、CPU2以上。
- 会社のプロキシがあるなら、
~/.docker/config.json
にproxies
を設定。
2) プロジェクト側の前準備
-
.env
を作成し、MSSQL_SA_PASSWORD
を設定(SQL Server要件を満たす強力なもの)。 -
docker-compose.yml
からplatform: linux/amd64
が消えていることを確認(またはlinux/arm64
明示でもOK)。
🚀 起動から検証まで(“事実”で確認する)
0. クリーンにしてから(超重要)
docker compose down -v --rmi all --remove-orphans
docker volume rm -f sqlserver_test_mssql_data
docker image rm -f mcr.microsoft.com/azure-sql-edge:latest || true
docker builder prune -a -f
docker image prune -a -f
docker container prune -f
docker system df
- 目的: コンテナ・イメージ・ボリューム・ビルドキャッシュを完全初期化し、変な残骸の影響をゼロにする。
1. ビルド&起動
docker compose up -d --build
docker compose ps
- 期待:
employee-db-mssql
とprisma-studio
がUp
2. ARM64ネイティブか“事実”で確認
docker exec employee-db-mssql uname -m
# => aarch64(これが出ないなら、amd64経路に迷い込んでます)
3. 旧式レイアウトに依存していないことを確認
docker exec employee-db-mssql sh -lc 'cat /proc/sys/vm/legacy_va_layout || true'
# => 0(旧式依存なし)
4. ポート疎通
nc -z localhost 1433 && echo PORT_1433_OK
# => PORT_1433_OK
5. DB起動ログの要点(実機例)
- 成功(ARM64ネイティブ)
Microsoft Azure SQL Edge Developer ... (ARM64)
Linux (Ubuntu ... aarch64) <ARM64>
...
SQL Server is starting ...
- 失敗(amd64経路を踏んだ場合の典型)
qemu: uncaught target signal 11 (Segmentation fault) - core dumped
Segmentation fault
→ これが見えたら即 platform: linux/amd64
を疑ってください。
💾 永続化(Volume)を“複数回”実証する
1) stop/start での永続化(スクリプトあり)
./test-persistence.sh
- 期待ログ(要点抜粋):
-
Step 3
:/var/opt/mssql/data
にEmployeeDB.mdf
/EmployeeDB_log.ldf
などが存在 -
Step 4-5
: db停止→起動 -
Step 7
: 再起動後も ファイル保持(サイズ/更新時刻OK) -
Step 8
: “ボリューム永続化証明完了!”
-
2) down/up(コンテナ再生成)でも永続化
docker compose down
docker volume ls | grep sqlserver_test_mssql_data # ← ここで名前付きボリューム残ってるのが鍵
docker compose up -d
docker exec employee-db-mssql ls -la /var/opt/mssql/data | head -20
- 期待: down/up後も
EmployeeDB.mdf
/..._log.ldf
が保持
🔗 Prisma初期化の完全理解(躓きやすい点と解決)
モデル名とクライアントの呼び方
-
schema.prisma
のモデル名:departments / employees / positions
- Prisma Clientのプロパティ名はモデル名小文字の複数形:
prisma.departments / prisma.employees / prisma.positions
- もし
prisma.employee
のような単数系を使うとundefined
になり、
Cannot read properties of undefined (reading 'deleteMany')
のようなエラーに直結します。
updatedAt の整合(とても大事)
- 現行の
schema.prisma
はupdatedAt
に@updatedAt
がついていません(NOT NULLで既定値なし)。 -
createMany
時にupdatedAt
を入れないとDB制約に当たる可能性。 - 解決の選択肢:
-
-
schema.prisma
をupdatedAt DateTime @updatedAt
に“設計修正”
-
-
-
seed.js
側でupdatedAt: new Date()
を全レコードに入れる
-
-
Prismaのモデル名を“事実確認”する(安全な方法)
# 1) コンテナ内にスクリプト配置(ヒアドキュメントで安全に)
docker exec -i prisma-studio sh -lc 'cat > /app/inspect.js << "EOF"
const { PrismaClient } = require("@prisma/client");
(async () => {
const p = new PrismaClient();
try {
console.log(Object.keys(p).filter(k => !k.startsWith("$")));
} finally {
await p.$disconnect();
}
})();
EOF'
# 2) 実行
docker exec -w /app prisma-studio node /app/inspect.js
# => ["departments","employees","positions", ...] が出ればOK
- ポイント:
node -e "..."
の1行実行は、$disconnect()
の$
が“シェル展開”されてハマることがあるため非推奨。
ヒアドキュメントで.jsファイルを作って走らせるのが安全です。
🧪 トラブルシューティング(発生しがちな問題と対処)
1) 起動直後に SEGV
- 症状:
qemu: ... Segmentation fault
- 原因: amd64経路(
platform: linux/amd64
、buildxの設定、別の理由でamd64が選ばれている) - 対処:
-
docker-compose.yml
からplatform: linux/amd64
を削除(またはlinux/arm64
を明示) -
docker exec employee-db-mssql uname -m
→aarch64
であることを確認
-
2) Prisma seed の Cannot read properties of undefined
- 原因: クライアントのプロパティ名の誤り(単数/複数)
- 対処:
prisma.employees/positions/departments
の複数形に合わせる
3) Prisma seed が updatedAt
でこける
- 原因: DB制約とデフォルトの不一致
- 対処:
- 設計を
@updatedAt
に変更(推奨) - もしくは seed ですべてに
updatedAt: new Date()
を入れる
- 設計を
4) cmdand
/ dquote>
状態で止まる
- 原因:
$
や引用符の扱いで、シェルが入力待ちになっている - 対処: まず
Ctrl-C
またはCtrl-D
で戻る。
以後はヒアドキュメントでNodeスクリプト化→実行に切替。
5) ポート疎通がNG
- 対処:
docker compose ps
でポート公開を再確認、nc -z localhost 1433
、企業プロキシでのローカル例外設定を見直し。
⚡ パフォーマンスと運用の小ネタ
- Rancher DesktopのK8sは無効が基本(余計なPodで負荷/競合が起きやすい)。
- VMのメモリは4GB以上を推奨。CPUも2コア以上。
- Azure SQL EdgeはEoL予定。長期運用は代替を検討(次章)。
🧭 EoLと代替選択肢(中長期の指針)
- Azure SQL Edge は提供終了(EoL)予定が公式ドキュメントで示されています。
- 中長期は以下いずれかを検討:
- Docker Desktopが許容なら、x64 SQL Server(公式手順に沿って最新のLinuxKit等で検証)
- リモートDB(Azure SQL / Managed Instance / 社内x64 VM上のSQL Server)に接続
-
開発限定ならPostgreSQL等(Prismaなら
provider
切替のインパクトが小さい)
🧰 コマンド辞典(全部“意味つき”で並べました)
-
docker compose down -v --rmi all --remove-orphans
コンテナ/ネットワーク/ボリューム/イメージ全部消して“完全初期化”する -
docker builder prune -a -f
/docker image prune -a -f
/docker container prune -f
ビルドキャッシュや使っていないイメージ/コンテナも全部掃除 -
docker compose up -d --build
バックグラウンド(-d)でビルド→起動 -
docker compose ps
各サービスの状態(Up/Exit)とポート公開状況を表示 -
docker compose logs --tail=200 db
DBログの最新200行で、起動成功/エラーを確認 -
docker exec employee-db-mssql uname -m
aarch64 であること(ARM64ネイティブ)を事実で確認 -
docker exec employee-db-mssql sh -lc 'cat /proc/sys/vm/legacy_va_layout || true'
0 であること(旧式要求が表面化していない) -
nc -z localhost 1433
ポート1433の疎通確認(OK
が出れば接続可能) -
./test-persistence.sh
stop/start の自動永続化テストを一発で行う -
docker compose down && docker compose up -d
down/up(コンテナ再生成)でもデータが消えないことの実証コマンド
🎉 最後に(このMDの使い方)
- まずは「先に結論」と「チェックポイント」を見ながら、**ARM64ネイティブで動いていることを“事実で確認”**してください(aarch64、legacy=0、1433 OK)。
- 次に
./test-persistence.sh
とdown/up
の2段階永続化テストで、データが保持されることを確認します。 - Prismaの
seed
は、複数形デリゲートと**updatedAt
の整合**だけ守れば堅いです。 - 以上を満たせば、会社のM2 Macでも安定稼働できます。
EoLに備えて中長期の代替も、このMDの最後の章から順に検討しましょう。
付録A: ありがちな落とし穴(実録)
- うっかり
platform: linux/amd64
が残っていた → 起動直後にSEGV。 - 1行の
node -e "..."
で$disconnect()
がシェル展開され、cmdand/dquote>
で固まる。
→ ヒアドキュメントで安全に実行へ。 -
prisma.employee
と単数を使っていた →undefined
でdeleteMany
に落ちる。 -
updatedAt
を設計で自動化していなかった →createMany
でNOT NULL制約に引っかかる。
付録B: 成功ログ(要点だけ再掲)
- ARM64ネイティブ起動ログ(要点)
Microsoft Azure SQL Edge Developer ... (ARM64)
Linux (Ubuntu ... aarch64) <ARM64>
...
SQL Server is starting ...
- stop/start 永続化テスト 成功抜粋
Step 3: ... EmployeeDB.mdf / EmployeeDB_log.ldf ...
Step 4-5: 停止→再起動
Step 7: 再起動後もファイル保持(サイズ/更新時刻OK)
Step 8: ボリューム永続化証明完了
- down/up 永続化テスト 成功抜粋
docker volume ls | grep sqlserver_test_mssql_data # 残存
... /var/opt/mssql/data に MDF/LDF が保持
以上です。
このMDだけ見れば、なぜ挟撃ちが起こり、どう避け、どう検証し、どう永続化できるかが“単語レベル”で分かるようにしました。
わからない箇所があれば、その行(またはコマンド)を丸ごと貼ってください。そこだけをさらに“行単位”で分解して解説します!
FROM mcr.microsoft.com/azure-sql-edge:latest
USER mssql
services:
db:
build: .
container_name: employee-db-mssql
ports:
- "1433:1433"
environment:
- ACCEPT_EULA=1
- MSSQL_SA_PASSWORD=${MSSQL_SA_PASSWORD}
volumes:
- mssql_data:/var/opt/mssql
healthcheck:
disable: true
prisma-studio:
image: node:18
container_name: prisma-studio
working_dir: /app
ports:
- "5555:5555"
environment:
- DATABASE_URL=sqlserver://db:1433;database=EmployeeDB;user=sa;password=YourStrong(!)Password123;trustServerCertificate=true
volumes:
- ./prisma:/app/prisma
- ./package.json:/app/package.json
- ./prisma-entrypoint.sh:/app/entrypoint.sh
entrypoint: ["/app/entrypoint.sh"]
depends_on:
- db
volumes:
mssql_data: {}
#!/bin/bash
# =============================================================================
# Prisma Studio カスタムエントリポイントスクリプト
# =============================================================================
#
# 【大目的】SQL ServerとPrismaの確実な接続とデータ初期化の実現
# 【中目的】データベース接続待機、マイグレーション、シーディング、Studio起動の順次実行
# 【小目的】初心者でも各行が理解できる詳細なコメント付きスクリプト
# 【選択理由】Docker Compose環境でのサービス依存関係とタイミング問題の解決
# -----------------------------------------------------------------------------
# Step 1: エラー発生時の即座終了設定
# -----------------------------------------------------------------------------
set -e
# ↑ 'set -e'はbashの組み込みコマンドです
# ↑ どんなコマンドでもエラー(戻り値が0以外)になったら
# ↑ スクリプト全体を即座に終了させる安全装置です
# ↑ これにより、途中で問題が起きたときに続行せずに止まります
# -----------------------------------------------------------------------------
# Step 2: 重要な変数の定義
# -----------------------------------------------------------------------------
DB_HOST="db"
# ↑ 'DB_HOST'という名前の変数に文字列"db"を代入しています
# ↑ この"db"はdocker-compose.ymlで定義したSQL Serverサービスの名前です
# ↑ Docker Compose内では、サービス名で他のコンテナにアクセスできます
DB_PORT="1433"
# ↑ 'DB_PORT'変数にSQL Serverの標準ポート番号1433を文字列として代入
# ↑ SQL Serverは通常このポートでクライアントからの接続を受け付けます
MAX_ATTEMPTS=30
# ↑ データベース接続を試す最大回数を30回に設定
# ↑ 1回につき2秒待つので、合計60秒まで待機することになります
CURRENT_ATTEMPT=1
# ↑ 現在何回目の接続試行かを記録するカウンター変数
# ↑ 最初は1回目から開始します
# -----------------------------------------------------------------------------
# Step 3: スクリプト開始の通知表示
# -----------------------------------------------------------------------------
echo "🚀 Prisma Studio エントリポイント開始"
# ↑ 'echo'コマンドは画面(標準出力)に文字を表示します
# ↑ 🚀は絵文字で、Docker Composeのログで見やすくするためです
echo "📋 設定情報:"
echo " データベースホスト: $DB_HOST"
# ↑ '$DB_HOST'は変数の値("db")を表示するbashの構文です
echo " データベースポート: $DB_PORT"
echo " 最大接続試行回数: $MAX_ATTEMPTS"
# -----------------------------------------------------------------------------
# Step 4: データベース接続の待機ループ
# -----------------------------------------------------------------------------
echo ""
echo "⏳ SQL Serverの起動を待機中..."
# whileループ:条件が真の間、繰り返し処理を実行
while [ $CURRENT_ATTEMPT -le $MAX_ATTEMPTS ]; do
# ↑ '[ $CURRENT_ATTEMPT -le $MAX_ATTEMPTS ]'は数値比較の条件式です
# ↑ '-le'は"less than or equal"の略で「以下」を意味します
# ↑ つまり「現在の試行回数が最大試行回数以下の間」という意味
echo "🔍 接続試行 $CURRENT_ATTEMPT/$MAX_ATTEMPTS 回目..."
# timeout+bashのビルトインでポート接続をテスト(netcatが無い場合の代替手段)
if timeout 3 bash -c "</dev/tcp/$DB_HOST/$DB_PORT" 2>/dev/null; then
# ↑ 'timeout 3'は3秒でタイムアウトする制限を設定
# ↑ 'bash -c'は一つのbashコマンドを実行するオプション
# ↑ '</dev/tcp/$DB_HOST/$DB_PORT'はbashの特殊ファイルでTCP接続をテスト
# ↑ '/dev/tcp/ホスト/ポート'に読み込みアクセスすることで接続確認が可能
# ↑ '2>/dev/null'はエラーメッセージを非表示にする
echo "✅ データベースに接続成功!"
break
# ↑ 'break'は最も内側のループ(while)から抜け出すコマンド
# ↑ 接続に成功したのでループを終了して次の処理に進みます
else
# ↑ 'else'は'if'の条件が偽の場合に実行される部分です
echo "❌ 接続失敗。2秒後に再試行..."
sleep 2
# ↑ 'sleep 2'は2秒間プログラムを一時停止するコマンド
# ↑ データベースの起動時間を考慮して待機します
fi
# ↑ 'fi'は'if'文の終了を示すキーワード('if'を逆から読んだもの)
CURRENT_ATTEMPT=$((CURRENT_ATTEMPT + 1))
# ↑ '$(( ))'は算術演算を行うbashの構文です
# ↑ カウンターを1増加させて次の試行に備えます
done
# ↑ 'done'はwhileループの終了を示すキーワード
# -----------------------------------------------------------------------------
# Step 5: 接続タイムアウト時のエラー処理
# -----------------------------------------------------------------------------
if [ $CURRENT_ATTEMPT -gt $MAX_ATTEMPTS ]; then
# ↑ '-gt'は"greater than"の略で「より大きい」を意味
# ↑ 試行回数が上限を超えたかチェック
echo "💥 エラー: データベースへの接続がタイムアウトしました"
echo "🔧 troubleshooting:"
echo " 1. SQL Serverコンテナが正常に起動しているか確認"
echo " 2. ネットワーク設定が正しいか確認"
echo " 3. DATABASE_URLの設定を確認"
exit 1
# ↑ 'exit 1'はスクリプトをエラーステータス(1)で終了
# ↑ 0は成功、1以上は失敗を表す標準的なUNIX規約です
fi
# -----------------------------------------------------------------------------
# Step 6: Node.jsパッケージのインストール
# -----------------------------------------------------------------------------
echo ""
echo "📦 Node.jsパッケージのインストール開始..."
npm install
# ↑ 'npm install'はpackage.jsonに記載された依存関係を全てインストール
# ↑ node_modules/フォルダにパッケージがダウンロードされます
if [ $? -eq 0 ]; then
# ↑ '$?'は直前に実行したコマンドの終了ステータスを表す特殊変数
# ↑ '-eq 0'は「等しい(equal)0」つまり成功を意味します
echo "✅ パッケージインストール完了"
else
echo "💥 エラー: パッケージインストールに失敗しました"
exit 1
fi
# -----------------------------------------------------------------------------
# Step 7: Prismaクライアントの生成
# -----------------------------------------------------------------------------
echo ""
echo "🔧 Prismaクライアントの生成開始..."
npx prisma generate
# ↑ 'npx'は一時的にパッケージを実行するNode.jsのコマンド
# ↑ 'prisma generate'はschema.prismaファイルからTypeScriptクライアントを生成
# ↑ これによりPrismaでデータベースにアクセスできるようになります
if [ $? -eq 0 ]; then
echo "✅ Prismaクライアント生成完了"
else
echo "💥 エラー: Prismaクライアント生成に失敗しました"
exit 1
fi
# -----------------------------------------------------------------------------
# Step 8: データベースマイグレーションの実行
# -----------------------------------------------------------------------------
echo ""
echo "🏗️ データベースマイグレーション開始..."
# マイグレーションファイルが存在しない場合は新規作成
if [ ! -d "prisma/migrations" ]; then
echo "📝 初回マイグレーションファイルを作成中..."
npx prisma migrate dev --name init --skip-seed
# ↑ 'migrate dev --name init'で初回のマイグレーションファイルを作成
# ↑ '--skip-seed'でマイグレーション後のシード実行をスキップ
# ↑ (後で手動実行するため)
else
echo "🔄 既存マイグレーションを適用中..."
npx prisma migrate deploy
# ↑ 'migrate deploy'は本番環境向けのマイグレーション実行コマンド
# ↑ prisma/migrations/フォルダ内のSQLファイルを順番に実行
fi
if [ $? -eq 0 ]; then
echo "✅ マイグレーション完了"
else
echo "💥 エラー: マイグレーションに失敗しました"
echo "🔧 トラブルシューティング:"
echo " 1. DATABASE_URLが正しく設定されているか確認"
echo " 2. データベースへの書き込み権限があるか確認"
exit 1
fi
# -----------------------------------------------------------------------------
# Step 9: サンプルデータのシーディング実行
# -----------------------------------------------------------------------------
echo ""
echo "🌱 サンプルデータの投入開始..."
npx prisma db seed
# ↑ 'db seed'はpackage.jsonのprisma.seedに指定されたスクリプトを実行
# ↑ 通常はprisma/seed.tsファイルで定義されたサンプルデータを投入
if [ $? -eq 0 ]; then
echo "✅ サンプルデータ投入完了"
echo "📊 投入されたデータ:"
echo " - 部署マスタ(営業部、開発部、人事部、経理部)"
echo " - 役職マスタ(部長、課長、主任、一般社員)"
echo " - 社員データ(6名のサンプル社員)"
else
echo "💥 エラー: サンプルデータ投入に失敗しました"
echo "🔧 トラブルシューティング:"
echo " 1. seed.tsファイルの構文エラーがないか確認"
echo " 2. データベーステーブルが正しく作成されているか確認"
exit 1
fi
# -----------------------------------------------------------------------------
# Step 10: 最終確認とPrisma Studio起動
# -----------------------------------------------------------------------------
echo ""
echo "🎯 最終確認中..."
echo "✅ SQL Server接続: OK"
echo "✅ パッケージインストール: OK"
echo "✅ Prismaクライアント生成: OK"
echo "✅ データベースマイグレーション: OK"
echo "✅ サンプルデータ投入: OK"
echo ""
echo "🎉 全ての準備が完了しました!"
echo "🌐 Prisma Studioを起動中..."
echo "📱 ブラウザで http://localhost:5555 にアクセスしてデータを確認してください"
# -----------------------------------------------------------------------------
# Step 11: Prisma Studioの起動(フォアグラウンド実行)
# -----------------------------------------------------------------------------
npx prisma studio --hostname 0.0.0.0 --port 5555
# ↑ 'studio --hostname 0.0.0.0'でPrisma Studioの管理画面を起動
# ↑ '--hostname 0.0.0.0'により外部(Dockerホスト)からのアクセスを許可
# ↑ '--port 5555'でポート番号を明示的に指定
# ↑ このコマンドは永続的に実行され続けるため、コンテナが動き続けます
# =============================================================================
# スクリプト終了
# =============================================================================
# このスクリプトは以下の順序で実行されます:
# 1. データベース接続待機 → 2. パッケージインストール
# 3. Prismaクライアント生成 → 4. マイグレーション実行
# 5. サンプルデータ投入 → 6. Prisma Studio起動
platform: linux/amd64 を排除(ARM64ネイティブを担保)
起動後に aarch64/legacy=0/1433 を事実確認
const didAttemptLoadRef = useRef(false);
useEffect(() => {
// 📊 useEffectの実行回数をカウント
useEffectCallCountRef.current += 1;
console.log(`🎯 [Theme×Employee Fetcher] useEffect実行回数: ${useEffectCallCountRef.current}`);
console.log(`🎯 [Theme×Employee Fetcher] テーマID: ${theme.id}, 社員ID: ${employee.id}`);
// ✅ 一度だけfetcher.loadを試す(StrictMode再マウントでも多重実行させない)
if (didAttemptLoadRef.current) {
console.log("⏸️ 既にloadを試行済みのためスキップ");
return;
}
didAttemptLoadRef.current = true;
actualFetcherCallCountRef.current += 1;
const apiUrl = `/api/recommendations/theme/${theme.id}/employee/${employee.id}`;
console.log(`🚀 [Theme×Employee Fetcher] API URL: ${apiUrl}`);
console.log(`🚀 [Theme×Employee Fetcher] fetcherを実行します! (実際の呼び出し回数: ${actualFetcherCallCountRef.current})`);
recommendationsFetcher.load(apiUrl);
}, [theme.id, employee.id]);
// ネットワーク遮断などでサーバーに到達できなかった場合:
// - 少なくとも一度はloadを試している(didAttemptLoadRef.currentがtrue)
// - 現在stateはidleに戻っている
// - dataがundefined(サーバーからのJSONが無い)
// 上記を満たせば「ネットワーク失敗」と判断してエラーUIを出す
const isNetworkError = didAttemptLoadRef.current && recommendationsFetcher.state === "idle" && !recommendationsFetcher.data;
// APIがJSONでsuccess:falseを返したケースと、ネットワーク失敗の両方をエラーとして扱う
const recommendationsError = isNetworkError || recommendationsFetcher.data?.success === false;
Index貼れてますか?
import { PrismaClient } from "@prisma/client";
export const prisma = new PrismaClient({
log: [{ level: "query", emit: "event" }],
});
prisma.$on("query", (e) => {
console.log("[SQL]", e.query, e.params, `${e.duration}ms`);
});
以下の3段階で確認するのが最短です(PostgreSQL)。
1) インデックスが存在するか確認
- psql:
-- テーブル名は実DBに合わせて。大文字名は必ずダブルクォートで
\d "Recommendation"
-- もしくは
SELECT schemaname, tablename, indexname, indexdef
FROM pg_indexes
WHERE tablename IN ('Recommendation','recommendation');
2) 実クエリに対して EXPLAIN で「どのインデックスが使われたか」を見る
- Prismaから直接(安全なパラメータバインドでOK):
// 例: themeId+employeeIdで絞って createdAt DESC + LIMIT
const plan = await prisma.$queryRaw`
EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
SELECT id, themeId, employeeId, title, rating, createdAt
FROM "Recommendation"
WHERE "themeId" = ${themeId} AND "employeeId" = ${employeeId}
ORDER BY "createdAt" DESC
LIMIT 20
`;
console.log(plan);
- 期待する表示:
- 「Index Scan」「Bitmap Index Scan」「Index Only Scan」のいずれか
- 使用インデックス名が出る(例:
using index "Recommendation_themeId_employeeId_createdAt_idx"
) - 「Seq Scan(全表走査)」ならインデックスが効いていない
3) 実運用で本当に使われたか統計で確認(回数が増えるか)
-- 実際にページを叩いた後に
SELECT relname AS table,
indexrelname AS index,
idx_scan, idx_tup_read, idx_tup_fetch
FROM pg_stat_user_indexes
WHERE relname IN ('Recommendation','recommendation')
ORDER BY idx_scan DESC;
-
idx_scan
が増えていれば、そのインデックス経由で実行されている
補足
- 作るべきインデックス例(並べ替えも効かせる想定):
model Recommendation {
id String @id @default(cuid())
themeId String
employeeId String
createdAt DateTime @default(now())
// ...
@@index([themeId, employeeId])
@@index([themeId, employeeId, createdAt])
}
- 反映:
npx prisma migrate dev --name add_recommendation_indexes
- クエリ側の形は「左端一致+ORDER BY末尾列」が基本:
await prisma.recommendation.findMany({
where: { themeId, employeeId },
orderBy: { createdAt: 'desc' },
take: 20,
});
- それでも
Seq Scan
になるときは、件数が極少・統計未更新等の可能性。ANALYZE "Recommendation";
を一度実行して再確認してください。