0
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?

Apple Silicon「挟撃ち」完全攻略

Last updated at Posted at 2025-08-17

🎯 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 -maarch64(ARM64ネイティブ実行の証拠)
    • /proc/sys/vm/legacy_va_layout0(旧式メモリレイアウト依存が表面化していない)
    • 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.jsonproxies を設定。

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-mssqlprisma-studioUp

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/dataEmployeeDB.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.prismaupdatedAt@updatedAt がついていません(NOT NULLで既定値なし)。
  • createMany 時に updatedAt を入れないとDB制約に当たる可能性。
  • 解決の選択肢:
      1. schema.prismaupdatedAt DateTime @updatedAt に“設計修正”
      1. 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 -maarch64 であることを確認

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.shdown/up2段階永続化テストで、データが保持されることを確認します。
  • Prismaのseedは、複数形デリゲートと**updatedAtの整合**だけ守れば堅いです。
  • 以上を満たせば、会社のM2 Macでも安定稼働できます。
    EoLに備えて中長期の代替も、このMDの最後の章から順に検討しましょう。

付録A: ありがちな落とし穴(実録)

  • うっかり platform: linux/amd64 が残っていた → 起動直後にSEGV
  • 1行の node -e "..."$disconnect()シェル展開され、cmdand/dquote> で固まる。
    ヒアドキュメントで安全に実行へ。
  • prisma.employee と単数を使っていた → undefineddeleteMany に落ちる。
  • updatedAt を設計で自動化していなかった → createManyNOT 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";を一度実行して再確認してください。
0
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
0
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?