Cybozu Inside Out | サイボウズエンジニアのブログ

 

サイボウズ版 MySQL パフォーマンスチューニングとその結果

こんにちは、アプリケーション基盤チームの青木(@a_o_k_i_n_g)です。先日親知らずを抜歯した時、つらすぎたので MySQL の JOIN のことを考えて心の平静を保っていました。

サイボウズの製品のひとつである kintone はニーズに応じて自由に業務アプリのようなものを手軽に作ることができ、データの検索条件やソート条件も細かくカスタマイズ可能で、様々なレベルでのアクセス権も設定可能という非常に便利なツールです。

しかしその機能を支える裏側では複雑なクエリが発行され、MySQL に多大な負荷をかけています。サイボウズのクラウドには数十テラバイトに登る MySQL データがあり、数千万件オーダーのテーブルを複数 JOIN するクエリが毎秒のように実行されるという、エンジニア魂が滾る環境です。

現在サイボウズでは性能改善に力を入れており、僕もその業務に従事しています。例えば2018年7月の更新では kintone の Innodb_rows_read を半減させており、その成果は記事の最後で紹介しています。

サイボウズの性能改善業で得た MySQL に関する知識知見をこの記事にまとめました。MySQL の性能に苦しむ皆様のお役に立てれば幸いです。

調査方法編

スロークエリログを読もう

まず第一にスロークエリログを読みましょう。

とは言っても上から順にただ読むのではなく、統計処理をして総合的に見てどのクエリがどのくらい遅いのかを可視化すると良いです。上から遅い順に改善していきましょう。

統計処理をする際は percona-toolkit の pt-query-digest が役に立つでしょう。

サイボウズでは、スロークエリログのパラメーターを潰して安全に扱える形式のログも用意しており、気軽にクエリや統計情報などを取得できるようになっています。また、読みやすさやいくつかの利便性の観点から統計情報を出すツールは自作しています。

スロークエリログにはクエリそのもの以外にもいくつかの項目があるので説明します。

  • Query_time: クエリ実行時間
  • Lock_time: ロックした時間
  • Rows_sent: ヒットしたレコード数
  • Rows_examined: スキャンしたレコード数

スロークエリに出ているログはクエリそのものや Query_time に目を奪われがちですが、Rows_examined にも注目しましょう。当然ながらこの値が多ければ多いほどスキャンに時間がかかるので、クエリの改善でスキャン数を減らしましょう。Query_time については、その瞬間に実行されている他のクエリなどに影響されるので水ものです。

なにはともあれ EXPLAIN

遅いクエリが判明したら EXPLAIN しましょう。

EXPLAIN 結果の読み方は nippondanji こと Mikiya Okuno 氏の記事が最高です。
漢(オトコ)のコンピュータ道: MySQLのEXPLAINを徹底解説!!

上記記事を読めばもうそれだけでほぼ十分なのですが、いくつか注意点を記します。

まずひとつ目。EXPLAIN の結果はあくまで 実行計画であって、実際に実行した結果ではありません。 そのため、EXPLAIN 上では高速そうでも実行すると遅かったり、あるいはその逆もあり得ます。

ふたつ目。MySQL のオプティマイザはあまり賢くありません。 そのため、適切なインデクスが使われていなかったり、JOIN するテーブルの順序が最適ではないということがしばしば起こります。そのような事が判明したら FORCE INDEX したり STRAIGHT_JOIN したりしましょう。

みっつ目。Extra 欄に Using temporary が出たからといって遅いとは限りません。これは当該クエリが一時テーブルを使うことを意味していますが、一時テーブルを使うといってもオンメモリで済む場合もあるので、常に遅いとは言えません。オンメモリで済むかどうかは tmp_table_sizemax_heap_table_size のしきい値で判断されるので、適宜調整しましょう。

最後に、Using filesort について同じく Mikiya Okuno 氏の素晴らしい記事があるので熟読しましょう。
漢(オトコ)のコンピュータ道: Using filesort

MySQL の状態を知ろう

MySQL が持つ様々な状態は SHOW GLOBAL STATUS で一覧することができます。とはいえ数百項目あり全てを見ることは大変ので、いくつかよく使う項目をリストアップします。

Variable_name 意味
Created_tmp_disk_tables ストレージ上に作られた一時テーブルの数
Created_tmp_tables メモリ上に作られた一時テーブルの数
Slow_queries スロークエリの数
Bytes_sent クライアントに送信したデータ量(バイト)
Bytes_received クライアントから受け取ったデータ量(バイト)
Com_select SELECT ステートメントが実行された回数。他の Com_xxx 系も同様で、例えば Com_insert は INSERT ステートメントの実行回数。
Handler_write レコード挿入のリクエスト数
Innodb_data_read 読み取られたデータ量(バイト)
Innodb_data_written 書き込まれたデータ量(バイト)
Innodb_row_lock_time 行ロック取得に要した合計時間(ミリ秒)

全項目の仕様はこちら。
MySQL :: MySQL 5.7 Reference Manual :: 5.1.9 Server Status Variables

5.6 版ですが日本語版もあります。ざっと目を通しておくと良いでしょう。

これらの値を見たい時、サイボウズでは SHOW GLOBAL STATUS クエリを発行せずとも Datadog 上で閲覧できるようになっています。過去の推移も閲覧できるので、同様のツールを導入しておくと非常に便利です。

こちらはとある環境の MySQL の Com_select の推移を表示した例です。 MySQL の Com_select の推移を表示したグラフ

MySQL の変数を知ろう

MySQL は SHOW GLOBAL VARIABLES クエリで MySQL の変数一覧を知ることができます。

例えばソートに関する設定を見たい時、下記のようなクエリで閲覧可能です(※各値はローカル環境での適当なものです)。

mysql> SHOW GLOBAL VARIABLES LIKE '%sort%';
+--------------------------------+-----------+
| Variable_name                  | Value     |
+--------------------------------+-----------+
| innodb_disable_sort_file_cache | OFF       |
| innodb_ft_sort_pll_degree      | 2         |
| innodb_sort_buffer_size        | 1048576   |
| max_length_for_sort_data       | 1024      |
| max_sort_length                | 1024      |
| myisam_max_sort_file_size      | 134217728 |
| myisam_sort_buffer_size        | 4194304   |
| sort_buffer_size               | 4194304   |
+--------------------------------+-----------+
8 rows in set (0.00 sec)

もちろん MySQL クライアント上から変数の書き換えは可能ですが、MySQL をシャットダウンするとその設定値は失われます。永続的に変更したい場合は my.cnf を修正しましょう。

よくある使い方としては、一時的にクエリログを出したい時に general_log 変数を変更することが多いです。

SET GLOBAL general_log = 'ON'

その他多種多様にあるパラメーターも同様の方法で変更可能です。

一般的なチューニングの改善度合いとして、クエリの改善そのものは 100 倍や 1000 倍くらい高速化するケースがありますが、パラメーターの修正は数パーセント程度の改善にとどまる場合が多いです。

クエリのプロファイルを取ろう

MySQL には性能に関する情報を保持する performance_schema というデータベースがあります。このデータベースを利用すると様々な情報を取得できるのですが、ここではクエリのプロファイルを取る方法を紹介します。

performance_schema が有効になってない場合、my.cnf に下記設定を取り込んで再起動しましょう。

[mysqld]
performance_schema=on

performance_schema が有効になったら、プロファイルの事前準備としてこれらのクエリを発行します。

