LastaFluteでMaster/SlaveDB

master/slave対応の概要

同じ Schema に対して、MasterDB と SlaveDB と二つ以上のDBを用意する、Master/SlaveDB 構成のときの LastaFlute でのデフォルトのやり方が存在します。

master/slaveの目的

そもそもの master/slave の目的として、以下のことを想定しています。

  • A. MasterDB に障害が発生したら SlaveDB が代わってサービス継続
  • B. 検索の一部をslaveにして、DBサーバーの負荷分散

A だけであればアプリは意識しないのであまり関係ありませんが、B を(アプリで)やるのであれば意識する必要があります。 (アプリとDBの間にロードバランサーがいて自動で振り分けるなら意識しない)

master/slave対応の流れ

LastaFluteでの master/slave 対応は、以下のような流れです。

  • master/slaveごとのDataSourceを統合した切り替え可能DataSource作成
  • アプリ実行時にどっちを使うかを指定する (明示か暗黙かはアプリの都合次第)

それを、LastaFluteでのオーソドックスな実装に当てはめるとこのようになります。

  • SelectableDataSourceProxy を想定した Di xml 構造を構築する
  • SlaveDBAccessor で SlaveDB に検索する (ここはアプリの都合の良い形にカスタマイズ)

キーポイント: SelectableDataSourceProxy

SelectableDataSourceProxy がキーポイントとなります。 DBFluteが使う DataSource を、この Proxy に差し替え、動的に master と slave の DataSource を差し替えられるようにします。

このクラス自体は、もともと master/slave のための機能ではなく、冗長化複数DBのための機能です。 master/slaveも冗長化複数DBの一つのパターンとして捉えています。

ただ、実際にコンポーネント登録するクラスは、SelectableDataSourceProxy を継承して、 master/slave に最適化させた MasterBasisSelectableDataSource です。

e.g. Master/SlaveDBの DI xml の include 構成 @ClassModel
javax.sql.DataSource
 ^
 |-SelectableDataSourceProxy // 冗長化複数DBのためのクラス
    ^
    |-MasterBasisSelectableDataSource // master/slaveのためのクラス

master/slaveの実装戦略

アプリでmaster/slaveを導入する場合、アプリの業務コードでどのようにmasterとslaveを意識して実装するか? それによって準備する内容が変わります。

想定戦略パターン

大きく四つの戦略パターンを想定しています:

A. master基軸でslaveつど指定
slaveにしたい検索だけ狙って切り替え
B. slave基軸でmasterアノテーション指定
Action単位でmaster or slave
C. slave基軸で更新時master自動切り替え
更新系を実行した時点でmasterに
D. インフラ側で自動切り替え
ロードバランサーなどで自動的に振り分け

LastaFluteの基本想定

LastaFluteで提供しているクラスは、"A" の "master基軸でslaveつど指定" を基本想定としています。 もちろん、"B" や "C" も実現できますが、アプリ側で少し実装を入れて適用させます。

オフィシャルのドキュメントとしては、まずは "A" を前提として、"B" や "C" に関しては追加で説明を入れていきます。

"D" の場合は、もはやアプリもフレームワークも何もする必要がない (と思われる) ので、特に書くことはありません。

基本想定の実装イメージ

SlaveDBAccessorというインターフェースを使ったやり方の実装イメージです。 (戦略が変われば、これは使わないので、あくまで基本想定の理解のために)

e.g. SlaveDBAccessorを使ってSlaveDBを狙ってアクセス @Java
@Resource
private SlaveDBAccessor slaveDBAccessor;

public void sea() { // デフォルトはmasterを向いている
    // 必ず slave に対して検索
    List<Member> memberList = slaveDBAccessor.accessFixedly(() -> {
        return memberBhv.selectList(cb -> ...);
    });

    // 引数の判定次第で slave に対して検索
    List<Member> memberList = slaveDBAccessor.accessIfNeeds(() -> {
        return memberBhv.selectList(cb -> ...);
    }, isSlaveDB()); // 何かしらアプリケーション的な判定

    // 引数の数値次第で半々に slave に対して検索
    List<Member> memberList = slaveDBAccessor.accessRandomFifty(() -> {
        return memberBhv.selectList(cb -> ...);
    }, getDeterminationNumber()); // ランダム判定のためのlong
}

