3. Java Beans
コンストラクタ
package com.business; import java.sql.*; import java.util.List; import com.google.appengine.api.rdbms.AppEngineDriver; import com.google.appengine.api.datastore.DatastoreService; import com.google.appengine.api.datastore.DatastoreServiceFactory; import com.google.appengine.api.datastore.Entity; import com.google.appengine.api.datastore.EntityNotFoundException; import com.google.appengine.api.datastore.FetchOptions; import com.google.appengine.api.datastore.Key; import com.google.appengine.api.datastore.KeyFactory; import com.google.appengine.api.datastore.Query; public class bomBean{ StringBuffer respBuf = new StringBuffer(""); StringBuffer rv1 = new StringBuffer(""); String rv = ""; // For rdb String db = "business"; String user = "user1"; String pass = "pass1"; Connection con = null; // For kvs DatastoreService ds = null; public bomBean(){ //(1) try{ DriverManager.registerDriver(new AppEngineDriver()); con = DriverManager.getConnection("jdbc:google:rdbms://branecosmology:business/business", user, pass); //(2) } catch (SQLException e) { respBuf.append("jdbc Driver load error<r>"); } // For kvs ds = DatastoreServiceFactory.getDatastoreService(); //(3) } // beans : }
ビーンズからはMySQLとBigtableの両方にアクセスするので、コンストラクタ(1)では、どちらのアクセスにも対応できるようにしています。
- (2)ではCloud SQLで提供される専用ドライバを使用してMySQLデータベースへの接続を行い、
- (3)ではBigtableアクセス用にDatastoreServiceインスタンス(ここではds)を生成しています。
以上の準備でこのビーンズからは、Cloud SQLのMySQLとBigtableの両方にアクセスできるようになります。
ここで(2)のデータベースへの接続では、ユーザ名とパスワードでuser1とpass1を指定していますが、これは事前にGoogle APIs Consoleから登録してあるもので、図3のようにuser1にはSELECT, INSERT, UPDATE, DELETEの権限が付与されています。
マスタ登録
Cloud SQL
public String addItemInfo(String item_no, String item_name, String final_flag, String vendor_no, String vendor_name){ // Start Timer long start = System.currentTimeMillis(); //(1) // Start Timer try{ String sql = "insert into item_info values(?, ?, ?, ?, ? )"; //(2) PreparedStatement ps = con.prepareStatement(sql); ps.setString(1, item_no); ps.setString(2, item_name); ps.setString(3, final_flag); ps.setString(4, vendor_no); ps.setString(5, vendor_name); ps.executeUpdate(); //(3) // End Timer long stop = System.currentTimeMillis(); //(4) // End Timer long time = stop - start; rv = " 実行時間: " + time +"ms"; //(5) }catch(SQLException e){ rv = "接続失敗\n理由:" + e.toString(); }catch(Exception e){ e.printStackTrace(); rv = "登録失敗\n理由:" + e.toString(); }finally{ try{ con.close(); }catch(Exception e){} } return rv; }
リスト4はCloud SQLでのマスタ登録メソッドですが、Cloud SQL独自の処理パターンはなく、一般的なJavaからのMySQLアクセスそのものです。処理の流れとしては、メソッドが呼ばれた直後の(1)で処理開始時刻(単位ミリ秒)を測定して、その後登録処理を行います。
ここでは動的SQLを使用しているので、SQL文では(2)のように変数を?で指定し、変数値セット後(3)で実行しています。SQLが正常に実行された場合は処理が次の行に移り、そこで処理終了時刻を測定します(4)。あとは、実行時間(終了時刻と開始時刻の差)を計算して、(5)で戻り値rvにセットしています。
Bigtable
Bigtableにエンティティを登録する場合、同一キーのエンティティがすでに存在する場合でも登録できてしまいます。この場合は前のエンティティを上書きする形で結果的に更新処理になります。
従って、新規登録を保証するためには、登録エンティティのキー値と同じ値を持つエンティティが存在しているかどうかをプログラム内でチェックし、存在しない場合のみ登録するようにする必要があります。
public String addItemInfo_kv2(String item_no, String item_name, String final_flag, String vendor_no, String vendor_name){ // Start Timer long start = System.currentTimeMillis(); // Start Timer Key key = null; Entity itemf = null; try{ key = KeyFactory.createKey("item_info", item_no); //(1) Entity itemt = ds.get(key); //(2) }catch(EntityNotFoundException e1){ //(3) try{ itemf = new Entity(key); //(4) itemf.setProperty("item_no", item_no); //(5) itemf.setProperty("item_name", item_name); itemf.setProperty("final_flag", final_flag); itemf.setProperty("vendor_no", vendor_no); itemf.setProperty("vendor_name", vendor_name); ds.put(itemf); //(6) // End Timer long stop = System.currentTimeMillis(); // End Timer long time = stop - start; rv = " 実行時間: " + time + " ms"; } catch(Exception e3){ rv = "登録不成功 " +e3; } }catch(Exception e2){ rv = "キー生成エラー " +e2; } return rv; }
リスト5では(1)でキーを生成した後、(2)ではそのキーでエンティティを検索しています。もしそのキーを持つエンティティがまだ存在していない場合、つまり新規登録の場合は(3)の例外(EntityNotFoundException)が発生するので、その場合のみ書き込みを行うようにしています。書き込み処理では(4)で生成されたキーからエンティティを作成し、(5)以下でそのエンティティに対するプロパティ値をセットした後、(6)のputメソッドで書き込みを実行しています。
createKeyでのキー生成では、親kindを指定するものなど4種類ありますが、ここでは、kind名と文字列のキー名からキーを生成する方式を使用しています。
キーの生成からエンティティの作成では、下記のように分けて記述することもできますが、(1)のように引数内でのキー生成に変えるだけで、書き込み速度は明らかに速くなっています。
Key key = KeyFactory.createKey("item_info", item_no); Entity itemf = new Entity(key);
登録処理時間の測定結果
1 | 2 | 3 | 4 | 5 | |
Cloud SQL 登録(クラウド) | 125 | 110 | 123 | 112 | 119 |
Cloud SQL 登録(ローカルCI) | 595 | 439 | 617 | 427 | 439 |
Cloud SQL 登録(ローカルMI) | 257 | 31 | 28 | 25 | 31 |
Bigtable 登録(クラウド) | 47 | 19 | 18 | 22 | 21 |
※CIはGoogle Cloud SQLインスタンス、MIはMySQLインスタンスを意味しています。
登録時間測定は、クラウド環境でCloud SQLとBigtableで、ローカル環境でCloud SQLインスタンスを使用した場合(CI)とMySQL インスタンス(MI)の2種類で、それぞれ5回測定しています。
表1を見ると、クラウド環境ではBigtableアクセスの方がCloud SQLよりかなりパフォーマンスが良くまっています。また、ローカルインスタンスではMySQL(MI)の方がCloud SQL(MI)より圧倒的に良いパフォーマンス結果になっていますが、これは前にも見たように、アクセスするMySQLデータベースがローカルかクラウド上かの差異も影響しているはずです。
全体としては、クラウド環境でのBigtableアクセスが最も高速で、その次に速いのがMySQLインスタンスを使用したローカルでのCloud SQL登録となっています。
他に登録以外の処理を含めたパフォーマンス全体としては、次の傾向が見られます。
- 一般に1回目の処理で2回目以降よりも時間がかかる場合があり、表1の登録ではローカルMIのCloud SQLとクラウドのBigtableでその傾向がでています。この傾向は他の測定ではもっと極端に出る場合もあります。
- 登録処理ではありませんでしたが、初回のアクセスで時間が掛からない場合でも、連続アクセスの途中で時間がかかる場合も出てきます。
- 計測は2日間に数回行い、最後に行った結果を掲載していますが、実行日および時間によっても測定結果がやや異なってくる傾向があります。
全平均 | 1-4平均 | |
Cloud SQL 登録(クラウド) | 117.80 | 116.00 |
Cloud SQL 登録(ローカルCI) | 503.40 | 475.00 |
Cloud SQL 登録(ローカルMI) | 74.40 | 28.75 |
Bigtable 登録(クラウド) | 25.40 | 20.00 |
表2は、表1の測定時間の平均値ですが、全平均の他に1-4平均の項目があります。ここで1-4平均とは、最も時間の掛かったアクセスを除いた平均で、リスト1では、Cloud SQLおよびBigtable2の1回目、Bigtable1の3回目を除外した平均値です。これは次の参照などで顕著ですが、1回目の測定で極端に時間が掛かる場合があり、平均値がその後のアクセス実感と異なってしまうことから掲載しています。
表1をグラフ表示するとグラフ1のようになります。グラフ表示により、クラウド環境ではCloud SQLよりもBigtableの方が登録処理速度で勝っているのがはっきり見て取れます。また、業務系・基幹系でCloud SQLを使用する場合は、既存オン・プレミスシステムからの移行になるケースがほとんどなので、グラフ1ではCloud SQL(ローカルMI)と、Cloud SQL(クラウド)の対比に近い形になると考えられます。
Google App Engineのクラウドを構成するサーバでは、一般的なパーツが使用されていると言われており、スケールアウトによって巨大な分散システムを構成しています。従って、登録データが書き込まれるハードディスクなども特別なものではなく、PCなどにも使用されている一般的なのもが使われているはずです。
この場合、ハードディスクアクセスの平均的な時間は、例えば1ブロック4kバイトのデータでは25~40ミリ秒程度です。