UPDATE performance_schema.setup_instruments SET ENABLED = 'YES', TIMED = 'YES' WHERE NAME LIKE '%statement/%';
UPDATE performance_schema.setup_instruments SET ENABLED = 'YES', TIMED = 'YES' WHERE NAME LIKE '%stage/%';
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME LIKE '%events_statements_%';
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME LIKE '%events_stages_%';

次に、プロファイルしたいクエリを実行。

SELECT *
FROM thread t
    INNER JOIN thread_comment c ON (c.threadId = t.id)
WHERE
    t.appId = 723
ORDER BY c.id DESC
LIMIT 21 OFFSET 100000;

その後、上記クエリの EVENT_ID を取得します。WHERE 句の SQL_TEXT LIKE ? でプロファイルしたいクエリを特定できる条件を書きましょう。

SELECT
  EVENT_ID,
  TRUNCATE(TIMER_WAIT/1000000000000,6) AS Duration,
  SQL_TEXT
FROM
  performance_schema.events_statements_history_long
WHERE
  SQL_TEXT LIKE '%OFFSET 100000%';

すると LIKE 句に書いた部分にマッチするクエリと EVENT_ID が表示されます。

ここでは仮に EVENT_ID を 1323 として、下記クエリを発行します。

SELECT
  event_name AS Stage,
  TRUNCATE(TIMER_WAIT/1000000000000,6) AS Duration
FROM
  performance_schema.events_stages_history_long
WHERE
  NESTING_EVENT_ID=1323;

結果。ステージ毎にどれだけ時間がかかっていたかが表示されます。このケースでは Sending data に時間がかかっていることがわかります。

mysql> SELECT
    ->   event_name AS Stage,
    ->   TRUNCATE(TIMER_WAIT/1000000000000,6) AS Duration
    -> FROM
    ->   performance_schema.events_stages_history_long
    -> WHERE
    ->   NESTING_EVENT_ID=1323;
+------------------------------------------+----------+
| Stage                                    | Duration |
+------------------------------------------+----------+
| stage/sql/starting                       | 0.000033 |
| stage/sql/Waiting for query cache lock   | 0.000000 |
| stage/sql/starting                       | 0.000000 |
| stage/sql/checking query cache for query | 0.000066 |
| stage/sql/checking permissions           | 0.000001 |
| stage/sql/checking permissions           | 0.000002 |
| stage/sql/Opening tables                 | 0.000015 |
| stage/sql/init                           | 0.000020 |
| stage/sql/System lock                    | 0.000006 |
| stage/sql/optimizing                     | 0.000006 |
| stage/sql/statistics                     | 0.000148 |
| stage/sql/preparing                      | 0.000011 |
| stage/sql/Sorting result                 | 0.000003 |
| stage/sql/executing                      | 0.000000 |
| stage/sql/Sending data                   | 9.995573 |
| stage/sql/end                            | 0.000001 |
| stage/sql/query end                      | 0.000006 |
| stage/sql/closing tables                 | 0.000006 |
| stage/sql/freeing items                  | 0.000015 |
| stage/sql/logging slow query             | 0.000030 |
| stage/sql/cleaning up                    | 0.000001 |
+------------------------------------------+----------+
21 rows in set (0.00 sec)

相関サブクエリを使うなど一部のケースではこの結果セットが長大になることがあります。その時は Stage カラムでグルーピングするとわかりやすいでしょう。

SELECT
  event_name AS Stage,
  TRUNCATE(SUM(TIMER_WAIT)/1000000000000,6) AS Duration
FROM
  performance_schema.events_stages_history_long
WHERE
  NESTING_EVENT_ID=1323
GROUP BY event_name;

Sending data について一つ注意点があります。Sending data は MySQL がクライアントにデータを送信するだけのイベント ではありません。 実際は、データベースからレコードをフェッチしてフィルタリング処理をしているか、クライアントへのデータ送信を指しているので注意しましょう。このケースでは OFFSET が巨大ゆえのレコードのスキャン数の多さがボトルネックになっています。

クエリのプロファイルについては SET profiling=1; して表示する方法もあるのですが、Deprecated なのでここでの紹介は割愛します。

参考
MySQL :: MySQL 5.7 Reference Manual :: 25.18.1 Query Profiling Using Performance Schema

ロック情報を見よう

MySQL の性能を引き出せない原因の一つとして、ロック待ちが多発してしまうケースが挙げられます。

ここでは information_schema を活用してロックに関する情報を取得する例を紹介します。information_schema とは MySQL の各種テーブルのメタデータを持つテーブルで、テーブル構造などはもちろん、テーブルが使用しているデータ量、ロック情報などを取得することができます。

ロック情報の取得手順は sh2 氏のこの記事が詳しいです。
MySQL InnoDBにおけるロック競合の解析手順

上記記事からクエリを引用します。

select t_b.trx_mysql_thread_id blocking_id,
       t_w.trx_mysql_thread_id requesting_id,
       p_b.HOST blocking_host,
       p_w.HOST requesting_host,
       l.lock_table lock_table,
       l.lock_index lock_index,
       l.lock_mode lock_mode,
       p_w.TIME seconds,
       p_b.INFO blocking_info,
       p_w.INFO requesting_info
from information_schema.INNODB_LOCK_WAITS w,
     information_schema.INNODB_LOCKS l,
     information_schema.INNODB_TRX t_b,
     information_schema.INNODB_TRX t_w,
     information_schema.PROCESSLIST p_b,
     information_schema.PROCESSLIST p_w
where w.blocking_lock_id = l.lock_id
  and w.blocking_trx_id = t_b.trx_id
  and w.requesting_trx_id = t_w.trx_id
  and t_b.trx_mysql_thread_id = p_b.ID
  and t_w.trx_mysql_thread_id = p_w.ID
order by requesting_id,
         blocking_id
\G

このクエリで、リクエストしている方とブロックしている方のクエリやトランザクションレベルを見ることができます。結果を解析すれば INSERT クエリや FOR UPDATE がついたクエリ、はたまた SERIALIZABLE なトランザクションが他のトランザクションをブロックしているというようなことが読み取れるでしょう。

サイボウズではこのクエリを定期的に発行し、結果を整形してログに出力しています。そのため日々気軽にロック情報の調査を行うことができ、その知見は製品改善に活かされています。調査方法としてはブロックしているクエリでグルーピングしてソート、あたりが簡単で明瞭です。他のクエリをブロックしやすいクエリが仮に INSERT クエリだったら一度に INSERT する量を減らすなどの対処を行い、なるべくロック競合が起きないよう製品を日々改善しています。

InnoDB の詳細な状態を見よう

SHOW ENGINE INNODB STATUS クエリで InnoDB の詳細な情報を取得できます。ただしこの結果は SHOW GLOBAL VARIABLES などとは異なり、ひとつの項目の長大なテキストとして表示されるので解読しにくいです。下記記事を参考に頑張って解読しても良いですが、innotop というツールを使うのも手です。
なぜあなたは SHOW ENGINE INNODB STATUS を読まないのか

innotop は MySQL の各種状態を可視化して top コマンド風に表示してくれるツールです。innotopSHOW ENGINE INNODB STATUS の結果もパースしてロック待ちの状況等を表示してくれます。あまりメンテナンスは活発ではないようですが、一応 MySQL 5.7 でも動きます。
MySQLのリアルタイムモニタリングに「innotop」

サイボウズではこの情報を元に Adaptive Hash Index のロック待ち時間が多いことを突き止めました。対策として innodb_adaptive_hash_index_parts パラメータの値を増やして改善を試みています。

実践編

オンライン DDL を活用しよう

