SQL Server 2000とSQL Server 2012のデータ連係(SymmetricDS)

せめてSQL Server 2005とSQL Server 2012であればリプリケーションを使って、片側のデータベースに対する更新を、もう片方のデータベースに反映して同期を取ることも出来るのだけど・・・と、良い方法を探していたところSymmetricDS(http://www.symmetricds.org/)というリプリケーションツールを見つけた。

SymmetricDSはJava SE6以上が動作するOSで、JDBCで接続可能なデータベース間であれば、およそ殆どの異なるアーキテクチャ間のDBを双方向にリプリケーション出来る優れものです。SQL Server 2000でも動作するはずなので、今回の用途にはぴったりです。さっそく試験環境を作成してテストしてみましょう。

1.SymmetricDSのインストール

1.1.SymmetricDSのダウンロード

SymmetricDSのホームページからzipファイルをダウンロードして、インストール先に解凍します。ここではC:\App\symmetricに解凍することにします。

1.2.ルートノードの設定ファイルを作成する

データベースへの接続設定を作成します。データベースへの接続に関する設定はC:\App\symmetric\enginesに保存します。C:\App\symmetric\samplesからcorp-000.propertiesを複製して設定します。今回はrootnode.propertiesという名称にします。

engine.nameには任意の名称を指定します。今回はrootnodeとします。

db.driverにはJDBCドライバの名称を指定します。デフォルトではH2 Databaseが指定されているので、これをコメントにしてnet.sourceforge.jtds.jdbc.Driverのコメントを外します。

db.urlには同期するデータベースへの接続文字列を指定します。デフォルトではH2 Databaseが指定されているので、これをコメントにしてjdbc:jtds:sqlserver://localhost:1433/rootdb;useCursors=true;bufferMaxMemory=10240;lobBuffer=5242880;に変更します。rootdbはデータベース名ですが、ここには日本語を指定できません。

db.userにはデータベースへのログインIDを指定します。本来はSymmetricDS用にアカウントを作るべきですが、今回はsaを指定しておきます。

db.passwordには先のログインIDで使用するパスワードを指定します。

sync.urlには同期に使用するHTTP URLを指定します。ここではhttp://localhost:31415/sync/rootnodeと指定します。sync移行に続く文字列はengine.nameで指定した文字列と一致しなくてはなりません。

group.idとexternal.idは、SymmetricDS上でデータベースを一位に識別するためのIDを指定します。ここではgroup.idをrootnode、external.idを000としておきます。

他にもパラメータがありますが、そのままにしておきます。

engine.name=rootnode

# The class name for the JDBC Driver
...#db.driver=org.h2.Driver
db.driver=net.sourceforge.jtds.jdbc.Driver

# The JDBC URL used to connect to the database
...
#db.url=jdbc:h2:corp;AUTO_SERVER=TRUE;LOCK_TIMEOUT=60000
db.url=jdbc:jtds:sqlserver://localhost:1433/rootdb;useCursors=true;bufferMaxMemory=10240;lobBuffer=5242880;

# The user to login as who can create and update tables
db.user=sa

# The password for the user to login as
db.password=xxxxxx

registration.url=
sync.url=http://localhost:31415/sync/rootdb

# Do not change these for running the demo
group.id=rootnode
external.id=000

1.3.同期対象となるノードの設定ファイルを作成する

データベースへの接続設定を作成します。データベースへの接続に関する設定はC:\App\symmetric\enginesに保存します。C:\App\symmetric\samplesからstore-000.propertiesを複製して設定します。今回はsubnode-000.propertiesという名称にします。

engine.nameには任意の名称を指定します。今回はsubnode-001とします。

db.driverにはJDBCドライバの名称を指定します。ルートノードと同じくデフォルトではH2 Databaseが指定されているので、これをコメントにしてnet.sourceforge.jtds.jdbc.Driverのコメントを外します。

db.urlには同期するデータベースへの接続文字列を指定します。ルートノードと同じくデフォルトではH2 Databaseが指定されているので、これをコメントにしてjdbc:jtds:sqlserver://localhost:1433/subdb01;useCursors=true;bufferMaxMemory=10240;lobBuffer=5242880;に変更します。

db.userにはデータベースへのログインIDを指定します。本来はSymmetricDS用にアカウントを作るべきですが、今回はsaを指定しておきます。

db.passwordには先のログインIDで使用するパスワードを指定します。

registration.urlにはルートノードsync.urlの値を指定します。ここではhttp://localhost:31415/sync/rootnodeと指定します。

group.idとexternal.idをルートノードと同様に設定します。ここではgroup.idをsubnode、external.idを001としておきます。

他にもパラメータがありますが、そのままにしておきます。

engine.name=subnode-001

# The class name for the JDBC Driver
...#db.driver=org.h2.Driver
db.driver=net.sourceforge.jtds.jdbc.Driver

# The JDBC URL used to connect to the database
...
#db.url=jdbc:h2:corp;AUTO_SERVER=TRUE;LOCK_TIMEOUT=60000
db.url=jdbc:jtds:sqlserver://localhost:1433/subdb01;useCursors=true;bufferMaxMemory=10240;lobBuffer=5242880;

# The user to login as who can create and update tables
db.user=sa

# The password for the user to login as
db.password=xxxxxx

registration.url=http://localhost:31415/sync/rootdb

# Do not change these for running the demo
group.id=subnode
external.id=001

1.4.ルートノードに制御テーブルを作成する

次のコマンドを実行してルートノードのデータベースに制御テーブルを作成します。これによりデータベースにsym~で始める各種テーブルが作成されます。制御テーブルは別のデータベースに作りたいと思うかもしれませんが、ルートノードで指定したデータベース以外の場所に作ることはできないようです。

bin\symadmin --engine rootnode create-sym-tables

続いてデータベース間の同期方法に関する設定を、上記コマンドで作成されたテーブルにINSERTします。

--既存の設定を削除。sym_channelには既定のテーブルがあるので、作成したものだけ削除するようになっています。
delete from sym_trigger_router;
delete from sym_trigger;
delete from sym_router;
delete from sym_channel where channel_id in ('tblcopy');
delete from sym_node_group_link;
delete from sym_node_group;
delete from sym_node_host;
delete from sym_node_identity;
delete from sym_node_security;
delete from sym_node;

--テーブルのコピーに使用するチャンネルを作成。
--同期でエラーが発生すると、同じチャンネルを使用しているその他の複製処理もエラーが回復するまで待たされます。
--エラーが発生しても並列して複製処理を行いたい場合には、チャンネルを複数作成します。
insert into sym_channel (channel_id, processing_order, max_batch_size, enabled, description)
values('tblcopy', 1, 100000, 1, 'Copy Sample Table');

insert into sym_node_group (node_group_id) values ('rootnode');
insert into sym_node_group (node_group_id) values ('subnode');

insert into sym_node_group_link (source_node_group_id, target_node_group_id, data_event_action) values ('rootnode', 'subnode', 'W');
insert into sym_node_group_link (source_node_group_id, target_node_group_id, data_event_action) values ('subnode', 'rootnode', 'P');

--複製の対象テーブルや複製を開始する判断条件を指定します。
--ここではワイルドカードで全てのテーブルを複製するように指定して、それ以外の項目は規定値にしています。
insert into sym_trigger
(trigger_id,source_table_name,channel_id,last_update_time,create_time)
values('tblcopy','*','tblcopy',current_timestamp,current_timestamp);

--複製の方向の指定と、複製するデータのフィルタ条件を指定します。
--複製する条件はdefaultとして全てを複製するように指示しています。
insert into sym_router (router_id,source_node_group_id,target_node_group_id,router_type,create_time,last_update_time)
values('root_2_sub', 'rootnode', 'subnode', 'default',current_timestamp, current_timestamp);
insert into sym_router (router_id,source_node_group_id,target_node_group_id,router_type,create_time,last_update_time)
values('sub_2_root', 'subnode', 'rootnode', 'default',current_timestamp, current_timestamp);

--sym_triggerとsym_routerを関連付けています。
insert into sym_trigger_router
(trigger_id,router_id,initial_load_order,last_update_time,create_time)
values('tblcopy','root_2_sub', 100, current_timestamp, current_timestamp);

--ノードの登録を行います。
insert into sym_node (node_id,node_group_id,external_id,sync_enabled,sync_url,schema_version,symmetric_version,database_type,database_version,heartbeat_time,timezone_offset,batch_to_send_count,batch_in_error_count,created_at_node_id)
values ('000','rootnode','000',1,null,null,null,null,null,current_timestamp,null,0,0,'000');
insert into sym_node (node_id,node_group_id,external_id,sync_enabled,sync_url,schema_version,symmetric_version,database_type,database_version,heartbeat_time,timezone_offset,batch_to_send_count,batch_in_error_count,created_at_node_id)
values ('001','subnode','001',1,null,null,null,null,null,current_timestamp,null,0,0,'000');

insert into sym_node_security (node_id,node_password,registration_enabled,registration_time,initial_load_enabled,initial_load_time,created_at_node_id)
values ('000','5d1c92bbacbe2edb9e1ca5dbb0e481',0,current_timestamp,0,current_timestamp,'000');
insert into sym_node_security (node_id,node_password,registration_enabled,registration_time,initial_load_enabled,initial_load_time,created_at_node_id)
values ('001','5d1c92bbacbe2edb9e1ca5dbb0e481',1,null,1,null,'000');

--ルートノードのnode_idを指定します。ルートノードは1つだけ作成します。
insert into sym_node_identity values ('000');

1.5.SymmetricDSを起動する

次のコマンドを実行してSymmetricDSを起動します。

bin\sym

正常に起動した場合、sym_~で指定したテーブルや、rootdbにある同期対象のテーブルがsubdb01に複製されデータの同期が開始されます。

2.その他TIPSなど

2.1.日本語への対応

日本語で作成したテーブル名やフィールド名があっても正常に動作しているようです。
ただしデータベース名に日本語を使用する場合はdb.urlで指定することができないので、データベースで使用するユーザーアカウントの既定のデータベースでデータベース名を指定する等の対応が必要になります。

2.2.設定に誤りがあった場合の訂正

設定に誤りがあった場合、テーブルを直接書き換えても認識して動作が変わるのですが、各テーブルに作成された中間データとの整合性が取れなくなる場合があるようで、設定が反映されない場合があります。この場合は”bin/symadmin –engine uninstall”のコマンドを実行して制御テーブルを削除し、あらためて作り直した方がよいです。

2.3.Microsoft製JDBCドライバの使用

Microsoft製JDBCドライバでも問題なく動作しています。
ただしSymmetricDSの開発サイドではjtdsのドライバを使用して動作確認しているとの事なので、そちらを利用したほうがよいでしょう。

Windows Vista以降でサービスから画面を表示する

Windows VISTA以降のWindowsではセッション0分離の機能により、サービスから直接UIを表示することができなくなりました。Microsoftの互換性情報では代替手段として「ユーザーログイン時に常駐プロセスを起動しサービスと通信を行う。」「WTSSendMessage関数を使用してメッセージボックス相当の表示を行う。」「CreateProcessAsUserを使用してログインユーザー権限でアプリケーションを起動する。」の三つの方法が提示されています。ここでは最後の「CreateProcessAsUserを使用してログインユーザー権限でアプリケーションを起動する。」サンプルを提示します。
サービスから画面を表示するにはRemote Desktop Services APIを使用します。
WTSGetActiveConsoleSessionId関数で物理コンソールのセッションIDを取得します。
WTSQueryUserToken関数で取得したセッションIDのユーザートークンを取得します。
DuplicateTokenEx関数で取得したユーザートークンを複製し、プライマリトークンを作成します。
CreateEnvironmentBlockを使用してユーザーの環境変数を取得します。
CreateProcessAsUser関数を使用して画面を表示するプロセスを起動します。

BOOL    bRet(FALSE);
HANDLE  hUserToken(INVALID_HANDLE_VALUE);
if (::WTSQueryUserToken(WTSGetActiveConsoleSessionId(), &hUserToken))
{
    SECURITY_ATTRIBUTES sa;
    memset(&sa, 0x00, sizeof(sa));
    sa.nLength	= sizeof(SECURITY_ATTRIBUTES);
    HANDLE	hDupedToken(INVALID_HANDLE_VALUE);
    if (::DuplicateTokenEx(hUserToken, 0, &sa, SecurityIdentification, TokenPrimary, &hDupedToken))
    {
        PVOID	lpEnvironment(NULL);
        if (::CreateEnvironmentBlock(&lpEnvironment, hUserToken, FALSE))
        {
            STARTUPINFO stStartUpInfo;
            memset(&stStartUpInfo, 0, sizeof(STARTUPINFO));
            stStartUpInfo.cb        = sizeof(STARTUPINFO);
            stStartUpInfo.lpDesktop = _T("winsta0¥¥default");
            PROCESS_INFORMATION ProcessInfo;
            memset(&ProcessInfo, 0, sizeof(PROCESS_INFORMATION));
            ::CreateProcessAsUser(hDupedToken, _T(""), (char*)(const char*)strCmd, NULL, NULL
                    , FALSE, CREATE_UNICODE_ENVIRONMENT, lpEnvironment, NULL, &stStartUpInfo, &ProcessInfo);
        }
    }
}

感染した未知のコンピュータウィルスを削除するには・・・実践編2/2

続いてレジストリの確認を取ります。実践編1/2でウィルスが起動しないようにしたあと、for VirusCheck OSを起動して、レジストリエディタから全レジストリの内容をエクスポートします。エクスポートしたレジストリはテキストファイルイン保存されますので、for Refrence OSのウィルス感染前のレジストリをエクスポートしたものと比較して変更箇所を検証していきます。
registry
実際に細かく追っていくとかなり根気のいる作業です。今回のウィルスでは以下の場所に改変がくわえられ、ログオン時にウィルスが起動されるようになっていました。

[HKEY_LOCAL_MACHINE&yenSOFTWARE&yenMicrosoft&yenWindows NT&yenCurrentVersion&yenWinlogon]
“Userinit”=”C:&yen&yenWINDOWS&yen&yensystem32&yen&yenuserinit.exe,C:&yen&yenProgram Files&yen&yenCommon Files&yen&yensvchost.exe -s”

今回のウィルスは比較的シンプルな作りになっていたので、ファイル1つ、レジストリ1箇所だけで済んでいます。etcやDNSサーバの情報を改ざんしたり、電子証明書を改ざんしたり、ドライバを組み込んだり、ブラウザやエクスプローラーにプラグインを組み込んだりと、複雑な動作をするウィルスもいます。気を抜かずに根気よく一つ一つ確認していく事が重要です。
ウィルスを駆除する為に削除するファイルやレジストリが明確になったら、実際に感染した環境でそれらのファイルを削除したり、あるいはレジストリを修復していく事になります。ウィルスが常駐している状態では削除などの操作はウィルスによってトラップされており、おこなえないのが常です。OSのモジュールの一部が置き換えられている場合には、たとえセーフモードで起動したとしてもウィルスは常駐してしまいます。KNOPPIXなどのCDからブートするLinux環境から、NTFSのファイルシステムをマウントして、ファイルの削除などをおこなうのが一番確実でしょう。
今回のウィルスは幸いな事に、セーフモードのコマンドプロンプトで起動すれば、ウィルスが常駐する事を防げます。したがってセーフモードのコマンドプロンプトで起動した後、C:¥Program Files¥Common Files¥svchost.exeのシステム属性と非表示属性を解除し、ファイルを削除することで除去できました。