181
クリスマスを支える 俺たちとJava 阪田 浩一 フリュー株式会社 関西Javaエンジニアの会 1

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

Embed Size (px)

Citation preview

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

阪田  浩一  フリュー株式会社  

関西Javaエンジニアの会

1

22

@jyukutyo

著書 3冊

関西Javaエンジニアの会 (関ジャバ) 中の人・発起人

blog:Fight the Future http://jyukutyo.hatenablog.com/

フリュー株式会社 所属

エンジニア歴12年 36歳SI業界での客先常駐 9年 Web系? 3年

文学部哲学科卒

塾講師アルバイト

阪田 浩一

要約

3

普通のエンジニアたちが

要約

4

いろんな  技術的課題、  

プロセスの課題、  チームの課題に

要約

5

ぶつかりつつも  サービスを開発、運用  しているお話です

6

扱えないこと:  大規模サービスを  

運用する  ベストな方法

目次

7

• 対象となるアプリケーションについて  • わき上がる課題とその対処  • RDBMS編  • JVM編  • サーバ編  

• まとめ

要約

8

本題へ

9

僕たちのアプリケーションは  クリスマスの負荷に  耐えられずにいた

Photo by Yuichi Sakuraba https://flic.kr/p/9JYRSQ

10

そう、クリスマスは  1年で最もアクセスが  

ある日

Photo by Yuichi Sakuraba https://flic.kr/p/9JYRSQ

11

恋人たちがみんな  撮影をする日だからだ

Photo by Yuichi Sakuraba https://flic.kr/p/9JYRSQ

12

撮影?  そう、プリントシール機  

での撮影…

プリントシール機とは

13

14

弊社フリュー株式会社は  プリントシール機の  トップメーカーです

15

全国の機械の  半分以上がフリューです

16

なのですが、  今回はこの筐体の  実装ではなく

簡単な図

17

ここの詳細に  ついてです

Internet

Webとの関係は?

18

撮影した画像は  Webサイト/アプリから  

取得できます

19

アプリを除く  Webシステム部分  (サイト、API、DB、  ファイルサーバなど)  について話します

20

会員数1000万人:  10代から20代の女性  

(女子高生は  クラスほぼ全員が会員)

サービスの概要

21

サービス名 ピクトリンク

会員数1000万人  

(有料会員数は秘密…)

主な機能プリントシール機で  撮影した画像を  取得できる

提供Webサイト  iOSアプリ  

Androidアプリ

課金携帯電話キャリア課金  

(月額制)

PV 数百万PV/日

会員数の推移

22

23

画像は10億枚弱  (うかつにcount(*)も  

できない)

24

大規模と思っています

25

現システムは  4年前に構築  

(前身サービスは  2003年に開始)

私の立ち位置

26

私は3年前にJoinし、  今サイト開発のリーダ  

という立場です  (つまりリリース後参画)

開発チーム

27

アプリ開発  (ネイティブ/API)  

7名

サイト開発  7名

インフラ  (サーバ/NW/DBA)  開発部門全体2名  チーム内2名  

ログ分析  2名

HTML/CSS  2名

28

リリース後1,2年は  クリスマスに  

サービスが瀕死

Photo by Yuichi Sakuraba https://flic.kr/p/9JYRSQ

29

なぜか?

30

まず、Webサーバの  ロードアベレージが  非常に高かった

31

平日は数百万PV/日  だが  

クリスマスはその4倍

Photo by Yuichi Sakuraba https://flic.kr/p/9JYRSQ

32

夕食の時間「前後」に  とくに集中する  (ピークタイムが  

数時間単位となる)

Photo by Yuichi Sakuraba https://flic.kr/p/9JYRSQ

33

リクエストが  さばけていない

34

プリントシール機で  撮影した画像(プリ画像)  を保存する処理なので  軽い処理ではない

35

ここでサーバ構成を  紹介

現在のサーバ構成

36台数は概数です

ユーザが保存処理を実行するまでの1週間分のみ  

37

ちょっと横道へ

38

MogileFSとは:  分散ファイルシステム  

(OSS)

MogileFSとは

39

tracker:  管理サーバ。  

クライアントとやり取りする。

storage:  ファイル保存  サーバ。  

同一ファイルを  複数台で保持。  

ファイルにはHTTP  でアクセスできる。

metadata:  RDBMS。  

ファイル情報や  全体の設定を  保存する。

40

ストレージノードが  数十台  

(まだ増え続ける…)

41

ここまで大きな問題は  出ていない

42

ファイルやノードを  管理するMySQLが  ボトルネックとなる  かもしれない

43

ストレージノードは  現実的には何台まで  

いけるのかわからないので、だんだん怖くなっている

44

リクエストが  さばけていない件

45

対応:Webサーバを  台数追加した

46

教訓:お金を払おう

47

