多層分散型基幹業務システム構築の課題と解決

ＮＵシステムズ株式会社
TEL:06-6233-8200

内容【システム説明】
今回のシステムの導入に至った経緯
次期システムの基本要件
システム構築スケジュールと開発規模
3層構造、DataSnap採用に至った経緯
全体システム概要とシステム構成の説明
DataSnap3層構造の全体システム説明図【DataSnapでの実装】
DataSnapを使った二層ライクな開発
多接続環境でのパフォーマンスの維持
カーソルセットの取扱いで注意すべきこと
DataSnapで発生する、ＳＱＬステートメント20k問題
DataSnapのDelta処理でのBlobデータの取扱い
課題とＴＩＰＳ今回のシステムの導入に至った経緯
基幹システムでありながら、Ｗｅｂベースで開発・運用されている （2013/1)
消費税率の変更に対応していない
Ｗｅｂベースの為、セキュリティが確保しきれない
Ｗｅｂ標準やＨＴＭＬ５など、ブラウザ依存でシステム側がフォローし切れない
ユーザー接続数、仕様の追加が頻繁に発生する
モバイル環境の増大
消費税率の変更まで、１年しかない

全く新しいシステムが必要 (2013/4)

次期システムの基本要件
ユーザー規模（システム運用開始時点）業種：フランチャイズチェーン店
現有店数１００店舗
同時接続数５００
１８時間３６５日運用
固定接続環境システム設計要件
現有店数３００店舗
同時接続数１５００
２４時間３６５日運用
モバイル環境、
固定接続環境併存
１０年間稼働保証
セキュリティ確保
柔軟なシステム変更
短期間でのシステム構築

システム構築スケジュールと開発規模
２０１３年１月～３月
２０１３年４月
２０１３年４月～６月
２０１３年６月～９月
２０１３年10月～２月末
２０１４年１月～３月末
２０１４年４月１日
総開発ページ数

要件定義開始基本開発契約締結
システム要件定義、要素技術開発
プロトタイプ版開発
本システム開発
ユーザーテスト、データー移行
本稼働
フロントエンド
外部連携サーバ
サーバサイドモジュール数

４００ページ３サイト
３０本

3層構造、DataSnap採用に至った経緯
短納期で要件定義からプロトタイプ作成、デバッグ、納入支援まで少人数で完成させる必要
長期間に渡り安定稼働を保証し、セキュリティを確保した上でユーザー接続環境に柔軟に対応できること
ユーザー環境に左右されない長期間のシステム運用の保証
ユーザーの運用要件で多数発生する、複雑な業務処理をリアルタイム処理する必要
Webベースではない為、クライアントのプロファイル情報を確実に取得
最新のWindowsプラットフォーム上で運用可能なこと⇒Windows8対応
サーバへの負荷を低減する為、フロントエンドで殆どの処理を完了したいシステムの停止を行うことなく拡張が行える、スケーラビリティの確保

DataSnap3層構造の全体システム説明図
データベース

DataSnapサーバ

DataSnapを使った二層ライクな開発
改善案

当初の構成予定
dbExpress一本での実装を検討。

データベース⇒FireDACのリリース。

データベース

比較した結果

DataSnapサーバ
dbExpress

・フェッチ件数制御：
FireDACなら意識する必要なし