性能問題に取り組むとスキーマの修正をしたくなることがあります。しかし性能問題が出るようなテーブルは大抵巨大で、カラムやインデクスを追加するだけでもそれなりに時間がかかります。以前の MySQL ではスキーマの修正をするとデータの更新が行えなくなり、サービスが正常稼働しているとは言えない状況に陥りました。

これを改善するのがオンライン DDL という仕組みで、この仕組みを使えばサービスを正常に稼働させたままスキーマの修正が行えるようになります。スキーマ修正の内容によっては処理中に書き込み出来ないケースもありますが、大抵のケースではオンライン DDL として扱うことができます。
MySQL :: MySQL 5.7 Reference Manual :: 14.13.1 Online DDL Operations

似たような仕組みでオンラインスキーマ変更を可能にする pt-online-schema-change というツールもあります。

また、スキーマの変更はしなくともカラムのデータを一気に更新したい場合があります。その際 UPDATE クエリで全データを一気に更新してしまうとクエリ実行中はデータの挿入ができなくなるので注意が必要です。例えば次に示すクエリは全レコードの body カラムをスキャンするので、レコード行数によっては時間がかかることが想定されます。

UPDATE blob_file SET size = LENGTH(body);

テーブル全体に対する UPDATE クエリはレコード件数によっては危険なので、アプリケーション側で id を指定するなどで小分けにすることを検討しましょう。
例:

UPDATE blob_file SET size = LENGTH(body) WHERE id BETWEEN 12000 AND 13000;

カバリングインデクスを活用しよう

クエリをチューニングする際はカバリングインデクス化することも検討しましょう。

通常、インデクスを利用したクエリは、

  1. インデクスデータにアクセスし、条件に一致する主キーを取り出す
  2. テーブルデータにアクセスし、手順1.で得られた主キーを元にレコードデータを取り出す

という手順で行われています。この時、もしクエリ実行に必要なデータが全てインデクスデータ内に載っていたら手順2.のレコードデータを取り出す作業が不要になり、インデクスデータのアクセスのみで済む ので高速に処理を終えることができます。

クエリがインデクスデータへのアクセスのみで済んでいるかどうかは EXPLAINExtra 欄に Using index があるかどうかで判断できます。Using index ならインデクスデータのアクセスのみで済んでいます。ちなみに、Using index なクエリにはインデクスカバークエリという名称がついています。

例えば下記のようなクエリがある時、user テーブルに (joinDate, name) というインデクスがあってそれを利用していればインデクスカバークエリになるでしょう。

SELECT name FROM user WHERE joinDate > ?

とは言え、常にカバリングインデクスを利用すれば良いというわけではありません。まず第一に、インデクスを作り過ぎると更新速度の低下を招きます。ケースによってはレコードの挿入が数十倍も遅くなることがあります。第二に、インデクスデータが大きくなると空間効率が悪化し、メモリを効率良く使えなくなる可能性があります。

また、クエリの変更に弱くなるという弱点も言えるかもしれません。インデクスカバークエリに何か条件文や SELECT するカラムを増やした時などはテーブルデータへのアクセスが発生し、予想外に性能が劣化するケースがあります。特に WHERE 句に条件を追加した時は「条件を追加したのだから選択するレコード数が減って速くなるのでは?」と思いがちですが、テーブルデータへのアクセスが発生することで思わぬ劣化を招くことがあります。よってインデクスカバークエリを修正する際はインデクスの更新も迫られるケースがある点に注意です。

JOIN する順序を制御しよう

性能の観点で言えば、JOIN するテーブルが一体どの順序で JOIN されるのかは非常に重要です。当然ですが、レコードのスキャン数が少なくて済むほどクエリは高速に処理を終えることができます。どの順序で JOIN を行うかは MySQL のオプティマイザが判定するのですが、前述したように MySQL のオプティマイザはさほど賢くありません。 よって、アプリケーション側でオプティマイザ以上に賢い戦略を取れるなら、その情報を用いて JOIN の順序を制御しましょう。

JOIN の順序を制御する方法は、JOIN するテーブルのインデクスを FORCE INDEX で強制したり、STRAIGHT_JOIN を使って制御したりする方法があります。EXPLAIN して実験しましょう。

JOIN の順序制御をする際は各テーブルのレコード件数が重要になりますが、COUNT クエリは遅い ので COUNT クエリを発行して計算するのは本末転倒になりかねません。近似値で良いなら EXPLAIN を発行して rows カラムで高速に取得できるので、これで代替するのも手です。

複数ソート条件を持つクエリに注意しよう

MySQL は複数ソート条件を持ったクエリの処理が苦手です。 複数ソート条件を持つクエリは人間の直感より激しく遅くなるケースがあります。

例えば下記のような article テーブルのデータを create_dateid でソートするケースを考えてみます。

SELECT id
FROM article
ORDER BY create_date DESC, id
LIMIT 20

この時、create_date の降順 20 件を取得し、その 20 件の中で id でソートすれば良いはずです(20個目と21個目の create_date が等しいならもう少し先まで読む必要がありますが)。しかし実際このクエリは article テーブルを全てスキャンしてしまいます。

これを改善するため、スキャンするレコードを削減するようにします。まず create_date で降順ソートした時の 21 件目の create_date の値を取得します。

SELECT create_date
FROM article
ORDER BY create_date DESC
LIMIT 1 OFFSET 20

元々のクエリで得られる結果に含まれる create_date は上記クエリで得た create_date の値より大きいので、そのような条件を追記します。このクエリは元のクエリに比べてスキャンするレコード数が少なく済むので性能が改善します。

SELECT id
FROM article
WHERE create_date >= '2018-07-15' -- 上記クエリで得た作成日をここに追加
ORDER BY create_date DESC, id
LIMIT 20

テーブルを最適化しよう

MySQL は レコードを削除しても実データは縮小しません。 そのため、レコードの追加と削除が頻繁に行われるようなテーブルではデータがフラグメンテーションを起こし、パフォーマンスに影響を与えることがあります。そのようなテーブルには OPTIMIZE TABLE table_name クエリを発行すれば最適化が行われ、実データも縮小します。

ではどのテーブルに対して最適化を行えば良いのでしょうか?

そのヒントとして、information_schema を使う方法があります。information_schema.tables テーブルには data_free カラムがあり、これはテーブル内の空きスペースを表しています。つまりこの data_free カラムのサイズが大きいほど、フラグメンテーションしていると考えられ、OPTIMIZE TABLE の効果を発揮できることでしょう。

data_free が大きいテーブルから順に出力するクエリを示します。ちなみに data_length はレコードのデータ量、index_length はインデクスのデータ量を表します。ただし実データ(*.ibd)のファイルサイズとは乖離があるようなのであくまで目安程度としておくのが無難です。

SELECT
  table_schema,
  table_name,
  sys.format_bytes(data_length) AS data_size,
  sys.format_bytes(index_length) AS index_size,
  sys.format_bytes(data_free) AS data_free_size
FROM
  information_schema.tables
ORDER BY data_free DESC
LIMIT 10;

このクエリで得られたテーブルは他のテーブルに比べてフラグメンテーションが進んだ状態と考えられるので、深夜にでも OPTIMIZE TABLE を発行しましょう。

統計情報を更新しよう

MySQL には各テーブルの統計情報を保持する機能があり、この情報はクエリの実行計画時に利用されています。統計情報は InnoDB であれば自動更新されるので通常気にする必要はありませんが、この統計情報はテーブルデータのうち一部をランダムに取得して計算しているため偏りが発生することがあります。また、最近挿入されたレコードでデータのカーディナリティが大きく変わった等でも実行計画に影響が出ることがあります。

