遅いクエリと向き合う仕組み
サイボウズ株式会社
⾚井 駿平
1
whoami
▌⾚井 駿平
l 2013年4⽉⼊社
l アプリケーション基盤チーム所属
l プログラミング⾔語の研究→博⼠号取得→サイボウズ
▌ミドルウェアからWebアプリまで
l 副業では個⼈でiOSアプリを作ってます
2
アプリケーション基盤チーム
▌サイボウズ内の複数の製品で使うプログラムを作るチーム
l ミドルウェア
l メッセージキュー、全⽂検索、サムネイル作成、ログ…
l Webアプリ
l ユーザー管理機能、プロフィール表⽰…
l Webアプリのライブラリ、フレームワーク
l Javaで作ってる製品
l 選定したり、作成したり
3
今⽇話すこと
▌ライブラリを作った話
l データベース周り
l 障害を減らす仕組み
l パフォーマンスを改善するためのORマッパー
4
kintoneの話
5
kintone?
▌Webでデータベースを作れるようなサービス
l ドラッグ&ドロップで⼊⼒フォームとデータベースが作れる
l SQLでのtable ≒ アプリ
l ただし、スキーマがどんどん変わる
l ドキュメント指向データベースっぽい
6
7
8
kintoneの中⾝のはなし
▌永続化にはRDBMS (MySQL) を使っている
l 1アプリ=1table ではない
l 固定のtableに⾊々な形のアプリがマッピングされる
l 複雑なクエリが多く発⾏される場合が…
l 複雑な条件での絞込
l パフォーマンスが…
▌詳しくは「kintoneの検索⾼速化への取り組み」を参照
l https://www.slideshare.net/RyoMitoma/kintone-73674134
9
複雑なクエリで障害が
▌複雑な絞り込みが⾏われるとリクエストに⼗分単位で時間が掛
かる場合が
l DBが遅い/⼤量のクエリ/⼤量のメモリ
▌ロードバランサーでは5分以上掛かるリクエストは強制切断し
ている
l アプリケーションサーバー側ではリクエストは⽣き続ける
l リロードされると…
10
どうしたものか
▌ クエリ⾃体を改善する
l 当然必要
l kintoneの性質上、遅いクエリはどうしても発⽣し得る
l 起きちゃった場合の対応をしないといけない
▌ クエリのタイムアウト
l MySQL 5.7ではSELECTのタイムアウトが出来る
l 当時使ってたのは5.6…
l kintoneではそこそこ遅いクエリが多く⾛る
l 使えない
▌ 遅すぎるリクエストのみを強制的に終了させたい
l JavaのServlet上で動いているのでkillとか出来ない
11
リクエストが⾃分で終了すればいい
12
▌リクエストの中のチェックポイントで経過時間を判定
l 超えていたら例外を投げて終了
▌コード中のあらゆる場所に判定するコードを書く?
l 量が多くて⼤変
l コードが汚くなる
l 漏れが発⽣する
よし、アスペクト指向だ!
▌アスペクト指向プログラミング(AOP)
l プログラミングの⾊々な場所に処理を挿⼊出来る技術
l リフレクションの凄いやつ
l JavaのWebフレームワークには⼊ってたりする
l あまり流⾏らなかったけど
▌データベースにアクセスするたびに経過時間をチェックする
13
aspectの例
14
public class RepositoryAspect {
@Pointcut("execution(public * com.cybozu.Repository+.*(..))")
void timeoutTarget() { }
@After(value = "timeoutTarget()")
public void after() {
DateTime expire = ((DateTime) request.getAttribute(“expire”));
if (expire != null && expire.isBeforeNow()) {
throw new RequestTimeoutException();
}
}
}
DBにアクセス全
てのメソッド
を実⾏した後で以
下の処理を実⾏す
る
効果は?
▌とある1⽇にタイムアウトしてたリクエストは90件くらい
l 致命的な障害は防げてそう
▌タイムアウトしたログを頼りにクエリを改善していけるかも
15
ORマッパーを乗り換える
16
S2Dao
▌kintone等ではS2Dao/Seasar2を使ってました
l アノテーションでクエリを書くと、メソッドが⾃動⽣成
17
@S2Dao(bean = User.class)
public interface UserDao {
@Query(“SELECT * FROM user WHERE id = /*id*/”)
List<User> getById(Long id);
}
ORマッパーがEOL!?
▌S2DaoがEOLになりそう! 移⾏しないと
▌Spring Data JPA + Hibernate に移⾏しました
l S2Daoと同じようにアノテーションで書ける
l JPAという標準だ
l JPAの中ではHibernateがメジャー?
18
⼤失敗
19
パフォーマンスが出ませんでした
▌制御できないキャッシュ
l kintone では可変⻑の巨⼤なIN句が使われていた
l それぞれの⻑さでクエリがキャッシュ
l メモリを圧迫してGC多発
l 他にも性能問題がぽろぽろと
▌詳しくはブログに
l 「我々はいかにして技術選択を間違えたのか? 2016」
l http://blog.cybozu.io/entry/2016/12/28/101500
20
Hibernateをやめたい
▌よい乗り換え先ある?
l 他のJPA実装?
l 仕組み上あまりかわらなそう
l MyBatis?
l IN句を使うのにforeach⽂みたいなのを書かないといけない
l SpringのJdbcTemplate
l 書き⽅が⼤きく変わる
l jdbcTemplate.query(“SELECT * FROM user”, rowMapper)
▌困った困った
21
⾃分で作っちゃえ
▌JdbcTemplateをSpring Dataでラップするものを作る
l 何故か誰も作ってないっぽい
▌コンセプト
l 今までと同じようにアノテーションで書ける
l 余計なことはしないシンプルなラッパー
l has-many has-one等すらサポートしない
22
コードの例
▌S2DaoやSpring Data JPAと⼤体同じように書ける
23
@Table(name = “user”)
class User {
@Id
@GeneratedValue
public Long id;
@Column(“login_name”)
public String loginName;
}
@Repository
public interface UserRepository extends
JdbcTemplateRepository<User> {
@Query(“SELECT * FROM user WHERE id=:id”)
List<User> getById(@Param(“id”) Long id);
}
切り替えた結果
▌kintoneで全⾯的に乗り換えてから約1ヶ⽉
l 今のところクエリ関係が原因の障害は起きてなさそう
l 社内独⾃の性能測定では、10%ほど性能が向上した
24
オープンソース化します
▌Spring Data Jdbc Template
l https://github.com/cybozu/spring-data-jdbc-template
▌まだソースのみ
l maven等に上げるのはこれから検討
▌もうすこし詳しい話はブログに書くかも
25
まとめ
▌アプリケーション基盤チームでは社内のライブラリの整備もし
ています
▌ものによってはオープンソース化もします
26

遅いクエリと向き合う仕組み #CybozuMeetup