Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

JPAのキャッシュを使ったアプリケーション高速化手法

JPOUG Tech Talk Night #2 で話した内容に飲み会で質問された内容を加えています。

Related Books

Free with a 30 day trial from Scribd

See all

Related Audiobooks

Free with a 30 day trial from Scribd

See all
  • Be the first to comment

JPAのキャッシュを使ったアプリケーション高速化手法

  1. 1. JPAのキャッシュを使った アプリケーション高速化手法 伊藤智博
  2. 2. ここで示されている見解は 私個人のものであり、 所属会社の見解を 反映したものではありません 2
  3. 3. 注意 • 都合上、省略している部分もあります • スペースの都合上、SQLでは*を多用します • DBの表名やJavaのクラス名などは論理名を使用します • JPAの実装系により、動作が異なる可能性があります 3
  4. 4. 目次 4 • 自己紹介 • JPAとは • キャッシュの効果 • キャッシュ使う前に • N+1問題を考える • よくある改善策 • キャッシュならではの改善策 • キャッシュチューニング
  5. 5. 自己紹介 • 名前: – 伊藤 智博(いとう ちひろ) • 勤務先: – 日本オラクル株式会社 • 仕事で使う製品/技術: – Java EE/SE/ME, JVM, OEP, Coherence, SQL • 連載: – Javaはどのように動くのか~図解でわかるJVMの仕組み – http://gihyo.jp/dev/serial/01/jvm-arc 5
  6. 6. はじめに • データベースにアクセスしないシステムは存在します。 • その理由は、 – データを保存しない – 保存先をデータベースにする必要は無く、ファイルで十分 – データベースにアクセスしていたら処理が間に合わない • という理由でしょう 6
  7. 7. はじめに • DBにアクセスしたら負けな処理は存在し、その多くはメモリ にデータをキャッシュすることで処理時間を短縮します • 他のシステムでもメモリにデータをキャッシュすれば処理時 間の短縮を見込めますが、メモリは揮発性のため永続化は難 しく、メモリ<->DB間での永続化の仕組みが必要です • 自分でこんな仕組みを作るのは難しいです • そんな時は、JPAを使いましょう! 7
  8. 8. JPAとは? 8
  9. 9. JPAって何なの? • JPA は Java Persistence API の略です。 • Java の規格である JSR317 として策定 • 以下の3つからなる – API (javax.persistenceパッケージ) – Java Persistence Query Language (JPQL) – オブジェクト/関係メタデータ 9 出典:wikipedia
  10. 10. JPAって何ができるの? • Entity の自動生成ができます • JPQL / Criteria API / Native Queryによる問い合わせ • IDE による JPQL のコードチェック • キャッシュ 10 今回はキャッシュのお話しです
  11. 11. キャッシュの効果 11
  12. 12. キャッシュの効果 • アプリの処理時間短縮 • Entityオブジェクトの共有 • SQL実行の負荷低減 12
  13. 13. キャッシュの効果 -処理時間短縮 13 SELECT * FROM 顧客 WHERE 顧客ID = 1 O/R Get顧客(1) 1 2 SELECT * FROM 顧客 WHERE 顧客ID = 2 顧客ID 名前 都道府県ID 2 BBB 1 顧客ID 名前 都道府県ID 1 AAA 1 Get顧客(2) ここでDBのデータから JavaのEntityを作成する SELECT * FROM 顧客 WHERE 顧客ID = 1Get顧客(1) 1 顧客ID 名前 都道府県ID 1 AAA 1 同じデータが何度もネットワークを通過する As-is アプリ DB
  14. 14. キャッシュの効果 -処理時間短縮 14 SELECT * FROM 顧客 WHERE 顧客ID = 1 JPA Get顧客(1) 1 2 SELECT * FROM 顧客 WHERE 顧客ID = 2 顧客ID 名前 都道府県ID 2 BBB 1 顧客ID 名前 都道府県ID 1 AAA 1 Get顧客(2) キャッシュされたEntityを返すため、 Entity作成処理が不要 Get顧客(1) 1 キャッシュされる 1 キャッシュに無いのはDBに取り行くTo-Be キャッシュにあるから DBに問い合わせしない アプリ DB
  15. 15. キャッシュの効果 -Entityオブジェクトの共用 15 SELECT * FROM 顧客 WHERE 顧客ID = 1 O/R Get顧客(1) 1 顧客ID 名前 都道府県ID 1 AAA 1 複数スレッドで同じ処理をしていると、 スレッド数分だけリソースを消費する SELECT * FROM 顧客 WHERE 顧客ID = 1Get顧客(1) 1 顧客ID 名前 都道府県ID 1 AAA 1 As-is DB
  16. 16. キャッシュの効果 -Entityオブジェクトの共用 16 DB SELECT * FROM 顧客 WHERE 顧客ID = 1 JPA Get顧客(1) 1 顧客ID 名前 都道府県ID 1 AAA 1 複数スレッドで同じ処理をしていると、 最初に処理したスレッドがキャッシュに載せて、 他のスレッドはキャッシュのEntityを使う Get顧客(1) 1 1 キャッシュにあるから このスレッドでは DBに問い合わせしない To-Be
  17. 17. キャッシュの効果 -SQLの負荷低減の例 • SELECT 顧客.*, 血液型.*, 性別.*, 都道府県.*, 郵便番 号.* FROM 顧客 JOIN 血液型 ON (顧客.血液型ID = 血液型.ID) JOIN 性別 ON (顧客.性別ID = 性別.ID) JOIN 都道府県 ON (顧客.都道府県ID = 都道府県.ID) JOIN 郵便番号 ON (顧客.郵便番号ID = 郵便番号.ID) WHERE 年齢 = ? 17 実際は*ではなく、各カラムが羅列されます 5つのテーブルを結合するのか、 大変だなぁ As-is
  18. 18. キャッシュの効果 -SQLの負荷低減の例 • キャッシュを使うと、外部キーでの参照は参照先のオブ ジェクトがキャッシュにあればキャッシュから取れます。 • そのため、SQLでJoinする必要がなくなり、DB側のI/O やJoin処理が無くなります。 • SELECT * FROM 顧客 WHERE 年齢 = ? 18 実際は*ではなく、各カラムが羅列されます 結合が無くなった♪ To-Be Joinの無くなったSQL
  19. 19. キャッシュを使う前に 19
  20. 20. キャッシュの種類 • 以下の2つのキャッシュがあります。 • Entityキャッシュ – Key-Valueの組み合わせでキャッシュされます – 主キー(複合キーも可能)をKeyとして、Entity(レコードに相当) をValueとして取得します • 問合せ結果キャッシュ – 問合せとパラメータの組合せで問合せ結果をキャッシュします 20 今回はEntityキャッシュを 重点的に紹介します
  21. 21. 検討項目 • キャッシュ対象を決めるために以下を検討しましょう – アクセス方法 • どのようにデータにアクセスするのか – データ数 • データはどのくらいあるのか 21
  22. 22. アクセス方法とは • データにどのようにアクセスするかを確認します – 主キーによるアクセス • 例:SELECT * FROM 顧客 WHERE 顧客ID = ? – 主キー以外によるアクセス • 例:SELECT * FROM 顧客 WHERE 都道府県ID = ? 顧客ID 名前 都道府県ID 1 AAA 1 2 BBB 1 3 CCC 2 主キー 外部キー 都道府県ID 名前 1 XXX 2 YYY 主キー 外部キーを辿るのも主キー によるアクセスです 22
  23. 23. データ数 • キャッシュサイズの概算見積もり方法 – キャッシュするデータ数 × データのオブジェクトサイズ • データ数が多いとヒープをたくさん使います。 23
  24. 24. アクセス方法とデータ量での分類 24 ここに挙げているデータは一例であり、 システムによってデータ数とアクセス方法は変わる可能性があります。 血液型 性別 都道府県 郵便番号 顧客 受注 注文 発注 少ない 多い 主キー 主キー以外データ数 アクセス方法
  25. 25. アクセス方法とデータ量での分類 • Entityキャッシュと相性が良いパターン • 主キーによるアクセス/データ量が少ない – Entityキャッシュとの相性は非常に高い – 更新が無ければDatabaseに入れずEnumにしてしまうのもアリ – 例:血液型、性別など • データ量問わず主キーによるアクセスがほとんど – 全てキャッシュしきれない場合は、アクセス数の多いもの(Hot データ)のみをキャッシュする – 例:都道府県、郵便番号、顧客 25
  26. 26. アクセス方法とデータ量での分類 • 問合せ結果キャッシュと相性が良いもの • 主キー以外でのアクセスが多いがパターンが限られてい る – 顧客を年齢や性別で検索などはコレと相性が良い。 – パターンが多いが殆どは特定のパターンしか使わない場合は、 使用頻度の高いHotパターンのみをキャッシュする – 例:受注、注文、発注で日付が新しい物だけをキャッシュ 26
  27. 27. アクセス方法とデータ量での分類 27 血液型 性別 都道府県 郵便番号 顧客 受注 注文 発注 少ない 多い 主キー 主キー以外 Entity キャッシュ Enum Hotデータのみを Entity キャッシュ 問合せ結果 キャッシュ DB データ量 アクセス方法 今回の範囲
  28. 28. N+1問題を考える 28
  29. 29. 要件 • 顧客とその居住都道府県の情報をDBへ問い合わせます 29 DB 顧客表:n行 都道府県表:m行 一般的には n > m (≒47) 問い合わせ(SQL) 結果 アプリ
  30. 30. サンプルソース 30 //顧客情報をn件取得 List<顧客> custs = findAll(); for(顧客 c : custs ){ //顧客の居住地を1件取得 都道府県 pref = c.get居住地(); } n回実行 1回実行 顧客毎の居住地を取得する 顧客情報を全て 取得する
  31. 31. N+1問題とは • SQL で顧客情報 n 行の結果を取得(1回) • 顧客情報を使って都道府県情報を問い合わせ(n回) 31 DB SELECT * FROM 顧客 DB SELECT * FROM 都道府県 WHERE 都道府県ID = 顧客.居住地顧客 参照 1件ずつ取得
  32. 32. SELECT * FROM 都道府県 ・・・ SELECT * FROM 都道府県 ・・・ シーケンス 32 顧客ID 名前 都道府県ID 1 AAA 1 2 BBB 1 3 CCC 2 DB SELECT * FROM 顧客 SELECT * FROM 都道府県 WHERE 都道府県ID = 1 都道府県ID 名前 2 YYY 都道府県ID 名前 1 XXX findAll() O/R 1 2 3 .get居住地() 1 .get居住地() 1 .get居住地() 2 1 2 3 都道府県ID 名前 1 XXX n回SQLが 発効される n件取得
  33. 33. よくある改善策は? 33
  34. 34. N+1問題 -よくある改善策 • 最初から結合しておく 34 DB SELECT * FROM 顧客 c JOIN 都道府県 p ON (c.居住地 = p.都道府県ID) 結合された結果 結合したSQL
  35. 35. シーケンス -よくある改善策 35 顧客ID 名前 都道府県ID 1 AAA 1 2 BBB 1 3 CCC 2 DB SELECT * FROM 顧客 JOIN 都道府県 p ON (c.居住地 = p.都道府県ID) findAll() O/R 1 2 3 .get居住地() 1 .get居住地() 1 .get居住地() 2 1 2 3 1 2 最初から紐付ける 都道府県ID 名前 1 XXX 1 XXX 2 YYY 結合した結果を取得する 最初から紐付いてる Entityを返す N+1から1に 減った
  36. 36. 36 SQLでは簡単にJoinできるので よくある改善策だけど、 JPAではどうすれば実現できるの?
  37. 37. 結合したSQLが発行されるようにするには • JPQL に JOIN FETCH を付けて結合する • フィールドに @JoinFetch を付与 37 SELECT c FROM 顧客 c JOIN FETCH c.居住地 @JoinFetch @OneToOne private 都道府県 居住地 または 都道府県を保存し ているフィールド
  38. 38. 38 とりあえず、N+1問題避けるために 全関係に @JoinFetch 付けとこうぜ!
  39. 39. 全部Join Fetchだと、どうなるの? • 最初から関係する表を全てJoin Fetchしておくと、 • SQLでは全ての表が結合されるため膨大になってしまう 39 SELECT * FROM 顧客 c JOIN 都道府県 p ON (c.居住地 = p.都道府県ID) ・・・(略)・・・ 処理に使わない表まで結合すると・・・ ・DBの結合処理が無駄 ・Entityオブジェクト作成処理が無駄 DB
  40. 40. 全部に @JoinFetch してはいけない • 一対多のように、 相手が多の関係が増えるとSQLの結果が爆発的に増加 • キャッシュに乗っていればEntityが使われますが、 乗っていなければEntityが作成されるため、 Java側の使用オブジェクト数も爆発的に増加する 40
  41. 41. @JoinFetch をどう使うのか • “必ず”セットで使うものを @JoinFetch する • それ以外は処理に応じてJPQL で JOIN FETCHする 41
  42. 42. 42 Join Fetchしたし、N+1問題も解決だ! ホントに?
  43. 43. キャッシュならではの改善策 43
  44. 44. 改善策のおさらい • 事前に複数のデータを結合すると – 実行回数:1回 – 転送量:n * (顧客のサイズ+都道府県のサイズ) 44 顧客ID 名前 都道府県ID 1 AAA 1 2 BBB 1 3 CCC 2 都道府県ID 名前 1 XXX 1 XXX 2 YYY n行
  45. 45. 改善策のおさらい 45 ん? 都道府県の情報って重複してない? 顧客ID 名前 都道府県ID 1 AAA 1 2 BBB 1 3 CCC 2 都道府県ID 名前 1 XXX 1 XXX 2 YYY n行
  46. 46. ベストは? • でも、ベストなのは 重複無く顧客情報と都道府県情報を取ってくる 46 顧客ID 名前 都道府県ID 1 AAA 1 2 BBB 1 3 CCC 2 都道府県ID 名前 1 XXX 2 YYYn行 m行
  47. 47. 47 そんなに都合良くいくのか?
  48. 48. キャッシュならではの改善策 • 関係へのアクセスは主キーによるアクセスのため Entityキャッシュを参照する • JPAでは事前に結合しなくてもn+1より良い – 最大SQL実行回数:m+1回 • ( n > 47 >= m なのでn+1より少ない) – 最大データサイズ:n * 顧客のサイズ + m * 都道府県のサイズ • 全てキャッシュされていれば – SQL実行回数:1回 – データサイズ:n * 顧客のサイズ 48 ↑で満足してはダメ ←これを目指します
  49. 49. シーケンス -1回目の実行 49 顧客ID 名前 都道府県ID 1 AAA 1 2 BBB 1 3 CCC 2 DB SELECT * FROM 顧客 SELECT * FROM 都道府県 WHERE 都道府県ID = 1 都道府県ID 名前 2 YYY 都道府県ID 名前 1 XXX findAll() JPA 1 2 3 .get居住地() 1 .get居住地() 1 .get居住地() 2 1 2 SELECT * FROM 都道府県 WHERE 都道府県ID = 2 キャッシュされる キャッシュされたEntityを返す 1 2 3 主キーアクセスでは無いデータは、 キャッシュしない設定にするのもあり
  50. 50. シーケンス -2回目の実行 50 顧客ID 名前 都道府県ID 1 AAA 1 2 BBB 1 3 CCC 2 DB SELECT * FROM 顧客findAll() JPA 1 2 3 .get居住地() 1 .get居住地() 1 .get居住地() 2 1 2 1回目でキャッシュされている 1 2 3 DBへのアクセスは1回になって 処理時間が短くなった!!
  51. 51. コラム –Lazy/Eager Joinとの違い- • キャッシュすることと、Lazy/Eager Joinは別物になります。 • Lazy Joinは関連先のEntityを使用するときに、関連先のデータを新 たに問い合わせます。 • Eager Joinは問合せ時に、関連先のデータをさらに新たに問い合わ せてEntityを構築しておき、使用時に新たな問合せをしません。 • Eager/Lazy Joinで、新たに問い合わせる際に、キャッシュされてい ればキャッシュを使用し、キャッシュされていなければDBに問い合 わせます。 • Lazy/Eager Joinは新たに問い合わせるタイミングが異なるだけです。 51 実装系やバージョンにより、動作が異なる可能性があります
  52. 52. キャッシュチューニング 52
  53. 53. 何をどのように検討するか 53 何をどのように検討すれば良いのだろう・・・?
  54. 54. チューニング事項 • ウォーミングアップ – 処理を受け付ける前に、必要なデータを全てを読み込んで キャッシュさせる • データ毎にキャッシュ数を検討する • データ特性に合わせた参照を検討する 54 まず考えるのはこの3つ
  55. 55. データの特性に合わせたキャッシュ設計 • キャッシュ数 – デフォルトは100個入るList構造を作成する – 適切な数にしないと、無駄なヒープを消費することもある • 参照方法 – デフォルトはSoft参照のため、ヒープが足りなくなると削除さ れる。 – 強参照(FULL)にすると常にキャッシュし続ける 55
  56. 56. データの特性に合わせたキャッシュ設計例 • 都道府県情報 – 全てキャッシュしておくならFULLで47個にする • 郵便番号 – 7桁なので最大10,000,000個の可能性がある – メモリに余裕があれば、ヒープサイズを大きくして全てキャッ シュするのもアリ。 – メモリに余裕が無ければ、全てキャッシュすることは現実的で は無い。ホットデータだけをキャッシュする 56 53個分すら勿体ない
  57. 57. まとめ 57
  58. 58. まとめ • JPAはJavaの標準規格 • キャッシュを使うとシステム全体の負荷を減らせる • アプリの処理時間が短くなる • Entityオブジェクトを保持するため常にメモリは使う • 使われないデータをキャッシュしても無駄なので、なん でもキャッシュすれば良い訳ではない。 • データ特性に合わせたキャッシュ設計が重要 58
  59. 59. FIN. 59

×