実際のところ問題になるケースはあまり多くありませんが、気になったら ANALYZE TABLE table_name クエリを発行して統計情報を更新しましょう。よく分からないけど遅いというようなクエリがある時は ANALYZE TABLE の前後で EXPLAIN 結果を比較したり性能比較したりすると良いです。

巨大オフセットについて

たとえ単純なクエリであっても、OFFSET に 100 万などの巨大な値が指定されるとどうしてもレコードのスキャン数が増えてしまい、遅くなります。巨大な OFFSET が指定されている以上少なくともその箇所まではレコードをスキャンする必要があるので、クエリの改善で超高速化はちょっと難しいです。

そのため、このケースでは別の手段を検討しましょう。kintone では巨大 OFFSET が指定されるケースのほとんどはレコードの全データのバックアップを目的としたプログラムによるアクセスであるということがわかっています。そこで OFFSET を増加させるのではなく、前回取得したデータの最後のレコード ID から N 件取得、という方式に変更することにより高速化出来ることが判明しており、下記記事で紹介しています。

kintoneの大量レコード取得を高速化 - cybozu developer network

これはユーザー側で対応してもらう必要があるのですが、データ取得処理が速くなるのでユーザー側にもメリットがあり、引き続き啓蒙していく予定です。

データ取得にかかる時間

実践結果

最近はどのリリースにも性能改善が取り込まれているのですが、ここでは2018年7月で行った kintone の性能改善の成果を紹介します。

この更新ではいくつかの性能改善取り込まれましたが、一番効果が大きかったのは複数 JOIN を持つクエリの順序制御でした。複数テーブルを JOIN するクエリについて、各テーブルの行数の概算値を見積もり、どのテーブルから JOIN すれば良いのかを計算して組み立てるというものです。

kintone はユーザーがアプリケーションを定義でき、細かく検索条件やソート条件を指定できることからもわかる通り、クエリは非常に多種多様です。そのためユーザーの使い方によって効果のほどに差があるのですが、実際に適用した結果を見る限り大きな効果があったと言えると思います。

このグラフはあるユーザー環境でのレスポンスタイム 99 パーセンタイル値です。7/8(日)に行われた更新の適用前後で 1/6 ほどにまで下がりました。 あるユーザー環境でのレスポンスタイム 99 パーセンタイル値

こちらは今回の改善が良く効いたとある環境の MySQL の Slow_queries の数で、更新後激減してることがわかります。7/7(土)は休日なので元からスロークエリの数も少ないですが、以降の平日もスロークエリ数は減ったままです。 今回の改善が良く効いたとある環境の MySQL の Slow_queries の数

kintone 全体で見た時も、MySQL の Innodb_rows_read の値がおよそ半減していました。kintone を使う全ての環境、あらゆる使い方をひっくるめて半減というのはなかなか効果が大きいのではと思います。 kintone 全体で見た時の、MySQL の Innodb_rows_read の値

性能に関する様々な調査をしてきて、改めて思うのは MySQL は驚異的に超高速である ということです。多くの遅いクエリは MySQL の気持ちに沿って書きなおせばきっと超高速化を実現できるでしょう。各種設定可能なパラメーターも非常に多く、複雑ではありますが、そういうチューニング可能なところもまた MySQL の魅力の一つだと思います。

今回紹介した性能改善の結果はあくまで一部であって、サイボウズクラウド全体を見渡すと遅い処理がまだまだあります。サイボウズと MySQL は切っても切れない関係にあり、今後もノウハウを蓄積し、引き続き改善していく予定です。サイボウズは大量のデータに多種多様で複雑なクエリと、なかなかチャレンジングで魅力的な環境だと思います。性能改善が好きな皆様、サイボウズで腕を奮ってみませんか。We Are Hiring!

 

QAがテスト設計プロセスの見える化に取り組んだ話

こんにちは。東京品質保証部 QAの矢引です。 今回は、今年の上半期に行った、試験設計プロセスの見える化の活動について紹介します。

製品チーム横断でQAのカイゼンを支援するチーム「SPITz」

サイボウズでは、製品ごとに開発チームが分かれており、QAメンバーがそれぞれの開発チームで活動し、担当製品のテストプロジェクト全般を担当しています。

QAの仕事内容についてはこちらの記事でもご紹介していますのでぜひご覧ください。

blog.cybozu.io

また、上記の活動とは別に、品質保証部内のQA全般のカイゼンを支援するチーム「SPITz」( Software Process Improvement in Test の略)があり、有志のメンバーが活動しています。 これまで、探索的テストの情報収集やTPI NEXTの情報収集・試験的導入などを行いました。

背景

サイボウズではリリースに関する品質の基準は統一されたものがありますが、 試験設計のプロセスについては、部内で大まかな指針はあるものの具体的なプロセスは各チームに任されています。

各チームでは、独自に試験設計プロセスの改善はされているものの、暗黙知として運用されている場合がほとんどで、他チームへ共有ができていない状況でした。そのため、SPITzでは以下の点を問題として取り上げました。

  • 他チームからのフィードバックが得られない。
  • 他チームの良いプラクティスに気づくきっかけがない。

そこで、これらの問題を解決すべく、まずは各チームの試験設計プロセスを見える化しようということになりました。

試験設計プロセスを見える化しよう!

このテーマについて情報収集を行ったところ、参考になる先行事例を見つけました。

SPI JAPAN 2013 「テスト設計プロセス可視化の取り組み」パナソニック株式会社 伊藤由起 http://www.jaspic.org/event/2013/SPIJapan/session1B/1B2_ID008.pdf

これを参考にして、PFDの形式で試験設計プロセスのフローを表現することにしました。

ここで、取り組む際に注意したことは必要以上に詳細化しすぎないということです。 こういったプロセス可視化の際に起こりがちなのは、現状のプロセスを一から十まで全て表現しようとしてしまい、膨大なコストがかかった割にすぐに陳腐化してしまい役に立たなくなってしまうことがあります。

今回は「他のチームと比較すること」「(一回限りではなく)今後も見直して活用すること」を目的としたため、 試験設計の入力となる情報(ソース)と、その情報を元にどのようなプロセスを経ているかの2点にフォーカスして可視化することにしました。

SPITzメンバーの担当製品をターゲットとして、5つのチームの試験設計プロセスを見える化しました。

例えばあるプロジェクトはこのようなフローになりました。 f:id:cybozuinsideout:20180727180433p:plainf:id:cybozuinsideout:20180801181133p:plain

効果

このようにテスト設計プロセスを見える化することで、各製品で全く違うと思われたフローにも共通している部分があることに気付きました。 また、作成した図をベースに各プロダクトのQAメンバーで意見を交換したところ、 自分のチームのプロセスの問題点に新たに気付けたり、 他チームの方法の一部を自チームに取り入れることで、問題点を改善できることに気付くことができました。

例えば、あるチームでは、試験仕様書のレビューはQAメンバー内でしかしていませんでしたが、他チームのフローを参考にすることで、PGによる試験仕様書のレビューのフローを取り入れることにした事例があります。 また、図をきっかけとしてチーム間のコミュニケーションが促進化され、よりQAメンバー間での情報交換をしやすくなったと感じています。

今後も継続的にQAメンバーのチームを超えた学び合いの促進に役立てたいと考えています。

最後に

今回は、製品チーム横断でQAのカイゼンを支援するチーム「SPITz」が行った、試験設計プロセスの見える化の活動について紹介しました。 現在、SPITzは次のテーマをAgile Testing として鋭意活動中です。

