© 2016 JustSystems Corporation.
スマイルゼミの裏側
~データベース編~
(株)ジャストシステム
ILS事業部開発部
菅井友之
© 2016 JustSystems Corporation.
• スマイルゼミ中学生コース
– サーバー担当
• スマイルゼミ小学生コース、中学生コースの立ち上げ
– サーバー主担当として参加
• 主にTomcat上で動いてPostgreSQLを使うアプリケーションを
やっています。
自己紹介
© 2016 JustSystems Corporation.
スマイルゼミでやらかしてしまった話
(DB的な意味で)
© 2016 JustSystems Corporation.
スマイルゼミでやらかしてしまった話
(DB的な意味で)
PostgreSQL 9.3
© 2016 JustSystems Corporation.
その前に
© 2016 JustSystems Corporation.
スマイルゼミ
ご存じですか?
© 2016 JustSystems Corporation.
• 小中学生向けの家庭学習サービス
• すべての学習がタブレット上で完結できる日本初のサービス
• 小中一貫で学べる唯一のタブレット教材
© 2016 JustSystems Corporation.
facebookやってます https://www.facebook.com/smile.zemi/
© 2016 JustSystems Corporation.
小学生 中学生
ある一週間のアクセス状況
© 2016 JustSystems Corporation.
• 小中学生向けのサービス
• ユーザーは、日中学校に行っている
小学生 中学生
ある一週間のアクセス状況
© 2016 JustSystems Corporation.
• 小中学生向けのサービス
• ユーザーは、日中学校に行っている
平常時、日中の負荷は低い
小学生 中学生
© 2016 JustSystems Corporation.
_人人人人人人人人人_
> 突然の負荷上昇 <
 ̄Y^Y^Y^Y^Y^Y^Y^Y ̄