複数DB構成のときは、DIするSlaveDBAccessorの変数の型は、アプリでDBごとの継承クラス(具象クラス)になるかもしれません。 (仕組みの設計次第ですが、基本的にはそうなります)

master/slaveの設定方法

まず、単一DB構成、もしくは、複数DBであってもメインスキーマ であることを前提にした設定方法です。複数DB構成でのサブスキーマでのmaster/slaveはつどつど補足します。

Di xml構造

"組み込みDi xml" と "自動生成Di xml" と "手作りDi xml" を組み合わせて構築します。

e.g. Master/SlaveDBの DI xml の include 構成 @Dixml
app.xml // 手作り: アプリの Di xml のルート
 |
 |-dbflute.xml // DBFlute自動生成: includes jta.xml, jdbc.xml, tx_aop.xml
 |  |
 |  |-rdb.xml // Lasta Di組込み: includes jta.xml, jdbc.xml, tx_aop.xml
 |  |  |
 |  |  |-jta.xml // Lasta Di組込み: TransactionManager, UserTransactionなど
 |  |  |-(jdbc.xml) // LastaFlute組込み: jdbc+.xmlに上書きされる
 |  |  |-tx_aop.xml // Lasta Di組込み: まあ、気にしなくていい
 |  |  |
 |  |  |-jdbc+.xml // ☆手作り: SelectableDataSourceProxy(dataSource)を定義
 |  |  |  |
 |  |  |  |-plugin/selectable_datasource.xml // LastaFlute組込み: SelectableDataSourceHolderの定義
 |  |  |  |
 |  |  |  |-jdbc-master.xml // ☆手作り: master用のjdbc.xml
 |  |  |  |  |
 |  |  |  |  |-jta.xml // ConnectionPoolなどがTransactionManagerを使うため
 |  |  |  |  |-lastaflute_assist.xml // [app]_config.propertiesを使うため
 |  |  |  |
 |  |  |  |-jdbc-slave.xml // ☆手作り: slave用のjdbc.xml
 |  |  |  |  |
 |  |  |  |  |-jta.xml // masterと同じ
 |  |  |  |  |-lastaflute_assist.xml // masterと同じ
... 

クラス概念図

DBFluteとの関係性、アプリとの関係性、クラス階層の参考に。

e.g. master/slave basic classes @Model
DBFlute --+
          | get
          v                   (jdbc+.xml)
   +----------------------------------------+      (jdbc-master/slave.xml)
   |  javax.sql.DataSource                  |     +----------------------+
   |       ^                                |     |                      |
   |    SelectableDataSourceProxy           |<>---+  master DataSource   |
   |          ^                             |     |                      |
   |       MasterBasisSelectableDataSource  |     |   slave DataSource   |
   +----------------------------------------+     |                      |
            | ref                                 +----------------------+
            |
            v  (plugin/selectable_datasource.xml)
    +------------------------------------------+
    | SelectableDataSourceHolder               |    +----------------------+
    |     ^                                    |    |  << thread local >>  |
    |   ThreadLocalSelectableDataSourceHolder  |<>--+                      |
    |         ^                                |    |  dataSource keyword  |
    |       (Schema Impl)                      |    |                      |
    +------------------------------------------+    |  (master or slave)   |
                       ^                            +----------------------+
                       | switch
                       |
                       |    (jdbc+.xml)
                   +-------------------------+
  app -----------> |  SlaveDBAccessor        |
        access     |       ^                 |
                   |     SlaveDBAccessorImpl |
                   |          ^              |
                   |       (Schema Impl)     |
                   +-------------------------+

jdbc+.xml

アプリで jdbc+.xml を作成します。("+" を付けると、LastaFlute組込みの jdbc.xml が完全上書きされます)

