Railsのデータベースコネクション管理

# Railsのデータベースコネクション管理

Railsアプリケーションにおけるデータベースコネクションのライフサイクル管理は、アプリケーションのパフォーマンスとスケーラビリティに大きな影響を与えます。この資料では、Railsがデータベースコネクションを確保するタイミングとリリースするタイミングについて詳しく解説します。

## 1. コネクションプールの基本

### 1.1 コネクションプールとは

Active Recordは、データベース接続を効率的に管理するために**コネクションプール**を使用します。これは事前に一定数のデータベース接続を作成し、必要に応じてアプリケーションに貸し出すシステムです。

### 1.2 コネクションプールの設定

`database.yml`ファイルで以下のように設定します：

```yaml
development:
  adapter: postgresql
  database: myapp_development
  pool: 5            # プールサイズ
  checkout_timeout: 5  # コネクション取得待機の最大時間（秒）
```

## 2. コネクションの確保（取得）タイミング

### 2.1 リクエスト処理開始時

- **Rack middlewareによる自動確保**: Railsは`ActiveRecord::ConnectionAdapters::ConnectionManagement`ミドルウェアを使用し、リクエスト処理の開始時にコネクションを確保します。
- **最初のデータベースアクセス時**: 実際には、リクエスト内で最初にデータベースにアクセスする時点でコネクションが確保されます。

### 2.2 明示的なコネクション確保

以下のコードでコネクションを明示的に確保できます：

```ruby
ActiveRecord::Base.connection_pool.with_connection do |conn|
  # コネクションを使った処理
end
```

### 2.3 トランザクション開始時

- `ActiveRecord::Base.transaction`メソッドを呼び出した時点でコネクションが確保されます
- トランザクションはコネクションプールからコネクションを借り出し、トランザクションが終了するまで保持します

```ruby
ActiveRecord::Base.transaction do
  # この時点でコネクションが確保され、トランザクション終了まで保持される
  User.create(name: "新規ユーザー")
end
```

### 2.4 その他のコネクション確保タイミング

- バックグラウンドジョブ実行開始時（例：Sidekiqワーカーでの処理）
- コンソール（`rails console`）起動時
- DBのマイグレーション実行時

## 3. コネクションのリリースタイミング

### 3.1 リクエスト処理終了時

- **自動リリース**: Rackミドルウェアにより、リクエスト処理が完了するとコネクションは自動的にプールに返却されます
- `ActionDispatch::Executor`が各リクエスト完了時にこの処理を担当します

### 3.2 明示的なリリース

コネクションを明示的に解放するには：

```ruby
ActiveRecord::Base.connection_pool.release_connection
```

### 3.3 トランザクション終了時

トランザクションブロックを抜けると、コネクションはプールに返却されます：

- 正常終了時（コミット後）
- 例外発生時（ロールバック後）

```ruby
begin
  ActiveRecord::Base.transaction do
    # トランザクション処理
  end
  # この時点でコネクションがプールに返却される
rescue
  # 例外時もコネクションはプールに返却される
end
```

### 3.4 アプリケーション終了時

- Railsサーバーの終了時
- バックグラウンドジョブの完了時
- コンソールセッションの終了時

## 4. アクセスパターンとコネクション管理

### 4.0 コントローラアクションとコネクション利用

以下のようなコントローラアクションを考えてみましょう：

```ruby
def update 
  sleep(50)                # 50秒の遅延
  User.update(params)      # データベース更新
  sleep(50)                # さらに50秒の遅延
end
```

このアクションでは、一見100秒間コネクションを確保しているように見えますが、**実際はそうではありません**：

1. 最初の`sleep(50)`：この時点ではデータベースアクセスが発生していないため、コネクションは確保されていません
2. `User.update(params)`：この時点で初めてコネクションが確保され、更新処理完了後すぐに解放されます
3. 次の`sleep(50)`：この時点ではすでにコネクションは解放されています

Railsは**遅延ロード**の原則を採用しているため、実際にデータベースアクセスが必要になるまでコネクションは確保されません。

