日曜日 7 12, 2009
日曜日 7 05, 2009
OpenMQ/WebLogic JMSメッセージ管理ツールQBrowserのバージョン2.5.0をリリースしました。
変更概要は以下の通りです。
[ローカルメッセージ]
・メッセージをzip形式のファイルに保存することが可能。
・保存したローカルメッセージは、QBrowser共通フォーマットであるため、どのMQ用のQBrowserでも
使用可能(開いたり、ドロップしたりできる:OpenMQ⇔WebLogicなど)
・保存したメッセージファイルを任意の宛先にドロップ可能
・保存したメッセージを元に新規作成パネルを開く
・メッセージのコピー/ペースト(ローカルストア→任意宛先)
[ローカルストア]
・ローカルフォルダに紐づく、QBrowser内で利用可能な仮想宛先
・通常の宛先と同じように、以下が可能
- 通常宛先(Queue, Topic)からローカルストアへメッセージを転送(コピー)
- ローカルストアから通常宛先へメッセージを転送
・ローカルストアへ転送されたメッセージはファイルとして永続化。
・特定のTopicメッセージの受信をトリガに、メッセージを自動でローカルストアへコピーする設定が可能
(Topicタブ、ローカルストアタブを右クリック)
・ローカルストア内のメッセージをコピー/他の宛先へペースト
[javax.jms.StreamMessage, javax.jms.Messageサポート]
・送信、転送、閲覧が可能
・これらの情報を流用して新規作成パネルを開く
[送信パネル]
・TextMessageボディ/Userプロパティ/StreamMessage/MapMessageのString属性編集パネルに、テキストファイルをドラッグアンドドロップすると中身をパネルにコピー
[その他]
・進捗ダイアログウインドウの高速化/バッファリング改善
・キーショートカット機能追加
・メッセージ一覧情報を作成するときには、一覧に必要な情報以外は保持しない。
必要になった時点でオンデマンドでメモリに展開する。(メモリ節約)
金曜日 6 12, 2009
OpenMQ/WebLogic MQ用 JMSメッセージ管理ツール「QBrowserV2」のバージョン2.4.0をリリースしました。
メッセージパネル(編集、詳細)を中心にリニューアルしました。

