アプリケーションに応じた AOP による高速化が可能な 永続システム 数理・計算科学専攻 千葉研究室 青木 康博 指導教官: 千葉 滋
永続オブジェクト・システム リファレンスを辿る際に自動的に SQL 発行 データベースを意識する必要がない EJB2 など リファレンスを辿る際に自動的に SQL 発行 データベースを意識する必要がない O/R 間のインピーダンス・ミスマッチを解消 アプリケーションの開発効率を高める < paper table > 永続クラス 1 Josh ・・・ 30 class Paper { String title Author author; … } class Author { String name; Location location; …. Paper p = …; Author a = p.author 2 GlunJ 98 ・・・ 自動的なデータ取得 id title authr_id < author table > SELECT id FROM author t0 WHERE t0.id = 98 ・・・ 98 Aoki 99 Tobe ・・・ データベース id name
リファレンスを辿る毎に SQL を発行していては 効率が悪すぎる 高速化の必要性 リファレンスを辿る毎に SQL を発行していては 効率が悪すぎる データベースからの効率よいデータ取得が必要 まとめてデータを取得すれば SQL 発行を最適化出来る E.g.) Author オブジェクトのみを一括して取得したい void showPaperList(List papers) { Iterator it = papers.iterator(); while(it.hasNext()) { Paper p = (Paper)it.next(); Author a = p.getAuthor(); / * p.title と a.name を表示 */ } <RDB> Author を取得 authrs papers 過剰なDB アクセス
高速化のためのカスタマイズ例 効率よくデータを取得したい 今後利用されるデータを先読みし、まとめて取得したい <RDB> 処理内容に応じて先読みするデータをカスタマイズ List papers Paper a0 a1 a2 Author p0 g0 g1 g2 Genre void getGenres(List papers) { while(…) { Paper p = (Paper)it.next(); Genre g = p.getGenre(); ….}} 過剰な DB アクセス Paper p1 Paper Author ではなく Genre を取得 p2 void showPaperList(List papers) { Iterator it = papers.iterator(); while(it.hasNext()) { Paper p = (Paper)it.next(); show(p);}} <RDB> 過剰なDB アクセス authrs Author を取得 papers
従来のオブジェクト指向による クラスごとのカスタマイズ 従来のオブジェクト指向による クラスごとのカスタマイズ 効率のよいデータ取得が実現出来ない 過剰な DB アクセスをなくそうとすると、不必要なデータを大量に取得してしまう void getGenres(List papers) { while(…) { Paper p = (Paper)it.next(); Genre g = p.getGenre(); ….}} class Paper { String title; …} @FETCH Author author; Genre genre; @FETCH Genre genre; Genre を取得 Author は不要 過剰なDB アクセス void showPaprList(List papers) { Iterator it = papers.iterator(); while(it.hasNext()) { Paper p = (Paper)it.next(); show(p);}} <RDB> Author を取得 Genre は不要 authrs papers
提案: AspectualStore の開発 アスペクト指向による高速化を支援する永続システム アプリケーションの文脈に応じたクラス横断的なカスタマイズ リファレンスの辿られ方に応じて最適化の指示を変更できる showPaperList(List) { … } Proceeding Paper Author prc List papers p0 ownr p0 最適化の 指示 getGenres(List) { … } p1 Paper Genre p0 gnr p2 最適化の指示をアスペクトとして分離 徐々に最適化の指示を追加できる
永続システム: AspectualStore アスペクト指向による高速化を支援 コード量: 18,000 行程度 外部企業が現在デモアプリを AspectualStore 上で 開発中 より大規模な実験を行う予定 <デモアプリ>
アスペクト記述 メソッド処理ごとのデータ取得の指示 class PrefetchDefine { @before(“{Persistable p = $1; Loader l = $1.getBody().getLoader(); l.addFetch(“author”); l.load(); }”) Pointcut p1 = Pcd.call(“showPaperList(..)”); @before(“{ Persistable p = $1; Pointcut p2 = Pcd.call(“getGenres(..)”); } showPaperList() に対する最適化 ・ Author オブジェクトを先読み getGenres() に対する最適化 ・ Genres オブジェクトを先読み
先読みすべきデータを動的に決定するアスペクトも可能 どのようにリファレンスが辿られるかを検出 リファレンスの辿られ方がわかりにくい場合 最適化の指示をまとめて記述したい場合 void showPaprList(List papers) { Iterator it = papers.iterator(); while(it.hasNext()) { Paper p = (Paper)it.next(); show(p); …}} papers 1st iteration : リファレンスの辿り方を収集 2nd iteration : papers 先読みの指示を出す それ以降の DB アクセスは 発生しない! Paper p0 Author a name title Location Paper p1 title Author a name ・・・ Paper p2 ・・・
アスペクト記述: GluonJ Context クラスに DB アクセス履歴を格納するためのフィールドを定義 @Glue public class PrefetchDefine { /* Context の定義 */ @Refine static class ContextDefine extends Context { Set loadedFields = new HashSet(); int depth = 0; } /* DB アクセスを引起したパスの格納 */ @Before(“{ // フィールド名を格納}") Pointcut pc = Pcd.call("...PersistentEntity#load*(…)"); /* prefetch 定義 */ @Refine static class IteratorDefine extends PersistentIterator { public Object next(Context cxt) { if(context.hasLoadedFields()) // context の内容を先読み super.next(cxt); }} Context クラスに DB アクセス履歴を格納するためのフィールドを定義 papers に付加された Context に showPaperList() 内で DB アクセスを 引き起こしたフィールドのパスを追加 Iterator.next() を拡張して、 本来の処理の前に先読み処理を追加
アスペクトによる最適化の支援 既存の永続システム + AOP だけでは不十分 AspectualStore が提供する主な機能 最適化の指示に煩雑な記述が必要 リファレンスの辿られ方を管理する必要がある アスペクト内からアプリケーションの文脈を利用出来ない AspectualStore が提供する主な機能 最適化を行ための API 永続オブジェクト(コレクション) への最適化の指示 アプリケーションの文脈を格納するための機構 アスペクトからアプリケーションの文脈を利用可能 Tomcat からのロードタイム・ウィービング アスペクトのウィーブ / アンウィーブの手間を削減
最適化を指示する API 永続オブジェクトに直感的に指示が出せる API 直接 SQL を記述するのは煩雑すぎる 外部キーの名前と値が必要 “FROM Paper FETCH p.title LEFT JOIN FETCH p.author.. WHERE t0.prc_id = 98” <Hibernate によるデータ取得の記述> author.name と title を取得 <データベース> PK title prc_id List papers 98 Proceeding 98 Paper X prc 著者X’ 著者P’ 著者Q’ 98 Journal Paper P name paperテーブル jrl journl PK name Paper Q name Genre gen name authorテーブル
最適化を指示する API 発行する SQL はリファレンスの辿られ方によって異なる Proceeding List papers prc author.name と title を取得 Proceeding List papers prc Paper X Journal 著者X’ 著者P’ 著者Q’ Paper P journl <Proceeding から 取得された場合> Paper Q proceeding テーブル PK 5 “FROM Paper FETCH p.title LEFT JOIN FETCH p.author.. WHERE t0.prc_id = 5” PK prc_id jrl_id 1 5 2 18 3 5 journl PK 4 5 <Journal から取得された場合> 5 18 “FROM Paper FETCH p.title LEFT JOIN FETCH p.author.. WHERE t0.jrl_id = 18” 6 18 18 paperテーブル journal テーブル
最適化を指示する API 永続オブジェクトに直感的に指示が出せる API 直接 SQL を記述するのは煩雑すぎる 外部キーの名前と値が必要 author.name と title を取得 loader.addFetch(“title”); loader.addFetch(“author.name”); loader.load(); <AspectualStore> <データベース> PK title List papers Paper X 著者X’ 著者P’ 著者Q’ ・データベースに関する作業を 意識する必要がない ・外部キーの名前や値 ・どのようにリファレンスを辿ってきたかを AspectualStore が内部で管理 (後述) Paper P paperテーブル journl PK name Paper Q authorテーブル
アプリケーションの文脈を格納 文脈を格納するための機構 アスペクトからアプリケーションの文脈を利用したい メソッド内でどのようなリファレンスが辿られるか SQL を発行するためには、親オブジェクトが必要 既存の永続システム + AOP では不十分 時間的に素なデータ・アクセスの取得は困難 refine や cflow を駆使した煩雑な記述が必要 永続オブジェクトは何度も再利用される 単純に親への参照を持たせるだけでは不十分 [’05 Awais ら] Proceedng prc List papers Paper p0 Author a Location title name Paper p1 Journal j ・・・
アプリケーションの文脈を利用可能 永続オブジェクトに Context オブジェクトを付加 キャッシュ: 何度も再利用される E.g.) 論文X => 学会Wから参照 論文X’=> 著者Aから参照 学会W Set 論文P <モデル> <キャッシュ> 論文X author 著者A papers 論文Q Paper PersistentEntity entity context values state 論文X’ 共有 <文脈情報> 個別 個別 Context キャッシュ Contxt Contxt 親コンテキストへの参照 親コレクションへの参照 title=“aop” year=1999 親: 学会 親: 著者A
ロードタイム・ウィービング ウィーブ/アンウィーブにかかる手間を削減 AspectualStore への拡張を JAR ファイルに反映させる必要なし Tomcat 用のクラスローダを実装 <アプリケーション起動時> 永続クラスへのバイトコード変換 ・ 永続化処理の追加 (リファレンスが参照された 際の SQL 発行) ・ Context クラスの追加 <クラスのロードタイム時> アスペクト記述の反映 Common CL Catalina CL Shared CL WebApp CL ASClassLoader
AspectualStore による最適化の得失 アプリケーションの挙動を変える最適化はできない 誤った最適化指示は無視される JDBC で直接指示する程の最適化は出来ない 例) 主キーは必ず取得 Proceeding prc = …; List papers = prc.getPaprs(); for( Paper p : papers) { if(p.getGenre().equals(“AOP”) { // show Paper details. }} Proceeding papers prc Paper p1 PK genre AOP 1 AOP 2 Paper p3 Paper p6 AOP 3 4 AOP 5 Paper p4 6 Expression exp = ExpFactory.eq(“genre”, “AOP”); loader.add(exp); AOP paperテーブル
時間・DBアクセス・ メモリ量の比較実験 文献検索システム 既存の永続システム AspectualStore SQL 直書き オブジェクト指向によるクラス毎の最適化 AspectualStore アスペクト指向による文脈に応じた最適化 SQL 直書き JDBC を直接利用してほしいデータを指示 Bibliography journals year proceedngs Journal Proceeding name vol name intro papers papers Paper title abstract content Genre genre name author サーバ:P4 Xeon 3.06G, 2GB, PostgreSQL 7.4.2 DBサーバ:Linux 2.6.7 アプリケーションサーバ:Mac OSX Tomacat 5.5, Java 1.5 LAN 1000 BaseTX papers Author Location name age location country town
実験結果 既存の永続システムに比べて大幅な速度向上を確認 DB 通信回数 / 取得データ数の削減 オブジェクト生成数は同等 実行時間 (秒) 括弧内は主キー と 外部キーを除いた値 実行時間 (秒) DB 通信 (回) オブジェクト 生成 (個) 取得データ 数 (個) 既存永続システム 3.48 43 381 2528(1522) AspectualStore 2.46 8 381 759(289) 30%向上 SQL 直書き 2.16 6 181 471(287) 38%向上 既存の永続システムに比べて大幅な速度向上を確認 DB 通信回数 / 取得データ数の削減 オブジェクト生成数は同等 予備的な実験 においては…
関連研究1:O/R mapper SQL 直書きとリファレンス参照の併用 リファレンス参照は従来同様、非効率 Hibernate Cayenne Torque … SQL 直書きとリファレンス参照の併用 String query = “FROM Proceeding p LEFT JOIN FETCH p.papers LEFT JOIN FETCH p.papers.author WHERE …”; // ルート・オブジェクトの取得 Proceeding p = s.createQuery(query).load; List<Papers> papers = p.getPapers(); showPaperList(papers); 利用されそうなオブジェクトを ルート・オブジェクトに含める リファレンス参照は従来同様、非効率 高速な参照が必要なときは SQL を直接書く AspectualStore ではSQL ではなく高速化のヒントを記述 最適化コードが元のプログラムに混在
関連研究2:自動的な最適化 アスペクト記述が必要ない AUTOFETCH [Ali et al. ‘06] AspectualStore Context-cotrolled prefetch [Philip at al. ‘99] データベースのアクセス・パターンを基に最適化 AUTOFETCH [Ali et al. ‘06] プロファイリングによる最適化 PrefetchGuide [Wook-Shin et al. ‘03] 実行時のアクセス・ログによる動的な最適化 AspectualStore 手動できめ細かな最適化 メソッド処理などのオブジェクト以外の文脈も利用可能 性能テストをしながら徐々に最適化が可能
まとめ AOP を利用して高速化が可能な永続システム: AspectualStore の開発 (コード量: 約18,000 行) アプリケーション文脈に応じた最適化が可能 クラス横断的な処理を AOP でモジュール化 クラス毎の最適化指示だけでは不十分 AOP による最適化を容易に実現する構造を用意 永続オブジェクトへ指示を出すための API アプリケーションの文脈を格納するための機構 ロードタイム・ウィービング