ここで SelectableDataSourceProxy をコンポーネント定義することで、 DBFlute が利用する DataSource がProxy化され、masterに接続するのかslaveに接続するのか切り分けられます。 また、SlaveDBAccessor も定義することで、アプリで SlaveDB に狙ってアクセスできます。

e.g. jdbc+.xml for master/slave  @Dixml
<components namespace="jdbc">
    <include path="plugin/selectable_datasource.xml"/>
    <include path="jdbc-master.xml"/>
    <include path="jdbc-slave.xml"/>

    <-- main cast -->
    <component name="dataSource" class="org.lastaflute.db.replication.slavedb.MasterBasisSelectableDataSource"/>
    <component name="slaveDBAccessor" class="org.lastaflute.db.replication.slavedb.SlaveDBAccessorImpl"/>
</components>

ただ、SelectableDataSourceProxyは、別に Master/SlaveDB 専用のクラスではなく、デフォルトのDBを定めていませんので、実際に登録する具象クラスは、MasterBasisSelectableDataSource にすると良いでしょう。そうすると、DataSourceのキーを何も指定してないときに、MasterDB をアクセスするようになります。 (SelectableDataSourceProxyだと、何も指定されていなければ例外)

SelectableDataSourceProxy が、SelectableDataSourceHolder を利用するので、LastaFlute組込みの plugin/selectable_datasource.xml を include します。(複数DBでサブスキーマの場合はここの設定が変わります: 後述)

SelectableDataSourceProxy のコンポーネント名は、DBFluteがDataSourceをDIするときに使う名前にします。 複数DB構成とかにしていなければ、多くの場合デフォルトの "dataSource" でOKです。 (厳密には、DBFluteも型でDIするので、複数DBであってもあまり名前は関係ないかもです)

slaveDBAccessorは、(単一DB構成なら)組み込みのSlaveDBAccessorImplでも基本的には問題ないですが、もし将来、複数DB構成かつ "複数DB master/slave" になったら "SlaveDBAccessorインターフェース" でのDIが曖昧になってしまいます。 (app.xmlでの DBFlute の Di xml のinclude順序に依存してしまいます。 メインスキーマを最初にincludeしていれば、SlaveDBAccessorの実体もメインスキーマとなりますが、あまりそこに依存しない方が良いです)

なので、"将来DB増えてあっちでもこっちでもmaster/slaveやりそう" なのであれば、最初からSlaveDBAccessorImplを継承した(DBごとの)slaveDBAccessorをアプリで作成して登録した方が良いです。

複数DB構成の場合 (jdbc+.xmlではなくjdbc-[schema].xml])

複数DB構成の場合、jdbc+.xml ではなく jdbc-[schema].xml となります。 (アプリ作成のrdb-[schema].xmlからincludeされている想定: これはmaster/slave関係なく複数DBの話)

複数DB構成でサブスキーマの場合、selectable_datasource.xml は include せず、 ThreadLocalSelectableDataSourceHolder を継承した(DBごとの)クラスをアプリで作成して登録します。 (ThreadLocalをDBごとに独立させるため: でないとmaster/slaveの向き先が混在してしまう) (厳密には、インスタンスが独立さえすれば継承したクラスでなく動作はしますが、holderをDIして制御する必要が出てきた時に必要になるので、あらかじめ作っておいたほうが良いです)

dataSourceのコンポーネント名は、サブスキーマであっても "dataSource" で問題ないかもですが、名前でDIをする場面などアプリで必要であれば "[スキーマ名]DataSource" にしておきましょう。

slaveDBAccessorは、SlaveDBAccessorImplを継承したアプリでDBごとのクラスを作成して登録します。 サブスキーマだけでなくメインスキーマも作成した方が無難です。 (アプリ内でSlaveDBAccessorインターフェースでDIが曖昧になるため、DBごとの具象クラスでDIできるように)

jdbc-master.xml

アプリで、jdbc-master.xml を作成します。

内容は、LastaFlute組み込みの jdbc.xml を参考にして、master用に書き換えたものにします。