解決(えっ

48

サーバは  増やせば対応できたが

49

今度は  RDBMSが  

ネックとなってくる

50

next:RDBMS編

51

ここでアーキテクチャを  紹介

アーキテクチャ

52

JavaフレームワークSeasarファミリー  

(Cubby/S2Dao  etc)

ファイルシステム  (画像ファイル保存)

MogileFS

RDBMS Oracle  11g(メイン)  MySQL  5.6

Webサーバ/  サーブレットコンテナ

Apache/  Tomcat

Javaバージョン 6  -­>  8

53

• 近い将来メモリが不足する予測ができた  

• IO性能が悪かった  • レコード件数が多い  • (テーブル設計もベストではないが)

54

クエリやインデックスを  変更してコストを削減、  

でどうにかなる  レベルではなかった

55

RDBMSの移行

56

変更前 Oracle  Database  11g    Standard  Edition 1台

変更後Oracle  Database  11g    Real  Application  Clusters(RAC)

複数台

57

RACとは:  ロードバランス型の  

Oracleクラスタリング構成

58

Copyright© 2011, Oracle. All rights reserved.

5.7

RACのスケーラビリティRACの特長

11

OracleInstance

OracleInstance

OracleInstance

• RACは、全ノードがデータベースの全データにアクセスでき、Active-Activeの構成をとることが可能

• RACは、ノード追加によるスケールアウトで性能向上

processprocess

processprocess

processprocess

「実践!高可用性システム構築  〜~RAC基本編〜~」より引用

59

全ノードが全データに  アクセスできる  

Acitve-­Active構成  となる

60

さらに  ハードウェアも変更した

61

パフォーマンスは  劇的に改善した

62

解決(えっ

63

教訓:お金を払おう

64

ただ、そうした増強分も  使い果たしつつある

65

レコードが増えたことで  見えていなかった問題が  

浮き彫りになる

66

DBAが来るまで、  DBサーバのOS、  Oracleとも  

適切にパラメータが  設定されていなかった

67

そうしたパラメータを  1つずつ精査しつつ…

68

クエリのコストや  実行回数を  調査すると…

69

DBA「夜間バッチの  1クエリがコスト  500万です!」

70

理由:テーブルを  フルスキャンしている

71

テーブルに  インデックスは…

72

貼られている!

73

クエリもインデックスを  使ったカラムを  where句で  指定している

74

ログに出したSQL文を  SQLDeveloperで  

実行してもすぐ終わる。  実行計画でも  

インデックスを使っている

詳細:

75

しかし実環境では  DATE型カラムへの  インデックスが  使われていない。

詳細:

76

???

詳細:

77

Oracleを見てみる

詳細:

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

datatype_string -> TIMESTAMP

詳細:

80

バインド変数の  値の型(Oracle上)が  

TIMESTAMPと  なっている!

81

Javaアプリケーションでの型 java.util.Date

永続化ライブラリでの型 java.sql.Timestamp

JDBCでセットする時の型 java.sql.Types.Timestamp

Oracleでの型 TIMESTAMP

結論:

82

OracleのDATEは  ANSI定義と異なり  時間の情報を持つ

結論:

83

Types.Timestampとすることで  時間情報も保持していた  

が、そのために  OracleでTIMESTAMPとなり  インデックスが使われなかった

結論:

84

対応:Oracle独自の  PreparedStetementを  

使うことにした

対応:

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

コスト:  500万  -­>  8万

結果:

87

大勝利

結果:

88

ってか何年間も  誰も気づかなかったのか

結果:

89

教訓:実行計画を  SQLクライアントで  見るだけでなく  

実環境の実行コストも  見よう

結果:

90

RDBMS関連だと  こんな問題も  ありました

91

企画「アプリでiOSの  絵文字を使えるように  

したい!」

92

お、Unicodeで  定義された  絵文字だな

93

SELECT VALUE FROM NLS_DATABASE_PARAMETERS WHERE PARAMETER=‘NLS_CHARACTERSET'; !-> JA16SJISTILDE

94

Shift_JISェ…

95

「NVARCHAR2に  カラムの型を  

変更すればいける!」

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

先にJVMパラメータだけ  つけてアプリケーション  

起動しておくか  (ステージングでは  

問題なし)

98

そう、あのときの  俺たちには  何の疑念も  

なかったんだ…

結果:

99

再起動した瞬間に  全ユーザが  

ログインできなくなる  (正確にはものすごく遅い)

100

理由:テーブルを  フルスキャンしている

詳細:

101

defaultNChar=true すると、VARCHAR2カラム  

のバインド変数に  SYS_OP_C2C関数が  

適用される

詳細:

102

とあるテーブルのカラムhogeが  VARCHAR2型のとき、  

defaultNChar=trueがあると…

PreparedStetemetの  SQL

where hoge = ?

Oracleで実行される  SQL

where hoge = SYS_OP_C2C(:1)

詳細:

103

hogeへの  インデックスが  使われない

対応:

104

SYS_OP_C2C関数を  使った  

関数インデックスを  すべて作成した

結果:

105

教訓:実行計画を  SQLクライアントで  見るだけでなく  

実環境の実行コストも  見よう

将来:

106

UTF-­8への移行を  計画中

将来:

107

next:JVM編

108

ある日、  JVMオプションがほとんど  

設定されていないことに気づく  (XmxとPermGenくらい)

109

僕「GCログ出してみよう。」  -Xloggc:gc.log -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:NumberOfGCLogFiles=3 -XX:GCLogFileSize=10M

110

数日後

111

112

僕「なんてこった…」

結果:

113

確保しているヒープ:  最大3.2GB  

フルGC時間:  最大0.9秒/回

114

こんなにヒープ領域  使うほどのシステムでは  

ないような…  これかなりSTWしてるよね

115

対応:GCアルゴリズムを  CMSにしよう!  

※CMS  =  Concurrent  Mark  &  Sweep  !-XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSClassUnloadingEnabled

詳細:

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

確保した  最大ヒープ領域

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

大幅改善!

119

120

ただし、CMSは  CPUリソースを使うので、  スループットが低下する  

恐れがある

121

僕らの場合、  スループットが1%低下し  

CPUの負荷も  やや上がった

結果:

122

教訓:JVMは  デフォルトでも  かなりいける  (人類の叡智)

結果:

123

教訓:ただし、  測定はしておき、  増加傾向が出たら  対処していく  

(最初から考えすぎない)

124

ある日、  アクセスの少ない夜中に  OutOfMemoryError  

が発生し、  再起動する

125

「-­XX:+HeapDumpOnOutOfMemoryError」  しているので  

ダンプファイルがある

126

java_pid<9999>.hprof  4.3GB…

127

このダンプファイル  どうしたらいいんだっけ?

128

Eclipse  Memory  Analyzer(mat)  

を使う

https://eclipse.org/mat/

インストール方法

129

• Eclipseプラグインとして     Update  Managerからインストール  • スタンドアローン版をダウンロード

130

いろいろな方法で  可視化してくれた

131

132

ふむ…

133

134

135

136

うん…

137

138

ん!?

139

net.rubyeye.xmemcached.impl.MemcachedTCPSession

のインスタンスが  ヒープの85.28%を  

占めてる!

140

1インスタンスが200MB  とか…

141

対応:このライブラリの  バージョンを上げよう!

結果:

142

教訓:  「-­XX:+HeapDumpOnOutOfMemoryError」  

ほんとに役立つ!  (これまで本番で  

OOME出たことなかった)

143

ある日、Java  SE  8が  リリースされる

144

もう6から8に  したくてしょうがなくなる

145

まずTomcatの  起動VMを6から8に  

してみた

146

結果:何も問題  なかった

147

プロジェクトのJDKを  6から8にしてみた

148

結果:ユニットテストが  ほとんど全部エラーになる

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(AsmClassModifier.java:49)  

150

JUnit  +  djUnit…

151

indyが入ったことで  djUnitが依存している  ASMがエラーとなる

152

ASMを最新にすると  APIが変わり  

djUnitがエラーとなる

153

うーん…

154

djUnitを止め、  違うモックライブラリを  

使うことにする

155

対応:jMockitにして  テストケースを  すべて書き直す

156

jMockitにした理由:  djUnitにある機能が  

すべてそろっていたから  (意味を考えずに  置換できる)

157

その他対応:  Javassistでエラーが  出たりしたので、  これもバージョンを  

上げた

結果:

158

教訓:  Java  SE  8を  導入しよう  

(せめて実行環境  からでも!)

159

next:サーバ編

160

そうこうしている間に  サーバ台数が  増えてきた

161

再起動時に  各サーバのログ見るの  

面倒だなあ

162

全台SSHで  tail  -­f  

(less  +Fも僕してたよ!)

163

見てられん…

164

ログ収集:  fluentd  +  Elasticsearch  

+  Kibana  で見るのが楽になった

165

166

fluentdでサーバ全台の  ログを集め、  

それ1つを見るだけで  済む

167

さらに

168

169

Kibanaで可視化  される

170

教訓:  「ログの可視化は  

エンジニアの責任」

171

ログはHDFSにも  入れているので  将来的に利用する  

予定だ

172

話は変わり、  継続的に  

サーバが追加される状況

173

サーバ追加で  いちいちインストール  

してられん…

174

サーバ構築:  Ansible化を  進めている

175

結局、去年は  クリスマスに  

何も起こりませんでした

Photo by Yuichi Sakuraba https://flic.kr/p/9JYRSQ

176

今日の教訓のまとめ  • お金を払おう  • 実行計画をSQLクライアントで見るだけでなく実環境

の実行コストも見よう  • JVMはデフォルトでもかなりいける(人類の叡智)  • ただし、測定はしておき、増加傾向が出たら対処し

ていく(最初から考えすぎない)  • Java  SE  8を導入しよう(せめて実行環境からでも!)  • ログの可視化はエンジニアの責任

まとめ

177

スーパーエンジニアが  いるわけじゃない

まとめ

178

普通のエンジニアたちが  悪戦苦闘しながら  運用してるよ

まとめ

179

かっこいい方法じゃ  ない部分もあるけど、  

運用できてる!

まとめ

180

また  こうやって得たものを  発信したいです!

181

ご清聴  ありがとうございました