© 2016 JustSystems Corporation.
• 中学生DBの朝の負荷であり、垂直に立ち上がっているので、バッチ処理
• グラフが変わる前の日に、hotfixがリリースされている。
• 調べてみると、course_results で1300万行のseq.scanが大量に発生して
いる。
© 2016 JustSystems Corporation.
CREATE VIEW AS study_log_memorycard_view (
WITH memorycard_info AS (
SELECT ... FROM course_results cr
JOIN course_master cm ON cr.course_id AND
cm.memory_card_info IS NOT NULL)
(SELECT ... FROM memorycard_set_results msr
JOIN memorycard_info ...
WHERE ...)
UNION ALL
(SELECT ... FROM memorycard_set_results msr
JOIN ... JOIN memorycard_info ...
WHERE ...
))
© 2016 JustSystems Corporation.
• 処理の内容は変えずに、viewを使うようにプログラ
ムを変更していた。
© 2016 JustSystems Corporation.
• 処理の内容は変えずに、viewを使うようにプログラ
ムを変更していた。
• view を使わずに処理する場合には、オプティマイザ
が後続のWHEREを先に処理してくれていたのだろう
© 2016 JustSystems Corporation.
• 処理の内容は変えずに、viewを使うようにプログラ
ムを変更していた。
• view を使わずに処理する場合には、オプティマイザ
が後続のWHEREを先に処理してくれていたのだろう
• view を使ってしまったために、course_resultsが絞り
込まれずに含まれてしまう。
© 2016 JustSystems Corporation.
CREATE VIEW AS study_log_memorycard_view (
WITH memorycard_info AS (
SELECT ... FROM course_results cr
JOIN course_master cm ON cr.course_id AND
cm.memory_card_info IS NOT NULL)
(SELECT ... FROM memorycard_set_results msr
JOIN memorycard_info ...
WHERE ...)
UNION ALL
(SELECT ... FROM memorycard_set_results msr
JOIN ... JOIN memorycard_info ...
WHERE ...
))
© 2016 JustSystems Corporation.
• 処理の内容は変えずに、viewを使うようにプログラ
ムを変更していた。
• view を使わずに処理する場合には、オプティマイザ
が後続のWHEREを先に処理してくれていたのだろう
• view を使ってしまったために、course_resultsが絞り
込まれずに含まれてしまう。
• さらに、ユーザーごとにviewに対するSELECTをして
いる。
– 長時間の高負荷状態に
© 2016 JustSystems Corporation.
対策
© 2016 JustSystems Corporation.
• 前日の学習結果を集計する処理なので、view定義の段階で
学習日で絞り込むことにした
• view 定義の各SELECT文に以下の絞り込み条件を追加
対策
© 2016 JustSystems Corporation.
• 前日の学習結果を集計する処理なので、view定義の段階で
学習日で絞り込むことにした
• view 定義の各SELECT文に以下の絞り込み条件を追加
対策
study_end > (now() - '2 days'::interval)
© 2016 JustSystems Corporation.
• 前日の学習結果を集計する処理なので、view定義の段階で
学習日で絞り込むことにした
• view 定義の各SELECT文に以下の絞り込み条件を追加
• 幸い、他の処理では使っていないviewだったので、同名のま
ますぐに定義変更できた。
対策
study_end > (now() - '2 days'::interval)
© 2016 JustSystems Corporation.
© 2016 JustSystems Corporation.
他の事例
© 2016 JustSystems Corporation.
2013年12月
小学生向けみまもるトーク
拡張され続けるサービス
© 2016 JustSystems Corporation.
2013年12月
小学生向けみまもるトーク
拡張され続けるサービス
2014年11月
中学生向けみまもるトーク
© 2016 JustSystems Corporation.
小学生向けに作ったトーク機能に、中学生を参加させる
© 2016 JustSystems Corporation.
• あるトークグループに参加しているユーザー(talker)の一覧
を取得するSQL文
• - talker は、親、小学生、中学生、その他(親が招待した人)
があり、それぞれ別体系のidで管理されている。
SELECT talkers.* FROM talkers
LEFT OUTER JOIN accounts
ON talkers.account_id = accounts.id
WHERE (((accounts.parent_id = xxxxxxxx OR
child_id = xxxxxxxx )
OR jh_child_id = xxxxxxxx )
OR account_id = xxxxxxxx )
© 2016 JustSystems Corporation.
• 中学生が増えたのでこの条件が増えた
• OR条件が追加されたことにより、実行計画が
くるってしまって、Seq Scan on accounts、Seq
Scan on talkersが発生
• indexはあるのに使ってもらえない。
OR jh_child_id = xxxxxxxx
© 2016 JustSystems Corporation.
対策
© 2016 JustSystems Corporation.
対策
SELECT talkers.* FROM talkers LEFT OUTER JOIN accounts
ON talkers.account_id = accounts.id
WHERE (accounts.parent_id = xxxxxxxx)
UNION
SELECT talkers.* FROM talkers WHERE (child_id = xxxxxxxx)
UNION
SELECT talkers.* FROM talkers WHERE (jh_child_id = xxxxxxxx )
UNION
SELECT talkers.* FROM talkers WHERE (account_id = xxxxxxxx )
ORDER BY id;
© 2016 JustSystems Corporation.
• Index scan ですむ単純SQLにして結果をUNIONすると1000-
10000倍速になった!
– (というよりは、ロジック変更で1000倍遅くなっていた)
対策
SELECT talkers.* FROM talkers LEFT OUTER JOIN accounts
ON talkers.account_id = accounts.id
WHERE (accounts.parent_id = xxxxxxxx)
UNION
SELECT talkers.* FROM talkers WHERE (child_id = xxxxxxxx)
UNION
SELECT talkers.* FROM talkers WHERE (jh_child_id = xxxxxxxx )
UNION
SELECT talkers.* FROM talkers WHERE (account_id = xxxxxxxx )
ORDER BY id;
© 2016 JustSystems Corporation.
• UNION が あるところを変更するときは慎重に
– UNIONしていいのは、中間処理結果が十分に小
さいとわかっている場合のみ。
– 特にviewの中でUNIONするのは危険
• 中間テーブルが十分に小さく、素早く、作れる
ところではUNIONはとても有効!
まとめ
© 2016 JustSystems Corporation.
スマイルゼミを一緒に育てる人、募集しています。
スマイルゼミの裏側(db編)