ただ、DataSourceコンポーネントに関しては(xaDataSourceの方ではありません)、コンポーネント名を masterDataSource に変更します。仕組みの中では、そのコンポーネント名で特定しますので重要です。

また、provider.config().getXxx() 部分は、デフォルトの接続設定をmasterのものとして扱うのであればそのままでOKです。 ([app]_env.properties上で master.jdbc... と明示したいのであれば修正してFreeGenしてDi xml上も直します)

そして、connectionPoolのプロパティで、provider.config().getOrDefault(...)になっている部分は、特に指定しないのであれば削除でOKです。 逆に指定するのであれば、getOrDefault()は使わず、FreeGenでメソッドを自動生成して provider.config().getXxx() 形式にしましょう。その方が、プロパティ名などを間違えた時に起動時エラーで検知できます。 (LastaFlute組み込みでgetOrDefault()を利用している理由は、組み込みとしての都合上のものなので、アプリ作成のDi xmlで使う必要はありません)

複数DB構成の場合 (jdbc-master.xml)

複数DB構成の場合は、サブスキーマの方は jdbc-[スキーマ名]-master.xml という名前にして区別できるようにします。

また、DataSourceコンポーネント名も master[スキーマ名]DataSource という名前にして区別できるようにします。 ここは切り替えする際に参照される名前なので重要です。

メインスキーマであっても jdbc-[スキーマ名]-master.xml という名前にした方が紛らわしくないです。 一方で、メインスキーマのDataSourceコンポーネント名は、SlaveDBAccessorのデフォルト設定に合わせて masterDataSource のままでOKです。(アプリで拡張して名前設定を変えていなければ)

jdbc-slave.xml

アプリで、jdbc-slave.xml を作成します。

内容は、LastaFlute組込みの jdbc.xml を参考にして、slave用に書き換えたものにします。

ただ、DataSourceコンポーネントに関しては(xaDataSourceの方ではありません)、コンポーネント名を slaveDataSource に変更します。仕組みの中では、そのコンポーネント名で特定します。

また、provider.config().getXxx() 部分は、それぞれ [app]_env.propertiesにて slave用のプロパティを定義して FreeGen でメソッドを自動生成して、それに合わせて Di xml での参照も修正しましょう。

[app]_env.propertiesでのslave

すべて slave.jdbc... と分離させるのか?slave固有のものだけslave用プロパティを作成して共通のものはmasterの設定を再利用するか? ここはアプリの要件に合わせて調整すると良いです。

例えば、ConnectionPoolのサイズとReadOnlyの設定はslaveで独立していた方が良いでしょう。 (ReadOnlyにしておけば、間違ってslaveで更新処理が走ってもDBMSによってはエラーになってくれます)

e.g. [app]_env.propertiesでのmaster/slaveの設定 @Properties
# The driver FQCN to connect database for JDBC
jdbc.driver = com.mysql.cj.jdbc.Driver

# The URL of database connection for JDBC
jdbc.url = jdbc:mysql://localhost:3306/maihamadb?allowPublicKeyRetrieval=true&useSSL=false

# The user of database connection for JDBC
jdbc.user = maihamadb

# @Secure The password of database connection for JDBC
jdbc.password = maihamadb

# The (max) pooling size of connection pool
jdbc.connection.pooling.size = 10

# The URL of database connection for JDBC as slave
slave.jdbc.url = jdbc:mysql://localhost:3306/maihamadb?allowPublicKeyRetrieval=true&useSSL=false

# The user of database connection for JDBC as slave
slave.jdbc.user = maihamadb

# The (max) pooling size of connection pool as slave
slave.jdbc.connection.pooling.size = 12

# Does it treat the slave schema as read only?
slave.jdbc.connection.pooling.read.only = true

複数SlaveDBの場合 (jdbc-slave.xml)

もし、複数SlaveDB (一つのDBスキーマに付き、複数のSlaveDBがあるケース) では、ファイル名やコンポーネント名の "slave" 部分を slaveSea や slaveLand などにして、SlaveDBの数だけ追加します。ただし、そのケースでは SlaveDBAccessor はそのままでは利用できません。 (SlaveDBAccessorは、基本的にslaveが一つであることを前提に実装されています)

