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.

クリスマスを支える俺たちとJava(JJUG CCC 2015 Spring AB4)

4,430 views

Published on

2015/04/11に開催されたJJUG CCC 2015 SpringのタイムテーブルAB4にて発表した「クリスマスを支える俺たちとJava」の資料です。

---
AB-4 クリスマスを支える俺たちとJava

阪田 浩一 (フリュー株式会社/関西Javaエンジニアの会)

プリントシール機が、話題になった10年以上前のころと変わらず若い女性に利用されていることをご存知でしょうか?

私が所属するフリュー株式会社は、プリントシール機(プリ機)を出している会社です。そしてWebにてプリ機と連動して画像を取得するサービスを提供しています。実はこのサービス、会員数が1000万人を超えており、女性で特定の年齢層であれば90%以上の方が会員となっています。

JavaとRDBMS、分散ファイルシステムMogileFSにて構築したこの少し古いWebアプリケーションは、当初ここまでの規模になるとは想定していませんでした。

スーパーエンジニアがいない普通のエンジニアチームが、この数年間で増加する会員数とともにぶち当たった問題とその解決策についてご紹介します。

Published in: Technology
  • Be the first to comment

クリスマスを支える俺たちとJava(JJUG CCC 2015 Spring AB4)

  1. 1. クリスマスを支える   俺たちとJava 阪田  浩一   フリュー株式会社   関西Javaエンジニアの会 1
  2. 2. 2 2 @jyukutyo 著書 3冊 関西Javaエンジニアの会 (関ジャバ) 中の人・発起人 blog:Fight the Future http://jyukutyo.hatenablog.com/ フリュー株式会社 所属 エンジニア歴12年 36歳 SI業界での客先常駐 9年 Web系? 3年 文学部哲学科卒 塾講師アルバイト 阪田 浩一
  3. 3. 要約 3 普通のエンジニアたちが
  4. 4. 要約 4 いろんな   技術的課題、   プロセスの課題、   チームの課題に
  5. 5. 要約 5 ぶつかりつつも   サービスを開発、運用   しているお話です
  6. 6. 6 扱えないこと:   大規模サービスを   運用する   ベストな方法
  7. 7. 目次 7 • 対象となるアプリケーションについて   • わき上がる課題とその対処   • RDBMS編   • JVM編   • サーバ編   • まとめ
  8. 8. 要約 8 本題へ
  9. 9. 9 僕たちのアプリケーションは   クリスマスの負荷に   耐えられずにいた Photo by Yuichi Sakuraba https://flic.kr/p/9JYRSQ
  10. 10. 10 そう、クリスマスは   1年で最もアクセスが   ある日 Photo by Yuichi Sakuraba https://flic.kr/p/9JYRSQ
  11. 11. 11 恋人たちがみんな   撮影をする日だからだ Photo by Yuichi Sakuraba https://flic.kr/p/9JYRSQ
  12. 12. 12 撮影?   そう、プリントシール機   での撮影…
  13. 13. プリントシール機とは 13
  14. 14. 14 弊社フリュー株式会社は   プリントシール機の   トップメーカーです
  15. 15. 15 全国の機械の   半分以上がフリューです
  16. 16. 16 なのですが、   今回はこの筐体の   実装ではなく
  17. 17. 簡単な図 17 ここの詳細に   ついてです Internet
  18. 18. Webとの関係は? 18 撮影した画像は   Webサイト/アプリから   取得できます
  19. 19. 19 アプリを除く   Webシステム部分   (サイト、API、DB、   ファイルサーバなど)   について話します
  20. 20. 20 会員数1000万人:   10代から20代の女性   (女子高生は   クラスほぼ全員が会員)
  21. 21. サービスの概要 21 サービス名 ピクトリンク 会員数 1000万人   (有料会員数は秘密…) 主な機能 プリントシール機で   撮影した画像を   取得できる 提供 Webサイト   iOSアプリ   Androidアプリ 課金 携帯電話キャリア課金   (月額制) PV 数百万PV/日
  22. 22. 会員数の推移 22
  23. 23. 23 画像は10億枚弱   (うかつにcount(*)も   できない)
  24. 24. 24 大規模と思っています
  25. 25. 25 現システムは   4年前に構築   (前身サービスは   2003年に開始)
  26. 26. 私の立ち位置 26 私は3年前にJoinし、   今サイト開発のリーダ   という立場です   (つまりリリース後参画)
  27. 27. 開発チーム 27 アプリ開発   (ネイティブ/API)   7名 サイト開発   7名 インフラ   (サーバ/NW/DBA)   開発部門全体2名   チーム内2名   ログ分析   2名 HTML/CSS   2名
  28. 28. 28 リリース後1,2年は   クリスマスに   サービスが瀕死 Photo by Yuichi Sakuraba https://flic.kr/p/9JYRSQ
  29. 29. 29 なぜか?
  30. 30. 30 まず、Webサーバの   ロードアベレージが   非常に高かった
  31. 31. 31 平日は数百万PV/日   だが   クリスマスはその4倍 Photo by Yuichi Sakuraba https://flic.kr/p/9JYRSQ
  32. 32. 32 夕食の時間「前後」に   とくに集中する   (ピークタイムが   数時間単位となる) Photo by Yuichi Sakuraba https://flic.kr/p/9JYRSQ
  33. 33. 33 リクエストが   さばけていない
  34. 34. 34 プリントシール機で   撮影した画像(プリ画像)   を保存する処理なので   軽い処理ではない
  35. 35. 35 ここでサーバ構成を   紹介
  36. 36. 現在のサーバ構成 36台数は概数です ユーザが保存処理 を実行するまでの 1週間分のみ  
  37. 37. 37 ちょっと横道へ
  38. 38. 38 MogileFSとは:   分散ファイルシステム   (OSS)
  39. 39. MogileFSとは 39 tracker:   管理サーバ。   クライアントとやり 取りする。 storage:   ファイル保存   サーバ。   同一ファイルを   複数台で保持。   ファイルにはHTTP   でアクセスできる。 metadata:   RDBMS。   ファイル情報や   全体の設定を   保存する。
  40. 40. 40 ストレージノードが   数十台   (まだ増え続ける…)
  41. 41. 41 ここまで大きな問題は   出ていない
  42. 42. 42 ファイルやノードを   管理するMySQLが   ボトルネックとなる   かもしれない
  43. 43. 43 ストレージノードは   現実的には何台まで   いけるのかわからないので、 だんだん怖くなっている
  44. 44. 44 リクエストが   さばけていない件
  45. 45. 45 対応:Webサーバを   台数追加した
  46. 46. 46 教訓:お金を払おう
  47. 47. 47 解決(えっ
  48. 48. 48 サーバは   増やせば対応できたが
  49. 49. 49 今度は   RDBMSが   ネックとなってくる
  50. 50. 50 next:RDBMS編
  51. 51. 51 ここでアーキテクチャを   紹介
  52. 52. アーキテクチャ 52 Javaフレームワーク Seasarファミリー   (Cubby/S2Dao  etc) ファイルシステム   (画像ファイル保存) MogileFS RDBMS Oracle  11g(メイン)   MySQL  5.6 Webサーバ/   サーブレットコンテナ Apache/   Tomcat Javaバージョン 6  -­>  8
  53. 53. 53 • 近い将来メモリが不足する予 測ができた   • IO性能が悪かった   • レコード件数が多い   • (テーブル設計もベストではな いが)
  54. 54. 54 クエリやインデックスを   変更してコストを削減、   でどうにかなる   レベルではなかった
  55. 55. 55 RDBMSの移行
  56. 56. 56 変更前 Oracle  Database  11g     Standard  Edition 1台 変更後 Oracle  Database  11g     Real  Application   Clusters(RAC) 複数台
  57. 57. 57 RACとは:   ロードバランス型の   Oracleクラスタリング構成
  58. 58. 58 Copyright© 2011, Oracle. All rights reserved. 5.7 Oracle Instance Oracle Instance Oracle Instance Active-Activeの構成をとることが可能 RACは、ノード追加によるスケールアウトで性能向上 process process process process process process 「実践!高可用性システム構築  〜~RAC基本編〜~」より引用
  59. 59. 59 全ノードが全データに   アクセスできる   Acitve-­Active構成   となる
  60. 60. 60 さらに   ハードウェアも変更した
  61. 61. 61 パフォーマンスは   劇的に改善した
  62. 62. 62 解決(えっ
  63. 63. 63 教訓:お金を払おう
  64. 64. 64 ただ、そうした増強分も   使い果たしつつある
  65. 65. 65 レコードが増えたことで   見えていなかった問題が   浮き彫りになる
  66. 66. 66 DBAが来るまで、   DBサーバのOS、   Oracleとも   適切にパラメータが   設定されていなかった
  67. 67. 67 そうしたパラメータを   1つずつ精査しつつ…
  68. 68. 68 クエリのコストや   実行回数を   調査すると…
  69. 69. 69 DBA「夜間バッチの   1クエリがコスト   500万です!」
  70. 70. 70 理由:テーブルを   フルスキャンしている
  71. 71. 71 テーブルに   インデックスは…
  72. 72. 72 貼られている!
  73. 73. 73 クエリもインデックスを   使ったカラムを   where句で   指定している
  74. 74. 74 ログに出したSQL文を   SQLDeveloperで   実行してもすぐ終わる。   実行計画でも   インデックスを使っている
  75. 75. 詳細: 75 しかし実環境では   DATE型カラムへの   インデックスが   使われていない。
  76. 76. 詳細: 76 ???
  77. 77. 詳細: 77 Oracleを見てみる
  78. 78. 詳細: 78 SELECT SA.SQL_ID, NAME, WAS_CAPTURED, BC.LAST_CAPTURED, VALUE_STRING, anydata.accesstimestamp(value_anydata), datatype_string, sql_text FROM GV$SQL_BIND_CAPTURE BC, GV$SQLAREA SA   WHERE BC.HASH_VALUE = SA.HASH_VALUE AND SA.SQL_ID = 'hoge'   ORDER BY LAST_CAPTURED DESC;
  79. 79. 詳細: 79 datatype_string -> TIMESTAMP
  80. 80. 詳細: 80 バインド変数の   値の型(Oracle上)が   TIMESTAMPと   なっている!
  81. 81. 81 Javaアプリケーションでの型 java.util.Date 永続化ライブラリでの型 java.sql.Timestamp JDBCでセットする時の型 java.sql.Types.Timestamp Oracleでの型 TIMESTAMP
  82. 82. 結論: 82 OracleのDATEは   ANSI定義と異なり   時間の情報を持つ
  83. 83. 結論: 83 Types.Timestampとすることで   時間情報も保持していた   が、そのために   OracleでTIMESTAMPとなり   インデックスが使われなかった
  84. 84. 結論: 84 対応:Oracle独自の   PreparedStetementを   使うことにした
  85. 85. 対応: 85 if (ps instanceof OraclePreparedStatement){ OraclePreparedStatement ops = (OraclePreparedStatement) ps; // Oracle JDBCドライバ独自のメソッド、引数の型を利用する ops.setDATE(index, new oracle.sql.DATE(new java.sql.Timestamp(toDate(value).getTime()))); } else { ps.setDate(index, toSqlDate(value)); }
  86. 86. 結果: 86 コスト:   500万  -­>  8万
  87. 87. 結果: 87 大勝利
  88. 88. 結果: 88 ってか何年間も   誰も気づかなかったのか
  89. 89. 結果: 89 教訓:実行計画を   SQLクライアントで   見るだけでなく   実環境の実行コストも   見よう
  90. 90. 結果: 90 RDBMS関連だと   こんな問題も   ありました
  91. 91. 91 企画「アプリでiOSの   絵文字を使えるように   したい!」
  92. 92. 92 お、Unicodeで   定義された   絵文字だな
  93. 93. 93 SELECT VALUE FROM NLS_DATABASE_PARAMETERS WHERE PARAMETER=‘NLS_CHARACTERSET'; ! -> JA16SJISTILDE
  94. 94. 94 Shift_JISェ…
  95. 95. 95 「NVARCHAR2に   カラムの型を   変更すればいける!」
  96. 96. 96 JDBCでNVARCHAR2列   に絵文字を入れる場合、   JVMパラメータに   「-Doracle.jdbc.defaultNChar=true」   を追加する必要がある http://otndnld.oracle.co.jp/document/products/oracle10g/102/doc_cd/java.102/B19275-03/global.htm
  97. 97. 97 先にJVMパラメータだけ   つけてアプリケーション   起動しておくか   (ステージングでは   問題なし)
  98. 98. 98 そう、あのときの   俺たちには   何の疑念も   なかったんだ…
  99. 99. 結果: 99 再起動した瞬間に   全ユーザが   ログインできなくなる   (正確にはものすごく遅い)
  100. 100. 100 理由:テーブルを   フルスキャンしている
  101. 101. 詳細: 101 defaultNChar=true すると、VARCHAR2カラム   のバインド変数に   SYS_OP_C2C関数が   適用される
  102. 102. 詳細: 102 とあるテーブルのカラムhogeが   VARCHAR2型のとき、   defaultNChar=trueがあると… PreparedStetemetの   SQL where hoge = ? Oracleで実行される   SQL where hoge = SYS_OP_C2C(:1)
  103. 103. 詳細: 103 hogeへの   インデックスが   使われない
  104. 104. 対応: 104 SYS_OP_C2C関数を   使った   関数インデックスを   すべて作成した
  105. 105. 結果: 105 教訓:実行計画を   SQLクライアントで   見るだけでなく   実環境の実行コストも   見よう
  106. 106. 将来: 106 UTF-­8への移行を   計画中
  107. 107. 将来: 107 next:JVM編
  108. 108. 108 ある日、   JVMオプションがほとんど   設定されていないことに気づく   (XmxとPermGenくらい)
  109. 109. 109 僕「GCログ出してみよう。」   -Xloggc:gc.log -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:NumberOfGCLogFiles=3 -XX:GCLogFileSize=10M
  110. 110. 110 数日後
  111. 111. 111
  112. 112. 112 僕「なんてこった…」
  113. 113. 結果: 113 確保しているヒープ:   最大3.2GB   フルGC時間:   最大0.9秒/回
  114. 114. 114 こんなにヒープ領域   使うほどのシステムでは   ないような…   これかなりSTWしてるよね
  115. 115. 115 対応:GCアルゴリズムを   CMSにしよう!   ※CMS  =  Concurrent  Mark  &  Sweep   ! -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSClassUnloadingEnabled
  116. 116. 詳細: 116 GCアルゴリズムの変更前後 変更前 Parallel GC【デフォルトGC】 (マイナーGCのみをパラレルで) 変更後 CMS GC【J2SE 1.4から】 ご存じない方は、ぜひsugarlife's blogさんの ブログエントリ「CMS GC おさらい」を! http://cco.hatenablog.jp/entry/2014/12/01/162240
  117. 117. 結果: 117 確保した   最大ヒープ領域 STW時間 変更前 3.2GB 最大 0.9秒/平均0.5秒 変更後 1.2GB 最大0.23秒/平均0.2秒 0 1 2 3 4 変更前 変更後 1.2 3.2 0 0.225 0.45 0.675 0.9 変更前 変更後 0.23 0.9
  118. 118. 118 大幅改善!
  119. 119. 119
  120. 120. 120 ただし、CMSは   CPUリソースを使うので、   スループットが低下する   恐れがある
  121. 121. 121 僕らの場合、   スループットが1%低下し   CPUの負荷も   やや上がった
  122. 122. 結果: 122 教訓:JVMは   デフォルトでも   かなりいける   (人類の叡智)
  123. 123. 結果: 123 教訓:ただし、   測定はしておき、   増加傾向が出たら   対処していく   (最初から考えすぎない)
  124. 124. 124 ある日、   アクセスの少ない夜中に   OutOfMemoryError   が発生し、   再起動する
  125. 125. 125 「-­XX:+HeapDumpOnOutOfMemoryError」   しているので   ダンプファイルがある
  126. 126. 126 java_pid<9999>.hprof   4.3GB…
  127. 127. 127 このダンプファイル   どうしたらいいんだっけ?
  128. 128. 128 Eclipse  Memory   Analyzer(mat)   を使う https://eclipse.org/mat/
  129. 129. インストール方法 129 • Eclipseプラグインとして     Update  Managerからインストール   • スタンドアローン版をダウンロード
  130. 130. 130 いろいろな方法で   可視化してくれた
  131. 131. 131
  132. 132. 132 ふむ…
  133. 133. 133
  134. 134. 134
  135. 135. 135
  136. 136. 136 うん…
  137. 137. 137
  138. 138. 138 ん!?
  139. 139. 139 net.rubyeye.xmemcached.impl.MemcachedTCPSession のインスタンスが   ヒープの85.28%を   占めてる!
  140. 140. 140 1インスタンスが200MB   とか…
  141. 141. 141 対応:このライブラリの   バージョンを上げよう!
  142. 142. 結果: 142 教訓:   「-­XX:+HeapDumpOnOutOfMemoryError」   ほんとに役立つ!   (これまで本番で   OOME出たことなかった)
  143. 143. 143 ある日、Java  SE  8が   リリースされる
  144. 144. 144 もう6から8に   したくてしょうがなくなる
  145. 145. 145 まずTomcatの   起動VMを6から8に   してみた
  146. 146. 146 結果:何も問題   なかった
  147. 147. 147 プロジェクトのJDKを   6から8にしてみた
  148. 148. 148 結果:ユニットテストが   ほとんど全部エラーになる
  149. 149. 149 Caused  by:  java.lang.ArrayIndexOutOfBoundsException:  31038     at  org.objectweb.asm.ClassReader.<init>(Unknown  Source)     at  org.objectweb.asm.ClassReader.<init>(Unknown  Source)     at  org.objectweb.asm.ClassReader.<init>(Unknown  Source)     at  org.objectweb.asm.ClassReader.<init>(Unknown  Source)     at   jp.co.dgic.testing.common.AsmClassModifier.getModifiedClass(AsmClas sModifier.java:49)  
  150. 150. 150 JUnit  +  djUnit…
  151. 151. 151 indyが入ったことで   djUnitが依存している   ASMがエラーとなる
  152. 152. 152 ASMを最新にすると   APIが変わり   djUnitがエラーとなる
  153. 153. 153 うーん…
  154. 154. 154 djUnitを止め、   違うモックライブラリを   使うことにする
  155. 155. 155 対応:jMockitにして   テストケースを   すべて書き直す
  156. 156. 156 jMockitにした理由:   djUnitにある機能が   すべてそろっていたから   (意味を考えずに   置換できる)
  157. 157. 157 その他対応:   Javassistでエラーが   出たりしたので、   これもバージョンを   上げた
  158. 158. 結果: 158 教訓:   Java  SE  8を   導入しよう   (せめて実行環境   からでも!)
  159. 159. 159 next:サーバ編
  160. 160. 160 そうこうしている間に   サーバ台数が   増えてきた
  161. 161. 161 再起動時に   各サーバのログ見るの   面倒だなあ
  162. 162. 162 全台SSHで   tail  -­f   (less  +Fも僕してたよ!)
  163. 163. 163 見てられん…
  164. 164. 164 ログ収集:   fluentd  +  Elasticsearch   +  Kibana   で見るのが楽になった
  165. 165. 165
  166. 166. 166 fluentdでサーバ全台の   ログを集め、   それ1つを見るだけで   済む
  167. 167. 167 さらに
  168. 168. 168
  169. 169. 169 Kibanaで可視化   される
  170. 170. 170 教訓:   「ログの可視化は   エンジニアの責任」
  171. 171. 171 ログはHDFSにも   入れているので   将来的に利用する   予定だ
  172. 172. 172 話は変わり、   継続的に   サーバが追加される状況
  173. 173. 173 サーバ追加で   いちいちインストール   してられん…
  174. 174. 174 サーバ構築:   Ansible化を   進めている
  175. 175. 175 結局、去年は   クリスマスに   何も起こりませんでした Photo by Yuichi Sakuraba https://flic.kr/p/9JYRSQ
  176. 176. 176 今日の教訓のまとめ   • お金を払おう   • 実行計画をSQLクライアントで見るだけでなく実環境 の実行コストも見よう   • JVMはデフォルトでもかなりいける(人類の叡智)   • ただし、測定はしておき、増加傾向が出たら対処し ていく(最初から考えすぎない)   • Java  SE  8を導入しよう(せめて実行環境からでも!)   • ログの可視化はエンジニアの責任
  177. 177. まとめ 177 スーパーエンジニアが   いるわけじゃない
  178. 178. まとめ 178 普通のエンジニアたちが   悪戦苦闘しながら   運用してるよ
  179. 179. まとめ 179 かっこいい方法じゃ   ない部分もあるけど、   運用できてる!
  180. 180. まとめ 180 また   こうやって得たものを   発信したいです!
  181. 181. 181 ご清聴   ありがとうございました

×