以下からQBrowserV2_2.4.0をダウンロードしてください。詳しい使い方はZIPを解凍して出てくる「ForOpenMQ起動方法ReadMe.txt」と「ForWebLogic起動方法ReadMe.txt」をご覧ください。
変更概要は以下の通りです。
・MapMessage対応(入力、表示)
・メッセージパネルを表ベースにリニューアル(JMSヘッダ、ユーザプロパティ、MapMessage)
・Stringプロパティ選択時のエディタ画面
・その他BugFix
(詳しくはリリースノートをご覧ください)
金曜日 5 08, 2009
こんにちは。
5月の連休の合間を利用して、Myはうすが提供しているメッセージキュー管理ツールを更新してみました。今回のリリース(2.3.0)でWebLogic MQに対応しました。
以下からQBrowserV2_2.3.0をダウンロードしてください。詳しい使い方はZIPを解凍して出てくる「ForOpenMQ起動方法ReadMe.txt」と「ForWebLogic起動方法ReadMe.txt」をご覧ください。
UIはJIDE Common Layer Open Source Projectを使用しリニューアルしました。OSSのパワーを実感した開発の日々(正味3日)でした。。。
変更概要は以下の通りです。
・WebLogic MQに対応 (付属のrun_wls_mq.bat/run_wls_mq_for_default_install_location.batを使用)
・JIDE Common Layer(OSS:https://jide-oss.dev.java.net/)を使用し、UIをリニューアル
・完全国際化(日本語、英語)
[UI部変更]
・宛先コンボボックスにTOPIC名も表示
・メッセージテーブルをタブ化し、複数宛先を並行表示
・アイコンメニュー追加
[追加機能]
・TOPICサブスクライブ(購読)メニュー/ボタン追加
・サブスクライブ(購読)一時停止/再開機能
・メッセージテーブルの行を右クリック→選択メッセージ転送
・メッセージテーブルの行を右クリック→選択メッセージ削除
・宛先リスト更新機能(QBrowser外で追加されたものを出す)
・ルックアンドフィール動的変更メニュー
・メッセージ送信時、永続化モードと圧縮モード選択
[仕様変更]
・起動時にJMSサーバに接続出来ない場合、エラーダイアログボックスとヒントを表示
・使用中にJMSサーバとの接続が切れた場合、別スレッドでバックグラウンドで接続試行
・宛先名を選択すると、選択された宛先タブが表示される(タブがない場合作成)
・削除最適化/スピードアップ
・テーブル全選択時、「全て選択」が「選択解除」へ
・(2.2.0から)宛先名を空欄にしてsearchすると、現在の全宛先を対象に検索
[WebLogic MQ]
・宛先自動検索機能(キュー、トピック、分散キュー/分散トピックなど)
GPLv2ライセンスですので、ソースコード含めご自由にお使いくださいませ。
バグレポートなどご協力いただけると幸いです・・・
土曜日 4 25, 2009
こんにちは。今日はMyISAMの内部動作をトレースする方法をご紹介します。
MyISAMはデバッグ目的に内部動作をログに書き出す機能を持っています。これを使う機会はあまり無いと思いますが、環境の動作分析やパフォーマンスチューニングの参考情報にはなると思います。
[myisam-logの出力方法]
my.cnfもしくはmy.iniファイルの[mysqld]セクションに以下のように設定します。
[mysqld]
log-isam=c:/temp/mysql_isam.log
c:/temp/mysql_isam.logの部分はmyisam動作トレースログを出力したいファイルパスに書き換えて設定を行ってください。設定後、MySQLサーバを再起動します。
早速mysql_isam.logを見てみましょう。myisamlogはバイナリ形式で出力されているので、MySQLに付属している専用の閲覧ツールで内容を確認してみます。
[myisamlogの使用例]
C:\MySQLEntServer5.1\bin>myisamlog -i -vvv c:\temp\mysql_isam.log
-vvvを指定すると最も詳細な情報を出力することができます。-v, -vv, -vvvの三段階で出力レベルを調節できます。
[出力例]
C:\MySQLEntServer5.1\bin>myisamlog -i -vvv c:\temp\mysql_isam.log
0: 5180 ./mysql/host: open -> 12
27: 5180 ./mysql/host: extra(13) -> 0
37: 5180 ./mysql/host: extra(12) -> 0
47: 5180 ./mysql/host: extra(5) -> 0
57: 5180 ./mysql/user: open -> 14
84: 5180 ./mysql/user: extra(13) -> 0
94: 5180 ./mysql/user: extra(12) -> 0
104: 5180 ./mysql/user: extra(5) -> 0
114: 5180 ./mysql/db: open -> 16
139: 5180 ./mysql/db: extra(13) -> 0
149: 5180 ./mysql/db: extra(12) -> 0
159: 5180 ./mysql/db: extra(5) -> 0
169: 5180 ./mysql/host: extra(3) -> 0
179: 5180 ./mysql/host: extra(4) -> 0
189: 5180 ./mysql/user: extra(3) -> 0
199: 5180 ./mysql/user: extra(4) -> 0
209: 5180 ./mysql/db: extra(3) -> 0
219: 5180 ./mysql/db: extra(4) -> 0
229: 5180 ./mysql/time_zone_leap_second: open -> 18
273: 5180 ./mysql/time_zone_leap_second: extra(13) -> 0
283: 5180 ./mysql/time_zone_leap_second: extra(12) -> 0
293: 5180 ./mysql/time_zone_leap_second: extra(5) -> 0
303: 5180 ./mysql/time_zone_name: open -> 20
340: 5180 ./mysql/time_zone_name: extra(13) -> 0
350: 5180 ./mysql/time_zone_name: extra(12) -> 0
360: 5180 ./mysql/time_zone_name: extra(5) -> 0
370: 5180 ./mysql/time_zone: open -> 22
402: 5180 ./mysql/time_zone: extra(13) -> 0
412: 5180 ./mysql/time_zone: extra(12) -> 0
422: 5180 ./mysql/time_zone: extra(5) -> 0
432: 5180 ./mysql/time_zone_transition_type: open -> 24
480: 5180 ./mysql/time_zone_transition_type: extra(13) -> 0
490: 5180 ./mysql/time_zone_transition_type: extra(12) -> 0
500: 5180 ./mysql/time_zone_transition_type: extra(5) -> 0
510: 5180 ./mysql/time_zone_transition: open -> 26
553: 5180 ./mysql/time_zone_transition: extra(13) -> 0
563: 5180 ./mysql/time_zone_transition: extra(12) -> 0
573: 5180 ./mysql/time_zone_transition: extra(5) -> 0
583: 5180 ./mysql/time_zone_transition: close -> 0
592: 5180 ./mysql/time_zone_transition_type: close -> 0
601: 5180 ./mysql/time_zone: close -> 0
610: 5180 ./mysql/time_zone_name: close -> 0
619: 5180 ./mysql/time_zone_leap_second: close -> 0
628: 5180 ./mysql/tables_priv: open -> 18
662: 5180 ./mysql/tables_priv: extra(13) -> 0
672: 5180 ./mysql/tables_priv: extra(12) -> 0
682: 5180 ./mysql/tables_priv: extra(5) -> 0
692: 5180 ./mysql/columns_priv: open -> 20
727: 5180 ./mysql/columns_priv: extra(13) -> 0
737: 5180 ./mysql/columns_priv: extra(12) -> 0
747: 5180 ./mysql/columns_priv: extra(5) -> 0
757: 5180 ./mysql/procs_priv: open -> 22
790: 5180 ./mysql/procs_priv: extra(13) -> 0
800: 5180 ./mysql/procs_priv: extra(12) -> 0
810: 5180 ./mysql/procs_priv: extra(5) -> 0
820: 5180 ./mysql/servers: open -> 24
850: 5180 ./mysql/servers: extra(13) -> 0
860: 5180 ./mysql/servers: extra(12) -> 0
870: 5180 ./mysql/servers: extra(5) -> 0
880: 5180 ./mysql/servers: extra(3) -> 0
890: 5180 ./mysql/servers: extra(4) -> 0
900: 5180 ./mysql/func: open -> 26
927: 5180 ./mysql/func: extra(13) -> 0
937: 5180 ./mysql/func: extra(12) -> 0
947: 5180 ./mysql/func: extra(5) -> 0
957: 5180 ./mysql/func: extra(3) -> 0
967: 5180 ./mysql/func: extra(4) -> 0
977: 5180 ./mysql/func: close -> 0
986: 5180 ./mysql/event: open -> 26
1014: 5180 ./mysql/event: extra(13) -> 0
1024: 5180 ./mysql/event: extra(12) -> 0
1034: 5180 ./mysql/event: extra(5) -> 0
1044: 5180 ./mysql/event: extra(4) -> 0
Commands Used count Errors Recover errors
open 14 0 0
close 6 0 0
extra 53 0 0
Total 73 0 0
まだ何もテーブル操作を行っていないので、ユーザテーブルは一覧に出て来ていないのがわかります。以下にログの見方を説明します。
isamlogのファイルポジション プロセス番号 ファイル名: 操作ごとのログ
「操作ごとのログ」には例えば以下のようなものがあります。
open -> ファイルオープン数
extra(操作関数番号) -> リターンコード
write at ファイル中の行位置, length=0 -> 行自体に使用したバイト数
update at ファイル中の行位置, length=0 -> 行自体に使用したバイト数
openは、MYDファイルのオープン時、extraはSELECT、updateはUPDATE、writeはINSERT、deleteはDELETE文発行時にログに出力されます。
ここで、試しにtest.testというテーブルに対してアクセスをしてみたいと思います。mysqlクライアントでselectをしてみます。
mysql> use test;
Database changed
mysql> select * from test;
Empty set (0.00 sec)
ログの方はどうなっているでしょうか。
<中略>
967: 5180 ./mysql/func: extra(4) -> 0
977: 5180 ./mysql/func: close -> 0
986: 5180 ./mysql/event: open -> 26
1014: 5180 ./mysql/event: extra(13) -> 0
1024: 5180 ./mysql/event: extra(12) -> 0
1034: 5180 ./mysql/event: extra(5) -> 0
1044: 5180 ./mysql/event: extra(4) -> 0
1054: 5180 ./test/test: open -> 28
1080: 5180 ./test/test: extra(13) -> 0
1090: 5180 ./test/test: extra(12) -> 0
1100: 5180 ./test/test: extra(5) -> 0
Commands Used count Errors Recover errors
open 15 0 0
close 6 0 0
extra 56 0 0
Total 77 0 0
testテーブルにアクセスされたという情報がログの最後の部分に出ています。括弧中はselect実行時に使用されたメソッドの種類になります。クエリキャッシュが効いている場合などログにselect操作の結果が出てこないこともあります。
テストのため、テーブルにデータを一行挿入してみます。
mysql> insert into test values(1);
[myisamlog]
1321: 576 ./test/test: write at 0, length=0 -> 5
1347: 576 ./test/test: extra(4) -> 0
最初の一行なので、データが0の場所から5バイト書き込まれています。さらにもう一行挿入してみます。
3384: 576 ./test/test: write at 7, length=0 -> 5
3410: 576 ./test/test: extra(4) -> 0
今度は7バイト目から5バイト書き込まれました。ちなみにtestの構造は以下のようになっています。Row_formatはFixedとなっています。
mysql> show create table test;
+-------+-------------------------------------
| Table | Create Table
+-------+-------------------------------------
| test | CREATE TABLE `test` (
`a` int(11) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 |
+-------+-------------------------------------
1 row in set (0.00 sec)
int型のaという名前のカラムが1つだけ定義されています。int型はストレージを4バイト使用し、行ヘッダーはこのテーブルの場合1バイト使用します。ヘッダー1バイト + int型ストレージ4バイト=5バイトが 「length=0 -> 5」の意味するところです。さらにMyISAMの場合、最小行バイト数はグローバルサーバ変数myisam_data_pointer_sizeに指定された値+1(削除リンク分)に設定されます。デフォルト値は6ですので、6+1=7バイトが最小バイト数になります。myisam_data_pointer_sizeはCREATE TABLE時に使用されるので、もしこの値を変更する場合はCREATE TABLE前である必要があります。
この操作を行っている部分のソースは以下になります。
mi_create.c
if (!(options & (HA_OPTION_COMPRESS_RECORD | HA_OPTION_PACK_RECORD)))
{
if (reclength <= pointer)
reclength=pointer+1; /* reserve place for delete link */
}
実際にストレージに書き込まれるのは、ヘッダー1バイト + int型ストレージ4バイト+最小7バイトに足りない分を00値でパッディングする2バイト = 7バイトとなります。これはmyisamlogからも観測することができます。何行もデータを挿入してみます。
mysql> delete from test;
Query OK, 4 rows affected (0.00 sec)
mysql> insert into test values(1);
Query OK, 1 row affected (0.00 sec)
mysql> insert into test values(2);
Query OK, 1 row affected (0.00 sec)
mysql> insert into test values(3);
Query OK, 1 row affected (0.00 sec)
mysql> insert into test values(4);
Query OK, 1 row affected (0.00 sec)
mysql> insert into test values(5);
Query OK, 1 row affected (0.00 sec)
myisamlogは以下です。
8679: 576 ./test/test: re-open -> 0
8688: 576 ./test/test: write at 0, length=0 -> 5
8714: 576 ./test/test: extra(4) -> 0
8724: 576 ./test/test: write at 7, length=0 -> 5
8750: 576 ./test/test: extra(4) -> 0
8760: 576 ./test/test: write at 14, length=0 -> 5
8786: 576 ./test/test: extra(4) -> 0
8796: 576 ./test/test: write at 21, length=0 -> 5
8822: 576 ./test/test: extra(4) -> 0
8832: 576 ./test/test: write at 28, length=0 -> 5
8858: 576 ./test/test: extra(4) -> 0
書き込みは5バイトで、実際は7バイトずつストレージに書かれていることがわかります。またmyisamlogを見れば、データが実際にどの位置に挿入されたかがわかります。MyISAMでは行の削除はレコードヘッダに削除フラグが立てられるだけというのは以前書きましたが、実際にmyisamlogで挙動を確認することが可能です。試しにaの値が3(テーブルの中ごろに入っている)を削除してから新しいレコードをインサートしてみましょう。
mysql> delete from test where a = 3;
Query OK, 1 row affected (0.00 sec)
mysql> insert into test values(6);
Query OK, 1 row affected (0.00 sec)
myisamlogには以下が出力されています。
8952: 576 ./test/test: delete at 14 -> 0
8969: 576 ./test/test: extra(4) -> 0
8979: 576 ./test/test: write at 14, length=0 -> 5
9005: 576 ./test/test: extra(4) -> 0
一度削除された部分に対してインサートが行われているのが確認できます。
[オマケ]
myisamlogユーティリティには、出力されたmyisamログからMyISAMデータファイル(MYD)を再構築する機能もあります(使うかどうかはまた別問題として)。以下の手順で行います。
[ログの最初から最後までを再適用して、テーブルデータをリカバリする場合]
最初に、データを全部消してみます。
mysql> delete from test;
Query OK, 15 rows affected (0.02 sec)
バッファ内容をディスクにFlushします。
mysql> flush tables;
Query OK, 0 rows affected (0.00 sec)
以下のコマンドで、myisamlogからリカバリを行います。
C:\MySQLEntData\data>c:\mysqlentserver5.1\bin\myisamlog -i -r -vvv c:\temp2\mysql_isam.log ./test/test
バッファをクリアします。
mysql> flush tables;
Query OK, 0 rows affected (0.00 sec)
テーブルを見ると、ログからリプレイされデータが復活しているのがわかります。
mysql> select * from test;
+------+
| a |
+------+
| 1 |
| 2 |
| 6 |
| 4 |
| 5 |
+------+
5 rows in set (0.00 sec)
-oオプションを使うと、特定Log番号以降のログのみをリプレイに使うこともできます。(deleteもログに記録されているので削除される前のログをどこかに退避しないと、再び全行消えます 。。)他にもいくつかオプションがあるので試して見るのも趣がありますね。まあ、公式にはバイナリログを使うべきだと思いますのであくまで情報収集やデバッグ目的で行う方が良いと思います。
ちなみに、myisamlog -?とやると簡単な使用方法がプリントされるのですが、By Montyっていうあたり、ウケました。。。
C:\MySQLEntServer5.1\bin>myisamlog -?
myisamlog Ver 1.4 for Win32 at ia32
By Monty, for your professional use
土曜日 4 18, 2009
こんにちは。キューデータ管理ツールQBrowserV2に少しだけ機能追加してみました。
久々の更新ということでバージョンはV2.2.0としました。
以下からQBrowserV2_2.2.0をダウンロードしてください。
[追加機能]
Browseボタンの右に「Search」ボタンを追加し、メッセージの検索機能を追加しました。

Searchボタンを押すと、メッセージ検索ウィンドウがポップアップします。検索したい宛先名と必要があればメッセージセレクタ文字列を入力して、OKボタンを押せば検索結果がメインウインドウに表示されます。メッセージセレクタはJMS仕様のMessageSelector構文を使用します(少しSQLに似ています)。メッセージセレクタ文字列を入力しないと指定された宛先のメッセージを全件出力します。

メッセージセレクタにちょっと取っ付きにくいという方のために、だいたいの構文を集めたテンプレートを付けています。また、セレクタ文字列を入力して検索すると、文字列が履歴としてテンプレートに入るようになっています。

[オマケ]
各種情報照会ウィンドウの文字ずれが出ないようにしてみました。もう少しすっきり見えると思います。


水曜日 4 15, 2009
こんにちは。今回はオープンソースのメッセージキューであるOpen Message Queue(OpenMQ)のHAストアにMySQL Clusterを使う方法について書いてみたいと思います。(Connector/J設定の参考にもなるかと思います)
OpenMQはオープンソースアプリケーションサーバGlassFishのサブプロジェクトとして進められているMQを開発するプロジェクトです。エンタープライズシステム・コンポーネントであるOpenESBや、GlassFish Enterprise Serverなどのコアモジュールとして使用されている大変重要なソフトウエアとなっています。疎結合化システムを構築するためには無くてはならない存在で、システムによっては非常に高い可用性を求められる部分です。Myはうす(まい・はうす改め)ではOpenMQについての色々なトピックを書いて来ましたがいよいよ本命の登場です。
OpenMQのHAクラスタについてはこちらで説明していますのでよろしければご覧ください。
以下のトピックでは、OpenMQのクラスタでSunのHADBを使用する手順を書いています。
HADBを使用したHAクラスタ構成その1
HADBを使用したHAクラスタ構成その2
[4/17日Update]
奇しくも、4/16日にOpenMQクラスタについてのWebcastがリリースされました。brokerとproducer,consumer,永続ストレージとメッセージデリバリの関係がすごくわかりやすいです。Cluster構成にした場合の動きなどが特に参考になりますね(動画です!!)http://www.sun.com/offers/details/message_queue_ha_clusters.html
MySQL Clusterを使用した設定を行う場合も、HADBの時と流れは一緒ですので参考にしてください。
アーキテクチャ図を一枚引用します。

MQ4.1HAクラスタ・アーキテクチャとなっていますが、基本的にMQ4.3HAクラスタは4.1のものと同一のアーキテクチャとなっています。複数のMQのブローカがクラスタを構成し、同一のシェアドHAストアを利用します。少しおさらいしてみたいと思います。MySQL Clusterとの設定方法だけを見たい方は飛ばしてください。
[ホームブローカ]
MQのクライアント(JMSクライアントなど)が最初に通信を行ったブローカのことを「ホームブローカ」と呼びます。JMSクライアントのロードバランス機能などにより各クライアントのホームブローカはバラバラになります。OpenMQのHAクラスタでは従来の「コンベンショナルクラスタ」と同様に、どのブローカからもメッセージを取り出すことができる仕組みとなっています。(コンベンショナルでは、永続化メッセージは各ブローカがローカルに持っています)
例えばbroker1に対してsendしたメッセージはbroker2からも同様にreceiveすることが可能です。ただしOpenMQのHAクラスタでは単純に同一のデータ・セッション・コネクションを全brokerが共有する形にはなっていません。ホームブローカでないブローカ経由でメッセージがリクエストされた場合、ブローカがHAストアから他のブローカが管理しているメッセージを探して、クライアントに返す形となります。
#JMSのBrowserでキューの中を覗いた場合ホームブローカ以外にはメッセージがないように見えます。これは覗いた時点ではメッセージがそのブローカに所属していないためです。
[ホームブローカがダウンした場合]
ホームブローカがダウンした場合、上記のような構造となっているのでそのままでは他のブローカからのメッセージリクエストに応えられません。broker間では事前定義された間隔でお互いにハートビートを確認しています。ハートビートチェックがタイムアウトしそのブローカがダウンしていると判断された場合には、ブローカが管理している資源(セッション、トランザクション、メッセージ)のフェイルオーバが発生します。ダウンしたブローカが持っていた資源をHAストアから引継ぎ先にテイクオーバします。ブローカリストを持ったクライアント内でブローカへの接続も自動でフェイルオーバしますので、クライアントはMQのダウンを関知する必要がありません。
[全体としてHAにするための考慮点]
HAクラスタ・アーキテクチャ図の黄色枠内については、各モジュールがHA化されている必要があります。さもなければそこが単一障害点(Single Point of Failure)となってしまいます。冗長化するポイントは以下です。
・OpenMQブローカインスタンス
・HAストア
ブローカインスタンスについては、ブローカインスタンスを複数並行に並べることで解決可能です。ブローカインスタンスを複数並べ、全体として1つの仮想インスタンスとして取り扱うことをMQクラスタと呼んでいます。HAストアについてはMySQLを使用する場合、単体のインスタンスではMySQLサーバプロセスがダウンした場合そこが単一障害点になります。今回はMySQL Clusterを使用します。(単体のMySQLインスタンスも使用可能です)
[HAストアに使用するMySQL Clusterの考慮点]
・ 複数のデータノードを起動
・ 複数のSQLノードを起動
・ 各ブローカからSQLノードに分散/フェイルオーバするように設定
今回MySQL Clusterは説明のため最小構成で設定を行います。(本番適用時には要件に応じて適宜構成設計を行ってください)今回使用する環境のndb_mgmでの構成リストを以下に載せます。
使用するOpenMQのバージョンはここからダウンロードできる4.3を使用しました。
-- NDB Cluster -- Management Client --
ndb_mgm> show
Connected to Management Server at: 192.168.70.128:1186
Cluster Configuration
---------------------
[ndbd(NDB)] 2 node(s)
id=2 @192.168.70.128 (mysql-5.1.30 ndb-6.3.20, Nodegroup: 0, Master)
id=3 @192.168.70.128 (mysql-5.1.30 ndb-6.3.20, Nodegroup: 0)
[ndb_mgmd(MGM)] 1 node(s)
id=1 @192.168.70.128 (mysql-5.1.30 ndb-6.3.20)
[mysqld(API)] 2 node(s)
id=4 @192.168.70.128 (mysql-5.1.30 ndb-6.3.20)
id=5 @192.168.70.128 (mysql-5.1.30 ndb-6.3.20)
[OpenMQ Cluster with MySQL Cluster設定方法]
1.MySQL Clusterを起動し、mysqlクライアントでSQLノードのどちらかに接続してopenmqデータベースを作成します。
Server version: 5.1.30-ndb-6.3.20-cluster-gpl MySQL Cluster Server (GPL)
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql> create database openmq character set utf8;
Query OK, 1 row affected (0.23 sec)
MQで大きなサイズのメッセージを扱う予定の場合、MySQLの以下のパラメータを設定しておきます。最大サイズによって値を調整してください。
config.ini
[tcp default]
SendBufferMemory=2M
ReceiveBufferMemory=1M
この値を設定しておかないと、JMSクライアントの仕様によってはNDB-APIエラー(SEND BUFFERS OVERLOADED IN NDB KERNEL)となり送信できないことがあります(ストリームバッファを多めに取っている場合など)。デフォルト値はそれぞれ256k,64kです。
my.cnf
[mysqld]
max_allowed_packet = 16M
なお、何も指定していないとデフォルト1Mとなります(最大指定可能は1GB,それ以上の数字を設定しても1GBになる)。それ以上大きなメッセージを保存しようとすると以下のようなエラーとなってしまいます。これより大きなサイズのメッセージを扱いたい場合はByteMessageを使って複数のメッセージに分割する方法もあります。
[15/4/2009:23:01:46 JST] エラー [B2011]: 16-192.168.11.9(f8:22:94:e5:d3:14)-3584-1239778905890 からの JMS メッセージの保存に失敗しました:
com.sun.messaging.jmq.jmsserver.util.BrokerException: [B4004]: メッセージ 16-192.168.11.9(f8:22:94:e5:d3:14)-3584-1239778905890 を維持できませんでした
at com.sun.messaging.jmq.jmsserver.persist.jdbc.MessageDAOImpl.insert(MessageDAOImpl.java:416)
at com.sun.messaging.jmq.jmsserver.persist.jdbc.MessageDAOImpl.insert(MessageDAOImpl.java:340)
at com.sun.messaging.jmq.jmsserver.persist.jdbc.JDBCStore.storeMessage(JDBCStore.java:239)
at com.sun.messaging.jmq.jmsserver.persist.jdbc.JDBCStore.storeMessage(JDBCStore.java:201)
at com.sun.messaging.jmq.jmsserver.core.PacketReference.store(PacketReference.java:1177)
at com.sun.messaging.jmq.jmsserver.core.Queue.routeNewMessage(Queue.java:542)
at com.sun.messaging.jmq.jmsserver.data.handlers.DataHandler.routeMessage(DataHandler.java:462)
at com.sun.messaging.jmq.jmsserver.data.handlers.DataHandler.handle(DataHandler.java:244)
at com.sun.messaging.jmq.jmsserver.data.handlers.DataHandler.handle(DataHandler.java:97)
at com.sun.messaging.jmq.jmsserver.data.PacketRouter.handleMessage(PacketRouter.java:181)
at com.sun.messaging.jmq.jmsserver.service.imq.IMQIPConnection.readData(IMQIPConnection.java:1489)
at com.sun.messaging.jmq.jmsserver.service.imq.IMQIPConnection.process(IMQIPConnection.java:644)
at com.sun.messaging.jmq.jmsserver.service.imq.OperationRunnable.process(OperationRunnable.java:170)
at com.sun.messaging.jmq.jmsserver.util.pool.BasicRunnable.run(BasicRunnable.java:493)
at java.lang.Thread.run(Thread.java:619)
Caused by: java.sql.SQLException: [INSERT INTO MQMSG41Cmqhamysqlcluster ( ID, MESSAGE_SIZE, STORE_SESSION_ID, DESTINATION_ID, TRANSACTION_ID, CREATED_TS, MESSAGE) VALUES ( ?, ?, ?, ?, ?, ?, ? )]: Packet for query is too large (2713149 > 1048576). You can change this value on the server by setting the max_allowed_packet' variable.
at com.sun.messaging.jmq.jmsserver.persist.jdbc.DBManager.wrapSQLException(DBManager.java:863)
at com.sun.messaging.jmq.jmsserver.persist.jdbc.MessageDAOImpl.insert(MessageDAOImpl.java:411)
... 14 more
また、OpenMQ自体にも取り扱えるメッセージの最大バイト数の設定がありますのでそれらの値も調整します。-1は無制限を意味します。OpenMQ側で制限をかけたい場合はこの値を正の数値に設定します。maxBytesPerMsgとmaxTotalMsgBytesのデフォルトは10MB、imq.message.max_sizeは70MBとなっています。
※autocreate宛先の場合、一度作成されてしまうと上の値の設定を行っても反映しないので注意してください。その場合は一度宛先を再作成すると新しい値が反映します。imq.autocreate.destination.maxBytesPerMsg=-1
imq.autocreate.destination.maxTotalMsgBytes=-1
imq.message.max_size=-1
2.MySQL Connector/J(MySQL用JDBCドライバ)をここからダウンロードします。ダウンロードしたzipファイルを解凍すると、mysql-connector-java-5.0.8-bin.jarという名前のJARが出てきますので、これをMQ_HOME\lib\extフォルダにコピーしておきます。
※OpenMQではConnector/J 5.1ではなく、Connector/J 5.0を使用します。5.1をHAストアとして利用すると以下のようになってしまい、ブローカを起動することが出来ません。
com.sun.messaging.jmq.jmsserver.util.BrokerException: [B4221]: ストアからすべての HA クラスタ情報を読み込めませんでした
at com.sun.messaging.jmq.jmsserver.persist.jdbc.BrokerDAOImpl.getAllBrokerInfos(BrokerDAOImpl.java:1077)
at com.sun.messaging.jmq.jmsserver.persist.jdbc.JDBCStore.getAllBrokerInfos(JDBCStore.java:2764)
at com.sun.messaging.jmq.jmsserver.persist.Store.getAllBrokerInfos(Store.java:1212)
at com.sun.messaging.jmq.jmsserver.cluster.ha.HAClusterManagerImpl$HAMap.updateHAMapForState(HAClusterManagerImpl.java:1498)
at com.sun.messaging.jmq.jmsserver.cluster.ha.HAClusterManagerImpl$HAMap.updateHAMap(HAClusterManagerImpl.java:1485)
at com.sun.messaging.jmq.jmsserver.cluster.ha.HAClusterManagerImpl.getKnownBrokers(HAClusterManagerImpl.java:486)
at com.sun.messaging.jmq.jmsserver.cluster.ha.HAClusterManagerImpl.getConfigBrokers(HAClusterManagerImpl.java:511)
at com.sun.messaging.jmq.jmsserver.service.HAMonitorService.<init>(HAMonitorService.java:437)
at com.sun.messaging.jmq.jmsserver.Broker._start(Broker.java:869)
これは以下のConnector/Jの問題のためです。(これが解決すれば利用可能だと思います)
http://bugs.mysql.com/bug.php?id=41484
3.broker1とbroker2を以下のコマンドでスタートし、初期設定ファイルを生成させます。
imqbrokerd -name broker1 -port 7676
imqbrokerd -name broker2 -port 7677
4. MQ_VARHOME/instances/以下のbroker1ディレクトリに、broker2ディレクトリが生成されています。その直下にpropsというディレクトリがありそこにインスタンス用コンフィグファイルが入っています。
これを以下のように編集します。
broker1のconfig.properties
imq.instanceconfig.version=300
imq.brokerid=broker1
imq.cluster.ha=true
imq.cluster.clusterid=mqhamysqlcluster
imq.persist.store=jdbc
imq.persist.jdbc.dbVendor=mysql
imq.persist.jdbc.mysql.property.url=jdbc:mysql://192.168.70.128:3308,192.168.70.128:3307/openmq
imq.persist.jdbc.mysql.user=root
imq.persist.jdbc.mysql.needpassword=false
imq.persist.jdbc.mysql.property.autoReconnect=true
imq.persist.jdbc.mysql.property.failOverReadOnly=false
imq.persist.jdbc.mysql.property.autoReconnectForPools=true
imq.persist.jdbc.mysql.property.maxReconnects=1
imq.persist.jdbc.mysql.property.roundRobinLoadBalance=true
imq.persist.jdbc.mysql.tableoption=ENGINE=NDBCLUSTER
imq.persist.jdbc.connection.limit=15
imq.autocreate.destination.maxBytesPerMsg=-1
imq.autocreate.destination.maxTotalMsgBytes=-1
imq.message.max_size=-1
broker2のconfig.properties
imq.instanceconfig.version=300
imq.brokerid=broker2
imq.cluster.ha=true
imq.cluster.clusterid=mqhamysqlcluster
imq.persist.store=jdbc
imq.persist.jdbc.dbVendor=mysql
imq.persist.jdbc.mysql.property.url=jdbc:mysql://192.168.70.128:3307,192.168.70.128:3308/openmq
imq.persist.jdbc.mysql.user=root
imq.persist.jdbc.mysql.needpassword=false
imq.persist.jdbc.mysql.property.autoReconnect=true
imq.persist.jdbc.mysql.property.failOverReadOnly=false
imq.persist.jdbc.mysql.property.autoReconnectForPools=true
imq.persist.jdbc.mysql.property.maxReconnects=1
imq.persist.jdbc.mysql.property.roundRobinLoadBalance=true
imq.persist.jdbc.mysql.tableoption=ENGINE=NDBCLUSTER
imq.persist.jdbc.connection.limit=15
imq.autocreate.destination.maxBytesPerMsg=-1
imq.autocreate.destination.maxTotalMsgBytes=-1
imq.message.max_size=-1
重要なプロパティについて補足説明したいと思います。
imq.brokerid=broker2
ブローカごとにユニークにする必要があります。
ブローカ名と同じに設定すると良いでしょう。
imq.cluster.ha=true
imq.cluster.clusterid=mqhamysqlcluster
OpenMQをHAクラスタで稼動させるために必須となります。同じクラスタ内に入るブローカは全て同じ値を指定する必要があります。
imq.persist.jdbc.mysql.property.url (Connector/J属性:url)
urlでは接続対象のホスト:ポート番号をカンマ区切りで書きます。
Connector/Jの仕様ではurlに?prop=value&prop=valueという書式が可能ですが、ブローカ設定では使用できないので注意してください(無視されます)。その代わりに、imq.persist.jdbc.mysql.property.+プロパティ名=値と書けばその値を反映することが出来ます。Connector/JをAPサーバなどから使う場合はプロパティ名=値のみを設定すればOKです。
imq.persist.jdbc.mysql.property.autoReconnect=true (Connector/J属性:autoReconnect)
imq.persist.jdbc.mysql.property.failOverReadOnly=false (Connector/J属性:failOverReadOnly)
フェイルオーバのための設定です。特にfailOverReadOnly=falseを忘れるとフェイルオーバ後のMySQLサーバに対して一切更新SQLが使えなくなりますので注意が必要です。
imq.persist.jdbc.mysql.property.autoReconnectForPools=true (Connector/J属性:autoReconnectForPools)
接続プールを使用している場合に追加すべきプロパティです。これがtrueとなっているとプールから取得した接続を使用しSQLを実行する時に、その接続が前回SQLクエリ実行に失敗している場合接続テスト(内部PING)が行われます。pingが返ってこない場合、接続が内部的に更新(リコネクト)されます。
imq.persist.jdbc.mysql.property.maxReconnects=1 (Connector/J属性:maxReconnects)
ホストリストの次のホストにFailOverする前に試行する再接続回数です。
imq.persist.jdbc.mysql.property.roundRobinLoadBalance=true (Connector/J属性:roundRobinLoadBalance)
ホストリストに対してラウンドロビン方式でアクセスを行います。
imq.persist.jdbc.mysql.tableoption=ENGINE=NDBCLUSTER
OpenMQのHAストアテーブルをNDBCLUSTERストレージエンジンでCREATEします。
MySQL ClusterをHAストアに設定する場合にMUSTな定義です。
imq.persist.jdbc.connection.limit=15
OpenMQでは、MySQLにアクセスを行うために内部的に次のクラスを使用しています。
・com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource
ブローカ起動時にここで設定した数の接続が作成され、接続プールに保持されます。
デフォルトは5です。mysqlクライアントのshow processlistコマンドでブローカからいくつの接続が張られているかを確認可能です。
mysql> show processlist;
+-----+-------------+-------------------+--------+---------+------+-----------------------------------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+-----+-------------+-------------------+--------+---------+------+-----------------------------------+------------------+
| 1 | system user | | | Daemon | 0 | Waiting for event from ndbcluster | NULL |
| 243 | root | localhost | NULL | Query | 0 | NULL | show processlist |
| 412 | root | 192.168.70.1:3255 | openmq | Sleep | 54 | | NULL |
| 423 | root | 192.168.70.1:3312 | openmq | Sleep | 64 | | NULL |
| 424 | root | 192.168.70.1:3316 | openmq | Sleep | 59 | | NULL |
| 425 | root | 192.168.70.1:3320 | openmq | Sleep | 54 | | NULL |
| 426 | root | 192.168.70.1:3324 | openmq | Sleep | 48 | | NULL |
| 427 | root | 192.168.70.1:3328 | openmq | Sleep | 43 | | NULL |
| 428 | root | 192.168.70.1:3334 | openmq | Sleep | 38 | | NULL |
| 429 | root | 192.168.70.1:3339 | openmq | Sleep | 32 | | NULL |
| 430 | root | 192.168.70.1:3343 | openmq | Sleep | 27 | | NULL |
| 431 | root | 192.168.70.1:3348 | openmq | Sleep | 22 | | NULL |
| 432 | root | 192.168.70.1:3352 | openmq | Sleep | 16 | | NULL |
| 433 | root | 192.168.70.1:3361 | openmq | Sleep | 11 | | NULL |
| 434 | root | 192.168.70.1:3366 | openmq | Sleep | 6 | | NULL |
| 435 | root | 192.168.70.1:3374 | openmq | Sleep | 0 | | NULL |
+-----+-------------+-------------------+--------+---------+------+-----------------------------------+------------------+
limit - 1の数が用意されます。
5. broker1とbroker2を停止します。初期ユーザIDとパスワードはadmin/adminとなります。
C:\OpenMQ\mq\bin>imqcmd shutdown bkr -b localhost:7676
ユーザ名: admin
パスワード: admin
指定されたブローカの停止中:
---------------------
ホスト プライマリポート
---------------------
localhost 7676
このブローカを停止してもよろしいですか ? (y/n)[n] y
localhost:7676 でのブローカの停止を待機しています...
ブローカの停止に成功しました。
C:\OpenMQ\mq\bin>imqcmd shutdown bkr -b localhost:7677
ユーザ名: admin
パスワード: admin
指定されたブローカの停止中:
---------------------
ホスト プライマリポート
---------------------
localhost 7677
このブローカを停止してもよろしいですか ? (y/n)[n] y
localhost:7677 でのブローカの停止を待機しています...
ブローカの停止に成功しました。
6. broker1とbroker2を起動します。
(broker2の起動ログは省略します)C:\OpenMQ\mq\bin>imqbrokerd -name broker1 -port 7676
[14/4/2009:21:26:03 JST]
==================================================================
Open Message Queue 4.3
Sun Microsystems, Inc.
バージョン: 4.3 (Build 7-g)
コンパイル: Fri 11/07/2008
Copyright (c) 2007 Sun Microsystems, Inc. All rights reserved. Use is
subject to license terms.
==================================================================
Java ランタイム: 1.6.0_12 Sun Microsystems Inc. C:\jdk1.6.0_12\jre
[14/4/2009:21:26:03 JST] IMQ_HOME=C:\OpenMQ\mq
[14/4/2009:21:26:03 JST] IMQ_VARHOME=C:\OpenMQ\var\mq
[14/4/2009:21:26:03 JST] Windows XP 5.1 x86 PSQ96040 (2 cpu) takemura
[14/4/2009:21:26:03 JST] Java ヒープサイズ: 最大=189376k、現在=18176k
[14/4/2009:21:26:03 JST] 引数: -name broker1 -port 7676
[14/4/2009:21:26:03 JST] [B1060]: 持続データの読み込み中...
[14/4/2009:21:26:05 JST] プラグイン持続ストアの使用:
version=410
brokerid=broker1
database connection url=jdbc:mysql://192.168.70.128:3307,192.168.70.128:3308/openmq
database user=root
[14/4/2009:21:26:05 JST] 警告 [B4239]: データベーステーブル MQVER41Cmqhamysqlcluster から持続ストアバージョンを読み込めませんでした:
java.sql.SQLException: [SELECT STORE_VERSION FROM MQVER41Cmqhamysqlcluster]: Table 'openmq.MQVER41Cmqhamysqlcluster' doesn't exist
[14/4/2009:21:26:05 JST] [B1114]: 持続的ストアが新規に作成されます...
[14/4/2009:21:26:05 JST] データベーステーブル MQVER41Cmqhamysqlcluster を作成しています...
[14/4/2009:21:26:06 JST] データベーステーブル MQBKR41Cmqhamysqlcluster を作成しています...
[14/4/2009:21:26:07 JST] データベーステーブル MQSES41Cmqhamysqlcluster を作成しています...
[14/4/2009:21:26:08 JST] データベーステーブル MQPROP41Cmqhamysqlcluster を作成しています...
[14/4/2009:21:26:10 JST] データベーステーブル MQCREC41Cmqhamysqlcluster を作成しています...
[14/4/2009:21:26:11 JST] データベーステーブル MQCON41Cmqhamysqlcluster を作成しています...
[14/4/2009:21:26:13 JST] データベーステーブル MQCONSTATE41Cmqhamysqlcluster を作成しています...
[14/4/2009:21:26:14 JST] データベーステーブルのインデックス MQCONSTATE41CmqhamysqlclusterIDX1 を作成しています...
[14/4/2009:21:26:16 JST] データベーステーブル MQDST41Cmqhamysqlcluster を作成しています...
[14/4/2009:21:26:18 JST] データベーステーブルのインデックス MQDST41CmqhamysqlclusterIDX1 を作成しています...
[14/4/2009:21:26:19 JST] データベーステーブル MQMSG41Cmqhamysqlcluster を作成しています...
[14/4/2009:21:26:21 JST] データベーステーブルのインデックス MQMSG41CmqhamysqlclusterIDX1 を作成しています...
[14/4/2009:21:26:22 JST] データベーステーブル MQTXN41Cmqhamysqlcluster を作成しています...
[14/4/2009:21:26:25 JST] データベーステーブルのインデックス MQTXN41CmqhamysqlclusterIDX1 を作成しています...
[14/4/2009:21:26:29 JST] [B1039]: ブローカ "broker1@PSQ96040:7676" の準備ができました。
初回起動時に関連するテーブルが存在しなければ自動的に作成されます。
MySQLテーブルは以下のようになります。
mysql> show table status;
+-------------------------------+------------+---------
| Name | Engine | Version
+-------------------------------+------------+---------
| MQBKR41Cmqhamysqlcluster | ndbcluster | 10
| MQCON41Cmqhamysqlcluster | ndbcluster | 10
| MQCONSTATE41Cmqhamysqlcluster | ndbcluster | 10
| MQCREC41Cmqhamysqlcluster | ndbcluster | 10
| MQDST41Cmqhamysqlcluster | ndbcluster | 10
| MQMSG41Cmqhamysqlcluster | ndbcluster | 10
| MQPROP41Cmqhamysqlcluster | ndbcluster | 10
| MQSES41Cmqhamysqlcluster | ndbcluster | 10
| MQTXN41Cmqhamysqlcluster | ndbcluster | 10
| MQVER41Cmqhamysqlcluster | ndbcluster | 10
+-------------------------------+------------+---------
7. Broker起動状態の確認 (imqcmd list bkr)
C:\OpenMQ\mq\bin>imqcmd list bkr
ユーザ名: admin
パスワード: admin
次のブローカが所属するクラスタ内のすべてのブローカをリストします:
---------------------
ホスト プライマリポート
---------------------
localhost 7676
クラスタ ID mqhamysqlcluster
クラスタは高可用性です true
--------------------------------------------------------------------------
ブローカの ID 前回からの時間
ブローカ ID アドレス 状態 ストア内のメッセージ 継承を実行しています 状態のタイムスタンプ
--------------------------------------------------------------------------
broker2 PSQ96040:7677 OPERATING 0 5 minutes
broker1 PSQ96040:7676 OPERATING 0 5 minutes
クラスタ内のブローカのリストに成功しました。
8.送受信のテスト
QK2もしくはQBrowserV2などでメッセージの送受信テストを行います。
Myはうすオリジナルツールの使い方は以下をご覧ください。
・送受信のテスト
・QBrowserV2(QK2の後継ツール)
QBrowserV2を使ってテストしてみます。接続先のポート番号を変更する場合は.batファイルの最後の部分を変更してください。
新規メッセージ→新規メッセージ作成を選択します。

宛先名をTESTにし、Message bodyに「broker1へ送ったメッセージ」と入力し送信ボタンを押します。

broker1(localhost:7676)にメッセージが入っていることが確認できます。

もう1つQBrowserV2を起動してbroker2(localhost:7677)に接続します。QUEUE名にTESTと入力してBrowseボタンを押しても何も出てこないことを確認します。[宛先コマンド]→[全宛先情報リスト]またはimqcmd list dst -b localhost:7677 -t q -n TESTコマンドを実行してキューの中を見てみます。

0になっていることが確認できます。ここで、broker1を強制シャットダウンし情報をbroker2に引き継がせてみたいと思います。broker1をシャットダウンするとすぐにbroker2がダウンを検知します。
[15/4/2009:22:23:56 JST] [B1214]: ブローカ broker1 が応答しません。継承を試みています
[15/4/2009:22:23:56 JST] brokerStatusChanged broker1:
BrokerStatus[LINK_DOWN:IN_DOUBT:UP]
BrokerStatus[LINK_DOWN:NOT_INDOUBT:DOWN]
null
[15/4/2009:22:23:56 JST] [B1183]: Unsuspect broker1
[15/4/2009:22:23:56 JST] [B1184]: Unsuspected /192.168.11.9:7676 [brokerID=broker1, brokerSession=8722663960158640128] (seq#=0, ts=1239774656921, i
[15/4/2009:22:23:56 JST] [B1186]: broker1 へのハートビートを停止します
[15/4/2009:22:23:56 JST] [B1182]: ハートビートエンドポイント /192.168.11.9:7676 [brokerID=broker1, brokerSession=8722663960158640128] (seq#=0, ts=1
[15/4/2009:22:23:56 JST] [B1216]: ブローカ broker1 のストアの継承を試みています
[15/4/2009:22:23:56 JST] [B1175]: このブローカはブローカ [brokerID=broker1, storeSession=8722663960158640128] を継承しようとしています
[15/4/2009:22:23:56 JST] [B1168]: 障害の発生したブローカ broker1 の継承ロックが取得されました (タイムスタンプ: 1239774656921)
[15/4/2009:22:23:56 JST] state = FAILOVER_PENDING broker1
[15/4/2009:22:23:56 JST] state = FAILOVER_STARTED broker1
[15/4/2009:22:23:57 JST] ブローカ broker1 から 0 個のローカル送信先を継承中です: []
[15/4/2009:22:23:57 JST] ブローカ broker1 から 1 個のメッセージを継承中です
[15/4/2009:22:23:57 JST] ブローカ broker1 から 0 個のトランザクションを継承中です
[15/4/2009:22:23:57 JST] ブローカ broker1 から 0 個のリモートトランザクションを継承中です
[15/4/2009:22:23:57 JST] ブローカ broker1 から 3 個のストアセッションを継承中です: [7125574942207197952, 8722663960158640128, 2586509467726251264]
[15/4/2009:22:23:57 JST] state = FAILOVER_COMPLETE broker1
[15/4/2009:22:23:57 JST] [B1217]: ブローカ broker1 の継承が成功しました。そのブローカが所有するデータと状態情報を処理しています
[15/4/2009:22:23:57 JST] [B1218]: ブローカ broker1 からの 0 トランザクションを処理しています
[15/4/2009:22:23:57 JST] [B1297]: ブローカ broker1 から 0 個のリモートトランザクションを処理しています
[15/4/2009:22:23:57 JST] monitoring 0 transactions
[15/4/2009:22:23:57 JST] [B1283]: 継承後に 0 のトランザクションを処理しています
[15/4/2009:22:23:57 JST] [B1221]: ブローカ broker1 からの 1 メッセージを処理しています
[15/4/2009:22:23:57 JST] Loading message 23-192.168.11.9(ff:e4:53:af:d7:4f)-2513-1239774044828 on Q:TEST
[15/4/2009:22:23:57 JST] attempting to set Message Bytes Limit to 10485760K for destination TEST [キュー]
[15/4/2009:22:23:57 JST] attempting to set Message Count Limit to 100000 for destination TEST [キュー]
[15/4/2009:22:23:57 JST] attempting to set Message Size Limit to 10240K for destination TEST [キュー]
[15/4/2009:22:23:57 JST] Loading Stored destination TEST [キュー] connectionUID=null
[15/4/2009:22:23:57 JST] [B1151]: 1 メッセージとともに送信先 TEST [キュー] を読み込み
[15/4/2009:22:23:57 JST] [B1152]: 送信先 TEST [キュー] の読み込み完了
[15/4/2009:22:23:57 JST] [B1196]: 送信先 TEST [キュー] で 1 メッセージを処理します
[15/4/2009:22:23:57 JST] Message 23-192.168.11.9(ff:e4:53:af:d7:4f)-2513-1239774044828 has 1 consumers
[15/4/2009:22:23:57 JST] loadTakeoverMsgs: lookup 23-192.168.11.9(ff:e4:53:af:d7:4f)-2513-1239774044828:[consumer:0, type=NONE] found tid=null
[15/4/2009:22:23:57 JST] non-transacted: Routing Message 23-192.168.11.9(ff:e4:53:af:d7:4f)-2513-1239774044828 to 1 consumers:[consumer:0, type=NON
[15/4/2009:22:23:57 JST] [B1223]: broker1 の継承処理が完了しました
[15/4/2009:22:23:57 JST] [B1177]: ブローカ [brokerID=broker1, storeSession=8722663960158640128] のブロードキャスト継承が完了しました...
[15/4/2009:22:23:57 JST] Processing G_TAKEOVER_COMPLETE(41)
broker2のTESTキューを見てみるときちんとメッセージが継承されて来ているのがわかります。

上記はあくまでメッセージの継承動作の説明のためのものです。broker1をわざわざdownさせてbroker2へ継承させずともJMSクライアントは普通にbroker1のメッセージをbroker2からレシーブすることが可能です。以下のようなプログラムを作成して試してみます。プログラムを実行する前にbroker1(localhost:7676)に対してメッセージを送っておきます。(broker1,broker2共に生きている状態です)
com.sun.messaging.ConnectionFactory conFactory = new com.sun.messaging.ConnectionFactory();
conFactory.setProperty(ConnectionConfiguration.imqAddressList, "localhost:7677");
javax.jms.QueueConnection tcon = conFactory.createQueueConnection("admin","admin");
tsession = tcon.createQueueSession(false,QueueSession.AUTO_ACKNOWLEDGE);
tcon.start();
javax.jms.Queue queue = tsession.createQueue("TEST");
MessageConsumer receiver = tsession.createConsumer(queue);
TextMessage tmessage = (TextMessage)receiver.receive();
receiver.close();
tsession.close();
tcon.close();
[結果]
broker2へ接続してもbroker1のメッセージが取得できました。
broker1に送ったメッセージですよ。
9. MySQL ClusterのSQLノードがダウンした場合
SQLノードは2重化してありますので、MQの稼動に問題は生じません。(Connector/Jのフェイルオーバ機能で活動中のSQLノードに自動でコネクトされます。フェイルオーバ中にアクセスしてきたJMSクライアントには例外が返されます。これはアプリ側で処理する必要があります。設定によってフェイルオーバ完了までに時間の差が生じますので必要に応じて調整を行ってください。以下のメッセージがブローカログに出てくればSQLノードのフェイルオーバが完了しています。
jdbc:mysql://192.168.70.128:3308,192.168.70.128:3307/openmq へのデータベース接続が再確立されました。
MySQL ClusterやOpen MQに関するご相談はSUN-PSまでお寄せください。設計、構築、アプリ開発、技術支援などに既に多数のお客様よりご好評いただいております。MySQLメニューについてはこちらをご覧ください。
土曜日 4 11, 2009
こんにちは。今回はMyISAMでのCOUNT()関数について書いてみたいと思います。
COUNT()は皆さんご存知のように、テーブル中の行数を数える関数です。
MyISAMではテーブル全件を数えるCOUNT(*)関数の実行が非常に速いというのが巷の噂ですが、実際のところはどうなのでしょうか?ちょっと見てみたいと思います。
まずは比較のため、innodbのテーブルでカウントしてみたいと思います。
少しは件数の入ったテーブルが良いので、MySQLのサンプルとして提供されている
WorldデータベースのCityテーブルを使ってみました。
[InnoDB結果]
全件
mysql> alter table city engine=innodb;
Query OK, 4079 rows affected (0.27 sec)
Records: 4079 Duplicates: 0 Warnings: 0
mysql> flush tables;
Query OK, 0 rows affected (0.00 sec)
mysql> select count(*) from city;
+----------+
| count(*) |
+----------+
| 4079 |
+----------+
1 row in set (0.02 sec)
mysql> show profile;
+--------------------+----------+
| Status | Duration |
+--------------------+----------+
| starting | 0.000106 |
| Opening tables | 0.001007 |
| System lock | 0.000009 |
| Table lock | 0.000016 |
| init | 0.000024 |
| optimizing | 0.000010 |
| statistics | 0.000018 |
| preparing | 0.000016 |
| executing | 0.000012 |
| Sending data | 0.005298 |
| end | 0.000022 |
| query end | 0.000009 |
| freeing items | 0.000336 |
| logging slow query | 0.000009 |
| cleaning up | 0.000009 |
+--------------------+----------+
WHERE句付き
mysql> flush tables;
Query OK, 0 rows affected (0.00 sec)
mysql> select count(*) from city where name = 'tokyo'
+----------+
| count(*) |
+----------+
| 1 |
+----------+
1 row in set (0.00 sec)
mysql> show profile;
+--------------------+----------+
| Status | Duration |
+--------------------+----------+
| starting | 0.000127 |
| Opening tables | 0.001020 |
| System lock | 0.000009 |
| Table lock | 0.000017 |
| init | 0.000052 |
| optimizing | 0.000022 |
| statistics | 0.000021 |
| preparing | 0.000022 |
| executing | 0.000011 |
| Sending data | 0.006663 |
| end | 0.000009 |
| query end | 0.000007 |
| freeing items | 0.000061 |
| logging slow query | 0.000007 |
| cleaning up | 0.000008 |
+--------------------+----------+
続けて、MyISAMテーブルでの結果です。
[MyISAM結果]
全件
mysql> flush tables;
Query OK, 0 rows affected (0.00 sec)
mysql> select count(*) from city;
+----------+
| count(*) |
+----------+
| 4079 |
+----------+
1 row in set (0.00 sec)
mysql> show profile;
+--------------------+----------+
| Status | Duration |
+--------------------+----------+
| starting | 0.000106 |
| Opening tables | 0.002326 |
| System lock | 0.000011 |
| Table lock | 0.000016 |
| init | 0.000023 |
| optimizing | 0.000012 |
| executing | 0.000021 |
| end | 0.000010 |
| query end | 0.000008 |
| freeing items | 0.000046 |
| logging slow query | 0.000007 |
| cleaning up | 0.000007 |
+--------------------+----------+
WHERE句付き
mysql> flush tables;
Query OK, 0 rows affected (0.05 sec)
mysql> select count(*) from city where name = 'tokyo';
+----------+
| count(*) |
+----------+
| 1 |
+----------+
1 row in set (0.02 sec)
mysql> show profile;
+--------------------+----------+
| Status | Duration |
+--------------------+----------+
| starting | 0.000131 |
| Opening tables | 0.002439 |
| System lock | 0.000010 |
| Table lock | 0.000017 |
| init | 0.000051 |
| optimizing | 0.000021 |
| statistics | 0.000022 |
| preparing | 0.000024 |
| executing | 0.000011 |
| Sending data | 0.003237 |
| end | 0.000011 |
| query end | 0.000009 |
| freeing items | 0.000094 |
| logging slow query | 0.000009 |
| cleaning up | 0.000012 |
+--------------------+----------+
まだまだ行数が少なかったようで、体感レスポンスタイムはあまり変わりませんでした。
しかし、プロファイリングしてみると中での動きは全然違うことが分かります。
・結果を出すためのステップがMyISAM-全件ではかなり省略されている
・MyISAM-全件では、特にSending dataのステップがない
以上のことから皆さんも「MyISAMの全件カウントは何か特別なことをしているな~」と感じられるのではないでしょうか。
実はMyISAMでは、テーブル中に現在何件の行があるかの情報をインデックスファイル(MYIファイル)に持っています。そして、MyISAMのテーブルで全件のCOUNT()が来たらMySQLサーバはデータファイルを一切読まず、計算などすることもなくインデックスファイルにある行数を返す仕様になっています。MyISAMテーブルに対するINSERTやDELETEが行われるたびに、この行数カウントが増減するようになっています。なお、InnoDBは構造上このようなフィールドを持つことができないため、件数カウントに時間がかかる仕様となっています(MVCC等のためです。詳しい理由は別の機会に・・・)
もうちょっと細かく見てみましょう。MYIファイルの中身はおおよそ以下のようになっています。
HEADER
ステート情報
ベース情報(主にデータ長定義)
キー定義情報
..
レコード情報
..
KEY
キーセグメント情報(キー長さ、ポインタ情報)
..
さらに、[ステート情報]の部分を細かく見てみると以下のようになっています。
ヘッダ:24バイト(1-24)
オープンカウント : 2バイト(25-26)
ステート変更フラグ:1バイト(27)
ソートキー(不使用):1バイト(28)
レコード数:8バイト(29-36)
削除レコード数:8バイト(37-43)
<以下略>
MyISAMでは全件のCOUNT()関数が来るとMYIファイルの[現在の行数]の部分からデータを読みこんで結果を返します。ちょっと実験してみましょう。
MYIファイルの件数をみてCOUNT()関数が結果を返すならば、そこを変えるとテーブルデータはそのままでも結果件数は変わるはずです(不整合となりますが・・・)
何もしないと4079行という結果が返ってきます。
mysql> select count(*) from city;
+----------+
| count(*) |
+----------+
| 4079 |
+----------+
1 row in set (0.01 sec)
ここで、MYIファイルをバイナリエディタでちょっといじってみます。編集するのは、city.MYIという名前のファイルです。下のスクリーンショットのちょうどカーソルがあたっている部分が該当する部分となります。現在値は0FEF(4079)なので、これを適当な数字に変更してみます。

[変更後]
150FEFに変更しました。

さて、COUNT(*)の結果はどうなっているでしょうか。
mysql> select count(*) from city;
+----------+
| count(*) |
+----------+
| 1380335 |
+----------+
1 row in set (0.00 sec)
1380335(150FEF)に変更されています。
今回は実験目的で実施しましたが、通常バイナリを直接編集する機会はあまりないと思います。
もしも、今回のようにMYI(インデックス)の内容とMYD(データ)に差異が出てしまった場合は以下のようにして調査、修復することが可能です。
mysql> check table city;
+------------+-------+----------+----------------------------+
| Table | Op | Msg_type | Msg_text |
+------------+-------+----------+----------------------------+
| world.city | check | error | Found 4079 keys of 1380335 |
| world.city | check | error | Corrupt |
+------------+-------+----------+----------------------------+
2 rows in set (0.00 sec)
上記のようにCHECK TABLE文(オンライン用)または、myisamchkユーティリティ(オフライン用)を使ってファイルの状態を検査します。今回はMYIの件数が間違っているので、きちんと1380335ではなく実数は4079であると警告されています。
このように問題を発見した場合、REPAIR TABLE文(オンライン用)もしくはmyisamchkユーティリティ(オフライン用)で修復しましょう。
mysql> repair table city;
+------------+--------+----------+---------------------------------------------+
| Table | Op | Msg_type | Msg_text |
+------------+--------+----------+---------------------------------------------+
| world.city | repair | warning | Number of rows changed from 1380335 to 4079 |
| world.city | repair | status | OK |
+------------+--------+----------+---------------------------------------------+
2 rows in set (0.03 sec)
mysql> select count(*) from city;
+----------+
| count(*) |
+----------+
| 4079 |
+----------+
1 row in set (0.00 sec)
これで、もとの行数に戻りました。MYIファイルが消失してしまった場合でもMYDからREPAIR TABLEコマンドでMYIが復活できますので便利です(消失したときはUSE_FRMオプションをつけてください)。
注:DISTINCTやWHEREを使うとMYIから件数を持って来なくなります(それはそうですね)。
日曜日 4 05, 2009
こんにちは。今回はMyISAMストレージエンジンの並列インサート機能(Concurrent Insert)についてちょっと書いてみたいと思います。
MyISAMは皆さんご存知のように、テーブルレベルのロックのみで行レベルでのロックがサポートされていません。MySQLではSELECTコマンドの実行時に暗黙的リードロック(Shared Read lock)がかかるようになっており、一般的にはこのロックが対象のテーブルに存在している場合には、書き込み処理(INSERT,UPDATE,DELETE,ALTER TABLEなど)がロック待ちとなります。SELECTの実行が完了してからINSERTなどが実行される形になります。
MyISAMはテーブルロックなので、SELECTの対象としている行以外への更新も同様に待たされてしまい、同時処理性能に影響を与えているであろうことは容易に想像できると思います。
MyISAMには、コンカレントインサート(Concurrent Insert)と呼ばれる機能があります。コンカレントインサートとは、他のコネクション(スレッド)がSELECT文を発行もしくはLOCK文を実行している時であってもコンカレントインサート処理が対象のテーブルに対して書き込みロックを獲得できる(=Shared Read lockがかかっている状態のテーブルにINSERTできる)機能です。なおSELECTのShared Read lock要求は既にターゲットのテーブルにコンカレントインサートが書き込みロックをかけていても通るようになっています。
要するに、1つまたは複数のSELECTと1つのINSERTを同時実行できるようにする機能です。
コンカレントインサートが使用可能になるLOCKコマンドは以下となります。
LOCK TABLE テーブル名 READ LOCAL;
(LOCALをつけないとコンカレントインサートは利用不可になります。)
[設定方法]
グローバルサーバ変数:concurrent_insertを設定します。
0 : コンカレントインサートを使用しません。(使用できる状況でも使用しない)
1 : 対象のMyISAMテーブルに「穴」がない場合に限り(最後尾にインサートできる場合)、コンカレントインサートを実施します。
2 : 対象のMyISAMテーブルに「穴」があっても、レコードを強制的に最後尾にインサートし、コンカレントインサートを実施します。
ここで言う「穴」とは、ストレージのレコードシーケンスの途中に「穴」がある場合のことを言います。
MyISAMでは、一度DELETEしたデータは物理的には消去されず削除フラグを立てるだけとなっています。INSERTのみで作られたテーブルデータには「穴」はありませんが一度最後尾以外の行をDELETEすると削除フラグが立ちそこが「穴」となります。MyISAMは次回インサート時になるべくその「穴」の場所にデータを挿入しようとします。
concurrent_insert=2の場合は、対象のテーブルに既にリードロックがかけられていた場合のみ「穴」ではなく最後尾へデータを挿入します(穴はそのまま)。テーブルに何もロックがない場合は「穴」の場所にデータが挿入されます。concurrent_insert=1に比べて領域の使用効率が悪くはなります。なお、「穴」は次のコマンドで物理的になくすことが出来ます。
mysql> optimize table test.locktest;
+---------------+----------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+---------------+----------+----------+----------+
| test.locktest | optimize | status | OK |
+---------------+----------+----------+----------+
1 row in set (0.00 sec)
[設定コマンド例]
set global concurrent_insert=0;
set global concurrent_insert=1;
set global concurrent_insert=2;
※デフォルトは1となっています。
グローバルサーバ変数なので、次回コネクション作成時から有効になります。
または、my.cnfなどの設定ファイルに書いておくと良いでしょう。
こう書いてみると常時1か2に設定したくなるような機能ですが実際のところはどうなのでしょうか。
ソース分析後に手元の環境で簡単なベンチマークをしてみました。
まずは、sysbenchでのテスト結果です。
コンカレントインサート有の場合(強制使用)
concurrent_insert=2;
OLTP test statistics:
queries performed:
read: 1400
write: 500
other: 0
total: 1900
transactions: 100 (473.84 per sec.)
deadlocks: 0 (0.00 per sec.)
read/write requests: 1900 (9002.99 per sec.)
other operations: 0 (0.00 per sec.)
Test execution summary:
total time: 0.2110s
total number of events: 100
total time taken by event execution: 9.3607
per-request statistics:
min: 35.74ms
avg: 93.61ms
max: 155.12ms
approx. 95 percentile: 140.13ms
Threads fairness:
events (avg/stddev): 1.0000/0.40
execution time (avg/stddev): 0.0936/0.04
コンカレントインサート無しの場合
concurrent_insert=0;
OLTP test statistics:
queries performed:
read: 1400
write: 500
other: 0
total: 1900
transactions: 100 (532.79 per sec.)
deadlocks: 0 (0.00 per sec.)
read/write requests: 1900 (10122.93 per sec.)
other operations: 0 (0.00 per sec.)
Test execution summary:
total time: 0.1877s
total number of events: 100
total time taken by event execution: 7.4302
per-request statistics:
min: 34.88ms
avg: 74.30ms
max: 152.66ms
approx. 95 percentile: 128.75ms
Threads fairness:
events (avg/stddev): 1.0000/0.76
execution time (avg/stddev): 0.0743/0.05
コンカレントインサート無しの方が若干ですがスループットが良いという結果となりました。
今回たまたまというわけではなく、ほぼ毎回このような結果となります。
実行時間の短いSELECTとINSERTは比較的効率的に処理されているためと推測されます。
逆にコンカレントインサートのオーバヘッドが目立つ形になったといったところでしょうか。
(sysbenchでは、INSERT,DELETE,UPDATEがミックスされてしまうので、INSERT以外の文が発生させるロックが多少結果に影響を与えると考えられます。念のためにmysqlslapでSELECTとINSERTのみのベンチを実施し結果は同様になるのを確認しました。詳細はここでは割愛します)
MyISAMの処理フローから逆算すると、コンカレントインサート機能が真価を発揮するのは実行時間の長いSELECT文が頻繁に流れているような状況下と仮定することができます。
そのような状況をシミュレートしてベンチマークテストを実施してみました。
今回は複雑なシナリオなので、JMeterを使ってみました。(SuperSmackなどでも良いと思います)
以下のスレッドを同時実行して性能を見るシナリオです。
・SELECT文(短い)とINSERT文をたくさん実行するスレッド(複数)
・長いSELECT文(ランダムで最大約1秒リードロックを保持する)を何度も実行するスレッド(1つ)
こんな感じです。

実行時間の長いSELECTはそれだけ長くリードロックを保持するので、コンカレントではないインサートは待ちとなってしまいます。
結果を見てみましょう。
concurrent_insert=2;
コンカレントインサート強制使用

テーブルロックの状況
mysql> show status like 'Table%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Table_locks_immediate | 1185 |
| Table_locks_waited | 20 |
+-----------------------+-------+
2 rows in set (0.00 sec)
コンカレントインサートを使用しない
concurrent_insert=0;

テーブルロックの状況
mysql> show status like 'Table%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Table_locks_immediate | 865 |
| Table_locks_waited | 390 |
+-----------------------+-------+
2 rows in set (0.01 sec)
コンカレントインサート使用時はINSERTのスループットは113.2、不使用時は66.6となっています。(SELECT REQ/SELECT REQ2はINSERTと同じスレッドなのでINSERTのスループットに引っ張られます)
※並列スレッドを100程度に絞り込んでいるのと、スレッド間での処理のずれを作り出すためにランダムでの待ち時間を入れているため絶対的なスループットは少なめとなっています。
長いSELECTが稼動していると、コンカレントインサートなしでは(当然ですが)ロック待ちになるケースが増えています。このロック待ちがスループットを落とす原因となっています。逆にコンカレントインサートを使っているとほとんどロック待ちが出ないのが観測されました。
結論としては、よく現在のMySQLの使われ方を分析した上でコンカレントインサート機能の使用/不使用を決定した方が良いでしょう。FITするケースにはパフォーマンスチューニングの一手段として使っても良いと思います。場合によってはOFFにすることも有効だと考えられます。
金曜日 3 20, 2009
今日は、最近導入が増えてきたMySQLのレプリケーション関連の記事を書いてみようと思います。
ご存知のようにMySQL5.1では以下の2種類+1のレプリケーションフォーマットを使用することが可能です。
・Statement-based-replication (SBR)
・Row-based-replication (RBR) ※5.1.5新機能
・Mixed ※5.1.8新機能
マスターからスレイブに対してどのようなフォーマットのバイナリログを送信するかの設定です。
SBRはSQLステートメントベースのイベントがログに書き込まれます。対してRBRは変更対象行自体をログに書き込んでスレイブに送り込む形式となります。
Non-Deterministic(実行するごとに異なる結果が出る)な関数はステートメントベースでは使用不能(Unsafe)であるというのが一般論だと思いますが、MySQL5.1では一部例外があります。Non-Deterministicなものについては全部RBRで対応すれば良いと思われるかもしれませんが、諸般の事情でSBRでやりたいケースはあると思います。
以下2つの関数について取り上げてみたいと思います。
・RAND()
・NOW()
RAND関数は、0以上1より小さい乱数を生成する関数です。実行結果はNon-Deterministicであり、実行ごとに結果が変化します。ステートメントベースでバイナリログにRAND()を書いてしまうと、スレーブにこのSQL文が送られリプレイされた時にマスターでの結果とは違う数字を生成してしまうと危惧される方もおられるのではないでしょうか。
実はMySQLではバイナリログ部分でRAND関数を扱うための特別なロジックを搭載しており、SBRでもRBRでもマスターの実行結果とスレイブの実行結果が同じになるように設計されています。また、バイナリログを使用したロールフォワードでも結果が同じになります。
ステートメントベースレプリケーションで、RAND()を使ってみます。
MASTER:[3306]> set binlog_format='STATEMENT';
Query OK, 0 rows affected (0.00 sec)
MASTER:[3306]> insert into test values(rand());
Query OK, 1 row affected (0.09 sec)
マスターでの結果
MASTER:[3306]> select * from test;
+------------------+
| a |
+------------------+
| 0.34047383287966 |
+------------------+
1 row in set (0.00 sec)
スレイブでの結果
SLAVE:[3307]> select * from test;
+------------------+
| a |
+------------------+
| 0.34047383287966 |
+------------------+
1 row in set (0.00 sec)
全く同じ結果ですね。結論を先に書いてしまうと、RAND()関数はSBRで安全です。
ソースの該当部分を見てみると理由が分かります。RAND()が呼び出された時に、シード値をスレッドクラスに保存しておくようになっています。
if (!thd->rand_used)
{
thd->rand_used= 1;
thd->rand_saved_seed1= thd->rand.seed1;
thd->rand_saved_seed2= thd->rand.seed2;
}
rand= &thd->rand;
バイナリログの書き込みタイミングで、先ほど保存しておいたシード値を本体SQLに先立って書き込んでいます。
if (thd->rand_used)
{
Rand_log_event e(thd,thd->rand_saved_seed1,thd->rand_saved_seed2);
if (e.write(file))
goto err;
}
バイナリログに2つのシード値が保存されているのが確認できます。
# at 951
#090319 21:48:45 server id 1 end_log_pos 986 Rand
SET @@RAND_SEED1=934874093, @@RAND_SEED2=782184184/*!*/;
# at 986
#090319 21:48:45 server id 1 end_log_pos 1080 Query thread_id=3 exec_time=0
SET TIMESTAMP=1237466925/*!*/;
insert into test values(rand())
/*!*/;
# at 1080
#090319 21:48:45 server id 1 end_log_pos 1107 Xid = 36
COMMIT/*!*/;
DELIMITER ;
# End of log file
スレイブがバイナリログ(リレーログ)からログイベントを読み込み、適用する時に保存されていたSEED1とSEED2がスレッド変数に再格納されます。スレイブはこのシードを使って乱数を生成するので結果が同じになります。
NOW()関数も同様となります。NOW()は現在のdate/timeを返す関数ですが、同じくNon-Deterministicなのでマスターとスレイブ間で差異が出そうな気がします。しかしRAND()同様に特別な処理が入っているのでSBRで安全な関数となっています。
ソースの該当箇所は以下となります。
/*
Timestamp on the master(for debugging and replication of
NOW()/TIMESTAMP). It is important for queries and LOAD DATA
INFILE. This is set at the event's creation time, except for Query
and Load (et al.) events where this is set at the query's
execution time, which guarantees good replication (otherwise, we
could have a query and its event with different timestamps).
*/
time_t when;
Log_event::Log_event(THD* thd_arg, uint16 flags_arg, bool using_trans)
:log_pos(0), temp_buf(0), exec_time(0), flags(flags_arg), thd(thd_arg)
{
server_id= thd->server_id;
when= thd->start_time;
cache_stmt= using_trans;
}
クエリとLoadステートメントの場合は、ステートメントの実行時のタイムスタンプが入ります。
ここで保存されたタイムスタンプはバイナリログにSET TIMESTAMP=xxxxxxという形で書き込まれます。
SET TIMESTAMP=1237087230/*!*/;
SET @@session.time_zone='SYSTEM'/*!*/;
insert into test values(now())
以下のSQLで実験してみましょう。(マスターとスレイブでNOW()の実行タイミングに差が出るように、スレイブのIO_THREADを一時停止しました)
MASTER:[3306]> set binlog_format='STATEMENT';
Query OK, 0 rows affected (0.00 sec)
MASTER:[3306]> insert into test values(now());
Query OK, 1 row affected (0.08 sec)
マスター結果
MASTER:[3306]> select * from test;
+---------------------+
| a |
+---------------------+
| 2009-03-19 22:31:14 |
+---------------------+
1 row in set (0.00 sec)
スレイブ結果
SLAVE:[3307]> select * from test;
+---------------------+
| a |
+---------------------+
| 2009-03-19 22:31:14 |
+---------------------+
1 row in set (0.00 sec)
同じ結果がテーブルに格納されました。
[注意点]
SBRでは、RAND()やNOW()と違って以下の関数は安全ではないので注意が必要です。
LOAD_FILE()
UUID()
UUID_SHORT()
SYSDATE()
FOUND_ROWS()
ROW_COUNT()
USER()
CURRENT_USER()
これらを処理するための特別なロジックが実装されていないためですが、
レプリケーションフォーマットをMIXEDに設定することで、いくつかの関数は安全に実行することができるようになります。MIXEDモードではこれらの関数を実行するときにレプリケーションフォーマット内部的にRBRへ変更します。(MySQL5.1.8からの機能)
UUID()
UUID_SHORT()
SYSDATE() 注1
FOUND_ROWS() 注2
ROW_COUNT() 注2
USER() 注2
CURRENT_USER() 注2
注1: NOW()のエイリアスとするオプションも使用可能
注2: MySQL 5.1.24から
関数以外にも、例えばSELECTをORDER BY句なしでLIMITと一緒に使った場合などにもMIXEDに指定していれば自動でRBRに変換してくれます。
噂のUUID()で実験してみましょう。
MASTER:[3306]> set binlog_format='MIXED';
Query OK, 0 rows affected (0.00 sec)
MASTER:[3306]> insert into uuidtest values(uuid());
Query OK, 1 row affected (0.09 sec)
マスター結果
MASTER:[3306]> select * from uuidtest;
+--------------------------------------+
| a |
+--------------------------------------+
| ab19b40b-1457-11de-8726-c70ad9252c6d |
+--------------------------------------+
1 row in set (0.00 sec)
スレイブ結果
SLAVE:[3307]> select * from uuidtest;
+--------------------------------------+
| a |
+--------------------------------------+
| ab19b40b-1457-11de-8726-c70ad9252c6d |
+--------------------------------------+
1 row in set (0.02 sec)
結果が同じになりました。
ついでなので、変換する部分のソースをチェックしてみます。
例えばUUID()は、以下のように作成時にSBRではUnSafeであるとマークしています。
Item*
Create_func_uuid::create(THD *thd)
{
thd->lex->set_stmt_unsafe();
thd->lex->safe_to_cache_query= 0;
return new (thd->mem_root) Item_func_uuid();
}
UnSafeであるとマークされたステートメントは、後でログフォーマットチェックロジックの中で、
MIXED設定の場合にRBRに変換されます。
if (thd->lex->is_stmt_unsafe() ||
(flags_all_set & HA_BINLOG_STMT_CAPABLE) == 0)
{
thd->set_current_stmt_binlog_row_based_if_mixed();
}
また、5.1.20からは「安全ではない」関数が使われるシチュエーションが発生した場合
(BINLOG_FORMAT='STATEMENT'とされているなど)、ログに警告が表示されるようになりました。
090319 16:35:35 [Warning] Statement is not safe to log in statement format. Stat
ement: insert into uuidtest values(uuid())
[オマケ]
MySQLのバージョンによる、binlog_formatのデフォルトは以下です。
5.1.5 - 5.1.7 STATEMENT
5.1.8 - 5.1.11 STATEMENT
5.1.12 - 5.1.28 MIXED
5.1.29 - STATEMENT
土曜日 2 21, 2009
DB自体の話が最近多くなってきたので、アプリ層のことも少し書いてみようと思います。
今回はプログラミング言語RubyからMySQLにアクセスするベーシックなプログラムを作ってみようと思います。最低限の要素が入っているようなものを作成したいと思います。
1. Rubyのインストール
今回はお手軽版ということで、以下を使ってみました。
One-Click Ruby Installer 186-26 Final Release
ダウンロードはこちらからできます
Rubyの既成バイナリは色々な種類があるのですが、その中でも1クリックでインストール出来てしまうと評判のものを今回使っています。ウィザード付のバイナリです。
インストール先にc:\rubyを指定しました。

2.MySQL/Rubyのインストール
Rubyのインストールが完了したら、MySQL-gemをインストールします。
以下のコマンドをc:\ruby\binから発行します。
gem install mysql -v 2.7.3
C:\ruby\bin>gem install mysql -v 2.7.3
Select which gem to install for your platform (i386-mswin32)
1. mysql 2.7.3 (mswin32)
2. Skip this gem
3. Cancel installation
> 1
Successfully installed mysql-2.7.3-mswin32
Installing ri documentation for mysql-2.7.3-mswin32...
Installing RDoc documentation for mysql-2.7.3-mswin32...
While generating documentation for mysql-2.7.3-mswin32
... MESSAGE: Unhandled special: Special: type=17, text="<!--
... RDOC args: --op C:/ruby/lib/ruby/gems/1.8/doc/mysql-2.7.3-
(continuing with the rest of the installation)
One-Click Ruby Installerはウィザードだけではなく、色々なWindows用バイナリも既にインストールされています。
gem listで、ローカルgemを確認すると良いと思います。
3. MySQLのbinフォルダを環境変数PATHに通しておきます。
別にインストールしてあるMySQLのbinをパスに通しておきます。c:\mysql5.1\binなどになります。
4. MySQLのテーブルを作っておきます。
MySQLサーバが起動していなければ起動します。
rubyという名前のデータベースをutf8キャラクタセット指定で作成します。mysqlクライアントプログラムを使用します。
C:\MySQLComm5.1.31\bin>mysql -uroot -p
create database ruby character set utf8
use ruby
次に以下のようなitemテーブルを作成します。
CREATE TABLE item (
id int NOT NULL,
name varchar(100) DEFAULT NULL,
price decimal(10,0) DEFAULT NULL,
times timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id));
テーブルには適当にデータを入力しておきます。今回はこんな感じに入れてみました。
*************************** 1. row ***************************
id: 1
name: 南イタリア産ブラッドオレンジ
price: 350
times: 2009-02-20 23:00:54
*************************** 2. row ***************************
id: 2
name: 奥久慈完熟りんご
price: 250
times: 2009-02-20 23:01:18
*************************** 3. row ***************************
id: 3
name: 沖縄産有機パイナップル
price: 500
times: 2009-02-20 23:01:38
3 rows in set (0.00 sec)
5. 適当なフォルダにmysql_app.rbという名前のテキストファイルを作成します。
6. mysql_app.rbファイルを編集します。
require "mysql"
dbname = "ruby"
m = Mysql.init()
m.options(Mysql::SET_CHARSET_NAME, "cp932")
m.real_connect("localhost", "root", "adminadmin", dbname)
result = m.query("SELECT name,price FROM item")
while (row = result.fetch_row) != nil do
puts row.at(0) + "は" + row.at(1) + "円ですよ"
end
m.close
今回はデータに日本語が含まれているので、Windows上のコマンドラインで表示させるためAPIのキャラクタセットをcp932(Windows-SJS)に指定しています。C APIのmysql_optionsと同じ機能です(名前は違いますが)。
あとは、コネクトしてクエリを投げた後にリザルトセットをプリントしてあげます。
5.コマンドラインでrubyアプリを実行してみます。
C:\ruby\bin>ruby c:\rubydev\mysql_app.rb
結果
C:\ruby\bin>ruby c:\rubydev\mysql_app.rb
南イタリア産ブラッドオレンジは350円ですよ
奥久慈完熟りんごは250円ですよ
沖縄産有機パイナップルは500円ですよ
Rubyはインタプリタなのでめんどくさいコンパイルが必要ありません。
Textエディタでプログラムを変更したらすぐに実行できます(あたりまえですが・・・)
そのうちJRuby on Rails/MySQLも書きたい思います。
金曜日 1 23, 2009
今回は、postgresqlからMySQLへの移行手順を書いてみたいと思います。
今回もMySQLマイグレーションツールキットを使用します。
全体の流れは、Oracleからの移行で説明したのと同様になります。
1.ツールを起動する前に、以下のフォルダにpostgresqlのjdbc-jarをコピーしておいてください。
postgresのJDBCドライバはhttp://jdbc.postgresql.org/download.htmlからダウンロードできます。
私の環境では以下をコピーしました。
C:\Program Files\MySQL\MySQL Tools for 5.0\java\lib
へpostgresql-8.3-604.jdbc3.jarをコピー
2.ツールを起動します。
3.移行元データベース(Source Database)の画面で以下のように設定します。
Database System : Generic jdbc
Class Name : org.postgresql.Driver
Connection String : jdbc:postgresql://ホスト名:ポート番号/データベース名
例: jdbc:postgresql://eslab177:5432/testdb
Username : postgres
4.移行先データベースの設定を行います。
今回はOracle移行の時と同じインスタンスを使います。
5.サーバへ接続処理されます。
接続が成功すると、Oracleの時同様にExecution completed successfullyと出力されます。
6.移行元スキーマの選択を行います。
Oracleの時と同様にスキーマ(データベース)の選択を行います。
※3の設定でデータベース名にtemplate1やpostgresを指定しているとスキーマは選択できますが、それ以降のオブジェクトが空になります
7.リバースエンジニアリングが実行され、メタデータが移行元DBから抽出されます。
8.移行するオブジェクトを選択します。
これもOracleの時と同様に移行対象と、それ以外とを指定します。
これ以降の流れはOracle移行の時と全く同一になります。移行のベースとするには恰好のツールですね。
ただし、現在Migration Toolkitでは明示的なpostgresqlサポートがないのでGeneric JDBCを使う形になっています。データタイプの移行などは自動成功する割合がOracle移行の時よりも少ないのが事実です。一度ツールで変換してから内容を変更していくか、Migration ToolkitのMigrationGeneric.javaに望ましいpostgresql→mysqlのデータマップ定義を埋め込んであげれば良いと思います。
水曜日 1 07, 2009
このたび業務の関係でMySQL認定試験を受けてみました。
MySQLの認定資格は何種類かあるのですが、それぞれ違う役割を想定して作られているそうです。
今回取得したのは以下です。
Certified MySQL 5.0 DBA(CMDBA)
Certified MySQL 5.0 Developer(CMDEV)
Certified MySQL 5.1 Cluster Administrator(CMCDBA)
以下引用です。
http://www-jp.mysql.com/certification/
CMDEV認定は、バックエンドストレージとしてMySQLを使うアプリケーションを開発する人を対象としており、CMDBA認定は、特に多くのアプリケーションは構築しないで、サーバに対してチューニング、プラニングそしてデータレイアウトを最適化するといった運用面に関与している人を対象としています。MySQL 5.1 Cluster DBA Certification(MySQL 5.1 ClusterDBA認定 (CMCDBA))は、MySQL Clusterセットアップをデザイン、構築そして運用する専門家に対する専門試験です。
MySQL 5.1 Cluster DBA Certificationは、CMDBAを取得してから「009-002: Certified MySQL 5.1 Cluster Administrator」という試験に通ると取得できます。合格者は以下に一部自動でリストされるみたいです。http://www-jp.mysql.com/certification/candidates.php?exam=5
準備としては、まず自分の環境を構築して触りまくること。結構重箱の隅的な問題が多いので、ソースを含めて挙動を確認することという感じですね。Study本としては一応以下も読んでおきました。
MySQL 5.0 Certification Study Guide
http://www-jp.mysql.com/certification/studyguides/
MySQL 5.1 Cluster DBA Certification Study Guide
http://store.vervante.com/c/v/595352502.html
この本、買う前に装丁を確認してなかったんですがSunのカバーになってて驚きました。
何でもSun の最初の Vervanté 製の本だったんだとか・・・
Sunプロフェッショナルサービスは、これからも質の高いサービス提供を目指して行く所存です。
DBレイヤのみならず、アプリ/ミドルレイヤも含めたトータルなエンタープライズソリューション(GlassFish/クラスタ-MySQL/クラスタ+監視ソリューションなど)も提供しておりますのでぜひSun-PSをご用命くださいませ。
皆様がお持ちの環境からの移行も承っております。(WebLogic→GlassFish移行など)
まずはお気軽にご連絡を。
月曜日 1 05, 2009
初日の出の写真を撮りました。
本年もどうぞよろしくお願いいたします。

今年も価値あるサービスを提供させていただく所存ですので、Sunプロフェッショナルサービスをどうぞよろしくお願い申し上げます^^
金曜日 12 05, 2008
前回にて、OracleサンプルテーブルからMySQLへの移行は完了しました。
今回は、気になる結果をチェックしてみたいと思います。
scott.emp表の移行結果
BEFORE : ORACLE DB
SQL> select dbms_metadata.get_ddl('TABLE','EMP','SCOTT') from dual;
DBMS_METADATA.GET_DDL('TABLE','EMP','SCOTT')
--------------------------------------------------------------------------------
CREATE TABLE "SCOTT"."EMP"
( "EMPNO" NUMBER(4,0),
"ENAME" VARCHAR2(10),
"JOB" VARCHAR2(9),
"MGR" NUMBER(4,0),
"HIREDATE" DATE,
"SAL" NUMBER(7,2),
"COMM" NUMBER(7,2),
"DEPTNO" NUMBER(2,0),
CONSTRAINT "PK_EMP" PRIMARY KEY ("EMPNO")
USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "USERS" ENABLE,
CONSTRAINT "FK_DEPTNO" FOREIGN KEY ("DEPTNO")
REFERENCES "SCOTT"."DEPT" ("DEPTNO") ENABLE
) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "USERS"
AFTER : MySQL DB
mysql> show create table emp;
+-------+---------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------+
| Table | Create Table
|
+-------+---------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------+
| emp | CREATE TABLE `emp` (
`empno` int(4) NOT NULL,
`ename` varchar(10) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
`job` varchar(9) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
`mgr` int(4) DEFAULT NULL,
`hiredate` datetime DEFAULT NULL,
`sal` decimal(7,2) DEFAULT NULL,
`comm` decimal(7,2) DEFAULT NULL,
`deptno` int(2) DEFAULT NULL,
PRIMARY KEY (`empno`),
KEY `fk_deptno` (`deptno`),
CONSTRAINT `fk_deptno` FOREIGN KEY (`deptno`) REFERENCES `dept` (`deptno`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+-------+---------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------+
1 row in set (0.14 sec)
上手に移行されているような印象ですね。単純な移行ではなくてちょっと工夫した?移行をしてる感じです。
例えば:
Oracle : NUMBER(4,0),
MySQL : int(4) NOT NULL,
なんかです。(ただしMySQLではint(4)とやってもストレージの節約にはなりませんが・・)あと、このテーブルに付加されている主キーと、外部キーはきちんとリバースエンジニアリングされて来ています。
Oracle
CONSTRAINT "PK_EMP" PRIMARY KEY ("EMPNO")
CONSTRAINT "FK_DEPTNO" FOREIGN KEY ("DEPTNO")
REFERENCES "SCOTT"."DEPT" ("DEPTNO") ENABLE
MySQL
PRIMARY KEY (`empno`),
KEY `fk_deptno` (`deptno`),
CONSTRAINT `fk_deptno` FOREIGN KEY (`deptno`) REFERENCES `dept` (`deptno`) ON DELETE NO ACTION ON UPDATE NO ACTION
次にEMP表のデータをチェックしてみます。きちんと移行されているでしょうか?左が移行されたMySQLデータ、右がオリジナルのOracleデータです。(クリックすると拡大します)
EMP表に関しては完璧ですね。若干表示形式が異なっていますが、これは私が使ったOracleのSQL*Plusの設定(SET~)とNLSの設定がmysqlの表示形式と若干異なっているためです。(完全に同じような表示にすることも設定次第で可能です)
他の表についても参考までに載せておきます。
scott.bonus表の移行結果
BEFORE : ORACLE DB
SQL> select dbms_metadata.get_ddl('TABLE','BONUS','SCOTT') from dual;
DBMS_METADATA.GET_DDL('TABLE','BONUS','SCOTT')
--------------------------------------------------------------------------------
CREATE TABLE "SCOTT"."BONUS"
( "ENAME" VARCHAR2(10),
"JOB" VARCHAR2(9),
"SAL" NUMBER,
"COMM" NUMBER
) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "USERS"
AFTER : MySQL DB
mysql> show create table bonus;
+-------+------------------------------------------------------------------
| Table | Create Table
+-------+------------------------------------------------------------------
| bonus | CREATE TABLE `bonus` (
`ename` varchar(10) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
`job` varchar(9) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
`sal` decimal(22,0) DEFAULT NULL,
`comm` decimal(22,0) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+-------+------------------------------------------------------------------
1 row in set (0.00 sec)
scott.dept表の移行結果
BEFORE : ORACLE DB
SQL> select dbms_metadata.get_ddl('TABLE','DEPT','SCOTT') from dual;
DBMS_METADATA.GET_DDL('TABLE','DEPT','SCOTT')
--------------------------------------------------------------------------------
CREATE TABLE "SCOTT"."DEPT"
( "DEPTNO" NUMBER(2,0),
"DNAME" VARCHAR2(14),
"LOC" VARCHAR2(13),
CONSTRAINT "PK_DEPT" PRIMARY KEY ("DEPTNO")
USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "USERS" ENABLE
) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "USERS"
AFTER : MySQL DB
mysql> show create table dept;
+-------+-----------------------------------------------------------------
| Table | Create Table
+-------+-----------------------------------------------------------------
| dept | CREATE TABLE `dept` (
`deptno` int(2) NOT NULL,
`dname` varchar(14) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
`loc` varchar(13) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`deptno`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+-------+-----------------------------------------------------------------
1 row in set (0.00 sec)
scott.salgrade表の移行結果
BEFORE : ORACLE DB
SQL> select dbms_metadata.get_ddl('TABLE','SALGRADE','SCOTT') from dual;
DBMS_METADATA.GET_DDL('TABLE','SALGRADE','SCOTT')
--------------------------------------------------------------------------------
CREATE TABLE "SCOTT"."SALGRADE"
( "GRADE" NUMBER,
"LOSAL" NUMBER,
"HISAL" NUMBER
) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "USERS"
AFTER : MySQL DB
mysql> show create table salgrade;
+----------+-------------------------------------
| Table | Create Table
+----------+-------------------------------------
| salgrade | CREATE TABLE `salgrade` (
`grade` decimal(22,0) DEFAULT NULL,
`losal` decimal(22,0) DEFAULT NULL,
`hisal` decimal(22,0) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+----------+-------------------------------------
1 row in set (0.00 sec)
全体としては、よく移行できていますね。今回はサンプルテーブルということでシンプルな構成のためですが・・・。(マイグレーション結果は警告なしでした)他にOracleを使用している方は色々な機能を使用していると思いますが(マテリアライズドビュー、シーケンス、シノニム、他)そういったものはやはり手動またはツールのカスタマイズ/作成での移行が必要な場合もあります。あと、テーブル名とカラム名は小文字で出てきてしまうので大文字にしたい場合はもう一工夫必要ですね(※1)。複数ステートメントを含むプロシージャもDELIMITER文を追加するなどマニュアルでの対応は必要です。
※1 Migration Tool Kit 1.1.14の実装ではMaxDB, Oracleからの移行ではテーブル名等のIdentifierは小文字に変換するようになっています。ここを変更したり結果をSQLとして出力してそのSQLに手を加えることでオリジナルCaseでの移行は可能です。MySQLではデフォルト設定でWindows機上のテーブル名の大文字小文字はステートメント中で区別されません。ただしUNIX機でもストレージエンジンにInnoDBを使用する場合はテーブル名は小文字のままが推奨です(またはシステム変数lower_case_table_names=1をセットしてMySQLサーバを起動後create文を発行する)。カラム名は大文字小文字の区別はありません
このツールはあくまでも移行のための手段です。MySQLマイグレーションツールキットの良い点は移行処理中にたくさんの、人間が介入する機会が与えられているところだと思います。つまりどのように使うかは使用する側次第です。(例えば、NUMBER(4,0)ならSMALLINTでも可能、、、とか)しかも移行処理全体をスクリプト化できるので、移行プログラムを作成するよりも非常に少ない工数にて完全カスタマイズされた移行を実現できる可能性を持っていると思います。
さらに、MySQLマイグレーションツールキットの移行主要ロジックはJavaで記述されています。各種DBからの移行部分とリバースエンジニアリングの部分について、既に用意されている基底クラスをextendしてそれらのモジュールを作成できるようになっているので(かつオープンソースなので)、デフォルトのモジュールでは実装されていない部分の移行や、顧客要件に応じたカスタマイズされた移行を実装することが可能です。
Sunプロフェッショナルサービスでは、MySQLベンダーとしてMySQLの移行サービスを提供しています。移行の要件に基づく移行計画/設計、移行環境の構築、移行の実施、テストなどを行うアセスメントサービスとなります。MySQLマイグレーションツールキットのカスタマイズ対応や顧客専用移行ツール作成も可能です(あくまで移行手段の中の一つとしてですが、、、目的は移行そのものです)。是非ご利用をご検討ください。
This blog copyright 2009 by naokitakemura