何かしら複数のslaveに自動で振り分ける仕組みを別途挟み込む必要があるでしょう。 (アプリ作成のSlaveDBAccessorの継承クラスで、そういった仕組みを実装するとか)

複数DB構成の場合 (jdbc-slave.xml)

複数DB構成に関しては、先述のmasterの方と同じ話となります。

LastaFluteをアップグレードするとき

アプリで、LastaFlute組込みの定義(jdbc.xml)やクラスを意識することになるので、めったには変わりませんが、LastaFlute のアップグレードをするときには必ずmaster/slave周りを意識して動作確認をしてください。(万が一、構造や名前が変わったりしたときのために)

アプリでの継承クラスの作り方

主に複数DB構成の場合は、LastaFluteで組み込まれてるクラスをアプリで継承して固有のクラスとして扱うことが多いです。

bizfwパッケージ

それら継承クラスは、bizfwパッケージ配下に (例えば) masterslave というパッケージを作って、その下に配置すると良いでしょう。

さらに、その配下にDBスキーマごとのパッケージを作って、DBスキーマ依存のクラスを区別できるようにすると良いでしょう。

e.g. bizfw配下のパッケージ構成 @Dixml
[root]
 |-app
 |-bizfw
 |  |-...
 |  |-masterslave
 |  |  |
 |  |  |-maihamadb    // main schema
 |  |  |  |-MaihamadbSlaveDBAccessor.java
 |  |  |
 |  |  |-resortlinedb // sub schema
 |  |  |  |-backstage
 |  |  |  |  |-ResortlineDBSelectableDataSourceHolder.java
 |  |  |  |-ResortlineSlaveDBAccessor.java
 |  |
 |  |-...
 |  |
 |-dbflute
 |-mylasta

SlaveDBAccessor

基本的には組み込みのSlaveDBAccessorImplを継承します。

メインスキーマの場合、特にオーバーライドするものはありません。 固有クラスを作ってDIするときに型でインスタンスを判別できれば良いだけなので、中の実装は空っぽでもOKです。 (もちろん、メインスキーマでもDataSourceコンポーネント名にスキーマ名が入っていればその限りではなく、サブスキーマと同じ実装をする必要があります)

サブスキーマの場合、mySchemaKeyword()メソッドをオーバーライドします。 DataSourceコンポーネント名を識別するためのキーワードを戻すようにします。 先頭文字は小文字でOKです。コンポーネント名の構築時に自動的に先頭大文字になります。

toString()のオーバーライドは必須ではありませんが、ログなどでDBを識別するのに使うこともあるので、オーバーライドしてスキーマが区別しやすい文字列にしておくと良いです。 (メインスキーマでもサブスキーマでも)

SelectableDataSourceHolder

基本的には組み込みのThreadLocalSelectableDataSourceHolderを継承します。

メインスキーマでは作らない(作らなくてもいい)ので、サブスキーマでのmaster/slaveの話になります。 (ただ、別にメインスキーマでも作って登録してもOKです。もしholderを直接DIというときに区別が明確になるというメリットがあります。その場合は、plugin/selectable_datasource.xmlはincludeしません)

ただ、メインスキーマでもサブスキーマでも、サブクラス側で実装は何も必要ありません。単に継承して型が明確になってインスタンスが独立すれば良いだけなので。

slave基軸での設定方法

slave基軸の実装概要

実装戦略が以下の場合:

  • B. slave基軸でmasterアノテーション指定
  • C. slave基軸で更新時master自動切り替え

ActionHookにて、Actionの開始時点をslaveにしつつ、master用アノテーションもしくは更新フック(BehaviorCommandHook)でmaster切り替えの制御を入れます。