今回の例に留まらず、サイボウズでは「日々の仕事をカイゼンしたい」「いろんな人と繋がって学び合いたい」という改善意欲をもった方がチャレンジできる環境があります。

ぜひサイボウズで一緒に働きましょう!

 

サイボウズの継続的性能検証

TE(テストエンジニアリングチーム) の川向です。 今回は性能検証(負荷検証)自動化のお話です。

サイボウズでの性能検証(負荷検証)

性能検証は、製品に対して大量のアクセスを実行し、これによって得られるデータを解析する試験です。負荷検証とも呼ばれます。

サイボウズでは解析結果を元に、バージョンアップによる性能の劣化調査や、パッケージ版向けのサーバーの構成例を作成しています。

検証は以下の流れで行っています。

・環境構築

検証対象の製品を構築します。クラウドの製品の場合、運用環境と同等の機材上に構築しています。 パッケージ版の製品では、利用が想定されるスペックのマシンを使用しています。

・テストデータの作成

製品の利用ユーザ数に応じてデータも増えるので、 性能検証でもある程度のユーザ数を仮定してデータを用意しています。 基本的にはバックアップしている過去の検証データを使います。データ作成時間の削減のためです。 データ量は例えばGaroonでは 1.2TB ほどです。

・テストの実施

実際に負荷をかけていきます。 これは内製のscalebenchを使用しており、自動化されています。 お客様のアクセスログを元に作成したいくつかのシナリオ(例: ログインしてから、スケジュールの一覧画面を開いて、そのうちの一つのスケジュールの詳細画面を開く)を同時に多数実行します。 検証中は少しずつ負荷を増やしていきます。

・テスト結果の集計/分析

scalebenchの検証結果やMySQLやマシンのパフォーマンスデータなどをはExcelのマクロで集計しています。

問題点は?

この性能検証にはいくつかの問題点がありました。

実行に時間がかかる

性能検証用の環境構築やバックアップデータの展開に時間がかかるという問題がありました。 これらは手動でコマンドを入力する作業も多かったのも一因でした。

また、人的リソースの問題もありました。 QAには性能検証専任のチームがおり、製品チームからの依頼を受けて検証を行っています。 チームの人数はそれほど多くないので、依頼が多くなると検証が遅れることがありました。

性能劣化の検知が遅れがち

実行には時間がかかるので、手戻りを防ぐため性能検証は開発の後期に行われることが多く、 開発中に性能劣化を起こすバグが入ってしまっても、それに気が付くのが遅れていました。 また、開発後期にはすでにいろいろなコミットが含まれているので、 どのコミットが性能劣化を引き起こしたかを特定するのにも時間がかかります。

検証結果がエクセル

前述のように、検証結果はエクセルマクロで集計されています。

社内ではセキュリティ上の理由で原則的にマクロの使用が禁止されています。 このため、製品チームのメンバーでもマクロが実行できない人もいます。 また、このマクロはWindows以外では動作しません。 MacやUbuntuをメイン環境にしている人は開くことができません。

どう解決する?

TEでは、これらの問題を自動化や新しいツールの導入によって解決しました。

te continuous performance test overview
性能検証の全体図

実行はJenkinsで

毎晩Jenkinsが実行するようになりました。 これにより、これまで開発後期に数回しか行っていなかったものが、 開発の全期間において毎日1回実施されるようになりました。

結果はWebで

実行結果はInfluxDBに保存し、Grafanaで閲覧するようにしました。 社内の開発環境ではPrometheusが導入されており、そちらの情報も参照できるようにしています。 これにより、ブラウザから検証結果をすぐに確認できるようになりました。

また、scalebenchの検証結果データもJenkins上に保存しており、以前と同じ方法でのエクセルでのデータ調査も行えるようになっています。

レストアはWalBで

cybozu.comでは14日間のバックアップデータを保持する仕組みがあります。 これは内製のWalBというツールで実現されています。今回の自動化でもこれを使用しました。

今回の自動化では、バックアップデータは14日分ではなく、 リリース済みのバージョンの製品の検証データをバックアップしています。 製品のリリースがあるときにそれを更新してバックアップをし直します。

検証を行うときはバックアップデータをレストアします。 そして、運用環境と同じく製品のバージョンアッププログラムでデータを更新してから検証を行っています。

この仕組みにより、これまで環境構築に13日間かかっていたのが、30分になりました。

通知はkintoneで

CIは回り続けるだけでは価値がありません。 問題を検知して適切なアラートを発行することが大切です。 今回の仕組みでは、性能が前日や以前のバージョンの製品からある程度低下したときに製品チームに通知を行うようにしています。

通知は、kintoneのREST APIを使って社内のkintoneにレコードを登録することで行っています。

成果は?

この仕組みは昨年からGaroonに対して動かしています。

稼働してまもないころ、製品の劣化を検知して製品チームにフィードバックできました。 結果として、この問題を起こすコードがすぐに特定されて取り除かれました。

振り返り

今回ご紹介しているツールや技術は、ほとんどがすでに社内にあるものでした。 ですが、知識の共有はうまくなされていない状況でした。

弊社のTEはプログラマとQAのジョイントチームとして結成されており、 性能検証自動化を担当したのも、 性能検証チーム、SRE QA、Garoonのプログラマをそれぞれ兼務しているメンバーが集まって行いました。 このおかげで、それぞれの知見(性能検証の実行方法、cybozu.com環境でのデータレストアの仕組み、CIの稼働方法)を組み合わせることができました。

ジョイントチームのメリットが発揮された良い例だと思います。

今後

現時点ではGaroonにのみ実施している状態なので、今後は他の製品へも展開していきたいと考えています。

また、最近組織上の変更があり、性能検証チームとTEが統合されました。 今後はこのような改善を加速できると思っています。

また、現状の性能検証は、製品がどの程度の負荷まで耐えられるかを調べるものです。 これは、弊社製品がパッケージ版のみだった時には有効でしたが、 クラウドの製品の展開が進む中では、不十分だと考えています。 このため、今後は別の方式も検討していきたいと考えています。

 

PSIRT Framework のご紹介

PSIRT Framework のご紹介

こんにちは。セキュリティ室の伊藤です。 昨年から取り組んできた PSIRT Framework(ドラフト版)を日本語に抄訳した成果物が公開されましたので、 本ブログで、ご紹介いたします。

PSIRT Services Framework 1.0 Draft (Japanese) ※ PDF ファイルです。 https://www.first.org/education/FIRST_PSIRT_Services_Framework_v1.0_draft_ja.pdf

抄訳のきっかけ

サイボウズが加盟するコンピュータソフトウェア協会(以下、CSAJ) は Software ISAC(以下、SWISAC)と 呼ばれる研究会を今年立ち上げました。

Sofrware ISAC http://www.csaj.jp/committee/security/softwareisac.html

ソフトウェアのよりセキュアな開発や更新等を行うための活動を行う SWISAC では、 活動の一環として PSIRT Framework を抄訳することとしました。 https://www.first.org/education/Draft_FIRST_PSIRT_Service_Framework_v1.0

抄訳のご支援および、FIRST との折衝など JPCERT/CC 様に多大なご支援をいただきました。 また抄訳成果物のレビューについて、Sony PSIRT 様ならびに、Panasonic PSIRT 様にご支援いただきました。 改めましてこの場を借りて、お礼申し上げます。

PSIRT Framework とは?

PSIRT Framework は自社製品の脆弱性に対応するセキュリティチームである 「PSIRT」が提供するサービス(≒役務)をまとめたドキュメントです。 国際的な CSIRT のコミュニティである「FIRST」が 2017 年 8 月に Draft 版が公開されました。

