Progress Corticon
【Corticon Tech コラム】
No.3 Corticonの呼び出し方と処理速度について (Webサービス編) (2016年7月29日)
|
|
今回はJavaのクライアントアプリケーションからSOAP (XML)およびREST (JSON)でCorticonのデシジョンサービスを呼び出し、それぞれの処理速度を検証します。
アプリケーションからCorticonを呼び出すには、Corticon Studioで作成したルールをCorticon Serverにデプロイします。Corticon Serverにデプロイしたルールをデシジョンサービスと呼び、アプリケーションからWebサービスとして呼び出します。
※SOAPとRESTについて
SOAPとはWeb APIを利用する方式の一種でXML形式のデータを送受信します。
RESTとはSOAPよりシンプルなWeb APIを利用する方式でCorticonはJSON形式のデータをサポートしています。REST呼び出しはCorticon V5.5から利用できます。
検証環境
処理速度を検証した環境はつぎのとおりです。
► CPU: 2.20 GHz * 2コア
► OS: Windows 7 Professional 64bit
► アプリケーションサーバー: Tomcat 7.0.70 Windows 64bit版
► Tomcat使用メモリ: 1GB
► Corticon Server: 5.5.2.7
検証用の語彙とルール
今回の検証で使用するシソーラス(階層語彙、データ構造体、Corticonにおける語彙)は、つぎのとおりです。
|
|
「Entity1」「Entity2」という2つのエンティティを用意し、1対nの親子構造になるように関連性を持たせています。またそれぞれのエンティティに整数型の属性「Attribute0」~「Attribute9」の10個を持たせます。
今回の検証で使用するルールは処理速度を図るために非常にシンプルにしています。デシジョンテーブル(Corticonにおけるルールシート)はつぎのとおりです。
|
|
ルールの内容は、子エンティティ「Entity2」の各属性にセットされた数値を親エンティティ「Entity1」の同名属性に足していくというものです。
デシジョンサービスの実行速度にはルールの複雑さや量も影響しますが、検証目的からは逸れるので、今回はこのシンプルなルールで検証します。
ルール作成後、Corticon Studioメニューの「プロジェクト」―「デシジョンサービスのパッケージとデプロイ」から、デシジョンサービス名「test」としてCorticon Serverにデプロイします。
検証用のデータとプログラム
まず、Corticon Studioのテストシートで以下の入力データを作成します。
|
|
これではテストデータとして量が少ないので、図の子要素「entity2(Entity2) [1]」のデータをコピーして10個, 100個, 1000個に増やしたデータを用意します。
出来上がった入力データを、Corticon Studioメニューの「ルールテスト」―「テストシート」―「データ」―「入力」―「要求をSOAPでエクスポート」および「要求をJSONでエクスポート」でファイル化します。ここでは、それぞれ「test.xml」、「test.json」という名前でデスクトップに保存します。
これらのファイルに保存したデータを読み込んで、Corticon ServerにHTTP通信でPOSTするシンプルなJavaプログラムを作成し、処理時間を計測します。プログラムの内容はつぎのとおりです。
※ Corticon StudioからエクスポートしたSOAPのXMLデータでは、デシジョンサービス名を記入する箇所が「InsertDecisionServiceName」となっているため、正しいデシジョンサービス名「test」に編集します。
★ REST(JSON)クライアントのサンプル
import java.io.*;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.HttpClient;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
class RestClient {
public static final int THREADNUM = 1;
static String readFileToString(String path) throws IOException {
String buf = "";
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(path), "UTF-8"));
String line;
while ((line = reader.readLine()) != null) {
buf += line;
}
reader.close();
return buf;
}
public static void main(String[] args) {
String body = "";
try {
body = readFileToString("C:/Users/progress/Desktop/test.json");
} catch (Exception e) {
System.out.println(e.getMessage());
return;
}
long start = System.currentTimeMillis();
SubThread[] subs = new SubThread[THREADNUM];
for (int i = 0; i < THREADNUM; i++) {
try {
// System.out.println(body);
subs[i] = new SubThread(body);
subs[i].start();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
for (int i = 0; i < THREADNUM; i ++) {
try {
subs[i].join();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
class SubThread extends Thread{
private String body;
public static final int EXEC_DECISION_NUM = 1000;
public SubThread(String body) {
this.body = body;
}
public void run() {
try {
StringEntity input = new StringEntity(body, "UTF-8");
input.setContentType("application/json; charset=UTF-8");
HttpPost post = new HttpPost("http://localhost:8080/axis/corticon/execute");
post.setEntity(input);
post.setHeader("dsName", "test");
for (int i = 0; i < EXEC_DECISION_NUM; i++) {
HttpClient client = HttpClientBuilder.create().build();
HttpResponse response = client.execute(post);
/*
BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
*/
System.out.println("POST exec: " + response.getStatusLine().getReasonPhrase());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
★ SOAP(XML)クライアントのサンプル
import java.io.*;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.HttpClient;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
class SoapClient {
public static final int THREADNUM = 1;
static String readFileToString(String path) throws IOException {
String buf = "";
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(path), "UTF-8"));
String line;
while ((line = reader.readLine()) != null) {
buf += line;
}
reader.close();
return buf;
}
public static void main(String[] args) {
String body = "";
try {
body = readFileToString("C:/Users/progress/Desktop/test.xml");
} catch (Exception e) {
System.out.println(e.getMessage());
return;
}
long start = System.currentTimeMillis();
SubThread[] subs = new SubThread[THREADNUM];
for (int i = 0; i < THREADNUM; i++) {
try {
// System.out.println(body);
subs[i] = new SubThread(body);
subs[i].start();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
for (int i = 0; i < THREADNUM; i ++) {
try {
subs[i].join();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
class SubThread extends Thread{
private String body;
public static final int EXEC_DECISION_NUM = 1000;
public SubThread(String body) {
this.body = body;
}
public void run() {
try {
StringEntity input = new StringEntity(body, "UTF-8");
//input.setContentType("application/json; charset=UTF-8");
input.setContentType("text/xml; charset=UTF-8");
//HttpPost post = new HttpPost("http://localhost:8080/axis/corticon/execute");
HttpPost post = new HttpPost("http://localhost:8080/axis/services/Corticon");
post.setEntity(input);
//post.setHeader("dsName", "test");
post.setHeader("SOAPAction", "localhost:8080/axis/services/Corticon#processRequest");
for (int i = 0; i < EXEC_DECISION_NUM; i++) {
HttpClient client = HttpClientBuilder.create().build();
HttpResponse response = client.execute(post);
/*
BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
*/
System.out.println("POST exec: " + response.getStatusLine().getReasonPhrase());
} catch (Exception e) {
e.printStackTrace();
}
}
}
※ これらのプログラムで使用しているHTTP通信用のライブラリは、Apache Software Foundationからダウンロードした「HttpComponents」です。
※ SOAPとRESTでは、Corticon Serverに送信する際のURLとHTTPヘッダが異なります。
※ 今回はRESTとSOAPを純粋に比較するため、リクエストデータ(XML, JSON)を直接POSTする方法で実装します。
※ 1,000回分集計し1回あたりの平均を求めるため、リクエスト実行回数(ループ回数; EXEC_DECISION_NUM)を1,000にします。
※ 今回の処理速度を検証した環境は2コア/CPUのため、本来は10スレッド程度の同時アクセスを行うほうが処理効率は良いが、今回の目的は1回あたりの処理時間を計測することなので1スレッドでの処理としています。(プログラム内では、THREADNUM = 1で固定)
処理速度の計測結果
| 子要素Entity2の個数 | 送信XMLデータサイズ (byte) | Corticon 内での処理時間 (ms) | レスポンス時間 (ms) |
|---|---|---|---|
| 10 | 5,562 | 1 | 35 |
| 100 | 47,213 | 2 | 59 |
| 1,000 | 464,814 | 319 | 579 |
■ RESTの結果
| 子要素Entity2の個数 | 送信JSONデータサイズ (byte) | Corticon 内での処理時間 (ms) | レスポンス時間 (ms) |
|---|---|---|---|
| 10 | 4,836 | 1 | 29 |
| 100 | 45,247 | 2 | 36 |
| 1,000 | 450,248 | 110 | 168 |
※ 処理時間(ms)は全て一回あたりの平均処理時間
※ レスポンス時間は今回のクライアントプログラムで集計した時間を1,000で割ったもの
※ Corticon 内での処理時間はCorticon Server Consoleでの表示で確認
まず、SOAP/REST両方に共通して見られる傾向を説明します。
「Corticon内での処理時間」は、Corticon Serverがデータを受け取ってからデシジョンサービスの実行が終わるまでの時間です。「レスポンス時間」は、クライアントプログラムからリクエストを出してレスポンスが戻るまでの実際の所要時間です。今回はCorticon Serverとクライアントプログラムは同じlocalhost内で動作しているため、データのネットワーク転送に要する時間は最小限ですが、それでも様々なオーバーヘッドがあるため、データ量が少ない間は「Corticon内での処理時間」と「レスポンス時間」を比較するとかなり差が見られます。
検証するデータ量が増えるにつれて、「Corticon内での処理時間」は増えていきます。Corticon Serverの特徴として、データ量が増えればその分デシジョンサービスの実行に時間がかかるということになります。Corticon Server内での一回あたりのデシジョンサービス処理時間を減らすには、まずは送受信するデータ量の削減が可能かどうかを検討すると効果的です。例えば、ルールの判定に必要最小限のデータのみの送受信で済むように、クライアント側の実装を修正する(場合によってはルール側の実装も修正する)、などです。
次に、今回の検証では、偶然にもJSONとXMLのデータサイズがほとんど同じでしたが、データサイズの差以上にREST(JSON)のほうが速い結果になりました。
この理由としては、XMLとJSONでは同じ内容のデータでもパース処理の差があるためだと推測されます。したがって、少しでも速くレスポンスを返すには、通信方式としてSOAP(XML)よりもREST(JSON)を検討する余地はあります。また実際の業務で必要な複雑な語彙であれば、同じ内容のデータでもJSONのほうがデータサイズはかなり小さくなることがあるかもしれません。その場合は、さらに速度差が出る可能性があります。
最後に、Corticonではネットワーク転送やデータのパース処理に関連するオーバーヘッドがないインプロセスという方法で実装することができ、より高速に動作させることができます。そのやり方や処理速度の検証は、また別の機会に紹介します。
著者紹介
|
|
情報基盤事業部 製品統括部 技術4部 |












