Tokyo TyrantによるHAなセッションストレージ 3 デュアルマスタ篇

デュアルマスタ構成にする基本的な方法についてはチュートリアルにもあるので省略。

期限切れレコードの削除はどちらで行うか

デュアルマスタにした場合、期限切れレコードの削除を行うttexpire.luaはどこで動かすのが良いか。
セッションデータの一貫性を保つためアクティブ-スタンバイ構成を想定しているので、アクティブ側でのみ動かすのが正統な選択になると思う。

しかし、そうするとスタンバイに切り替わった後にexpireの実行が行われなくなってしまう。今のところTokyo Tyrant(TT)は起動中にextpcオプションに相当する機能を操作する事はできないようだ。

解決策を2つ考えてみた

  • アクティブとスタンバイの両方でexpireを実行する
  • TT起動中にexpireの実行/停止を行えるようにする
アクティブとスタンバイの両方でexpireを実行する

両マスタでexpireを実行して問題なければ、上記の問題は簡単に解決する。

例えばMySQLレプリケーションの整合性が取れなくなるとレプリケーションが停止してしまうけど、調べたらTTの場合はまずい事(意訳)になったりはしないと書いてある。

Note that updating both of the masters at the same time may cause inconsistency of their databases. By default, the servers do not complain even if inconsistency is detected. The option `-rss' make them check the consistency and stop replication when inconsistency is detected.

http://1978th.net/tokyotyrant/spex.html#tutorial_replication

実際に試してみると、ログに

do_slave: detected inconsistency

というINFOレベルの出力があるものの、レプリケーションは継続されていた。*1

レプリケーションが停止しないのであれば両マスタでexpireを実行する方法で良さそうだ。
と思ったのだけど、スタンバイへのレプリケーションがある程度の時間遅延すると、期限が切れていないセッションがアクティブ側でのみ削除される可能性がある。
通常運用であればレプリケーションの遅延がそれほど大きくなる事は無いはず。でも、スタンバイを一時的に停止させたりした場合は再起動時には遅延が発生しているので、レプリケーションが追いつくまでの間が危険な時間になる。
通常時は両マスタでexpireを実行するにしても、起動中に実行/停止が行えるようにしておいた方が良さそうだ。

TT起動中にexpireの実行/停止を行えるようにする

TTの起動中にexpireの実行/停止を制御できれば、運用の手間は増えるものの、expireを動かしたいのに動かせなかったり、逆に動かして消してはいけないデータを消してしまう事は無くなる。

expireの動作を制御するため関数内で実行/停止を制御する値を参照するようにし、その値をtcrmgrのextサブコマンドで呼び出す関数内で変更できるようにしてみた。値の格納にはスタッシュを使用。

新しいttexpire.lua

TTEXPIRE_KEY = "ttexpire"
ENABLE_EXPIRE = "t"
DISABLE_EXPIRE = "f"

function expire()
   if not is_enabled_expire() then
      return
   end
   local args = {}
   local cdate = string.format("%d", _time())
   table.insert(args, "addcond\0x\0NUMLE\0" .. cdate)
   table.insert(args, "out")
   local res = _misc("search", args)
   if not res then
      _log("expiration was failed")
   end
end

function is_enabled_expire()
   return _stashget(TTEXPIRE_KEY) == ENABLE_EXPIRE
end

function ttexpire(key)
   local message = nil
   if key == "start" then
      _stashput(TTEXPIRE_KEY, ENABLE_EXPIRE)
      message = "starting 'expire'"
      _log(message)
   elseif key == "stop" then
      _stashput(TTEXPIRE_KEY, DISABLE_EXPIRE)
      message = "stopping 'expire'"
      _log(message)
   else
      if is_enabled_expire() then
         message = "'expire' is running"
      else
         message = "'expire' was stopped"
      end
   end
   return message
end

function _begin()
   _stashput(TTEXPIRE_KEY, DISABLE_EXPIRE)
end

function _end()
   _stashout(TTEXPIRE_KEY)
end

ttexpire関数を{start|stop|status}引数付きで呼び出すと、expireの実行開始/実行停止/状態確認が行える。

tcrmgr ext localhost ttexpire start
tcrmgr ext localhost ttexpire stop
tcrmgr ext localhost ttexpire status

これにより、多少手間はかかるものの、expireの実行を一時的に停止したり、アクティブでのみ実行されるように運用したりといった事ができるようになる。

更新ログ削除

デュアルマスタ構成では両マスタで更新ログ(ulog)を出力する事が必須になる。更新ログは放っておけば際限なく増え続けるので、適当なタイミングで削除する必要がある。

TTをulimオプション付きで起動すれば指定したサイズ(付近)で更新ログが分割される。今後レプリケーションに使用されない更新ログファイルはrmコマンドで削除しても構わないそうなので*2、現在使用中の更新ログファイル以外で、更新時刻が相手ホストのレプリケーション遅延時間(を現在時刻より引いた時刻)より前の更新ログを定期的に削除すれば良さそうだ。

遅延時間はそれぞれのマスタがレプリケーション相手に対して

tcrmgr inform -st host

を実行するとdelay行で得られる。

あとはfindコマンドのmminやmtimeオプションを使用して古い更新ログを削除すればdisk fullの憂き目は避けられる。

なお、スタンバイでexpireを定期実行していない場合、スタンバイ→アクティブでレプリケーションする内容が無いためアクティブの遅延時間が増加していってしまう。
この現象は、スタンバイで定期的に「何も削除しないoutコマンド」のようなデータに影響しない更新処理を実行すると回避できる。

ホストの復帰

アクティブからスタンバイに切り替える方法は華麗にスルー。

スタンバイへの切り替えがアクティブマスタの計画的な短期停止によるものであれば、expireの実行/停止に気をつけつつスタンバイ(元アクティブ)のTTを起動すればレプリケーションが再開されるので良いけれど、ハードウェア故障等によって新しいホストに交換された場合、そのままレプリケーションを再開する事はできない。

そういう時は一度その時点でのアクティブ環境のバックアップファイルを作成し、そのファイルをスタンバイに移して起動すれば良いらしい。

マニュアル(Setting Replication on Demand)によると、tcrmgrのcopyサブコマンドに次のシェルスクリプトを渡して実行すればデータベースファイルのバックアップが行えるそうだ。

ttbackup.sh

#! /bin/sh
srcpath="$1"
destpath="$1.$2"
rm -f "$destpath"
cp -f "$srcpath" "$destpath"
tcrmgr copy localhost '@/path/to/ttbackup.sh'

tcrmgrのcopyサブコマンドは、大雑把にはTT内部でデータベースファイルをロックしてコピーしてロックを解除する処理を呼び出しているみたいで、リモートから実行してもバックアップファイルが作成されるのはサーバ側になる。結構恐ろしい仕様。
また、渡すttbackup.shファイルのパスはTTから見たパスになる。TTはdmnオプション付きで起動した場合、/にchdirしているので絶対パスを渡さないと奇妙なエラーを見る事になる。

アクティブ側で作成したバックアップファイルをスタンバイに移し(多分インデックスのバックアップファイルはいらない)、ファイル名に含まれるタイムスタンプをrtsファイルに書き込み、もしあるのなら古いデータベースファイルや更新ログを全て削除してから起動すれば復帰できる。


おしまい

  1. 検討篇
  2. PHPから利用する篇
  3. デュアルマスタ篇

*1:なお、-rccオプション(ドキュメントの'-rss'は多分typo)を付けて起動した場合は、ドキュメントに書いてある通りERRORレベルのログを出力してレプリケーションが停止した。

*2:mixiのサポートコミュニティ書き込みより