PSIRT Framework ができるまでの経緯については、下記の記事が大変参考になりますので、 ぜひご参照ください。

現代のCSIRTが提供するサービスをまとめた一覧「FIRST CSIRT Framework Version 1.1」 https://internet.watch.impress.co.jp/docs/column/security/1099603.html

PSIRT Framework では、サービスを6つの大分類(Service Area)にまとめ、 これらの関係性を次のようにまとめています。

PSIRT Organuzation Structure https://www.first.org/education/psfw_media/image1.png

PSIRT の組織構造は、取り扱う製品に応じて様々な形があります。 他の Service Framework と同様に全てのサービスを PSIRT に実装する必要はありません。 組織内の各チームがどのサービスエリアの業務を担っているかを俯瞰し、 不足しているサービスについて各チームに紹介したり、実装について議論するといった使い方ができます。 また各サービスエリアごとに収集するべき定量的なメトリクスの例が提示されているため、 PSIRT の業務を継続的に改善するために必要な基礎的な情報源の洗い出しや、見直しを行う際にも活用できます。

この他に PSIRT 活動を通して必要となる機能が「General PSIRT Activities(Draft 版では「Operational Foundation(運用基盤)」)」として、まとめられています。 PSIRT を構築する上で必須となる活動が集約されていますので、特にこれから PSIRT を構築するという際には、ぜひご参照ください。

ここからは6つの「Service Area」に分類される個々の「Service」と、 それを実現するための「機能(Function)」について、簡単にご紹介します。

  • Service Area 1 Stakeholder Ecosystem Management(ステークホルダエコシステムマネジメント)
  • Service Area 2 Vulnerability Discovery(脆弱性の発見)
  • Service Area 3 Vulnerability triage and analysis(脆弱性情報のトリアージと分析)
  • Service Area 4 Remediation(対策)
  • Service Area 5 Vulnerability Disclosure(脆弱性の開示)
  • Service Area 6 Training and Education(トレーニングと教育)

Service Area 1 Stakeholder Ecosystem Management(ステークホルダエコシステムマネジメント)

内部および、外部のステークホルダのそれぞれに応じたコミュニケーション方法の確立、 セキュアな開発ライフサイクルの構築などが含まれます。Bug Bounty の機能もこの Service Area に含まれています。

Service 1 Internal Stakeholder Management(内部のステークホルダ管理)

  • Function 1 Engage Internal Stakeholders(内部ステークホルダとの交流)
  • Function 2 Internal Secure Development Lifecycle(社内のセキュアな開発ライフサイクル)
  • Function 3 Incident Post-mortem process(インシデント事後対応プロセス)

Service 2 Finder Community Engagement(発見者のコミュニティとの交流)

  • Function 1 Engage Finders(発見者との交流)

Service 3 Community and Organizational Engagement(コミュニティと組織との交流)

  • Function 1 Define & Engage with Upstream Communities & Partners(上流コミュニティとパートナーの定義と交流)
  • Function 2 Engage with Peer PSIRTs(ピア PSIRT 間の交流)
  • Function 3 Engage with Coordinators (CERTs, CSIRTs, or other coordination center organizations)(コーディネーター(CERT、CSIRT および、その他の調整組織)との交流)
  • Function 4 Engage with security vendors(セキュリティベンダとの交流)
  • Function 5 Engage with Bug-Bounty Vendors(バグバウンティベンダとの交流)

Service 4 Downstream Stakeholder Management(下流のステークホルダマネジメント)

  • Function 1 Engage with Downstream Stakeholders(下流のステークホルダとの交流)

Service 5 Incident Communications Coordination within the Organization(組織内でのインシデントに関するコミュニケーションの調整)

  • Function 1 Provide Communication Channels/Outlets(通信チャネル/情報提供方法を提供する)
  • Function 2 Secure Communications Management(安全なコミュニケーションの管理)
  • Function 3 Security Defect Tracking System Updates(脆弱性をトラッキングするシステムのアップデート)
  • Function 4 Information Sharing and Publishing(情報の共有および公開)

Service 6 Reward Finders with Recognition & Acknowledgement(広告と謝辞による報酬を発見者に与える)

  • Function 1 Provide Acknowledgements(謝辞の提供)
  • Function 2 Reward Finders(発見者への報償)

Service 7 Stakeholder Metrics(ステークホルダメトリクス)

  • Function 1 Understand Stakeholder Artifact Requirements(ステークホルダの要件を理解する)
  • Function 2 Collect Stakeholder Metrics(ステークホルダのメトリクスを収集する)
  • Function 3 Analyze Stakeholder Metrics(ステークホルダメトリクスの分析)
  • Function 4 Provide Stakeholder Metric Artifacts(ステークホルダメトリクスの報告書を提供する)

Service Area 2 Vulnerability Discovery(脆弱性の発見)

脆弱性ハンドリングにおける「脆弱性情報の受付」に関する機能や、脆弱性情報の収集に関する機能が含まれています。

Service 1 Intake of Vulnerability Reporting(脆弱性報告の受付)

  • Function 1 Ensure Reachability(到達性を確保する)
  • Function 2 Handle Vulnerability Reports(脆弱性報告の取り扱い)

Service 2 Identify Unreported Vulnerabilities(報告されない脆弱性を特定する)

  • Function 1 Monitor Exploit Databases(攻撃情報データベースの監視)
  • Function 2 Monitor Conference Programs(カンファレンスプログラムの監視)
  • Function 3 Monitor Publications by Renown Finders(高名な報告者による発表を監視する)
  • Function 4 Monitor Mass Media(マスメディアの監視)

Service 3 Monitoring for Product Component Vulnerabilities(製品コンポーネントの脆弱性のモニタリング)

  • Function 1 Inventory of Product Components(製品コンポーネントの目録)
  • Function 2 Monitor Third-Party Advisories(サードパーティのアドバイザリのモニタリング)
  • Function 3 Monitor Vulnerability Intelligence Sources(脆弱性に関するインテリジェンスソースのモニタリング)
  • Function 4 Set-up Procedures for Intake of Vendor-Internal Supply Chain Vulnerabilities(ベンダ組織内のサプライチェーンの脆弱性情報の受付手順を確立する)
  • Function 5 Notification of Internal Development Teams(組織内の開発チームへの通知)

Service 4 Identifying New Vulnerabilities(新しい脆弱性を特定する)

  • Function 1 Vulnerability Assessment(脆弱性アセスメント)
  • Function 2 Maintain Expertise for Security Testing Tools(セキュリティテストツールの専門知識の維持)

Service 5 Vulnerability Discovery Metrics(脆弱性発見のメトリクス)

  • Function 1 Operational Reports(運用レポート)
  • Function 2 Business Reports(ビジネスレポート)

Service Area 3 Vulnerability triage and analysis(脆弱性情報のトリアージと分析)

受け付けた脆弱性情報をトリアージし、脆弱性として認定するための機能が含まれています。

Service 1 Vulnerability Qualification(脆弱性の認定基準)

  • Function 1 Quality gate and Bug bars(品質ゲートとバグバーズ)
  • Function 2 Continuous improvement(継続的改善)

Service 2 Established Finders

  • Function 1 Finder Database(発見者データベース)
  • Function 2 Accelerated Handling for Established Finders(関係が良好な発見者の対応を加速)
  • Function 3 Finder Profile(発見者プロファイル)
  • Function 4 Defining Finder Report Quality(報告者のレポートの品質を定義する)