DataSnapサーバ
FireDAC・フェッチ速度：
FireDACが圧倒的に高速(dbExpressでも実装は可能だが処理が複雑化）

ClientdbExpress

・制御の簡易化：
FireDACなら、最小３個程度のコンポーネントで実装可

Client
FireDACDataSnapを使った二層ライクな開発
しかし問題発生
データベース
ApplyUpdatesされないClient側のデータセットをOpenするだけ
では、サーバ側のデータセットとリンクできない。

SQLを使えば更新可能だが…

データベース
・コード量の増大
・バグの誘発・開発工数の肥大化

ApplyUpdates

DataSnapサーバ

DataSnapサーバ

FireDAC

FireDAC

ApplyUpdates
Client
FireDAC

当時、FireDACにはサーバとクライアントで、双方向カーソルのデータセットを開く手段が未実装。

FireDACとdbExpressを組み合わせることでApplyUpdatesによる更新が可能に。
パフォーマンスも良好。

ApplyUpdates
ClientdbExpress

DataSnapを使った二層ライクな開発
詳細な構成は以下の通り。
DSServer

データベース

Query

TCP

Provider実行メソッドConnection
ServerClass サーバメソッド (FireDAC)

DataSnap
サーバ①DataSnapサーバに対して接続オープン②SQLConnectionを通してサーバメソッドのDataSetProviderを利用可能に。

③サーバ側の処理として実行メソッド経由でADQueryにSQL発行。
Openする。

ProviderConnection

SqlServerMethod④DataSetProviderを接続先として指定し、OpenConnection

Client (dbExpress)

ClientDataSet

DataSnapを使った二層ライクな開発
双方向カーソルでの注意点●行移動の際、DataSnapサーバに対して通信が発生。（カーソル位置の通知）⇒行移動が多発する画面、処理では単方向カーソルの方がパフォーマンスが良いケースも。
⇒単方向カーソルの場合、SQLによる更新が必要となる。
●二層での双方向カーソルデータセット同様、テーブル結合したクエリには使用不可。
⇒極力テーブル結合が発生しないテーブル設計が非常に重要。（正規化を落とすことも重要）●ClientDataSetのProviderName指定を誤ると、最悪想定したテーブルとは別のテーブルが更新される。
⇒DataSnapサーバ側のADQuery、DataSetProviderの組み合わせは、同じ数字としておく。
●トランザクション制御は、DataSnap側のデータベース接続（=ADConnection）で行う必要がある。
クライアント側の接続(=SQLConnection）のトランザクションではない。
⇒DataSnap側にADConnectionに対してStartTransaction、Commit、Rollbackを実行するだけのサーバメソッドを用意。
●IDEでのデザインでは、単方向カーソルのみが使用可能。
⇒フィールドの取得などで使用するための、本処理とは別のSQLServerMethod、DataSetProvider、DataSourceが必要。

多接続環境でのパフォーマンスの維持
・1000件程度のselect結果が返るまで、時間が30秒近くに…
・150端末前後から、DataSnapに接続すらできない…

データベース■サーバの接続制限？ ⇒ 無制限の状態■DBサーバの負荷？ ⇒ ほぼ横ばい■DataSnapの制限？ ⇒ 多くのプロパティがあるが、情報が少なく全パターン試行に…

DataSnap

ClientClient…×200

大きな工数増大の危機…

●一つのDataSnapでダメなら、複数で処理すればいいのでは？
パフォーマンス回復。
500端末より秒間1回、結果1,000件のクエリ結果が1秒以内に。(秒間500回)

データベース

DataSnapClient…×100

DataSnap

Client…×100

DataSnap

Client…×100×想定されるユーザー規模分

多接続環境でのパフォーマンスの劣化を防ぐ
最終的には以下の構成に。
実処理DataSnapは全て同じソース。
(ポートのみ相違)
負荷増大しても、実処理DataSnapの配置、振り分け用DataSnapの変更だけで対応可能。

振り分け用
DataSnap②接続数を確認し、接続可能なDataSnapの情報をクライアントに通知。

①接続可能な実処理
DataSnapを問い合わせ。

データベース実処理
DataSnap１

実処理
DataSnap２③①の接続は切断。
②で取得したDataSnapに接続。
以降の処理は全て実処理DataSnap側。

Client

実処理
DataSnap３・・・

多接続環境でのパフォーマンスの劣化を防ぐ
コード例②接続数を確認し、接続可能なDataSnapの情報をクライアントに通知。
(振り分けDataSnap側)②接続数を確認し、接続可能なDataSnapの情報をクライアントに通知。
(実処理DataSnap側)

function TServerMethods1.GetUsePortNo :integer;
begin
SqlServerMethod1.ExecuteMethod;
SqlServerMethod2.ExecuteMethod;function TServerMethods1.SessionCount:integer;
begin
Result :=TDSSessionManager.Instance.GetSessionCount;end;

if SqlServerMethod1.Params[0] <= 101then begin
Result := 10000;
end
else
if SqlServerMethod2.Params[0] <= 101then begin
Result := 20000;
end;
end;

各実処理DataSnapのポート番号。
各実処理DataSnapモジュールの・メモリ使用量・CPU使用率などで振り分けることも可能。

カーソルセットの取扱いで注意すべきこと
DBの定義ではNOT NULLでない列…

単方向カーソルのデータセットへのデータ追加で、以下の現象。
Append＆Post

DataSnapサーバ

ClientFireDAC

双方向カーソルでは発生しない。
RequiredプロパティがTrueになるため。
（コードでOpenしても同じ)①IDEであらかじめフィールドを取得しておき、SQLServerMethodのフィールドの不要なRequiredを外す。
②Open後、コードにてSqlServerMethodおよびClientDataSetの全フィールドのRequiredを外す。
①の対応がお勧めDataSnapで発生する、ＳＱＬステートメント20k問題
全角文字を含むSQLは20k以上は切り捨てられる…
Select コード１,文字１,コード２,文字２,コード３,文字３,コード４,文字４,コード５,文字５,コード６,文字６,コード７,文字７,コード８,文字８,コード９,文字９,コード１０,文字１０,コード１１,文字１１,コード１２,文字１２,コード１３,文字１３,コード１４,文字１４,コード１５,文字１５,コード１６,文字１６,コード１７,
文字１７,コード１８,文字１８,コード１９,文字１９,コード２０,文字２０,コード２１,文字２１,コード２２,文字２２,コード２３,文字２３,コード２４,文字２４,コード２５,文字２５,コード２６,文字２６,コード２７,文字２７,コード２８,文字２８,コード２９,文字２９,コード３０,文字３０,コード３１,文字３１,コード３２,文字３２,コード３３,文字３３,コード３４…文字３４,コード３５,文字３５,コード３６,文字３６,コード３７,文字３７,コード３８,文字３８,コード３９,文字３９,コード４０,文字４０, from テーブル A where コード１ = ‘001’ and コード２ <> 1 …

Base64でエンコード、デコードすれば問題なし。
クライアント側
SqlServerMethod1.Params[0].AsString :=String(EncodeBase64(TEncoding.Unicode.GetBytes(strSQL),
TEncoding.Unicode.GetByteCount(strSQL)));
ClientDataSet1.Open;
DataSnap側
strSQL := TEncoding.Unicode.GetString(DecodeBase64(AnsiString(strSQL)));
ADQuery1.SQL.Text := strSQL;
ADQuery1.Open;
Result := ADQuery1;

DataSnapのDelta処理でのBlobデータの取扱い
双方向カーソルのデータセットのBlob列で、以下の現象。
Blobフィールド
OldValueとNewValueを比較しても、バイト数が同じだと変更があったか判定できない…

変更
1,024Kバイト

1,024Kバイト

begin
by1 := bFld.OldValue;by2 := bFld.NewValue;
if Length(by1) = Length(by2) then begin
for i := Low(by1) to High(by1) do begin
if by1[i] <> by2[i] then begin
flg := True;
Break;
end;
end;
end
end;

バイト配列にOldValueとNewValueセット
ループで変更有無をチェック課題とＴＩＰＳ【全般】
・実例が少ないため、DataSnap側で細かい制御を行いすぎると、問題発生時の対応に時間が…
⇒DataSnap側は極力シンプルな構成に。
①単方向カーソルのデータセットを返すメソッド②双方向カーソルのデータセットを開くメソッド③ＳＱＬを実行するメソッド④トランザクション制御用のメソッド⑤排他制御用のメソッド

上記５点があれば、十分システム構築が可能。
【通信】・クライアント側とDataSnapサーバ側のKeep-Aliveは、デフォルトでは非常に長い(1分)。
通信切断時に応答が帰るまでの時間に直結するため、システム要件に応じた調整が必要。
・DataSnapサーバ側のインスタンス（=DSServerClass)のDestoryイベントで、インスタンス内部のDB接続を切断する処理を記載しておかなければ、通信切断時もデータベースとの接続が保持されたままに…。