Writer/Reader DataSource
概要
idp-server は、Writer/Reader DataSourceの自動分岐により、読み取り負荷分散とパフォーマンス最適化を実現しています。
2つのデータソース選択機能
- DB種別の選択: PostgreSQL または MySQL(アプリケーション単位)
- Writer/Readerの選択: 主従レプリケーションでの自動分岐(@Transaction(readOnly)による)
@Transaction(readOnly=true) アノテーションに基づいて、自動的にWriter(主)またはReader(従)DataSourceを選択します。また、ApplicationDatabaseTypeProviderにより、アプリケーション単位でPostgreSQL/MySQLを切り替えることができます。
Spring などのFWに頼らず、JDK ProxyとThreadLocalによる明示的制御で実装することで、OSSとしての拡張性・ポータビリティを高めています。
ここでは、Writer/Reader DataSourceの分岐の仕組みと、それを支える各コンポーネントの責務について説明します。また、Spring との比較も記載します。
関連ドキュメント: トランザクション分離レベルとRead Your Own Writesについては トランザクション を参照してください。
アーキテクチャ
レイヤー構成
┌─────────────────────────────────────────── ──────────┐
│ Application層 │
│ IdpServerApplication │
│ └─ EntryService実装をProxy経由で取得 │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ Platform層 (Proxy) │
│ TenantAwareEntryServiceProxy │
│ ├─ @Transactionアノテーション検出 │
│ ├─ readOnly属性チェック │
│ └─ OperationType決定 (READ/WRITE) │
└───────── ────────────────────────────────────────────┘
↓
┌─────────────────┴──────────────────┐
│ │
readOnly=false readOnly=true
(デフォルト) (明示的)
│ │
↓ ↓
┌──────────────────┐ ┌──────────────────┐
│ OperationType │ │ OperationType │
│ = WRITE │ │ = READ │
└──────────────────┘ └──────────────────┘
↓ ↓
┌──────────────────┐ ┌──────────────────┐
│ TransactionManager│ │ TransactionManager│
│ .beginTransaction│ │ .createConnection│
└──────────────────┘ └──────────────────┘
↓ ↓
┌──────────────────┐ ┌──────────────────┐
│DbConnectionProvider│ │DbConnectionProvider│
│.getWriterConnection│ │.getReaderConnection│
└──────────────────┘ └──────────────────┘
↓ ↓
┌──────────────────┐ ┌─────── ───────────┐
│ Writer DataSource│ │ Reader DataSource│
│ (主DB) │ │ (従DB/Replica) │
│ INSERT/UPDATE/ │ │ SELECT only │
│ DELETE可能 │ │ 読み取り負荷分散 │
└──────────────────┘ └──────────────────┘
主要コンポーネント
| コンポーネント | 実装クラス | 役割 |
|---|---|---|
| Proxy | TenantAwareEntryServiceProxy.java | @Transactionアノテーション検出、Writer/Reader分岐 |
| TransactionManager | TransactionManager.java | トランザクション開始・コミット・ロールバック |
| OperationContext | OperationContext | READ/WRITE判定保持(ThreadLocal) |
| ApplicationDatabaseTypeProvider | ApplicationDatabaseTypeProvider.java | アプリケーション単位でDB種別解決(PostgreSQL/MySQL) |
| DbConnectionProvider | DbConnectionProvider.java | Writer/ReaderからConnection供給 |
ApplicationDatabaseTypeProviderの役割
アプリケーション単位でDB種別(PostgreSQL/MySQL)を指定します。
public interface ApplicationDatabaseTypeProvider {
DatabaseType provide(); // 引数なし: アプリケーション全体で共通
}
特徴:
- テナントごとではなく、アプリケーション全体で共通のDB種別を使用
- 環境変数または設定ファイルでPostgreSQL/MySQLを切り替え
- 起動時に決定され、実行中は変更されない
用途:
- 開発環境: PostgreSQL
- 本番環境: MySQL(またはPostgreSQL)
- Repository実装でDB種別に応じたSQL文を選択(例:
RETURNINGvsLAST_INSERT_ID())
注意: テナントごとに異なるDB種別を使用する場合は、別途DialectProviderを使用します(現在は未実装)。
処理フロー
- TenantAwareEntryServiceProxy: @Transactionアノテーション検出、readOnly属性チェック
- OperationType決定: readOnly=false → WRITE、readOnly=true → READ
- TransactionManager: OperationTypeに基づいてトランザクション開始またはConnection作成
- DbConnectionProvider: Writer/Reader DataSourceからConnection取得
- EntryService: ビジネスロジック実行
Spring との比較
| 機能カテゴリ | Spring Framework | idp-server |
|---|---|---|
| AOPによる横断処理 | @Transactional → AOP | TenantAwareEntryServiceProxy(JDK Proxy + invoke())で制御 |
| Txの開始/終了 | PlatformTransactionManagerが制御 | TransactionManagerが begin/commit/rollback を制御 |
| データソースの選択 | ルーティングを独自実装する必要あり | ApplicationDatabaseTypeProvider.provide() でDB種別を解決 |
| DataSourceContext | ThreadLocal: RoutingContextHolder | TransactionManagerが OperationContextと DbConnectionProvider を利用し解決する |
| Writer/Reader分岐 | @Transactional(readOnly=true) などを利用しルーティングを独自実装する必要あり | @Transaction(readOnly = true) で自動制御 |
Writer/Reader分 岐の詳細
TenantAwareEntryServiceProxyによる自動分岐
@TransactionアノテーションのreadOnly属性に基づいて、自動的にWriter/Readerを選択します。
書き込み操作(デフォルト)
@Transaction // readOnly = false(デフォルト)
public class ClientManagementEntryService implements ClientManagementApi {
public ClientManagementResponse create(...) {
// ✅ Proxyが自動的にWriter DataSourceを選択
// ✅ TransactionManager.beginTransaction()でWRITEモード
// ✅ OperationType.WRITE → DbConnectionProvider.getWriterConnection()
// ✅ Read Your Own Writes: 同一トランザクション内で更新後のデータを再取得可能
}
}
Writer DataSourceの特性:
- INSERT/UPDATE/DELETEが可能
- トランザクション分離レベル: READ COMMITTED
- Read Your Own Writes: 更新後即座に再取得可能
- DB関数(now()等)の値も取得可能