Service 3 Vulnerability Reproduction(脆弱性の再現)

  • Function 1 Establish Service Level Agreement for Vulnerability Reproduction(脆弱性の再現に関するサービスレベルアグリーメント(SLA)の設置)
  • Function 2 Reproduction Test Environment(再現テストの環境)
  • Function 3 Reproduction Tools(再現ツール)
  • Function 4 Vulnerability Storage(脆弱性情報の保管場所)
  • Function 5 Affected Products(影響のある製品)

Service Area 4 Remediation(対策)

認定した脆弱性を改修し、各ステークホルダーに修正プログラムを提供するための機能が含まれています。 また PSIRT に必要となるインシデントハンドリングに関する機能も定義されています。

Service 1 Security Patch Release Management Plan(セキュリティパッチリリースマネジメント計画)

  • Function 1 Product Lifecycle Management(製品ライフサイクル管理)
  • Function 2 Method of Delivery(提供方法)
  • Function 3 Delivery Cadence(提供間隔)

Service 2 Remediation(対策)

  • Function 1 Analysis(分析)
  • Function 2 Remedy Resolution(対策の決定)
  • Function 3 Remedy Delivery(対策の提供)
  • Function 4 Risk Management(リスクマネジメント)

Service 3 Incident Handling(インシデントハンドリング)

  • Function 1 Establish Situation Room(緊急対応室を作る)
  • Function 2 Incident Management(インシデント管理)
  • Function 3 Communication Plan(コミュニケーション計画)

Service 4 Vulnerability Release Metrics(脆弱性リリースメトリクス)

  • Function 1 Operational Reports(運用レポート)
  • Function 2 Business Reports(ビジネスレポート)

Service Area 5 Vulnerability Disclosure(脆弱性の開示)

改修した脆弱性について、脆弱性情報を公開するための機能が含まれています。

Service 1 Notification(通知)

  • Function 1 Intermediate Vendor (Downstream Vendor)(中間ベンダ (下流ベンダ))
  • Function 2 Coordinators(調整者)
  • Function 3 Finder(発見者)

Service 2 Coordination(コーディネーション)

  • Function 1 Bi-Lateral Coordination(双方向なコーディネーション)
  • Function 2 Multi-Vendor Coordination(マルチベンダのコーディネーション)

Service 3 Disclosure(情報公開)

  • Function 1 Release Notes(リリースノート)
  • Function 2 Security Advisory(セキュリティアドバイザリ)
  • Function 3 Knowledge-Base Articles(ナレッジベースの記事)
  • Function 4 内部のステークホルダとのコミュニケーション

Service 4 Vulnerability Metrics(脆弱性情報マネジメントの評価指標)

  • Function 1 Operational Reports(運用レポート)

Service Area 6 Training and Education(トレーニングと教育)

Service Area を担当する各チームに必要となる継続的な教育プログラムに関する機能が含まれています。 PSIRT メンバ、開発者、診断者を始めとして、組織内の各ステークホルダに必要となるトレーニングが記載されています。

Service 1 Training the PSIRT team(PSIRTチームのトレーニング)

  • Function 1 Technical training(技術的なトレーニング)
  • Function 2 Communications Training(コミュニケーショントレーニング)
  • Function 3 Process Training(プロセスのトレーニング)
  • Function 4 Task Tools Training(タスクツールのトレーニング)
  • Function 5 Tracking All Training Initiatives(すべてのトレーニングの取り組みをトラッキング)

Service 2 Training the Development Team(開発チームのトレーニング)

  • Function 1 PSIRT Process Training(PSIRTプロセスのトレーニング)

Service 3 Training the Validation Team(診断チームのトレーニング)

  • Function 1 PSIRT Process Training(PSIRTプロセスのトレーニング)

Service 4 Continuing Education for All StakeholdersS(すべてのステークホルダへの継続的な教育)

  • Function 1 Training the Executive Management(経営層のマネジメントに関するトレーニング)
  • Function 2 Training the Legal Team(法務チームの教育)
  • Function 3 Training the Government Affairs and Compliance Team(政府関係者、コンプライアンスチームの教育)
  • Function 4 Training the Marketing Team(マーケティングチームのトレーニング)
  • Function 5 Training the Public Relations Team(広報チームのトレーニング)
  • Function 6 Training the Sales Team(セールスチームのトレーニング)
  • Function 7 Training the Support Team(サポートチームのトレーニング)

Service 5 Provide Feedback Mechanism(フィードバック機能の提供)

draft 版から正式版へ

2018/06/21 に正式版がリリースされました。全体的に図が多数追加されており、各 Function が理解しやすくなっています。 ここでは主な変更点について、Service Area ごとに確認していきます。

Service Area 1 Stakeholder Ecosystem Management(ステークホルダエコシステムマネジメント)

Service2 に「CSIRT」と「PSIRT」とのかかわりに関する節として Function6 が追加されました。 また Service2 と Service3 の項目が整理されました。 Service3 に定義されていた各ステークホルダとのコミュニケーションに関する記載が Service2 に移動し、 Downstream Community の定義が Service3 に追加されました。

Service 2 Finder Community Engagement(発見者のコミュニティとの交流)

  • Function 1 Engage Finders(発見者との交流)
  • Function 2 Engage with Peer PSIRTs(ピア PSIRT 間の交流)
  • Function 3 Engage with Coordinators (CERTs, CSIRTs, or other coordination center organizations)(コーディネーター(CERT、CSIRT および、その他の調整組織)との交流)
  • Function 4 Engage with Security Researchers(セキュリティリサーチャーとの交流)
  • Function 5 Engage with Bug-Bounty Vendors(バグバウンティベンダとの交流)
  • Function 6 Anticipate the needs of the CSIRTs(CSIRT のニーズを予測する)

Service 3 Community and Organizational Engagement(コミュニティと組織との交流)

  • Function 1 Define & Engage with Upstream Communities & Partners(上流コミュニティとパートナーの定義と交流)
  • Function 2 Define & Engage with Downstream Communities & Partners(下流コミュニティとパートナーの定義と交流)

Service Area 2 Vulnerability Discovery(脆弱性の発見)

Sevice 4 の Function1 が Vulnerability Assessment から Product Security Assessment に変更されています。 内容が大きく変更されたわけではありませんが、自社とサードパーティの双方をアセスメントする必要があることが 明確に示され、分かりやすくなりました。

Service 4 Identifying New Vulnerabilities(新しい脆弱性を特定する)

  • Function 1 Product Security Assessment(製品セキュリティアセスメント)
  • Function 2 Maintain Expertise for Security Testing Tools(セキュリティテストツールの専門知識の維持)

Service Area 3 Vulnerability triage and analysis(脆弱性情報のトリアージと分析)

Sevice 3 の Function5 の標題が変更されていますが、内容に大きな変更はありません。

Service 3 Vulnerability Reproduction(脆弱性の再現)

  • Function 1 Establish Service Level Agreement for Vulnerability Reproduction(脆弱性の再現に関するサービスレベルアグリーメント(SLA)の設置)
  • Function 2 Reproduction Test Environment(再現テストの環境)
  • Function 3 Reproduction Tools(再現ツール)
  • Function 4 Vulnerability Storage(脆弱性情報の保管場所)
  • Function 5 Impacted Products(影響のある製品)

Service Area 4 Remediation(対策)

Service Area 5 Vulnerability Disclosure(脆弱性の開示)

Service Area 6 Training and Education(トレーニングと教育)

これらの Service Area には大きな変更はありません。

終わりに