e.g. slave basis  classes @Model
                             (jdbc+.xml)
  +----------------------------------------+
  |  [App]BaseAction                       |
  |      |                                 |
  |      |-hookBefore()                    |
  |      |  o to slave                     |
  |      |  o annotation or update hook    |
  |      |                                 |
  |      |-hookFinally()                   |
  |      |  o clear                        |
  |                                        |
  +----------------------------------------+
           | call
           |                      +-----------------------+
           |                      | Unit[App]TestCase     |
           |                      |   setUp()/tearDown()  |
           |                      +-----------------------+
           |                                      | call
           v     (jdbc+.xml/jdbc-[schema].xml)    |
   +------------------------------------------+   |
   | [Schema]MasterSlaveManager               | <-+
   +------------------------------------------+
                      | new (choose)
                      | call
                      |
                      v
  +-----------------------------------+
  |  SlaveBasisAgent                  |
  |   ^     ^                         |
  |   |   SlaveBasisAnnotationAgent   | // master by annotation
  |   |                               |
  |  SlaveBasisOnDemandAgent          | // master by update hook
  +-----------------------------------+
                    | uses
                    v
           SlaveDBAccessor
                SelectableDataSourceHolder

こちらは、webのみを想定しています。 (jobはslave基軸にせず、ピンポイントでSlaveDBAccessorを使ってアクセスする方が合うと想定しています。 ただ、やろうと思えば LastaJob の拡張ポイントに仕掛けることはできるとは思います)

slave基軸のセットアップ

今までやった設定方法に加えて、少し手を入れます。

  • slavebasicのライブラリクラスをExampleからコピー
  • master用のアノテーションを作成 (Bの場合のみ)
  • MasterSlaveManagerクラスを作成
  • [App]BaseActionのActionHookで利用
  • UnitTest環境での切り替え有効化

slave基軸のライブラリクラス

ライブラリ (LastaFluteのjarファイル) には含まれていませんが、slave基軸を実現するための汎用的なクラスがテストプロジェクトの lastaflute-test-fortress に含まれています。

slavebasisパッケージごとコピーしてOKです。

master用のアノテーションを作成 (Bの場合のみ)

こちらもテストプロジェクトのものを参考に。

MasterSlaveManagerクラスを作成

こちらもテストプロジェクトのものを参考に。

アノテーション方式 (Annotation way) の場合:

自動切り替え方式 (OnDemand way) の場合:

基本的にはnewするagentが変わるだけです。

[App]BaseActionのActionHookで利用

こちらもテストプロジェクトのものを参考に。

おおまかには以下の通り:

  • それぞれのDBごとのmanagerをDI
  • hookBefore()でbegin
  • hookFinally()でend
  • superに対して外側で呼ぶの推奨

UnitTest環境での切り替え有効化

UnitTest環境でslave基軸の切り替えが有効になるようにするためには、ActionHookでやったことをUnitTestのスーパークラスで実装する必要があります。 (UnitTestでは、ActionHookは呼ばれないので)

アノテーション方式の場合

TODO jflute こちらまだ課題です。test-fortressである程度の実装があるので参考に。(アノテーション方式で難あり) (2023/08/08)

自動切り替え方式の場合

特に問題ないので、UnitFortressBasicTestCase を参考に。

何にせよExampleを

テストプロジェクトの lastaflute-test-fortress でブランチを分けてExampleがあるので、細かくはそちらを参考に。

Bのslave基軸のアノテーション方式
function/masterslave_slavebasis_annotation
Cのslave基軸の自動切り替え方式
function/masterslave_slavebasis_ondemand

(わからないことあったら、jfluteに問い合わせくださいm(_ _)m)

アプリでの実装方法

Aのmaster基軸でslaveつど指定のアプリ実装

master/slaveの実装戦略が A. master基軸でslaveつど指定 であれば、デフォルトはmasterに向いているので、slaveに向けたい検索処理だけ SlaveDBAccessor で囲ってアクセスします。

e.g. SlaveDBAccessorを使ってSlaveDBを狙ってアクセス @Java
@Resource
private SlaveDBAccessor slaveDBAccessor;

