FIM One は 2 つのデータベースバックエンドをサポートしています: SQLite(デフォルト、ゼロコンフィグ)と PostgreSQL(本番環境推奨)。バックエンドは DATABASE_URL 環境変数によって決定されます。
# SQLite (デフォルト — 設定不要)
DATABASE_URL=sqlite+aiosqlite:///./data/fim_one.db
# PostgreSQL (本番環境)
DATABASE_URL=postgresql+asyncpg://user:pass@localhost:5432/fim_one
テーブルは初回起動時に自動的に作成されます — 手動マイグレーションステップは不要です。
SQLite vs PostgreSQL
| SQLite | PostgreSQL |
|---|
| セットアップ | ゼロコンフィグ、ファイルベース | 別のサーバーが必要 |
| 並行処理 | シングルライター(グローバルロック) | 完全なMVCC、行レベルロック |
| マルチワーカー | サポートなし(WORKERSは1である必要があります) | 完全にサポート |
| SSEストリーミング | ストリーム中に保持される接続は他のリクエストをブロックする可能性があります | 並行読み取りと書き込みは影響を受けません |
| バックアップ | .dbファイルをコピー | pg_dumpまたはストリーミングレプリケーション |
| 最適な用途 | 開発、シングルユーザー、デモ | 本番環境、マルチユーザー、チーム |
ローカル開発: SQLiteはそのまま動作します — データベースサーバーなし、Redisなし、設定するものはありません。コーディングを開始するだけです。本番環境: Docker Composeでデプロイし、PostgreSQL + Redisが自動的にプロビジョニングされます。手動でのデータベースセットアップは不要です。
既知の制限: SQLite 同時ストリーミング
SQLite は同時負荷下でボトルネックになる可能性があります。FIM One は Server-Sent Events (SSE) を使用して AI レスポンスをストリーミングします。ストリーミング中、各アクティブな SSE 接続はストリームの期間中、接続プールから 1 つのデータベース接続を保持します。SQLite はグローバル書き込みロックを適用するため、一度に 1 つの書き込み操作のみが実行でき、他のすべての書き込みはその後ろでキューに入ります。接続プールは最大 30 個の同時接続をサポートしていますが(pool_size=20 + max_overflow=10)、ボトルネックはプールではなくロック自体です。複数のユーザーが同時にチャットしている場合、メッセージの保存やトークンカウントの更新などの書き込み操作は互いに対して直列化されます。観察される可能性のある症状:
- 別のユーザーがストリーミング中に会話リストの読み込みが遅い
- アクティブなチャットセッション中に設定ページの反応が鈍い
- 複数のストリームがアクティブな場合、API レスポンスが遅延する
推奨事項: 2~3 人以上の同時ユーザーがいる場合は、PostgreSQL に切り替えてください。PostgreSQL は MVCC(Multi-Version Concurrency Control)と行レベルロックを使用するため、同時読み取りと書き込みは互いにブロックすることなく独立して進行します。
コネクションプール設定
FIM One は各バックエンド用に SQLAlchemy コネクションプール設定を内部で設定します。これらは調整されたデフォルト値であり、環境変数を必要としません — DATABASE_URL スキームに基づいて自動的に適用されます。これらを理解することは、ランタイム動作を説明するのに役立ちます。
SQLite プール設定
| 設定 | 値 | 説明 |
|---|
pool_size | 20 | プール内の永続接続の基本数 |
max_overflow | 10 | 負荷時に作成される追加接続(合計最大30) |
| WAL ジャーナルモード | 有効 | Write-Ahead Logging により、書き込み中の同時読み取りが可能になり、ロック競合が大幅に削減されます |
busy_timeout | 30s | 書き込みロックが保持されている場合、他の書き込み者は即座に失敗する代わりに、最大30秒待機してからエラーを発生させます |
synchronous | NORMAL | WAL モードで安全です。デフォルトの FULL よりも優れた書き込みスループットを提供します |
WAL モードと30秒の busy_timeout は、新しい接続ごとに SQLite PRAGMA を介して設定されます。この組み合わせにより、短期間の読み取り(会話リストの読み込み、設定の取得)が長時間実行される書き込みトランザクションによってブロックされず、同時書き込みが失敗する代わりに適切にキューイングされることが保証されます。
PostgreSQL プール設定
| 設定 | 値 | 説明 |
|---|
pool_size | 10 | プール内の永続接続の基本数 |
max_overflow | 20 | 負荷時に作成される追加接続(合計最大30) |
pool_timeout | 30s | タイムアウトエラーを発生させる前に、プールから空き接続を待つ最大時間 |
pool_recycle | 1800s | 接続は30分ごとにリサイクルされ、古い接続を防止します(クラウドホスト型データベースがアイドル接続を閉じる場合に重要) |
PostgreSQL は MVCC を介してネイティブに同時実行性を処理するため、プール設定は主にリソース使用量を制御し、競合ではなく制御します。30分のリサイクル間隔は、ファイアウォール、ロードバランサー、またはアイドル TCP 接続を静かに切断するマネージドデータベースサービスの問題を回避します。
PostgreSQLへの切り替え
ステップ 1: PostgreSQL インスタンスを起動する
最も簡単な方法は Docker を使用することです:
docker run -d \
--name postgres \
-e POSTGRES_PASSWORD=secret \
-e POSTGRES_DB=fim_one \
-p 5432:5432 \
postgres:16-alpine
ステップ 2: DATABASE_URL を設定する
.env ファイルに以下の行を追加または更新します:
DATABASE_URL=postgresql+asyncpg://postgres:secret@localhost:5432/fim_one
ステップ 3: FIM One を再起動する
ローカル開発
./start.sh portal
またはプロセスマネージャー / systemd サービスを再起動します
テーブルは初回起動時に自動的に作成されます。手動のスキーママイグレーションは不要です。
<Note>
**既存の SQLite データは自動的には移行されません。** `DATABASE_URL` を SQLite から PostgreSQL に切り替えると、新しいデータベースで開始されます。SQLite に既存の会話、エージェント、またはコネクタがあり、それらを保持する必要がある場合は、以下の[データ移行](#data-migration)セクションを参照してください。
</Note>
### Docker Compose(本番環境に推奨)
Docker Composeでデプロイする場合、**PostgreSQLとRedisは既に含まれており、自動的に設定されます** — 追加のセットアップは不要です。`docker-compose.yml`は`DATABASE_URL`を内部で設定します — `.env`の値はオーバーライドされます:
```yaml
environment:
DATABASE_URL: postgresql+asyncpg://fim:fim@postgres:5432/fim_one
Docker Composeを使用する場合、追加のデータベースセットアップは必要ありません。
データ移行
SQLiteからPostgreSQLへの組み込み移行ツールはありません。ほとんどのデプロイメントでは、推奨されるアプローチはあなたの状況によって異なります:
新規デプロイメント(既存データなし): DATABASE_URLをPostgreSQLの接続文字列に設定し、FIM Oneを起動するだけです。すべてのテーブルは自動的に作成されます。
保持する必要がある既存データ: 手動のエクスポート/インポートが必要です。一般的なアプローチは以下の通りです:
sqlite3 CLIやPythonスクリプトなどのツールを使用してSQLiteからデータをエクスポートする
- 必要に応じてデータを変換する(SQLiteとPostgreSQLにはマイナーな型の違いがあります)
psql、pg_restore、またはアプリケーションレベルの挿入スクリプトを使用してPostgreSQLにインポートする
開発セットアップから本番環境にアップグレードするほとんどのユーザーにとって、PostgreSQLで新規に開始し、UIを通じてエージェントとコネクタを再作成する方が簡単です。会話履歴は通常、手動移行を正当化するほど重要ではありません。