一方、同じ処理をトランザクションブロック内で行うと状況が変わります：

```ruby
def update
  ActiveRecord::Base.transaction do
    sleep(50)
    User.update(params)
    sleep(50)
  end
end
```

この場合、トランザクションブロックの開始時にコネクションが確保され、ブロック終了まで解放されないため、実際に100秒間コネクションが占有されます。これがコネクションプール枯渇の原因となる典型的なパターンです。

### 4.1 長時間実行トランザクション

```ruby
ActiveRecord::Base.transaction do
  # 大量のレコード処理
  many_records.each do |record|
    record.process_something
    sleep(1) # 何らかの遅い処理
  end
  # トランザクションが終了するまでコネクションは解放されない
end
```

### 4.2 コネクションリークの例

```ruby
def risky_method
  connection = ActiveRecord::Base.connection_pool.checkout
  # エラーが発生すると以下が実行されず、コネクションがリークする
  ActiveRecord::Base.connection_pool.checkin(connection)
end
```

正しい実装:

```ruby
def safe_method
  ActiveRecord::Base.connection_pool.with_connection do |connection|
    # 処理が終了するとコネクションは自動的に返却される
  end
end
```

### 4.3 無限ループや処理中断

```ruby
def dangerous_loop
  users = User.where(active: true)
  # 無限ループに陥るとコネクションが返却されない
  while users.count > 0
    # 何らかの処理で条件が変わらないとループが終わらない
  end
end
```

## 5. コネクションプール枯渇の兆候と対策

### 5.1 典型的なエラーメッセージ

```
ActiveRecord::ConnectionTimeoutError: could not obtain a connection from the pool within 5.000 seconds
```

### 5.2 対策

1. **プールサイズの適切な設定**:
   - Webサーバーのワーカー数/スレッド数と合わせる
   - Puma (threads: 5, workers: 4) → pool: 5 * 4 = 20

2. **長時間トランザクションの回避**:
   - 大量データ処理の分割
   - バッチ処理の活用

3. **非同期処理の活用**:
   - ActiveJob/Sidekiqなどを使ったバックグラウンド処理

4. **読み取り/書き込み分離**:
   - 読み取り専用操作用の別プール設定

```ruby
ActiveRecord::Base.connected_to(role: :reading) do
  # 読み取り専用の処理
end
```

## 6. Rails 6以降の改善点

### 6.1 コネクション管理の改善

- **DatabaseSelector**: 読み取り/書き込み分離をより簡単に
- **コネクションハンドラー**: 複数のデータベース接続を簡単に切り替え

```ruby
class ApplicationRecord < ActiveRecord::Base
  connects_to database: { writing: :primary, reading: :replica }
end
```

### 6.2 自動コネクション切り替え

```ruby
Rails.application.configure do
  config.active_record.database_selector = { delay: 2.seconds }
  config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
  config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
end
```

## 7. Rails 7での最新機能

### 7.1 水平シャーディング対応

```ruby
class ApplicationRecord < ActiveRecord::Base
  connects_to shards: {
    default: { writing: :primary },
    shard_one: { writing: :shard_one },
    shard_two: { writing: :shard_two }
  }
end
```

### 7.2 デッドロック対策

```ruby
ActiveRecord::Base.transaction(requires_new: true, isolation: :read_committed) do
  # 再試行可能な分離レベルでのトランザクション
end
```

## 実践的なコネクション管理チェックリスト

- [ ] プールサイズがワーカー数・スレッド数と適切に設定されているか
- [ ] トランザクションが不必要に長時間実行されていないか
- [ ] 例外発生時にコネクションがリークしていないか
- [ ] 大量データ処理は適切に分割されているか
- [ ] New Relicなどのツールでコネクション使用状況を監視しているか

## まとめ

Railsのデータベースコネクション管理を理解し、適切に設計することで、アプリケーションのパフォーマンスとスケーラビリティを大幅に向上させることができます。特に高トラフィックのアプリケーションでは、コネクションプールの適切な管理が重要な成功要因となります。