PSIRT Framework v1.0 は PSIRT に必要な機能が具体的かつ、網羅的にまとめられており、 現在 PSIRT を持つ組織の方にも、これから PSIRT を作ろうとお考えになられている組織の方にも参照いただくことができる体系立てられたドキュメントです。

サイボウズは FIRST に加盟している訳ではありませんが、Framework の動向を追い、 微力ではありますが PSIRT に関する普及・啓発といった観点からも協力すべく、正式版を抄訳する計画を立てております。 今後も PSIRT をお持ちの企業の皆様とも意見交換をさせていただきながら、より安全な脆弱性情報ハンドリングができるように努めてまいります。

 

Burp Suiteの拡張機能 Custom-Request-Handlerを作成しました

はじめに

こんにちは、Cy-PSIRTのアルバイトの西谷(@no1zy_sec)です。

Cy-PSIRTでは、脆弱性診断にBurp Suite Professionalを使用しています。

今回、私が診断時に困ったことを解決するためにBurp Suite のExtensionを開発しましたので紹介します。

Burp SuiteにはSession Handling Rulesという機能があり、非常に便利な機能の一つです。 例えば、HTTPレスポンスからCSRFトークンを自動で取得してリクエストにセットすることが可能です。 詳しくはこちらのスライドをご覧ください。

※掲載の許可を頂いています。

www.slideshare.net

診断対象のサービスでは、クライアントとサーバーとの通信にWebAPIが用られることが多いです。 そしてその多くのリクエストボディがJSONフォーマットです。しかし、Session Handling Rulesは取得したパラメータの値をJSONの値としてセットする事をサポートしていないようでした。*1

そこで、リクエストボディがJSONの場合でも、取得したパラメータの値をそのJSONの値としてセットする事を可能にしたのが私が開発したExtensionです。

Custom-Request-Handlerとは

Custom-Request-Handler(以下、CRH)は次の機能を持っています。

  • HTTPレスポンスから値を抽出してJSONやリクエストヘッダーの値にセットすることができます。

  • ファイルからペイロードを読み込み、指定したパラメータに渡すことができます。これは、IntruderのSimple Listのような機能です。

CRHを使用することでJSONの値にCSRFトークンを含めないといけない場合でもCSRFトークンを自動取得することが可能になります。

また、ファイルからパラメータを読み込んで指定したパラメータに渡すことができます。この機能は値をインクリメントしてリクエストを送信したい時に有効です。

インストール方法

CRHはPythonで実装しているのでJythonをダウンロードして、Python Environmentで設定する必要があります。

Jythonの設定

  1. Jythonをダウンロードします。(jython-standalone-2.7.0.jarで動作確認済み)
  2. Burp Suiteを開きます。
  3. [Extender] > [Options]タブを開きます。
  4. [Python Enviroment]の[Location Jython standalone JAR file]に手順1でダウンロードしたファイルを設定します。

Extensionのインストール

  1. GitHub - No1zy/custom-request-handler をgit cloneします。
  2. [Extender] > [Extensions]のAddをクリックします。
  3. [Extension Details]の設定を以下のように変更します。
    • Extension type: Python
    • Extension file : /path/to/custom-request-handler.py

    ※手順1でcloneしたフォルダの中にあるcustom-request-handler.pyを指定します。

  4. nextをクリックします。

CRHを使った診断

診断対象

2つのページを使ってデモを行います。

  1. アクセスすると2つのランダムな文字列をJSONで表示するWebAPI f:id:cybozuinsideout:20180622173811p:plain
  2. 送信されてきたJSONの値を全て表示するWebページ f:id:cybozuinsideout:20180622174042p:plain どちらも一度アクセスして、リクエストをRepeaterに送っておきます。

マクロの登録

  1. [Macros]の[Add]をクリックします。
  2. [Macro Recoder]から取得したい値があるリクエストを選択します。 f:id:cybozuinsideout:20180626170717p:plain
  3. [OK]をクリックします。
  4. マクロを保存します。

Session Handling Rulesの登録

  1. [Project options] > [Sessions] > [Session Handling Rules] の [Add] をクリックします。
  2. [Rule Actions]の[Add]をクリックします。
  3. [Run a macro]をクリックします。 f:id:cybozuinsideout:20180626170845p:plain
  4. [Select macro]で保存したマクロを選択します。
  5. [After running the macro. invoke a Burp extension handler]にチェックを入れます。
  6. ドロップダウンメニューから[custom request handler]を選択します。
  7. [OK]をクリックします。 f:id:cybozuinsideout:20180626171343p:plain
  8. [Scope] > [URL Scope]で適用したいスコープを入力します。 f:id:cybozuinsideout:20180626172103p:plain
  9. [OK]をクリックします。

パラメータの抽出、セット

  1. 自動取得したい値があるレスポンスを右クリックします。
    • 共通設定の手順2で設定したマクロの最後のレスポンスと一緒にする必要があります。
  2. [Send to CRH]をクリックします。 f:id:cybozuinsideout:20180626173156p:plain
  3. 対象のタイプ(JSON/Header)を選択します。 f:id:cybozuinsideout:20180628114728p:plain
  4. 書き換えたいパラメータ名を入力します。
  5. 取得したい値をドラッグします。
    • Extract regex from groupにチェックを入れることで正規表現で記述することも可能です f:id:cybozuinsideout:20180628114800p:plain
  6. [Add]をクリックします。タブ右側のテーブルにレコードが追加されます。 f:id:cybozuinsideout:20180628114945p:plain
  7. 対象のリクエストを送信します。
    • 指定した値を取得して、上書きできているか確認します。 まず、送信前のリクエストです。 f:id:cybozuinsideout:20180628115206p:plain
    • [Go]をクリックしてリクエストを送信します。 f:id:cybozuinsideout:20180628115352p:plain

正常にパラメータが上書きされることが確認できました。 これでリクエストがJSONフォーマットであってもSession Handling機能のような使い方をすることができます。

ペイロードのセット

今回は以下のペイロードのリストを使用します。

user+0@example.com     
user+1@example.com     
user+2@example.com     
user+3@example.com     
user+4@example.com     
user+5@example.com     
user+6@example.com     
user+7@example.com     
user+8@example.com     
user+9@example.com     
user+10@example.com    
user+11@example.com    
user+12@example.com    
user+13@example.com    
user+14@example.com    
user+15@example.com    
user+16@example.com    
user+17@example.com    
user+18@example.com    
user+19@example.com    
user+20@example.com    
  1. [Payload Sets]のタイプ(JSON/Header)を選択します。
  2. 書き換えたいパラメータ名を入力します。
  3. [Load]をクリックして、読み込みたいファイルを選択します。 f:id:cybozuinsideout:20180628134042p:plain
  4. 対象のリクエストを送信します。
    • ロードしたファイルのペイロードを順番読み取り、指定した値を書き換えているかどうか確認します。まずはリクエストの送信前です。 f:id:cybozuinsideout:20180628135044p:plain
    • リクエストを送信します。 f:id:cybozuinsideout:20180628135113p:plain
    • もう一度リクエストを送信して、次の値が読み込まれるか確認します。 f:id:cybozuinsideout:20180628135150p:plain

正常にロードしたファイルのペイロードを順番読み取り、指定した値を書き換えていることが確認できました。 Repeaterでこれで値をインクリメントしたい時などで便利になります。

まとめ

今回はBurp SuiteでJSONフォーマットのWebAPIにCSRFトークンが含まれていてもスムーズに診断ができるようなBurp Suite Extensionを紹介させていただきました。 このExtensionでJSONの診断に少しでも貢献できればと思います。