public void sea() {
    // 必ず slave に対して検索
    List<Member> memberList = slaveDBAccessor.accessFixedly(() -> {
        return memberBhv.selectList(cb -> ...);
    });

    // 引数の判定次第で slave に対して検索
    List<Member> memberList = slaveDBAccessor.accessIfNeeds(() -> {
        return memberBhv.selectList(cb -> ...);
    }, isSlaveDB()); // 何かしらアプリケーション的な判定

    // 引数の数値次第で半々に slave に対して検索
    List<Member> memberList = slaveDBAccessor.accessRandomFifty(() -> {
        return memberBhv.selectList(cb -> ...);
    }, getDeterminationNumber()); // ランダム判定のためのlong
}

もし、DB固有のSlaveDBAccessorの実装クラスがあるのであれば、それをDIしましょう。少なくともサブスキーマであれば必ず存在するはずです。 (単一DB構成やメインスキーマでも作成してるかもしれないので確認しましょう)

e.g. DB固有のSlaveDBAccessorを使ってSlaveDBを狙ってアクセス @Java
@Resource
private MaihamaSlaveDBAccessor maihamaSlaveDBAccessor;

slaveの更新は自動切り替え

万が一、SlaveDBのメソッド内で更新処理をしてしまったとしても、更新処理は自動的に master にアクセスします。 SlaveDBAccessorには、そういった安全対策が施されています。(ただし、そこに頼らないようにしましょう)

Bのslave基軸でアノテーションを使ったアプリ実装

master/slaveの実装戦略が B. slave基軸でmasterアノテーション指定 であれば、masterアクセスしたときにアノテーションを付与します。

masterアクセスしたいActionクラスもしくはExecuteメソッドにmaster用のアノテーションを付けます。 (どういうアノテーションにするか?どういうルールにするか?はbizfwの実装次第でアプリで自由に変更できますので、現場ごとに確認しましょう)

Cのslave基軸で自動切り替えを使ったアプリ実装

master/slaveの実装戦略が C. slave基軸で更新時master自動切り替え であれば、更新時に自動でmasterに切り替わるので、アプリの業務コードでは何も気にせず実装します。

ちょこちょこ注意点

Transactionは独立

Master と Slave のようなレプリケーション構成の場合は、そもそも更新処理を Master に集中させないといけないためあまり気にする必要はありませんが、TransactionはそれぞれのDBごとに独立したものになります。 そのことだけはしっかり理解しておいた方が良いでしょう。

SlaveDBは遅延の可能性

master/slaveをどのように実現しているか次第ですが、RDBのmaster/slave機能の都合上、どうしてもmasterの更新からslaveへの反映までに遅延が発生する可能性があります。 それを許容できない検索なのに slave を使ってしまうと、思わぬ事故を引き起こしてしまうかもしれませんので注意しましょう。

ローカル開発環境では、どうする?

ローカル開発環境で、master/slave構成がすんなり構築できるのであれば特に問題はありませんが、そうでない場合はちょっと注意が必要です。

その場合、masterもslaveも同じDBを参照すれば、つじつまが合ってテストはおおよそできますが、UnitTest などで、masterとslaveに対するTransactionが同時に発行されるときはちょっと困ります。

例えば、UnitTestの中でmasterに対して更新した結果が、slaveに対する検索で参照ができません。 同じDBでも、Tranasction が(同時に)別になっているので、masterに対する更新が Commit されるまで、slaveの方の Tranasction ではその更新結果を検索できないのです。 そこに依存しているロジックがあると、UnitTestがまともにできないという可能性もあります。

しょっちゅう出てくる問題ではないと思うので、問題が出てくるまでお茶を濁すか、ローカルだけは SlaveDBAccessorNothing を使うようにするとか、工夫が必要でしょう。(ローカルで簡単にmaster/slaveができちゃえば一番世話ないですが)

Cのslave基軸の自動切り替えのすれ違い?

実装戦略が "C" のslave基軸の更新時master自動切り替えの場合は、厳密な一貫性に注意です。

  1. select (slave)
  2. update (master)
  3. select (master)
  4. update (master)

というように、最初のupdateより前のselectはslaveになります。 slaveのちょっとした遅延などでデータの一貫性が崩れる可能性はありますので、それを許容できるか?を考えた上で利用しましょう。