スマイルゼミの裏側(db編)

  • 1.
    © 2016 JustSystemsCorporation. スマイルゼミの裏側 ~データベース編~ (株)ジャストシステム ILS事業部開発部 菅井友之
  • 2.
    © 2016 JustSystemsCorporation. • スマイルゼミ中学生コース – サーバー担当 • スマイルゼミ小学生コース、中学生コースの立ち上げ – サーバー主担当として参加 • 主にTomcat上で動いてPostgreSQLを使うアプリケーションを やっています。 自己紹介
  • 3.
    © 2016 JustSystemsCorporation. スマイルゼミでやらかしてしまった話 (DB的な意味で)
  • 4.
    © 2016 JustSystemsCorporation. スマイルゼミでやらかしてしまった話 (DB的な意味で) PostgreSQL 9.3
  • 5.
    © 2016 JustSystemsCorporation. その前に
  • 6.
    © 2016 JustSystemsCorporation. スマイルゼミ ご存じですか?
  • 7.
    © 2016 JustSystemsCorporation. • 小中学生向けの家庭学習サービス • すべての学習がタブレット上で完結できる日本初のサービス • 小中一貫で学べる唯一のタブレット教材
  • 8.
    © 2016 JustSystemsCorporation. facebookやってます https://www.facebook.com/smile.zemi/
  • 9.
    © 2016 JustSystemsCorporation. 小学生 中学生 ある一週間のアクセス状況
  • 10.
    © 2016 JustSystemsCorporation. • 小中学生向けのサービス • ユーザーは、日中学校に行っている 小学生 中学生 ある一週間のアクセス状況
  • 11.
    © 2016 JustSystemsCorporation. • 小中学生向けのサービス • ユーザーは、日中学校に行っている 平常時、日中の負荷は低い 小学生 中学生
  • 12.
    © 2016 JustSystemsCorporation. _人人人人人人人人人_ > 突然の負荷上昇 <  ̄Y^Y^Y^Y^Y^Y^Y^Y ̄
  • 13.
    © 2016 JustSystemsCorporation. • 中学生DBの朝の負荷であり、垂直に立ち上がっているので、バッチ処理 • グラフが変わる前の日に、hotfixがリリースされている。 • 調べてみると、course_results で1300万行のseq.scanが大量に発生して いる。
  • 14.
    © 2016 JustSystemsCorporation. CREATE VIEW AS study_log_memorycard_view ( WITH memorycard_info AS ( SELECT ... FROM course_results cr JOIN course_master cm ON cr.course_id AND cm.memory_card_info IS NOT NULL) (SELECT ... FROM memorycard_set_results msr JOIN memorycard_info ... WHERE ...) UNION ALL (SELECT ... FROM memorycard_set_results msr JOIN ... JOIN memorycard_info ... WHERE ... ))
  • 15.
    © 2016 JustSystemsCorporation. • 処理の内容は変えずに、viewを使うようにプログラ ムを変更していた。
  • 16.
    © 2016 JustSystemsCorporation. • 処理の内容は変えずに、viewを使うようにプログラ ムを変更していた。 • view を使わずに処理する場合には、オプティマイザ が後続のWHEREを先に処理してくれていたのだろう
  • 17.
    © 2016 JustSystemsCorporation. • 処理の内容は変えずに、viewを使うようにプログラ ムを変更していた。 • view を使わずに処理する場合には、オプティマイザ が後続のWHEREを先に処理してくれていたのだろう • view を使ってしまったために、course_resultsが絞り 込まれずに含まれてしまう。
  • 18.
    © 2016 JustSystemsCorporation. CREATE VIEW AS study_log_memorycard_view ( WITH memorycard_info AS ( SELECT ... FROM course_results cr JOIN course_master cm ON cr.course_id AND cm.memory_card_info IS NOT NULL) (SELECT ... FROM memorycard_set_results msr JOIN memorycard_info ... WHERE ...) UNION ALL (SELECT ... FROM memorycard_set_results msr JOIN ... JOIN memorycard_info ... WHERE ... ))
  • 19.
    © 2016 JustSystemsCorporation. • 処理の内容は変えずに、viewを使うようにプログラ ムを変更していた。 • view を使わずに処理する場合には、オプティマイザ が後続のWHEREを先に処理してくれていたのだろう • view を使ってしまったために、course_resultsが絞り 込まれずに含まれてしまう。 • さらに、ユーザーごとにviewに対するSELECTをして いる。 – 長時間の高負荷状態に
  • 20.
    © 2016 JustSystemsCorporation. 対策
  • 21.
    © 2016 JustSystemsCorporation. • 前日の学習結果を集計する処理なので、view定義の段階で 学習日で絞り込むことにした • view 定義の各SELECT文に以下の絞り込み条件を追加 対策
  • 22.
    © 2016 JustSystemsCorporation. • 前日の学習結果を集計する処理なので、view定義の段階で 学習日で絞り込むことにした • view 定義の各SELECT文に以下の絞り込み条件を追加 対策 study_end > (now() - '2 days'::interval)
  • 23.
    © 2016 JustSystemsCorporation. • 前日の学習結果を集計する処理なので、view定義の段階で 学習日で絞り込むことにした • view 定義の各SELECT文に以下の絞り込み条件を追加 • 幸い、他の処理では使っていないviewだったので、同名のま ますぐに定義変更できた。 対策 study_end > (now() - '2 days'::interval)
  • 24.
    © 2016 JustSystemsCorporation.
  • 25.
    © 2016 JustSystemsCorporation. 他の事例
  • 26.
    © 2016 JustSystemsCorporation. 2013年12月 小学生向けみまもるトーク 拡張され続けるサービス
  • 27.
    © 2016 JustSystemsCorporation. 2013年12月 小学生向けみまもるトーク 拡張され続けるサービス 2014年11月 中学生向けみまもるトーク
  • 28.
    © 2016 JustSystemsCorporation. 小学生向けに作ったトーク機能に、中学生を参加させる
  • 29.
    © 2016 JustSystemsCorporation. • あるトークグループに参加しているユーザー(talker)の一覧 を取得するSQL文 • - talker は、親、小学生、中学生、その他(親が招待した人) があり、それぞれ別体系のidで管理されている。 SELECT talkers.* FROM talkers LEFT OUTER JOIN accounts ON talkers.account_id = accounts.id WHERE (((accounts.parent_id = xxxxxxxx OR child_id = xxxxxxxx ) OR jh_child_id = xxxxxxxx ) OR account_id = xxxxxxxx )
  • 30.
    © 2016 JustSystemsCorporation. • 中学生が増えたのでこの条件が増えた • OR条件が追加されたことにより、実行計画が くるってしまって、Seq Scan on accounts、Seq Scan on talkersが発生 • indexはあるのに使ってもらえない。 OR jh_child_id = xxxxxxxx
  • 31.
    © 2016 JustSystemsCorporation. 対策
  • 32.
    © 2016 JustSystemsCorporation. 対策 SELECT talkers.* FROM talkers LEFT OUTER JOIN accounts ON talkers.account_id = accounts.id WHERE (accounts.parent_id = xxxxxxxx) UNION SELECT talkers.* FROM talkers WHERE (child_id = xxxxxxxx) UNION SELECT talkers.* FROM talkers WHERE (jh_child_id = xxxxxxxx ) UNION SELECT talkers.* FROM talkers WHERE (account_id = xxxxxxxx ) ORDER BY id;
  • 33.
    © 2016 JustSystemsCorporation. • Index scan ですむ単純SQLにして結果をUNIONすると1000- 10000倍速になった! – (というよりは、ロジック変更で1000倍遅くなっていた) 対策 SELECT talkers.* FROM talkers LEFT OUTER JOIN accounts ON talkers.account_id = accounts.id WHERE (accounts.parent_id = xxxxxxxx) UNION SELECT talkers.* FROM talkers WHERE (child_id = xxxxxxxx) UNION SELECT talkers.* FROM talkers WHERE (jh_child_id = xxxxxxxx ) UNION SELECT talkers.* FROM talkers WHERE (account_id = xxxxxxxx ) ORDER BY id;
  • 34.
    © 2016 JustSystemsCorporation. • UNION が あるところを変更するときは慎重に – UNIONしていいのは、中間処理結果が十分に小 さいとわかっている場合のみ。 – 特にviewの中でUNIONするのは危険 • 中間テーブルが十分に小さく、素早く、作れる ところではUNIONはとても有効! まとめ
  • 35.
    © 2016 JustSystemsCorporation. スマイルゼミを一緒に育てる人、募集しています。