Quantcast
Channel: Cybozu Inside Out | サイボウズエンジニアのブログ
Viewing all 681 articles
Browse latest View live

ベトナムの新オフィスへ行ってきました!

$
0
0

こんにちは。Xin chào! 東京品質保証部の青田です。Cybozu Vietnamへ出張してきたので、そのオフィスを紹介しつつ、どのような感じで一緒に働いているかを紹介したいと思います。

Cybozu Vietnam とは

サイボウズは国内外にいくつかの開発拠点を持っています。ベトナム拠点は2006年にホーチミン市に設立し、2009年独立法人化、かれこれ10年ほどGaroonや周辺製品の開発・試験を担っています。増員に伴ってオフィスを移転して、増床工事を進めていました。

Garoonチームは日本・上海・ベトナムの各拠点にメンバーがいて、昨年からはスクラム開発を導入しています。そのあたりの苦労話は色々あり、先日JaSSTというイベントでスクラムマスターが講演していました。

blog.cybozu.io

製品担当チームとは別にTest Engineering TeamProduct Security Incident Response Teamがあります。これらも拠点をまたいで活動しているチームです。

どのチームも普段はグループウェアやTV会議を利用して仕事を進めています。メンバーの出張はわりと頻繁に行っていて、チームによってペースは異なりますが、四半期ごとに行ったり来たりしているような感じです。仕事仲間とはリアルに顔を合わせておこう、というスタンスですね。私も今回そういう理由で、毎週定例会議を持っているメンバーに会いに行きました。ついでに工事が完了した新フロアを見物してきた次第です。

オフィス内

Cybozu VietnamはHo Chi Minh CityにあるCentre Pointというビルにあります。空港からタクシーで10分ほど、結構交通量が多い市街地。

エントランス

f:id:cybozuinsideout:20180522170212j:plain:w400

会議室

増床した結果、会議室は質量ともに充実していました。多くは名称・内装がベトナムの名所由来で、個人的にはHang Sơn ĐoòngをモチーフとしたSON DOONGという会議室がお気に入り。なぜか和風の会議室もありました。

f:id:cybozuinsideout:20180522171537j:plain:w300f:id:cybozuinsideout:20180522171604j:plain:w300

通路側からは、会議室内に設置されているカンバンがちらっと見えたり。

f:id:cybozuinsideout:20180522172920j:plain:w300

イベントスペース

50人超を収容できるイベントスペースもあります。写真は開発本部長の佐藤がベトナムメンバー向けに方針説明会を開催しているところです。

f:id:cybozuinsideout:20180522172146j:plain:w300

イベントスペースの一角にはくつろげる場所やキッチンが用意されています。

f:id:cybozuinsideout:20180522172602j:plain:w300f:id:cybozuinsideout:20180522174344j:plain:w300

開発本部長による情報の共有 & 質問会

ベトナムの開発・QAに対して、開発本部長が

  • 進行中のプロジェクト
  • DevOps、アジャイル(スクラム)など開発体制

について共有する会がありました。

f:id:cybozuinsideout:20180522173302j:plain:w300f:id:cybozuinsideout:20180522173529j:plain:w300

リードタイムの短縮という目標、我々が抱えている課題、目指したいチーム体制といったあたりについての説明があり、参加者からも活発な質問・意見が出ていました。 徐々に改善を進めてきてはいますが、未だにTest Pyramidは逆三角形であり、今後も開発とQAが協力して安定感のあるピラミッドにしていきたいという話も強調していました。そのためにテスト自動化を推し進めていったり、Dev/QA/Opsの職能間の垣根を無くしてゆく努力が必要で、各拠点で強みを生かして協力する必要がありますね。

規模や扱う内容は様々ですが、海外拠点に赴いて情報を共有・議論するのは当社における常套手段です。オンラインで情報共有ができるサービスを開発している会社としては、ちょっと不思議な気もしますが、強いチームを作る際には対面の方が良いのかもしれません。

さいごに

品質保証部では、新卒・キャリア採用を行っています。

あなたも一緒にサイボウズで品質保証活動を行ってみませんか?

www.wantedly.com


ざっくりわかった気になるモダンGC入門

$
0
0

どうも!@yokotasoです!

2018/05/26のJJUG CCC 2018で「ざっくりわかった気になるモダンGC入門」というタイトルで登壇させていただきました。

現在開発中の新しいGCアルゴリズムをざっくり理解することをテーマに発表しました。

発表練習用に作ったカンペの内容を公開します。ブックマークコメントでもツイートでも感想を書いていただけると喜びます!

発表資料は、speakerdeckにあります。はじまり〜はじまり〜

はじめに

f:id:cybozuinsideout:20180527142809p:plain:w300f:id:cybozuinsideout:20180527142805p:plain:w300

今日はざっくりわかった気になるモダンGC入門というお話をさせていただきます。

現在開発中のGCアルゴリズムの全体像を理解してもらうことを目的としたセッションです。よろしくおねがいします。

さて今日のアジェンダですが、まず簡単にこれまでのGCを復習した後に新しいGCが必要になってきた背景について少し話します。

次にShenandoahGC、ZGC、Epsilon GCと3つ紹介します

これまでのGC

f:id:cybozuinsideout:20180527142801p:plain:w300

まずは、これまでのGCについて少し

f:id:cybozuinsideout:20180527142757p:plain:w300

まずはParallel GCです。young/old領域でわかれたメモリ管理をしておりまして、これに合わせてyoung GC/old GCを行います。 この世代を分けるメモリ管理ですが、多くのGCで採用されています。

young/oldのGCそれぞれで、コンパクションGCをします。GCスレッドが動いている間は、アプリケーションスレッドは停止します。

f:id:cybozuinsideout:20180527142754p:plain:w300

次にコンカレントマークスイープGCです。young領域はコンパクションを伴うGCをします。

Old領域はコンパクションを行わず、アプリケーションスレッドを動かしながらマーキングと呼ばれるゴミオブジェクトの確定作業をします。

ゴミが確定したオブジェクトの領域はメモリは開放されて再利用されます。

マークスイープ型GCの問題点としましては、old領域がフラグメントし、メモリ利用効率が悪くなる問題があります。

f:id:cybozuinsideout:20180527142750p:plain:w300

そしてG1GCです。メモリを細切れのサイズに分解するリージョン型のメモリ管理をしています。リージョンごとにyoung/oldなどの世代を設定します。

young GCはアプリケーションスレッドを停止してコンパクションGCをします。 old GCも同様にアプリケーションスレッドを停止しますが、目標停止時間を定めてGCをします。

young/old gcはコンパクションGCなので、メモリのフラグメントはしません

f:id:cybozuinsideout:20180527142746p:plain:w300f:id:cybozuinsideout:20180527142743p:plain:w300

3つのGCをまとめて見てみます。

今利用できるGCはコンパクションGC時にアプリケーションスレッドの停止が発生します。これは、young/old GC関係なく発生してしまいます。

逆にコンパクションGCをしないとメモリがフラグメントしていきます。メモリ利用効率が悪くなってしまうわけです。

なぜ新しいGCが必要になっているのか

f:id:cybozuinsideout:20180527142739p:plain:w300f:id:cybozuinsideout:20180527142735p:plain:w300

こういった話を踏まえて、なぜ新しいGCが提案されるようになったのかを見ていきましょう

ひとつは巨大なヒープを利用するアプリケーションの台頭です。

こういったアプリケーションの場合、young領域も大きくなる可能性があるので、コンパクションGCに伴うアプリケーションの停止も無視できなくなってきます。

あわせて複数コアや大量メモリといったハードウェア上の進化もあり新しいGCの必要性がでてきています。

Shenandoah GC

さて、前説を終えたので、Shenandoah GCを見ていきたいと思います

f:id:cybozuinsideout:20180527142731p:plain:w300f:id:cybozuinsideout:20180527142727p:plain:w300

こちらredhat社によって開発されています。 100GBと2GBの停止時間がかわらないという触れ込みです。

なお、利用したい場合はバックポートしてjavaをビルドしなおせば利用できます。

f:id:cybozuinsideout:20180527142724p:plain:w300

GCの特徴としましては、これまでのGCで利用されていた世代型メモリ管理をしません。 G1GCと同じリージョン型のメモリ管理を採用しています。

並列マーキングは他GCでも行われていますが、このGCの特徴として並列コンパクションを採用している点があります。

アプリケーションと並列で動くコンパクションがなぜ難しいのかを見ていきましょう。

f:id:cybozuinsideout:20180527142720p:plain:w300

アプリケーションと並列に動作するため、コンパクションGCに伴うオブジェクトの移動とアプリケーションスレッドの参照の更新が同時に起る可能性があります。

GCの実装を工夫しないと、捨てる予定のオブジェクトを参照してしまう危険性があります。

f:id:cybozuinsideout:20180527142717p:plain:w300

これを解決するために、JVM内部のオブジェクトの中にポインタとは別にBrooks pointerと呼ばれるヘッダをつけて解決しています。

オブジェクトが待避されていなければ、自分自身のポインタをヘッダが指すようになります。この図でいうと右の図です。

オブジェクトが待避済みの場合は、ポインタは待避後のオブジェクトを参照するようにします。この図で言うと左の図です。

f:id:cybozuinsideout:20180527142713p:plain:w300

このおかげで、待避前・待避後のオブジェクトであっても、ポインタを辿って、待避後のオブジェクトを参照できるようになります。

f:id:cybozuinsideout:20180527142708p:plain:w300

アイデアのキモは説明できたので、GC全体を眺めてみましょう。

f:id:cybozuinsideout:20180527142705p:plain:w300f:id:cybozuinsideout:20180527142701p:plain:w300

まず、アプリケーションスレッドを停止してルートから辿れるオブジェクトをマークしてきます。

次にアプリケーションと並列でInitial-Markでマークしたオブジェクトをさらに辿っていきます。

f:id:cybozuinsideout:20180527142657p:plain:w300

アプリケーションスレッドを停止して、前のフェーズから変更があったオブジェクトがゴミオブジェクトになっていないかの最終確定をします。

f:id:cybozuinsideout:20180527142654p:plain:w300f:id:cybozuinsideout:20180527142650p:plain:w300

あとは、ゴミオブジェクトの多いリージョンがマーキングでわかっているので、そのリージョンに対してコンパクションGCをしていきます。

f:id:cybozuinsideout:20180527142644p:plain:w300

説明したBrooksポインタで古いオブジェクトの参照を新しいオブジェクトのヘッダに付け替えていきます。

このフェーズはアプリケーションが並列でうごいているので参照の更新がおこります。

f:id:cybozuinsideout:20180527142640p:plain:w300

並列コンパクションをしているときから発生した参照の追加と更新をアプリケーションスレッドを止めて最終確定します。これでGCが完了しました。

f:id:cybozuinsideout:20180527142636p:plain:w300

さて、GCの全体像を最後に眺めてみます。

コンカレントコンパクションのおかげで、アプリケーションスレッドの停止を減らしつつ、メモリのフラグメントも回避できているのが お分かりいただけたかと思います。

ZGC

さて、続きましてZGCの方に行きたいと思います。

f:id:cybozuinsideout:20180527142633p:plain:w300f:id:cybozuinsideout:20180527142629p:plain:w300

ZGCですが、Oracleで開発されたGCです。数TBのヒープでもGCによる停止時間が多くないという触れ込みです。 こちらもZGCを利用するにはバックポートが必要です。

f:id:cybozuinsideout:20180527142625p:plain:w300f:id:cybozuinsideout:20180527142622p:plain:w300

ちょっとShenandoah GCでお腹いっぱいの方もいらっしゃる方もいらっしゃるかと思いますが…

基本的なコンセプトは、ShenandoahGCと同じなので、安心してください。 世代管理型のメモリ管理をせず、リージョン型のメモリ管理です。そして、並列コンパクションをします。

ZGCの大きな特徴としてLinux 64bit OSでしか今のところ利用できません。Javaの世界観からするとロックな存在ですね。 ZGC特有の特徴ですが、おおきくは3つほどあります。あとから詳しく説明していきます。

カラーポインタ、仮想メモリの有効活用、最後にフォワーディング・テーブルです。

f:id:cybozuinsideout:20180527142616p:plain:w300

一つずつ見ていきましょう。まずはカラーポインタです。

ShenandoahGCのときもオブジェクトが待避済みかどうかを判定するのがキモでした。

ZGCでは、オブジェクトの状態をカラーとして表現するわけです。 マーク済み0、マーク済み1、オブジェクト待避済みと3つのカラーを持っています。

マークはマーキングフェーズで利用するのですが、0と1の二種類が存在する理由はあとできっちり回収しますのでお待ち下さい。

f:id:cybozuinsideout:20180527142612p:plain:w300

カラーポインタの色の管理ですが、64bitのメモリアドレスをフル活用します。特定のアドレスのフラグを立てて管理します。

オブジェクトが配置されている位置は下位42ビットを利用しています。そして、4ビットを使ってオブジェクトの状態を管理します

f:id:cybozuinsideout:20180527142608p:plain:w300

これを64bitのアドレス空間全体をみてみます。最大4TB分のヒープメモリは42ビットで表現されます。

43bitから上位4bitを使って、オブジェクトの状態を表現します。アドレスのカラーポインタのビットが立っていたり立たなかったりします。

64bitのアドレス空間をすべてつかうと128TBのメモリが理論上は必要になってきますがそんなマシンみたことないですね

f:id:cybozuinsideout:20180527142605p:plain:w300

見せかけの巨大メモリの正体は仮想メモリです。LinuxのOSに詳しくないと、想像しにくいかとおもいます。 最近Linuxのすばらしい入門書に仮想メモリの章があるので、興味がある方は是非購入していただければと思います。

f:id:cybozuinsideout:20180527142601p:plain:w300

さて、GC全体を眺めていきましょう

f:id:cybozuinsideout:20180527142557p:plain:w300f:id:cybozuinsideout:20180527142544p:plain:w300

アプリケーションスレッドを停止してRootから辿れるオブジェクトをマーキングしていきます。 次にアプリケーションと並列でRootからさらにオブジェクトの参照を辿っていきます。

f:id:cybozuinsideout:20180527142539p:plain:w300

並列マーキング中に発生したオブジェクトの参照の更新を更新して、ゴミオブジェクトの最終確定をします。

f:id:cybozuinsideout:20180527142535p:plain:w300f:id:cybozuinsideout:20180527142531p:plain:w300

ゴミの割合が多いリージョンがマーキングでわかるので、待避リージョンを決めます。 待避したオブジェクトには待避済みのマークを付けておきます。

待避すると決めたリージョンにはフォワーディングテーブルを作ります。

f:id:cybozuinsideout:20180527142528p:plain:w300

このフォワーディングテーブルですけれども、Shenandoah GCだとすべてのオブジェクトにヘッダをヘッダを付けているので、オーバーヘッドがあります。

ZGCでは、カラーポインタで待避済みかどうかは判定できるので退避先を参照するフォワーディングテーブルを作り待避後のオブジェクトの参照先を管理します。 要するに、メモリ節約したいんですね。

f:id:cybozuinsideout:20180527142521p:plain:w300

実はこれでGCは終わりなんですが、参照の更新が終わってません。

f:id:cybozuinsideout:20180527142517p:plain:w300f:id:cybozuinsideout:20180527142513p:plain:w300

GCのサイクルを全体で眺めてみましょう。並列コンパクションまではみました。 参照の付け替えですが、2週目のマーキングフェーズで一緒にやります。図のように、マーキングと参照の付替えがオーバラップします。

f:id:cybozuinsideout:20180527142509p:plain:w300

そのため、ZGCではConcurrent-Markは厳密には正しくないです。正しくはCuncurrent-Mark/Cuncurrent-Remarkなのです。

参照の付替えフェーズを別途設けずにオーバーラップすることでオブジェクトの参照をたどる回数を節約できるんですね。

ただ、こうすると前のGCサイクルでマーキングされたのか?今のGCサイクルでマーキングされたのか?判断がつかなくなります。 これを見分けるために、マーク用に定義されたカラーポインタが2個(mark0/mark1)存在しています。

f:id:cybozuinsideout:20180527142505p:plain:w300f:id:cybozuinsideout:20180527142501p:plain:w300

Cuncurrent Mark/Remapのフェーズを見てみますと前のGCサイクルとは違う色でマーキングをしていきます。

そうすると、前のGCサイクルでマークされたか、新しく参照されてマークされていないオブジェクトの判断が カラーポインタからできます。その結果をもとに、マーキングし直したり、オブジェクトを待避したりします。

リージョンが空にできました。これでZGCの説明はおしまいです。

Epsilon GC

f:id:cybozuinsideout:20180527142458p:plain:w300

最後にEpsilon GCです。

f:id:cybozuinsideout:20180527142454p:plain:w300

アプリケーションスレッドの停止をしないCPUの負荷がほとんどないGCの時間は限りなく0に近いというGCになっております。

f:id:cybozuinsideout:20180527142450p:plain:w300f:id:cybozuinsideout:20180527142447p:plain:w300

そんな夢みたいな話あるのかと思うかもしれません。そんなGCがあるんです。

なぜならこのGCはなにもしないGCだからです。

f:id:cybozuinsideout:20180527151344p:plain:w300f:id:cybozuinsideout:20180527151340p:plain:w300

オチが付いたところで、種を明かしますと

JVM開発目的のGCです。 パフォーマンス測定でGCによる性能劣化を排除するために作られました。ヒープが埋まるとOutOfMemoryErrorが発生します。

本番環境では使わないようにしてください。

まとめ

f:id:cybozuinsideout:20180527151337p:plain:w300

現在、開発中の巨大ヒープ・多コアが前提のアプリケーション用GCを紹介しました。 運用中のアプリケーションのGCによる停止時間が問題になっている場合、将来的に検討の余地があるかもしれません。

Linux 64 bit OSならZGC。それ以外ならSheandoah GCが利用できます。Epsilon GCは何もしないGCです。

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

最後に

最後まで読んでいただきありがとうございます!ざっくり理解していただけたでしょうか?

実際に生の発表を聞きに来てくださった方もありがとうございました!

参考URL

アクセシビリティの祭典2018参加レポート

$
0
0

こんにちは。デザイングループの小林(@sukoyakarizumu)です。 5月17日、神戸で開催された「アクセシビリティの祭典」というイベントに、弊社の杉山(@oogFranz)と2人で登壇しました。この記事では、当日の様子と、自分が登壇したセッションについて紹介します。

「アクセシビリティの祭典」とは?

アクセシビリティとは、高齢者・障がい者を含めて、すべての人が製品やサービスを利用できることを表す言葉です。
アクセシビリティの祭典は、神戸で開催されるアクセシビリティに関するお祭りです。Web制作会社や障がい者支援の機材を開発する会社など、多くの企業が参加・協賛しています。サイボウズも協賛企業のひとつです。

当日の様子

当日は、視覚障がいや聴覚障がい、身体障がいを持つ車椅子の方など、多くの方が参加していました。どんな参加者にも情報を伝えるため、会場の情報保障がとても充実していました。どのセッションにも手話通訳がつき、かつ、発表者の発言内容が自動的に字幕に書き起こされ、さらに英語に翻訳された結果とあわせて表示されるようになっていました。

下の写真はセッションの様子です。手話通訳と日英の字幕が表示されていることがわかります。

セッションの様子
セッションの様子。向かって右側に手話通訳と日英の字幕が表示されている。
セッション自体にも、障がい者の方が多く登壇され、手話を使って登壇したり、視線入力装置などを使って、ご自身の生活状況を伝えたり、最新の情報機器の活用事例を伝えていました。支援機器やWeb技術の発展によって、多くの障がい者の方が積極的に情報を入手したり身の回りの問題を解決している姿がとても印象的でした。

会場に設けられた展示ブースにも、障がい者の方を支援する情報機器や、アクセシビリティに関するツールが多数展示されていました。

運転中の電動車椅子
電動車椅子。左右の歯を噛みしめるだけで前進や後退、左右転回ができる。

視線入力装置を取り付けたノートPC
視線入力装置を取り付けたノートPC。視線でカーソルをボタンに合わせ、注視で確定する。

登壇したセッション

私たちは、セッション「トークバトル60分一本勝負「禁断の対決!サイバーエージェント vs サイボウズ」」に登壇しました。 このセッションは、サイバーエージェントさんの桝田さん・土岐さんと一緒に、freeeの伊原さんも交えて、Webサービスでアクセシビリティを確保する取り組みについてトークバトルするという内容でした。 セッションは口上を交えながら、プロレステイストで入場するスタイルでした。

セッション中は、サイボウズのアクセシビリティの現状についてお話ししました。
サイボウズは、アクセシビリティを「ユーザがチームにアクセスできる能力」ととらえ、アクセシビリティの確保に取り組んでいます。 もともと一部の有志メンバーがはじめた取り組みでしたが、今では多くの開発チームが自主的に活動を行うようになりました。 また、広報や営業など、開発以外の部署にもアクセシビリティに興味を持つ社員が増えてきました。
アクセシビリティの取り組み - サイボウズ

社内では勉強会が活発に行われています。アクセシビリティに関する書籍を輪読したり、製品のテストをしてみたり、障がい当事者による講演会やユーザビリティテストを開催したりしています。

また、製品の改善も少しずつ進んでいます。特にデザインのリニューアルの際には、アクセシビリティの確保がコンセプトの一つとして組み込まれるようになりました。 今年2月〜5月にかけて行われたサイボウズガルーンのデザインリニューアルでも、コンセプトの1つとしてアクセシビリティが挙げられています:
新デザインのご紹介 - サイボウズガルーン

サイバーエージェントさんも、多くのアクセシビリティ改善を行っているようでした。特にFRESH!というサービスは、 WebアクセシビリティのJIS規格である「JIS X 8341-3」への準拠を目標としているとのことでした。国内Webサービスでの準拠は前例がなく、素晴らしいチャレンジだと思います。 また、多くの社員の方が関わり、わかりやすいアクセシビリティガイドラインをつくっていることも、とても印象的でした。

お互いの取り組みには共通の悩みも多く、「バトル」を意識して相手に不足しているところを攻めると、そのまま自分の弱点を突いてしまうようなところもありましたが、充実した議論ができました。

余談:実況スレッド

サイボウズには、社内イベントや会議中に、コメントを自由に書き込む「実況スレッド」というコミュニケーションスペースがあります。セッション中は、弊社の多くの社員が実況スレッドに感想を書き込んでくれていました。

当日はサイバーエージェントさんの動画サービス「Fresh!」で生放送が行われており、 会場にいない社員にも、オフィスにいながらセッションの内容を体験してもらうことができました。情報保証の大切さを実感しました。

セッション中に実況スレッドで多くの社員が感想を書き込む様子
セッション中の実況スレッドの様子

QA若手メンバーへインタビュー

$
0
0

f:id:cybozuinsideout:20180524130725j:plain

こんにちは!品質保証部の中園です。

本記事では、品質保証部(QA)に所属して2年目となるQA若手メンバーにスポットを当て、 現在の仕事内容や、QAのやり甲斐などをインタビューしてみました。

インタビューした若手メンバーの紹介

三牧さん: 2016年新卒入社。QA配属後、kintoneの試験業務を担当。 現在はkintoneと、そのモバイルアプリのQA責任者を担当。

小山さん: 2016年新卒入社。Garoonの不具合調査や問い合わせ対応を担当した後、 2017年7月から Garoon 開発のスクラムチームに参加。趣味は読書。

森さん: 2016年新卒入社。Site Reliability Engineeringチーム(SRE)でQAを担当。趣味はペンシルパズル。

普段はどういった仕事をしているのですか?

三牧さん: kintoneと、モバイル版kintoneのQA責任者をしています。 主に、PGやPMとリリーススケジュールの調整をしたり、試験進捗などの管理をしています。

その他にも、バグのトリアージや、試験仕様書のレビュー、試験の実施もやっています。 PGやPMとの調整以外にも、試験に関わることであれば何でもやっています。

小山さん: Garoon 開発チームに所属しています。チームでは試験設計や試験実施など試験関連のタスクと、仕様書のレビューを行っています。

Garoon ではスクラム開発を取り入れているのですが、スクラムイベントでも QA の観点から積極的に発言するようにしています。 (ここの処理では新しいエラーを追加しますか?とか、これも影響範囲に入りますか?とか) 最近ではテスト自動化のマネジメントも行っています。

スクラム開発については、Cybozu Meetup #11 アジャイルQAで発表した資料があるので、詳細はこちらをご覧ください。 speakerdeck.com

森さん: SREチームでQAをやっています。主に、SREが作ったコマンドの試験や、自動テストの作成をしています。 仕事では基本的にシェルを使っています。 今年の3月末まで、半年間ほどSREと兼任もしていました。

試験業務以外にも、開発環境の管理もしています。 開発環境で障害が起きたときの対応や、利用しているメンバーへのアナウンスなどをしています。

中園:2年目とは思えないほど、皆さんそれぞれのチームで活躍していますね!

どうしてQAになろうと思ったのですか?

三牧さん:学生時代からQAのアルバイトをしていました。 そこで、QAにやり甲斐を感じており、将来はQAになることを目指していました。

森さん:私は最初、QAを志望していたわけではなく、PGを志望していました。 就活のときもQAという職種は知らず、PGで受けていました。 入社後の全体研修でのQA体験で、不具合を見つけたときの喜びが忘れられず、QAを志望しました。

中園:QAのやり甲斐や面白さに気づいてしまったのですね。その気持ち分かります。

QAの仕事のやりがい、面白いところはありますか?

三牧さん: 仕様の穴を見つけたときや、不具合を見つけたときですね。 リリースプロセスの最後に、製品の保証をしているところに責任感はありますが、その分やり甲斐を感じています。

あとは、スケジュールを組み立てたり、調整したり、試験プロセスにチームの要望をとりいれて改善していくことは面白いですね。 目に見えて改善の効果が出ているときは嬉しいです。

関連:Cybozu Meetup #11 アジャイルQA発表資料

www.slideshare.net

サイボウズ自体そうなのですけど、やりたいことをさせてもらえる、チャレンジできる環境があることは素晴らしいと思います。

小山さん: 自分は複雑なものをわかりやすく表現することに面白さを感じるので、試験設計時に仕様を整理したりモデル化したりするのが好きです。

配属後しばらくは、社内のテクニカルサポートからの問い合わせに対応する業務を行っていました。 このとき、製品のリリース後に不具合が発覚するとお客様に迷惑がかかりますし、製品の信頼度も下がってしまうということを身をもって知りました。 そこで、開発プロセスの上流工程で不具合を作り込まない活動をしたいと思うようになりました。

今はスクラムチームに入り、それを実現しています。 これは品質保証部だけでなくサイボウズ全体にも当てはまると思いますが、やりたいことを相談できる環境が整っていると思います。

中園:問題点を見つけ、自ら改善しようとする姿勢が素晴らしいですね。

QAになって辛かったこと、大変だったことはありますか?

小山さん: Garoon の不具合調査や問い合わせ対応を担当していたころは、なかなか回答が出せないと辛かったですね。テクニカルサポートのメンバーや、その先にいらっしゃるお客様をお待たせしてしまうことになるので。

最近はテスト自動化のタスクも抱えているので、少し忙しいですね。TEともうまく相談しながら進めていきたいと思っています。

参考:テストエンジニアリングチーム(TE)についてblog.cybozu.io

森さん: 緊急リリースの時が大変ですね。 急がないといけないのですが、試験もしっかりしないといけないので、 こういう時でも、いかに冷静さを保つかが重要ですね。

QAとして、チームで働くうえで大事にしていることはありますか?

三牧さん: 対話することを大事にしています。コミュニケーションが大事ですね。 チームで仕事をしているので、メンバーとの意見交換をマメにするように意識しています。

あとは、物事をわかりやすく・早く伝えることです。 例えば、試験中に不具合が発見されたら、出来るだけ早めにPGに共有しています。 早ければ早いほど、不具合の改修を直近のリリースに含めることができたり、改修に時間がかかるような不具合だと、リリースを遅らせてしまう可能性もあるためです。

小山さん: 「QA だから○○をする」「QA だから○○をしない」という風に考えすぎないようにしています。QA 的な業務がメインですが、それ以外のところにも関与したいと思っています。

PG が何を考えてどういう仕事をしているか把握したり、QA 以外のメンバーに試験仕様書の見方を教えたりなどをしています。 自分が QA 以外の仕事を知ることや、チーム全体で品質について関心を持ってもらうことが、最終的には製品の品質の向上につながるのではと思っています。

あとはチーム全体では「謙虚・尊敬・信頼(HRT)」の精神が大事にされているように感じます。 互いを尊敬しながらも、気を使いすぎないよう、しかし言うべきことは言う。 それが実現できているのも、チームメンバーが皆HRTを大事にしているからだと思います。

森さん: 自分以外のメンバーのための行動をすることですね。 たとえば、他のSRE QAメンバーのために、 試験をしたときの手順やログを残しておくことです。 似たような改修をするときに、以前どのようなログが出たのか、どのような試験をしていたか、など 記録から参考になることはたくさんあります。

今後どのようなことにチャレンジしたいですか?

三牧さん: 直近だと、リリースサイクルを短くすることがチーム内のテーマです。 今は2~3ヶ月に1度のリリースなのですが、それをもっと短縮しようとしています。 それに合わせて、QAがやる試験も柔軟に変えていきたいと思っています。 高い品質を保ちつつも、早くリリースできるようチャレンジしていく予定です。 具体的に言うと、試験に関わることを、もっともっと自動化したいですね。

小山さん: 直近だと、まだ始まったばかりの自動テストを安定運用までもっていきたいです。テスト結果の安定と、ルールの浸透を目指します。 Garoon 開発で複数チームでのスクラムを行っているので、共通の指針としての品質目標やテスト戦略の整理もしてみたいですね。 実装にも興味があるので、機会があればテストファーストなコーディングにも挑戦したいです。

森さん: テストの自動化にチャレンジしていきたいです。 といっても、今まさにテストの自動化はやっているのですが、 テストの準備など、テスト以外でもできるところは全て自動化していきたいですね。

中園:3人とも”自動化”というのがキーワードですね。自動化がこんなに熱いとは!

最後にひと言

三牧さん: これからもいろいろなことにチャレンジして、品質の向上に貢献していきたいです。 あとはそうですね、kintoneのQAは人が足りないので、メンバーを絶賛募集中です。

小山さん: 私のチームもやりたいことに対して人数が足りてないので、ぜひ来てください。 これまでやってきたことにかかわらず、論理的に考えることが得意な方や、物事を順序立ててすすめたい方、細かいことに気がつく方は、QAに向いていると思います。

森さん: QAは責任感ある仕事で、その分やりがいがある部署です。毎日が楽しいです。 これからも、品質の面から、サイボウズ製品を支えていきたいと思います。

QAの仕事に興味をもった方へ

本記事でサイボウズのQAについて、雰囲気の一端を紹介できたのではないかと思います。

この夏、学生向けのインターンシップを開催します。 実際にサイボウズのQAを体験できますので、興味のある方はぜひご応募ください! (応募締め切り:6月30日)

blog.cybozu.io

また定期的にMeetupも開催していますので、こちらもチェックしてみてください。

Cybozu Inside Out - connpass

品質保証部では、新卒とキャリア採用を行っています。 私たちと一緒に働く仲間を募集中です!

www.wantedly.com

デザイナー発案で実現した、製品新デザイン化の学び

$
0
0

こんにちは、サイボウズ デザイングループの@kochitakuです。Garoonという中堅・大規模組織向けグループウェアを担当しています。最近クラウド版Garoonのデザインが新しくなったのですが、新デザイン化はデザイナー発案で始まり、いろいろな人を巻き込みチームワークで実現しました。この記事ではその時に得た学びを紹介します。細かいデザイン変更内容については割愛し、汎用性を意識して書きました。読んでいただいた方に1つでもお土産があると嬉しいです。

新デザインについて

PC用Web画面の変更をしました。 デザイン変更イメージ

製品内にユーザーさんからフィードバックをいただけるしくみを用意しまして、「気に入っている」を一番多くいただきました。

  • 気に入っている:563件
  • 不満がある:459件
  • アイデアがある:75件

厳しいご意見も全部確認して改善検討の材料にしています。何より1,000件以上のフィードバックをいただけたことに感謝しています。 フィードバック結果の棒グラフ

一番大きい自社イベントのCybozu Daysでも大きく紹介されました。会場で見ていて胸が熱くなりました。 イベントの写真

新デザイン発案のきっかけ

以前のデザインは5年以上前のもので、社内外の声からもモダン化への必要性を感じていました。サイボウズにはデザイナーが自主的に提案する風土があり、「More Modern活動(通称:MM活動)」と勝手に名乗り活動を始めました。「MM」と言い続けたところ、まわりにもどんどん認知されました。言葉って流行るのですね! 今回の新デザイン化はMM活動の第一弾で、その後も活動は継続しています。

まわりの心を動かす

自主的に活動を始めるのは簡単ですが、ステークホルダーの共感がなければものごとが進みません。サイボウズの場合、最初にPM(プロダクトマネージャー)の心を動かすことが必要です。どのように心を動かし、共感を得たかを紹介します。 まわりの心を動かす図

価値の共感

新デザインの軸を「トレンド(見た目・操作・効果)」「社内他製品との親和性」「アクセシビリティ」として、それぞれの価値を説明しました。ざっくり「モダンにしたい」だけでは説得力が乏しく、価値を探求して軸を決めました。

リスクの共感

やらないことへのリスクを説明しました。時には危機感をあおることも必要だと思います。 また、変えることへのリスクヘッジも非常に大切です。結果として今回は以下のように段階的に広げて、フィードバックを得ながら進めました。社内で実際に利用している製品という利点を活用しています。

  • 社内:開発メンバー

   

  • 社内:幅広い部署のメンバー(限定) 

   

  • 社内:全体

   

  • 社外:お試し用でリリース

   

  • 社外:正式リリース(デフォルトデザイン変更)

過去の経験から旧デザインも選択できる提案もしました。メンテナンスコストも考慮してCSSレベルで旧デザインをある程度保持できる仕様になりました。

コスト情報

PMが工数を判断しやすいよう、できるだけ正確なコスト情報を用意できるのが理想です。デザインの理想を追求すると開発全体のコストがどんどん膨らんでいくので、PGの工数を抑えた「html/CSS/image変更レベル」などレベルを分けた話をしながら詰めて行きました。松・竹・梅のイメージです。

価値の実感

今回はこれが一番ポイントだと思っています。前述のとおりGaroonは社内でも利用していて、社内環境で手軽に新デザインを疑似体験できないか考えました。掘り下げていくとCSS/imageの変更である程度は新デザインを実現できることわかったので、Stylishという自作のCSS/imageを適用できるChrome拡張を使って疑似体験の拡散に成功しました。これにより価値の実感の啓蒙が進み、正式リリースに向けて加速がつきました。

プロトタイプツールやテスト環境でのお試しより、普段の環境での疑似体験はかなり強力です。Stylish上のCSSを外部リンクにしておけば、フィードバック対応もリンク先のCSS変更だけで済むのでオススメです。 Chrome拡張を使ってもらいやすいよう、画面キャプチャ入りのマニュアルも用意しました。途中でつまずくと使ってもらう率が減るので、こういったマニュアルには労力を惜しまない方が良さそうです。

マニュアルの見本
実際のマニュアル
ちなみにサイボウズのデザイングループ内にはChrome拡張を自作する猛者もいます。自作が厳しい場合、PGに相談もオススメします。

専任PMの存在

Garoonは3人のPMがいるのですが、PMに相談して新デザイン化のあらゆる決断ができる専任PMを1人決めてもらいました。デザインの議論は好みに依存するケースもあり、決定スピードが鈍くなる過去の経験からの学びです。多忙なPMの予定調整やオンラインでの返事待ちコストも抑えることもできます。実際、専任PMのスピード感ある判断の連続でスムーズに進みました。

最後に

デザイナーの発案はきっかけにすぎず、たくさんの英断をしたPM、たくさんの画面を実装したPG、テストしたQA、ヘルプのキャプチャを刷新したTC(テクニカルコミュニケーション)、外部発信に尽力したマーケティングメンバー、その他大勢のメンバーやユーザーさんのフィードバック、問い合わせ対応しているサポート…とにかくたくさんのチームワークによって新デザイン化は実現に至りました。本当に良い経験ができました。ありがとうございます。

先月ベトナムでGaroon開発メンバーが集うMeet Upがあったのですが、新デザインのLTをしてきました。夜のKARAOKEは異常に盛り上がりました。。。 Meet Up・カラオケの写真

私事ですが最近は内弁慶を脱すべく社内外のデザイナーイベントにときどき参加していますので、なにか質問があればお声がけください。

関連リンク

CoreOS Container Linuxにおいてリアルタイムプロセスを実行できない問題

$
0
0

はじめに

こんにちは、技術顧問の武内です。

本記事はサイボウズの次期インフラ開発チーム(Necoチーム)が遭遇した、CoreOS Container Linux (以降 CoreOS)においてリアルタイムプロセスを実行できないという問題について、次のようなことを記載したものです。

  • どういう問題なのか
  • どのように根本原因を突き止めたのか
  • 今後どのように対処するのか

問題要旨

本来なら成功するはずのroot権限におけるリアルタイムプロセスの実行が失敗する

根本原因

  • CoreOSではカーネルのリアルタイムグループスケジューリングという機能が有効になっている
  • 同機能が有効な場合、cpu cgroup配下のプロセスはデフォルトではリアルタイムプロセスを実行できない
  • systemd環境下で生成されたプロセスは何らかのcpu cgroupに所属させられる

対処方法

リアルタイムプロセスが属するcpu cgroupの設定を変更する。具体的には/sys/fs/cgroup/cpu/<cpu cgroup名>/cpu.rt_runtime_usを1以上にする

リアルタイムプロセスとは

リアルタイムプロセスについて知っているかたは本節を飛ばしてもらっても構いません

CPUコア上に複数の実行可能なプロセスが存在している場合、タイムスライスと呼ばれる所定の時間ごとにCPUコア上で動作するプロセスが変化していきます。たとえばCPUコア上に2つのプロセスp0,p1が存在する場合にはp0->p1->p0->p1...というように順番に動作します。これに対してリアルタイムプロセスは通常のプロセスが何個存在していようとも、それらよりも必ず優先的に、自発的にCPUを明け渡すまで無制限に実行できます。

プロセスをリアルタイムプロセスにするにはsched_setscheduler()システムコールやchrtコマンドを用います。実行可能になった場合には何が何でもすぐに実行したいようなリアルタイム性が必要な処理がある場合にリアルタイムプロセスを使います。例えばクラスタのハートビート処理などに用いられます。

リアルタイムプロセスは通常のプロセススケジューリングに求められる「各プロセスを平等に動作させる」という機能を無視して動作するため、バグによって自発的にCPUを明け渡さないときにCPUを無期限に占有してしまいます。このため通常はrootユーザでしか実行できないようになっています。

本記事ではリアルタイムプロセスについてはこれ以上述べませんが、詳細が気になるかたはman sched_setschedulerを参照してください。

調査ログ

問題発生

あるサービスの開発中に、当該サービスをリアルタイムプロセスとして動作させられないか検討した上で、その検証をすることにしました。そのためにはsystemdの当該サービスに関する設定ファイルに次のような記載をします。

...
[Service]
...
CPUSchedulingPolicy=rr           # プロセスをリアルタイムプロセス化する
CPUSchedulingPriority=50       # リアルタイム優先度を設定する。ここではリアルタイム優先度は重要ではないので気にしなくていいです
...

この設定はUbuntu 18.04上で実行した場合はうまく機能したのですが、同じことをCoreOS上で実行するとデーモンのリアルタイムプロセス化処理が権限違反(EPERM)によって失敗しました。このサービスはroot権限で動作させているので、通常は失敗しないはずです。このため、この事象がどういう理由によって発生しているのかを調査することにしました。

最小の再現手順を見つける

問題検出当初の問題再現方法がsystemdを介したものであるため、もっと簡単に再現できる方法を調査しました。幸いにもroot権限でchrtコマンドを使ってプロセスをリアルタイムプロセスとして動かすだけで簡単に再現することがわかりました。

# chrt -r 50 echo hello
chrt: failed to set pid 0's policy: Operation not permitted
# 

straceを使ってプログラムを動かしたところ、次のようにsched_setscheduler()システムコールを発行していました。

...
sched_setscheduler(0, SCHED_RR, { 50 }) = -1 EPERM (Operation not permitted)
...

sched_setscheduler()システムコールにはさまざまなオプションを指定できますが、ここではとくに凝ったことはしていないことがわかりました。

Ubuntu上では次のようにコマンドが成功しました。

# chrt -r 50 echo hello
hello
# 

カーネルソースの違い

問題が発生した原因にはいくつか考えられます。Ubuntuでは成功したのにCoreOSでは失敗したことより、まずはCoreOSのカーネルがリアルタイムプロセスの実行を許さない特殊なものになっていないかを確認しました。問題発生時のCoreOSのカーネルは4.14.4-CoreOS-r1というものでした。このカーネルは公式の安定版カーネル4.14.14に、下記のいくつかCoreOS独自パッチを当てたものです。

github.com

これらパッチをすべて調査してUbuntuのカーネルと比較をしましたが、スケジューラのコードは変更されていませんでした。これによってスケジューラに関してはCoreOSは特殊なカーネルを使っていないことがわかりました。

ソース調査

カーネルソースを見て、どのような条件ならプロセスをリアルタイムプロセスにできないかを調査しました。これに該当するコードは以下ソースの中の__sched_setscheduler()関数です。

github.com

これを見ると上記のようにコマンドを発行した場合、次のような条件でこの関数はEPERMによって失敗することがわかりました。

  • CAP_SYS_NICE capabilityが無い(4192行目)
  • セキュリティモジュールのポリシーによって禁止されている(4244行目のsecurity_task_setscheduler()関数)
  • リアルタイムグループスケジューリング機能(後述)が有効になっている、かつ、プロセスが何らかのcpu cgroupに属している、かつ、そのcpu cgroupのリアルタイムプロセス実行可能時間が0(4291行目のif文)

今回の問題が発生した根本原因は上記いずれかではないかと疑って、総当たり式に確かめることにしました。

CAP_SYS_NICEについての確認

chrtコマンドを実行するときにrootにどのようなcapabilityが付与されているかはgetpcapsコマンドによって確かめられます。

# getpcaps $$
Capabilities for `1582': = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,37+ep
# 

コマンドの出力を見るとCAP_SYS_NICEに該当するcap_sys_niceという文字列がありました。これによってCAP_SYS_NICEはシロだとわかりました。

リアルタイムグループスケジューリング機能についての確認

続いてリアルタイムグループスケジューリング機能について確認しました。ソースの順番的にはセキュリティモジュールのほうが先にありますが、簡単に確かめられそうなこちらを先にやることにしました。

CoreOSのカーネルの設定ファイルを見ると、リアルタイムグループスケジューリング機能は有効になっていました(設定項目はCONFIG_RT_GROUP_SCHED)。これは適当なcpu cgroupの中を見て、cpu.rt{period,runtime}usというファイルがあれば有効と判断できます。

# ls /sys/fs/cgroup/cpu/user.slice/cpu.rt_*
/sys/fs/cgroup/cpu/user.slice/cpu.rt_period_us  /sys/fs/cgroup/cpu/user.slice/cpu.rt_runtime_us
# 

ここでリアルタイムグループスケジューリングについて少し説明しておきます。これはリアルタイムプロセスの暴走を防ぐための機能です。この機能を使うことによってグループ内のリアルタイムプロセスはcpu.rt_period_us(マイクロ秒単位)という期間ごとにcpu.rt_runtime_us(マイクロ秒単位)だけ動作できて、残りの時間は通常のプロセスが動作できるようになります。

続いてbashがどのようなcpu cgroupに属しているかを調べました。systemdは通常のプロセスをuser.slice cgroupに属させますので、chrtコマンドを発行するbashがこのグループ入っているかを確かめました。

# grep $$ /sys/fs/cgroup/cpu/user.slice/tasks
1616
# 

ではこのグループのcpu.rt_runtime_usの値を見てみますと…

# cat /sys/fs/cgroup/cpu/user.slice/cpu.rt_runtime_us
0
# 

0でした。これが原因でrootでもプロセスをリアルタイムプロセス化できなかったというわけです。なお、詳細は述べませんが、リアルタイムスケジューリング機能は新たにcpu cgroupを作るとcpu.rt_runtime_usを0にすることもわかりました。この問題に対する対処方法は単にcpu.rt_runtime_usに1以上の値を書き込むだけです。

なお、Ubuntuのカーネルにおいてはリアルタイムグループスケジューリング機能は無効化されています。

もとの再現方法による再現試験

ここまでで「コマンドラインからroot権限でプロセスをリアルタイムプロセス化したらEPERMで失敗する」ことがわかりましたが、元のサービス起動失敗が同じ原因だとは限りません。このため、もとの再現方法による再現試験をする必要があります。

幸いにもサービスが所属するcpu cgroupのcpu.rt_runtime_usを1以上にしてサービスを起動したところ、成功しました。

対処方法の調査

最後に、実際のシステムにおいてどのように対策すればよいかを調査しました。systemdの設定ファイルに設定項目が無いかと調査していると、systemdのissueに次のようなものを見つけました。

github.com

I am not sure this can be supported in any sane way since the propagation and summing rules are so weird. I generally encourage everyone to disable rt group scheduling in the kerbel these days.

README: document that RT group sched should be turned off by poettering · Pull Request #553 · systemd/systemd · GitHub

+        kernel when using systemd. RT group scheduling effectively
+        makes RT scheduling unavailable for most userspace, since it
+        requires explicit assignment of RT budgets to each unit whose
+        processes making use of RT. As there's no sensible way to
+        assign these budgets automatically this cannot really be
+        fixed, and it's best to disable group scheduling hence.
+           CONFIG_RT_GROUP_SCHED=n

少なくともsystemdについては今のところサービス起動時にcpu.rt_runtime_usの値は変更する方法が無いこと、および将来的にできるようにする気もないことがわかりました。ではCoreOSのkernel configを変えてもらうかというと、

  • 回避方法はあるのでそこまでしてもらう話でもない
  • 説得材料に欠ける
  • 説得できてもいつ修正されるか不明

などの理由によって、他の方法を探すことにしました。今のところはサービスの起動前にcpu.rt_runtime_usを1以上にするスクリプトを仕掛けて問題を解決する予定です。

おわりに

本記事を読まれた結果、読者のみなさまにこのような問題があることに加えて、なんらかの問題が発生した場合にどのように調査するかという思考プロセスの一例を知っていただけたらと思います。

URLSession の困った挙動

$
0
0

こんにちは。モバイル開発チームに所属している小島です。

先日、URLSessionを使っててバグっぽい挙動を見つけたのでメモしておきます。

URLSession とは

詳しく書く必要はないかと思いますが、Swift (iOS アプリ開発) で使用する通信クライアントです。HTTP 接続で API を呼び出すのに使用しています。

どんな挙動だったか?

GET メソッドで、リクエストボディを設定した際に、0 のバイト配列でデータが送信されました。

経緯

事の発端は、弊社のクラウドサービスの API を呼び出す処理を書いていたときのことです。 https://developer.cybozu.io/hc/ja/articles/202331474#step2

リクエストパラメータはかなり複雑で、クエリ文字列で送るよりリクエストボディで送る方が json で渡せるためパラメータの構築は楽ちんです。

ところが、実際に動作させてみるとどうもうまくいきません。 最初は認証周りでうまく行ってないのかと思いましたが、curl で同じようなリクエストを出せば期待通りの結果になるので、設定してるリクエスト情報に問題はなさそうです。

仕方がないので、プロキシを通して実際の通信の中身を見てみることにしました。すると以下のような結果に...

httpbody
httpbody.png

なんと、Content−Length がちゃんと設定されているにもかかわらず、中身が全部ゼロじゃないですか😲 これではうまくいくはずもないですね。

RFC的には?

まあ、GET でリクエストボディ送るのはどうなん?とも思ってたのでググってみます。

https://tools.ietf.org/html/rfc7231#section-4.3.1

リクエストボディを送ったときの挙動は未定義で、サーバーによっては接続を拒否する場合があるとなっています。 つまり、送っては駄目とは書いてないけど、その挙動はサーバー側の実装依存ということです。

結論

というわけで、iOS アプリの開発では GET でリクエストボディを送るのはやめたほうが良さそうです。 とはいえ、全く送らないならまだしも中途半端な状態で送られるのはかなりバグっぽい挙動だと感じます。

ファイルシステムサイズの拡張時にデータベースアクセスがスローダウンする問題の解決

$
0
0

はじめに

こんにちは、技術顧問のsatです。

サイボウズでは、ファイルシステムサイズ拡張時にデータベースアクセスがスローダウンするという問題に長年悩まされてきました。本記事では運用本部の藤田と深谷がこの問題を解決した流れについて報告いたします。問題を解決するために2人はLinuxカーネルを修正しました。修正は社内に閉じたものではなく、執筆当時の最新 Linuxカーネルであるv4.17にマージされています。

問題

以下の操作の後にデータベースへのアクセスが一時的にスローダウンする

  1. ブロックデバイスのサイズを拡張する
  2. 上記デバイス上にあるファイルシステムのサイズを拡張する

原因

linuxカーネルはブロックデバイスのサイズ変更(縮小および拡張)時に、当該デバイス上にあるファイルシステムのページキャッシュ(後述)を無効化する*1

解決方法

ブロックデバイスのサイズ拡張時にはページキャッシュを無効化しないようにlinuxカーネルを変更する

ページキャッシュ

ページキャッシュについて既にご存知のかたはこの節は飛ばしてください。

ページキャッシュとはストレージデバイス上のデータへのアクセス速度を高速化するためのカーネルの機能です。

最近のコンピュータシステムにおいてメモリ上のデータをCPUに転送する速度はナノ秒オーダーです。これに対してストレージデバイス上のデータをメモリに転送する速度はSSDではマイクロ秒オーダー、HDDに至ってはミリ秒オーダーです。つまりストレージデバイス上のデータへのアクセス速度はメモリ上のデータへのそれに比べて数桁遅いと言えます。この問題を解決するのがページキャッシュです。

Linuxカーネルはプロセスがファイルシステム上のファイルにアクセスした場合、ファイルのデータを保持するストレージデバイスに毎回アクセスしません。読み出しにおいては初回読み出し時にメモリ上のページキャッシュと呼ばれる領域にストレージデバイス上のデータをコピーした上で、プロセスにはページキャッシュのデータをコピーして渡します。二回目以降の読み出しはストレージデバイスにはアクセスせず、ページキャッシュ上のデータを渡します。書き込みについてもストレージデバイスには直接アクセスせず、ページキャッシュにデータを書き込んだ段階でプロセスには書き込みが終わったと報告します。その後所定のタイミングでページキャッシュからストレージデバイスにデータを書き戻します。これによってストレージデバイス上のデータへのアクセスが実質的にメモリアクセスと同等の速度で実現できるわけです。

調査ログ

問題の検出

弊社のシステムではデータの一部をSQLite上に保存しています。データベースのサイズが増加してファイルシステムの空き領域が足りなくなってくると、次のような手順でファイルシステムのサイズを拡張します。

  1. ファイルシステムが存在するブロックデバイス*2のサイズを拡張する
  2. 上記デバイス上にあるファイルシステムのサイズを拡張する

ここで手順2の後にデータベースへのアクセスが一時的にスローダウンするという問題が発生していました。この問題はお客様が使用するアプリケーションの応答速度劣化として顕在化していました。

統計情報の調査

freeコマンドやsarコマンドによってLinuxの統計情報を確認した結果、次のようなことがわかりました。

  • 手順1の後にシステムに存在するページキャッシュの量が大幅に減少していた
  • 手順2の後にデータベースアクセス性能の劣化に連動してストレージデバイスへのアクセスが発生していた
  • 上記性能劣化の解消に連動してストレージデバイスへのアクセスは減少していた

この事実から、ブロックデバイスのサイズ拡張において、その上にあるファイルシステムのページキャッシュが無効化されているであろうことがわかりました。

カーネルソースの調査

ページキャッシュはカーネルの機能なので、深谷がカーネルソースの調査にとりかかりました。その結果、ブロックデバイスのサイズ変更処理の延長で呼ばれるcheck_disk_size_change()関数の中に次のような箇所を見つけました。

/**
 * check_disk_size_change - checks for disk size change and adjusts bdev size.
 * @disk: struct gendisk to check
 * @bdev: struct bdev to adjust.
 *
 * This routine checks to see if the bdev size does not match the disk size
 * and adjusts it if it differs.
 */
void check_disk_size_change(struct gendisk *disk, struct block_device *bdev)
{
    loff_t disk_size, bdev_size;

    disk_size = (loff_t)get_capacity(disk) << 9;
    bdev_size = i_size_read(bdev->bd_inode);
    if (disk_size != bdev_size) {                                              # ... (1)
        printk(KERN_INFO
               "%s: detected capacity change from %lld to %lld\n",
               disk->disk_name, bdev_size, disk_size);
        i_size_write(bdev->bd_inode, disk_size);
        flush_disk(bdev, false);                                           # ... (2)
    }
}
EXPORT_SYMBOL(check_disk_size_change);

変更前のサイズ(bdev_size)と変更後のサイズ(disk_size)が異なれば(1)が真と評価されて、(2)において当該デバイス上のファイルシステムのページキャッシュが無効化されていることがわかりました。

解決方法の調査

この問題を回避する何らかの設定項目がカーネルに存在しないかと更にソースを調査しましたが、残念ながら見つかりませんでした。このためカーネルの修正によって問題を解決できないかという検討を始めました。

まずは、そもそもブロックデバイスのサイズを変更する際にページキャッシュを無効化する理由が何かということを調べました。これにはflush_disk()関数を呼ぶようになったcommitをgit blameコマンドによって見つけた上で、そのdescriptionを読むという方法を使いました。コードが書かれた意図が分からない場合はコメントあるいは変更時のdescripionを見るのが王道です。

github.com

上記commitの言わんとすることを要約すると、次のようになります。

  • 縮小時に縮小対象領域のデータを読み書きしていると、読み書き中のデータが行先を失うためにI/Oエラーが発生する
  • 拡張については、「縮小直後に拡張」という特殊な場合に縮小時との問題が発生しうる

深谷はこの後、関連ソースの全調査によって、サイズ縮小直後に拡張したとしてもカーネル内の排他制御により上記の問題は起こりえないことを突き止めました。つまり、サイズ拡張時のページキャッシュの無効化は実は不要だったというわけです。

このアイデアを形にしたものが次のパッチです。

fs/block_dev.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/fs/block_dev.c b/fs/block_dev.c
index 44d4a1e..d17603c 100644
--- a/fs/block_dev.c
+++ b/fs/block_dev.c
@@ -1078,7 +1078,14 @@ void check_disk_size_change(struct gendisk *disk, struct block_device *bdev)
                "%s: detected capacity change from %lld to %lld\n",
                name, bdev_size, disk_size);
         i_size_write(bdev->bd_inode, disk_size);
-        flush_disk(bdev, false);
+        if (bdev_size > disk_size) {
+            flush_disk(bdev, false);
+        } else {
+            if (!bdev->bd_disk)
+                return;
+            if (disk_part_scan_enabled(bdev->bd_disk))
+                bdev->bd_invalidated = 1;
+        }
     }
 }
EXPORT_SYMBOL(check_disk_size_change);

このパッチを当てたカーネルを開発環境においてテストをした後に本番環境に適用しました。これによって社内の問題は解決しました。

upstream化

開発環境におけるパッチのテストと並行して、upstream(公式)のlinuxカーネルへのパッチの取り込み作業に藤田がとりかかりました。社内の問題は既に解決しているのにこのようなことをする理由は次の通りです。

  • 弊社には、弊社のシステムを支えるOSSの改善に貢献するという方針がある
  • 将来のOSアップグレード時に逐次修正パッチをバックポートしないで済む

最初に投稿したときのパッチの文面は次のようなものでした。

linux-kernel - [RFC PATCH] fs: don't flush pagecahce when expanding block device

このメールは、linuxコミュニティの誰からも返事がもらえませんでした*3。しかし、この後めげずにパッチおよびその投稿文面をシンプルにしながら何度も再投稿した結果、ようやく数か月後にlinuxの開発版とも言えるnext treeに、最終的にv4.17にパッチが取り込まれました。

github.com

これにて、この問題に関するすべての作業が完了しました。今後v4.17以降の公式カーネル、およびその派生版である各種linuxディストリビューションのカーネルではこの問題が発生しなくなりました。

おわりに

この問題の修正パッチは二千万行におよぶlinuxカーネルのソースコードの中のたった4行の追加、2行の削除に過ぎませんが、ここにたどり着くまでに上記のような様々な試行錯誤がありました。これはソース修正の手間は変更の行数に比例しないといういい例ではないでしょうか。

本記事が読者のみなさまの今後のトラブルシューティングに役立てば幸いです。

*1:正確にはページキャッシュだけではなくファイルシステム内のパス解決などに用いるデータのキャッシュなども無効化しているのですが、ここでは割愛します。

*2:リモートサーバ上のLVMボリュームに対応するiSCSIデバイスを2つ束ねてRAID1構成にしたmultiple device

*3:パッチ投稿においては無反応が一番やっかいです。なぜかというと、改善案を提示されたり反対されたりするのは善かれ悪しかれ誰かに注目されているので改善なり議論なりをすればよいのですが、反応が無い場合は次の一手が打ちにくいためです。


Necoのネットワーク - アーキテクチャと設計編

$
0
0

こんにちは。「Neco」の @ueokandeです。

サイボウズでは、cybozu.comのアーキテクチャ刷新プロジェクト「Neco」を実施してます。 その思いについては以下の記事からどうぞ。

今回は、Necoにおけるネットワーク設計についてお話します。

ネットワークの方針

ネットワークの耐障害性は必須の条件です。 Necoでも同様で、ネットワーク機材の単一障害点が無いネットワーク構成にする必要があります。 具体的には以下のような要件です。

  • ホストマシンのNICを二重化して、片方のNICが故障しても通信が行えること
  • ネットワークスイッチを冗長化して、スイッチが故障してもスイッチ下のホストが停止しないこと

スイッチを冗長化する方法は、以下のような選択肢があります。

  1. STP (Spanning Tree Protocol)
  2. MC-LAG (Multi-Chassis Link Aggregation)
  3. BGP (Border Gateway Protocol) + BFD (Bidirectional Forwarding Detection)

STPは他の2つに比べて利点が少ないので、Necoでも採用しませんでした。 MC-LAGは、2台のスイッチを論理的に1つにする技術で、現在のcybozu.comで採用しています。 BGPは動的ルーティング・プロトコルの1つで、古くから使われている信頼性のあるプロトコルです。BFDはネットワーク機器間で障害検知する仕組みです。

Necoではマルチベンダー対応も目標の1つに入っています。 現在のcybozu.comではMC-LAGを採用していますが、スイッチ側の実装はベンダーの独自技術となり、トラブルシューティングが難しいという問題がありました。 BGPとBFDはベンダーに依存せず、L3レイヤーで経路を制御するので、トラブルが発生したときも従来のIPのトラブルシューティングが適用できます。

NecoではBGPによる経路制御を行い、BFDによる障害検知とECMP (Equal-Cost Multi-Path routing)による冗長経路で構成しました。 大規模ネットワークで広く用いられてるリンク・アグリゲーションではなく、ベンダー非依存の技術を使って経路を制御しているのも、Necoの大きな特徴です。 経路制御をスイッチ側に任せるのではなく、ホストマシンにL3リンクを2つ持たせて、ホストマシン側にもBGPサーバーを実装して経路を制御します。

ネットワークのアーキテクチャ

Necoのネットワークアーキテクチャ
Necoのネットワークアーキテクチャ

NecoではCLOSアーキテクチャの1つであるLeaf-Spineアーキテクチャを採用しました。 CLOSアーキテクチャは、各ホストマシン間の通信は同一ホップで到達できます。 このメリットは水平方向にスケールできる構成で、East-West間の通信が多いネットワークでは有利な構成となります。 将来Kubernetesを導入したとき、Pod間の通信が増えて不特定多数のホストマシン間で通信が発生しても安心できるアーキテクチャです。 詳しい説明は以下のリンクからどうぞ。

https://eos.arista.com/ja/cloud-clos-architecture/#CLOS

各ラックにはLeafスイッチとなる2つのToR (Top of Rack) スイッチがあり、それぞれのToRスイッチは冗長化されたSpineスイッチに接続されます。 そして各ホストマシンは両方のToRスイッチに接続します。 これで、仮に片方のNIC、あるいはスイッチごと故障したとしても、ホストマシンがネットワーク的に到達不能ということはありません。 MC-LAGを利用しないので、各ToRスイッチはそれぞれ異なるL2サブネットを持つことになります。 このため、各ホストマシンは2つのNICがそれぞれ異なるL2サブネットのアドレスを持ちます。

それぞれのホストマシンはBGPサーバーを実装して、IPアドレスをToRスイッチに広告します。 各スイッチ間で経路を交換するので、ラックをまたいでも通信できるようになります。 ホストマシンがToRスイッチに広告するIPアドレスは、NICが持つIPアドレスではなく、各ホストに仮想IPアドレスを持たせて、そのIPアドレスをToRに広告します。 この仮想IPアドレスを、Necoでは「代表IPアドレス」と名付けています。 つまりラックをまたぐホスト間の通信は、NICのアドレスではなく代表IPアドレスを使います。 したがって各ホストマシンは、代表IPアドレスと、それぞれのNICのIPアドレスの、計3つのIPアドレスを持つことになります。

なぜ経路広告に仮想IPアドレスを使うかというと、Kubernetesでネットワークを制御するCalicoやRomanaの設定で、指定できるノードのIPアドレスが1つだったからです。 なのでCalicoやRomanaで片方のNICのIPアドレスを指定してしまうとECMPがうまく働きません。 したがってNecoでは、仮想的なIPアドレスを代表IPとして持たせ、それをBGPに広告するような設計にしました。 この「ノードには単一のIPアドレスで到達できる」という設計は、結果的に他のアプリケーションの実装も楽になりました。

ネットワークの検証

さて、Necoのネットワークで上記のアーキテクチャに至るまで、実験と再検討の繰り返しでした。 Necoでは、クラスタを実マシンではなく仮想マシンで実験できるよう、開発環境の構築と自動化にも力を入れています。 結果的にVM用に書いたネットワーク構成やBGPの設定を、そのまま実マシンに投入することができます。 開発環境の構築方法について、書き始めるとこれもまた長くなるので、また日を改めて記事を書きたいと思います。

以下の情報は、ホストマシンに別のラック内の経路が広告されているという確認です。 ホストマシンは node0という名前の仮想インターフェイスに 10.69.0.4/32のIPアドレスを持っています。 そして別のラックにあるホストは代表IPアドレスである 10.69.1.133/32を、クラスタ内に広告しています。 代表IPアドレスへの経路は/32のIPアドレスで広告されており、それがOSのルーティングテーブルに反映されてるのが確認できます。

cybozu@rack0-worker4 ~ $ ip -4 addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    inet 10.69.0.68/26 brd 10.69.0.127 scope link eth0
       valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    inet 10.69.0.132/26 brd 10.69.0.191 scope link eth1
       valid_lft forever preferred_lft forever
4: node0: <BROADCAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
    inet 10.69.0.4/32 scope global node0
       valid_lft forever preferred_lft forever

cybozu@rack0-worker4 ~ $ ip -4 route
...
10.69.1.133 proto bird metric 32
        nexthop via 10.69.0.65 dev eth0 weight 1
        nexthop via 10.69.0.129 dev eth1 weight 1
...

もちろんpingも届きます。

cybozu@rack0-worker4 ~ $ ping 10.69.1.133
PING 10.69.1.133 (10.69.1.133) 56(84) bytes of data.
64 bytes from 10.69.1.133: icmp_seq=1 ttl=61 time=0.499 ms
64 bytes from 10.69.1.133: icmp_seq=2 ttl=61 time=0.698 ms
64 bytes from 10.69.1.133: icmp_seq=3 ttl=61 time=0.593 ms
^C
--- 10.69.1.133 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 0.499/0.596/0.698/0.086 ms

以下はホストマシンがダウンした時の経路収束の様子です。 2つのホスト「rack0-worker4」と「rack1-worker4」はそれぞれ別のラックに属するホストマシンです。 rack0-worker4は 10.69.0.4/32という代表IPアドレスを持っており、それをBGPで広告しています。 隣のラックにあるrack1-worker4のルーティング情報にも経路が反映されてます。

障害を再現するために、rack0-worker4の仮想インターフェイスを一時的にDOWNにします。 すると次の瞬間に、rack0-worker4の経路が無くなった事がクラスタ全体に通知され、rack1-worker4のルーティング情報からも削除されました。

BGPによる経路広告と障害の再現
BGPによる経路広告と障害の再現

おわりに

Necoでは、ベンダーに縛られないプロトコルのBGPとBFDを使って、高信頼かつスケール可能なネットワークを設計・構築しました。 今のcybozu.comのインフラとは全く違う構成なので、新しい事ばかりで、実験と調査の繰り返しでした。 しかし本番環境を想定したネットワークを各開発者の手元で再現できたことは、非常に開発をスムーズに進めることができました。

この記事ではNecoではアーキテクチャについて説明しました。次回はNecoのネットワークの実装について説明したいと思います。

Necoでは今後、Kubernetesクラスタのデプロイや、その上のアプリケーションのデプロイなどをやっていく計画です。 まだまだやることは山積みですが、Necoプロジェクトでは一緒に働ける人を募集しています。

www.wantedly.com

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

$
0
0

はじめに

こんにちは、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の診断に少しでも貢献できればと思います。

PSIRT Framework のご紹介

$
0
0

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 をお持ちの企業の皆様とも意見交換をさせていただきながら、より安全な脆弱性情報ハンドリングができるように努めてまいります。

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

$
0
0

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が統合されました。 今後はこのような改善を加速できると思っています。

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

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

$
0
0

こんにちは。東京品質保証部 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 として鋭意活動中です。

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

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

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

$
0
0

こんにちは、アプリケーション基盤チームの青木(@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_selectSELECT ステートメントが実行された回数。他の 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   |
+--------------------------------+-----------+8rowsinset (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 = 723ORDERBY 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=1323GROUPBY 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
orderby 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 SETsize = LENGTH(body);

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

UPDATE blob_file SETsize = LENGTH(body) WHERE id BETWEEN12000AND13000;

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

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

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

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

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

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

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

SELECT name FROMuserWHERE 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
ORDERBY 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
ORDERBY create_date DESC
LIMIT 1 OFFSET 20

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

SELECT id
FROM article
WHERE create_date >= '2018-07-15'-- 上記クエリで得た作成日をここに追加ORDERBY 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
ORDERBY 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!

2017年 報奨金制度を振り返って

$
0
0

こんにちは。Cy-PSIRT の山西です。

本エントリでは、

  • 2017 年に実施した脆弱性報奨金制度の結果
  • 参加者からの要望への回答
  • 2018年の取り組み

についてまとめました。

2017 年に実施した脆弱性報奨金制度の結果

定量情報

2017年の脆弱性認定件数は102件、報奨金支払い額は10,996,000円でした。

f:id:cybozuinsideout:20180809132803p:plain

前年度と比較して報告件数が増加し、報奨金支払金額が倍以上に増えています。「報奨金最大5倍キャンペーン」と「バグハン合宿」が主な要因です。 7月7日から開始した「報奨金最大5倍キャンペーン」では期間中に 146 件の報告があり、その支払金額は 4,150,000 円でした。またキャンペーン期間中に開催した「バグハン合宿」では 2 日間で 57 件の報告があり、本年度の報告数の4分の1を占めています。

詳しくはバグハン合宿のレポートをご覧ください。

blog.cybozu.io

トレンド

1位(不適切な入力確認)と2位(XSS)で認定件数の半分を占めています。前年と比べると1位と2位が逆転しました。 3位の「CWE-DesignError システム設計の問題」では、機能として実装または設定していないことで脅威につながる問題が報告されていました。

認定した脆弱性を脆弱性タイプ毎に集計

f:id:cybozuinsideout:20180806115229p:plain

「XSS」として認定するまでに至らなくても機密性・完全性・可用性を侵害する場合は「不適切な入力」として扱います。「不適切な入力確認」は、誰にどこまで影響があれば脆弱性と扱うか判断が難しいです。 都度社内で議論し、結果を脆弱性認定ガイドラインに反映してまいります。

2017年度報奨金獲得ランキング

獲得した報奨金額が最も多かったのは Masato Kinugawa 様でした。他にもたくさんの方からご報告をいただいています。皆様、ご報告ありがとうございました。

順位掲載名
1位 Masato Kinugawa 様
2位閏間 修一 様
3位東内裕二(@yousukezan)様
4位小勝純(@shhnjk)様
5位ixama 様

参加者からの要望への回答

本年度も、報奨金制度へ参加してくれた方に対してアンケートを実施しました。今後の制度運営やイベントの企画に活かしてまいります。いただいた要望に回答します。

また合宿を開催してほしい

「熱き戦いを再び!」

2019年4月を目標に開催を検討しています。

やる気はあります!続報をお待ちください。

対象サービスを増やして欲しい

確約はいたしかねますが、対象を広げることも検討しています。

サイト上でやり取りできるようにしてほしい

長期の施策として検討を進めています。短期間での実現は難しいです。 しばらくはメールでの対応とさせていただきます。

製品のソースコードが欲しい

関係者外へのソースコードの開示は行っておりません。ご了承ください。

2018年の取り組み

報奨金のお支払い金額の見直しとお支払金額の計算式の開示

前年の最大5倍キャンペーンに合わせて、報奨金額の計算式を変更いたしました。計算式は、脆弱性報奨金制度 - 報奨金制度ルールブックにも反映しました。「6.1 お支払金額の計算式」をご参照ください。

サイトの英語化

以下のドキュメントの英語化を進めています。

シークレットパーティの開催

参加者・関係者の方にお声がけし、クローズドな情報交換の場をご用意しようと考えています。参加いただく方にNDAを結んでいただくことで、非公開情報を含む内容の LT など、より内容の濃いイベントにしたいと考えています。

ポイント制度の導入

ポイント制度の導入を検討しています。まだ、漠然としたアイディアの段階ですが、報奨金のお支払いには反映できない部分をポイントに反映していきたいと考えています。具体的には以下のような部分をポイントに反映していきたいと考えています。

  • 弊社内での報告のインパクト
  • 報告の丁寧さ・わかりやすさ
  • 弊社内の新しい検証観点につながるか
  • 脆弱性認定されなかったがセキュリティ向上に有益な報告
  • 報告数

ポイントは希望者のみでランキング化し、ランキング上位の方には報奨金以外でお礼ができればと考えています。また、ランキングの公開も検討しています。

今後の活動について

ここ数ヶ月で、報告の件数が増えています。報告者の皆様には評価連絡が遅くなりご不便をおかけしております。 本年度の残りもだいぶ少なくなってきましたが、より良い制度を報告者の方と一緒に作っていきたいと考えています。引き続き報奨金制度の改善に努めてまいります。報奨金制度に限らず「サイボウズにこんな企画してほしい」という要望がございましたら、 productsecurity@cybozu.co.jp までお気軽にご連絡ください。


【RxSwift】Singleton で DisposeBag を使うことの考察

$
0
0

こんにちは。モバイル開発チームに所属している小島です。

弊社のプロダクトでもようやく RxSwift を使い始めています。今回は RxSwift の Disposable について思うところがあったので、メモしておきます。

Disposable と DisposeBag

Observable を subscribe すると、Disposable を返してきます。 Disposable は、subscribe したものを unsubscribe するための仕組みで、これを無視すると subscribe した処理が永遠に解放されずにメモリリークやリソースリークに繋がる恐れがあります。 なので、とりあえず DisposeBag に入れておけばいいよというのはよく見かけます。

classSomeClass {
    privateletdisposeBag= DisposeBag()
    privateletdependentObject:DependentClassfuncsomeMethod() {
        letdisposable= dependentObject.getObservable().subscribe(onNext: {
            // do someting.
        })
        self.disposeBag.insert(disposable)
    }

    funcsomeMethod2() {
        // 書き方が違うだけで↑と意味は同じ
        dependentObject.getObservable().subscribe(onNext: {
            // do someting.
        })
        .disposed(by:self.disposeBag)
    }
}

DisposeBag は、Disposable をためておいて、自身が解放 (deinit) されるときに保持している Disposable を dispose してくれます。上記の場合だと、SomeClass が解放されるときに disposeBag も解放されこれまで subscribe したものが unsubscribe されるという流れになります。

Singleton オブジェクトの場合も同じように考えていいのだろうか?

DisposeBag は、便利な仕組みではありますが、保持しているオブジェクトのライフサイクルと同一になりますので、Singleton オブジェクトの場合にはアプリのライフサイクルと同一ということになります。

classSingletonClass {
    privateletdisposeBag= DisposeBag()
    privateletdependentObject:DependentClassfunccalledFrequently() {
        dependentObject.getObservable().subscribe(onNext: {
            // do someting.
        })
        .disposed(by:self.disposeBag)
        // disposeBag が解放されることはない
    }
}

例えば上記のような場合、calledFrequently が呼ばれるたびに disposeBag に Disposable が追加されますが、disposeBag は開放されないので、subscribe された Observable は unsubscribe されることはありません。

チーム内で改善策を決めたけど、期待通りに unsubscribe されないことがありました

チームメンバーとも相談し Singleton オブジェクトの場合は以下のような形にしようと決めました。

classSingletonClass {
    privateletdependentObject:DependentClassfunccalledFrequently() {
        vardisposable:Disposable?
        disposable = dependentObject.getObservable().subscribe(onNext: {
            // do someting.
            disposable?.dispose()
        })
    }
}

ところが上記の書き方の場合、とあるケースで期待通りの動作になりませんでした。 onNext で渡しているクロージャの実行タイミングは Observable によって異なっているのです。

classSingletonClass {
    privateletdependentObject:DependentClassfuncmethod1() {
        vardisposable:Disposable?
        disposable = dependentObject.getAsyncSubjectAsObservable().subscribe(onNext: {
            // do someting.
            print(disposable?)
        })
    }

    funcmethod2() {
        vardisposable:Disposable?
        disposable = dependentObject.getBeheiviorSubjectAsObservable().subscribe(onNext: {
            // do someting.
            print(disposable?)
        })
    }
}

上記のコードでは、method1 は AsyncSubject を Observable として受け取り、method2 は BeheiviorSubject を Observable で受け取る想定です。BeheiviorSubject を subscribe した時の onNext は同期的に呼び出されるので、method1 は disposable オブジェクトが出力されますが、method2 は nil が出力されます。 つまり method2 の場合では、想定通りに unsubscribe されず getBeheiviorSubjectAsObservable の中の BeheiviorSubject の値が変わるたびにクロージャが実行されてしまいました。

(正確には、method1 の場合でも getAsyncSubjectAsObservable の中で実行している AsyncSubject が同期的に onNext された場合は nil が出力されます)

問題点のまとめ

問題は onNext が同期的に呼び出されるのか、非同期で呼び出されるのか、呼び出し元にはわからないということです。都度、中身の実装を確認しに行くのは辛いですし、メソッド名を工夫してわかるようにするということも考えましたが、なんとなく微妙だなぁとも思いました。

今のところの改善案

チームメンバーと再度検討し、とりあえず以下のような形に落ち着きました。

classSingletonClass {
    privateletdependentObject:DependentClassfuncuseTake() {
        _ = dependentObject.getObservable().take(1).subscribe(onNext: {
            // do someting.
        })
    }

    funcuseCompositeDisposable() {
        letcompositeDisposable= CompositeDisposable()
        letdisposable= dependentObject.getObservable().subscribe(onNext: {
            // do someting.if (conditionalExpression) {
                compositeDisposable.dispose()
            }
        })
        _ = compositeDisposable.insert(diposable)
    }
}

そもそも onNext の処理が1回きりで良い場合は、take(1) を使って確実に1回で onCompolete が呼ばれるようにすることにしました。onCompolete が呼ばれれば unsubscribe の必要はないので、戻り値の Disposable は無視しても問題ありません。

onNext 内で dispose する必要がある場合は CompositeDisposable を使うようにしました。CompositeDisposable は、dispose を呼び出したときに insert してあった diposable を dispose してくれます。予め dispose を呼び出していた場合には、あとから insert した diposable は、即 dispose してくれます。これにより、onNext の実行が同期的か非同期的かは気にしなくても良くなります。

これがベストかはもう少し使ってみないとなんとも言えませんが、RxSwift は非常に強力なツールであると感じるとともに難しさを感じる部分もあります。

リモート開発をテーマにしたMeetupを開催しました──西日本開発部の活動紹介

$
0
0

こんにちは、西日本開発部の岡田(@y_okady)です。 2018年7月26日(木)にサイボウズ東京オフィスで「Cybozu Meetup 西日本でのリモート開発事情」を開催し、西日本開発部でどんな風にリモート開発してるのかをご紹介しました。 今回はMeetupのレポートと、Meetupではお話しできなかった西日本開発部の活動をちょこっとご紹介します。

f:id:cybozuinsideout:20180815144630j:plain

西日本でのリモート開発事情

Meetupでは、松山オフィス勤務で近々広島に移住予定のマネージャーの水戸、大阪オフィス勤務でkintoneの新機能開発を担当しているエンジニアの榎原、京都での在宅勤務中心でUS向けkintoneの開発を担当しているエンジニアの三苫の3名が、それぞれの立場でリモート開発についてお話ししました。

speakerdeck.comspeakerdeck.com

www.slideshare.net

リモート開発でお悩みの方や将来東京以外で働きたいと考えている多くの方にご参加いただき、どのセッションもみなさん熱心に聞いてくださって質問が次から次へと出てきたのが印象的でした。懇親会では「働く場所の自由化」という考え方やリモートスクラム・リモートモブプロといった具体的な開発手法について、熱い議論が交わされていました。

f:id:cybozuinsideout:20180815145222j:plainf:id:cybozuinsideout:20180815145525j:plain

西日本開発部の活動紹介

西日本開発部大交流会

少し前になりますが、大阪と松山に勤務する西日本開発部のエンジニア全員が松山に集合し、交流会を開催しました。初対面のメンバーも多く、今回は自己紹介やチーム紹介が中心でした。担当製品は違うけど業務内容の近いチーム同士でランチに行ったり、テレビ会議やグループウェア上でしかやり取りしないメンバーと直接話したり、普段は離れていてもたまにこうやって集まるのは大事だなぁと感じました。またみんなで集まって開発合宿とかやりたいですね。

f:id:cybozuinsideout:20180815150244j:plain
前夜祭は松山で大人気の複合温浴施設「そらともり」にお邪魔しました。オシャレ!

f:id:cybozuinsideout:20180815151021j:plain
交流会本編は松山オフィスにて。ボウズマン存在感ある。

f:id:cybozuinsideout:20180815150903j:plain
松山オフィスのメンバーが四国の名産品を用意してくれました。

9月は西日本各地でイベントラッシュ!

東京でのMeetupでもご紹介したリモート開発について、9月開催の西日本各地のイベントで西日本開発部のメンバーがお話しします。上記の発表資料に載っていない内容もお話しする予定です。お楽しみに!

おわりに

サイボウズでは、「チームワークあふれる社会を創る」という理念に共感してくれる仲間を募集中です。住みたい場所に住んで、いろんな場所で働く仲間と力を合わせて、世界中のチームを支えるサービスを一緒に開発しませんか?西日本をはじめ、住みたい場所で面白い仕事がしたいエンジニアの方、お待ちしてます!

www.wantedly.com

さようなら ImageMagick

$
0
0

こんにちは、アプリケーション基盤チームの青木(@a_o_k_i_n_g)です。

一般的な Web アプリケーションがそうであるように、サイボウズのグループウェアにも画像をサムネイルで表示する機能があります。サイボウズでは日々数万件やそれ以上のサムネイルを生成しており、それらは全て ImageMagick によって生成されていました。

そこで得た知見はこちらの記事で公開されています。
blog.cybozu.io

しかし現在、サイボウズから ImageMagick は消え去りました。その理由と、我々が取った代替手段について紹介します。

ImageMagick を外した理由

言うまでもなく ImageMagick は優秀なツールで、画像変換に関する何らかのサービスやツールを作る場合には採用の第一候補になることでしょう。あらゆる画像フォーマットに対応し、出力画像をきめ細かに制御できる膨大なオプションがあるからです。

しかし一方で、 ImageMagick には脆弱性が大量に存在します。2017 年に報告された ImageMagick の脆弱性は 236 件でした。大量にある上にリモートコード実行級の脆弱性もあり、安全性という観点ではかなり厳しい評価をしなければなりません。こちらに ImageMagick の脆弱性情報があり、その多さが伺えると思います。
Imagemagick : Security Vulnerabilities

もちろん、我々は AppArmor による厳重なアクセス制御やサムネイル作成専用の環境を使うなどの策は施しています。しかしそれでもこれだけ多数の脆弱性があるとなると、 ユーザーに安心安全なクラウドサービスを提供できているか?という点で疑問がありました。

また、脆弱性報告が来るたびにサイボウズに影響が無いか調査しており、そのコストが無視できないほど大きくなりました。脆弱性報告が来た際は否応無しに調査に時間を取られますし、意図しないタイミングでバージョンアップを迫られることもあります。スムーズな運用のためにも ImageMagick を採用し続けるのは得策ではないだろうという意見が出ていました。

検討を重ねた結果、我々は ImageMagick を捨てる決意をしました。

ImageMagick の代わりに

代替として使えそうなツールがいくつかありましたが、サイボウズのサムネイル事情は少々仕様が凝っている部分があり、自作することにしました。そして調査の結果、Go 言語と imagingパッケージが良さそうということでこれを用いて実装することにしました。

結論から言えば、ImageMagick に依存しないサムネイル作成ツールを実装し、移行も終えています。いくつか判明した点について記します。

PNG の変換画像が僅かに暗くなるケースがある

Go 版サムネイル生成ツールと ImageMagick では、PNG を変換した際に Go 版の方がわずかに色が暗くなるケースがあることが判明しました。

Wikipedia の PNG のページにある PNG 画像を例に取って説明します。一枚目が ImageMagick で変換した画像、二枚目が Go 版サムネイル生成ツールで変換した画像です。

f:id:cybozuinsideout:20180817180448p:plain
ImageMagick で変換した画像
f:id:cybozuinsideout:20180817180534p:plain
Go 版で変換した画像
・・・というわけで、肉眼ではわかりませんね。ImageMagick の compare コマンドで画像の差分を出力すると差分が浮かび上がります。

f:id:cybozuinsideout:20180817175457p:plain

稀に肉眼でわかる程度の差が出る画像もあるにはありますが、実用では問題無いと判断しました。

変換できない画像がある

ImageMagick で変換出来ても Go 版サムネイル生成ツールでは変換出来ない画像があります。

まず、Go 版では BMP の一部は変換出来ません。BMP にはいくつか種類があり、その種類を表す値は Bitmap Information Headerというヘッダ部分で指定されます。ここで指定される値と種類は下記の通りです。

意味
40Windows V3
108Windows V4
124Windows V5
12OS/2 V1
64OS/2 V2

しかし Go の image パッケージの当該部分を見ると 40 という値が来ることを前提としており、Windows V3以外の BMP のバージョンは未サポートでした。
https://github.com/golang/image/blob/c73c2afc3b812cdd6385de5a50616511c4a3d458/bmp/reader.go#L151

ImageMagick では Windows V3以外の BMP も変換可能で、この点では Go 化して劣化した部分と言えます。とはいえ近年は BMP ファイルがアップロードされる回数はさほど多くなく、大きな問題には至ってません。

他にも、ImageMagick は壊れた画像も変換できるという謎機能があり、もちろん壊れ方にも依るのですが、Go 版では変換に失敗する画像でも ImageMagick だと変換できることがあります。なおそういったケースは全体から見れば極僅かなのでこのケースも実用では問題無いでしょう。

運用後

ImageMagick を廃止し Go 版サムネイル生成ツールを運用してからしばらく経ちましたが、大きな問題は出ていません。我々はサムネイル作成の成功率をモニタリングしており、 成功率は 99.9%付近を保っています。巨大画像や壊れた画像が来るケースを考慮すると十分な成功率と言えるでしょう。

これで以前より一歩安全なクラウドサービスに近づけたかと思います。「現状動いているからそれでいい」というような安易な運用ではなく、安全性を確保するためなら作りなおしも厭わないという姿勢はサイボウズの誇れるところだと思います。

また、脆弱性対応の工数を減らせるという点でも目的を達成できました。脆弱性対応は工数だけでなく精神力も消耗するものなので、こういった部分の改善はサービスの安定運用に繋がると思います。もちろん Go 化したからといって脆弱性が無くなるわけではなく、関連する脆弱性情報は引き続き注視していきます。

サムネイル機能というものはあって当然で、あまり目立たない地味な機能かも知れません。でもその裏では様々な画像を変換してくれる賢いツールが活躍してくれているのです。ImageMagick は長期間にわたりサイボウズのサムネイル事情を支えてくれました。さようなら ImageMagick、いままでサムネイルをありがとう。

セキュリティキャンプ全国大会 2018 集中開発コース 「Linux開発者を目指そう! 」テーマのレポート

$
0
0

はじめに

こんにちはNecoチームのsatです。本日はNecoチームの話ではなく、私が先週講師として参加した「セキュリティキャンプ全国大会 2018」というイベントの参加報告をいたします。このイベントの中でもとくに私が受け持った集中開発コース「Linux開発者を目指そう!」テーマについて述べます。

セキュリティキャンプ全国大会とは、お盆の時期に全国の学生が一か所に集まって、5日間その道のプロと一緒にセキュリティ技術について学ぶイベントです。イベントそのものについての詳細は以下リンク先をご覧ください。

www.ipa.go.jp

「Linux開発者を目指そう!」コースの概要

「Linux開発者を目指そう!」コースは受講者が実際にLinuxカーネルの部品を開発、追加することによってLinuxカーネルの開発についての理解を深めてもらうためのものです*1。今回は2人の学生がこのテーマに取り組みました。

一人の方はLinuxのカーネルモジュール(後述)を独自開発しました。もう一人の方は独自のslabアロケータ(後述)を実装しました。では、お二方が具体的にどのようなことをしたかについて述べていきたいと思います。

独自カーネルモジュールの開発

カーネルモジュールとは

カーネルモジュールとはシステム起動時、およびその後の動作中にカーネルに機能を追加するための部品です。Webブラウザでいうプラグインを思い浮かべてもらえればいいかと思います。カーネルの機能のうちの多くの部分は最初からカーネルに組み込んでおくこともできますし、モジュールとして独立したファイルにしておいて必要になった時にカーネルに組み込むこともできます。

たとえばみなさんのPCに繋がっている各種デバイスを操作するデバイスドライバなどがそうです。モジュールはカーネル本体と同時にビルドできますし、後から別途個別にビルドもできます。本コースでは後者のアプローチをとりました。

開発の流れ

まずはカーネルモジュールのロード時にカーネルのログ*2に"hello world"と表示するだけの数行のプログラムを作るところから始めました。単純なプログラムなのですが、自分自身のコードがカーネルの一部として動くというのはなかなか体験できるものではありません。

続いてカーネルモジュールに問題があったらどうなるかというのを確認していただきました。具体的には通常のプロセス、およびカーネルモジュールの両方からNULLポインタにアクセスするプログラムを書きました。そうすると前者はNULLポインタにアクセスしたプログラムが異常終了するだけで終了しました。

f:id:cybozuinsideout:20180820181609j:plain

これに対して後者はシステム全体が動作しなくなってしまいました。Windowsでいうブルースクリーンが出た状態に相当します。

f:id:cybozuinsideout:20180820181623j:plain

実はこれ、実際のカーネルでこのような障害があれば立派な脆弱性になります*3。マルチユーザシステムで悪意のある一般ユーザのプログラムによってシステム全体がダウンしてしまうような場合を考えればわかっていただけるかと思います。こう書くだけでなんとなく理解はできるのですが、やはり聞くだけなのと実際にシステム全体を落としてみるのとでは理解の度合いが違います。

続いて彼は所定時間後に所定の処理を呼び出すカーネルタイマーというものについて学びました。最初はカーネルモジュールをロードしてから10秒後にメッセージを一回出力して終わり、というものから始まって、タイマーを二つ同時に起動するもの、二つのタイマーに別のメッセージを表示させるもの、などなど、多種多様なカーネルモジュールを作りました。

残念ながら本イベントの短い期間ではここで終わってしまいましたが、彼が具体的にどういうものを開発したのか、および、その先にどういうメニューが用意されていたのかについては下記の一連の中の文書とC言語ソースファイルをごらんください(独自開発ツールの使い方について述べている部分は読まなくていいです)。

qiita.com

講師の目から見て成長したところ

彼は低レイヤのプログラムに慣れていないことから最初はC言語の理解がおぼつかなかったのですが、最後には立派にカーネルモジュールが作れるまでに成長しました。初心者にとっての最難関であるポインタについてもプログラムのメモリマップを何度も図示しながら必死に学んだ結果、ある程度理解できるまでにこぎつけました。ポインタは本来一朝一夕で理解できるものではないので、これはすごいことです。

裏話: カーネル内インターフェースの変更

カーネルタイマーを使ったカーネルモジュールを作っていた時に思わぬ問題に遭遇しました。それは私が提供したサンプルソースが数年前に書いた古いものだったため、今回開発に使ったカーネルv4.18ではビルドできなかったことです。全然意図していなかったことながら、あるバージョンのカーネルに対して作ったカーネルモジュールが将来も修正なしに使える保証はLinuxカーネルにおいてはどこにもないことも学んでいただきました。この件についての詳細は下記の記事をごらんください。

qiita.com

独自slabアロケータの実装

slabアロケータとは

カーネルのメモリ管理サブシステムはハードウェアとの間でメモリをページという単位(通常4KB)でやりとりします。メモリ管理サブシステムはカーネルの他のコンポーネント、およびプロセスに対してはページを割り当てます。この割り当てプログラムのことをbuddyアロケータと呼びます。

しかし、バイト単位のメモリオブジェクトが欲しいカーネルサブシステムにとってこれは使いにくいです。たとえばメモリを8バイトだけほしいのにページ単位でしか要求できなければ、8バイトのために1ページを消費するという壮絶な無駄を発生させるか、あるいは自分自身でページを小分けして管理しなければなりません。

ここで「ページを小分けして管理」してくれるのがslabアロケータというしくみです。slabアロケータはbuddyアロケータからページを獲得した上で、ページをカーネル内サブシステムにバイト単位に小分けして渡します。図中にkmalloc()と書いてあるのは、ユーザ空間におけるmalloc()に相当するものであり、バイト単位のメモリ割り当てをする関数です。

f:id:cybozuinsideout:20180820181807j:plain

linuxにはslabの実装が複数個存在します。具体的にはSLOB, SLAB, SLUBという3つです。似たような名前がたくさんあってややこしいですが、気にしないでください。

開発の流れ

彼は自分のslabアロケータにSLOBA(そば)というかっこいい名前を付けました。名前さえ付ければ終わったようなものです…というのは冗談として、最終日までの目的は「特定用途ではSLOB(最も単純な実装。メモリが少ないような環境で使われる)とSLAB(汎用。エンタープライズサーバ向けにも長きにわたって使われてきた)に勝つ」という明確なものでした*4

フルスクラッチでslabアロケータを作るのはなかなか骨が折れるので、最初はSLOBをもとにして作り始めて、次第に独自のコードを増やしていくというアプローチをとりました。ベンチマークテストとして採用したのはkernelソースに対するdu -sコマンド発行の所要時間です。ディレクトリエントリごとにdentryと呼ばれるカーネル内のデータがslabアロケータから割り当てられるので、slabへの大量アクセスの所要時間が計測できるというわけです*5

開発の初期段階で「SLOBのメモリ獲得処理はSLOBが持っているページの量に比例して多くなる」という事実に気づいた彼は、さっそくデータ構造を工夫して、定数時間でメモリ獲得できるように工夫しました。さらにその後には「SLOBは複数CPUから同時アクセスされるとスケールしない」などのSLOBの問題を次々に見つけ出し、改善していきました。スケーラビリティを上げる改善においては、spinlockの使い方に難儀したために無数のバグを仕込んでいましたが、最終日にはなんとか全て解決したようです。

最終的にはdu -sについてはシングルコア性能、マルチコア(4コア)性能共にSLOBには10倍以上の大差をつけて圧勝、SLABとはほぼ互角、という結果を叩き出しました。その上、SLOBAのコード量はSLABの約1/6に過ぎないというのですから驚きです。

筆者の見立てでは、SLOBにはほとんどのワークロードでも圧勝すると思いますが、SLABに対しては勝ったり負けたり、といったところでしょう。ぜひ色々なシステム構成、ワークロードでデータをとっていただきたいところです。

これ以上技術的な詳細には踏み込みませんが、参考までにSLOBとSLAB、SLOBAの構造を表す概要図を載せておきます。kmalloc()で取得するサイズごとにslabアロケータが存在するというイメージで見ていただければと思います*6

  • SLOB

f:id:cybozuinsideout:20180820185057j:plain

  • SLAB

f:id:cybozuinsideout:20180820185107j:plain

  • SLOBA

f:id:cybozuinsideout:20180820185116j:plain

講師の目から見て成長したところ

彼は手を動かす速さについては最初から教えることは何もなかったです。今回はLinuxカーネル開発のお作法、効率的なソースコード解析およびトラブルシューティングの方法などを体験できたのがよかったのではないかと思います。今後はさらに高速に、かつ、高品質なコードを書けるエンジニアになってくれるのではないでしょうか。

裏話: 開発はインクリメンタルに

彼は二日目あたりで、とあるバグが取れない状態で悩んでいました。その際、「問題の無かったことがわかっているコミット」から「問題を検出したコミット」までの間のどのコミットが犯人かを二分探索によって見つけるbisectと呼ばれる方法*7を使おうとしましたが、それは不可能でした。なぜなら問題が起きなかった状態から起きるようになるまで一切コミットせずに数百行を変更してしまっていたからです。

彼の記憶にはバグに悩まされたことに加えて「次はインクリメンタルに開発しよう」という思いが刻まれたことでしょう。多分。

おわりに

本コースは「Linux開発者を目指そう!」と銘打っていますが、おふたりが今後Linux開発を続けるかどうかは彼ら次第です。本コースがLinuxという特定ソフトウェアの開発技術だけではなく、「トラブルに遭遇した時の対処方法」や「何かを変えるときは一回に一つだけという原則」、「ソースコードを読むコツ」など汎用的な知識を得るきっかけとなったのであれば幸いです。

*1:一見セキュリティには関係なさそうに見えますが、Linuxに限らずカーネルに関するセキュリティ知識を得るにはその基礎としてカーネルそのものの知識が必要なので、実は十分関係があるのです

*2:dmesgコマンドによって見られます

*3:たとえばCVE-2018-11232があります。興味のあるかたは読んでみてください。

*4:SLUBについては時間の都合上省略しました

*5:ファイルシステムをストレージデバイスではなくメモリ上に存在するtmpfs上に作ることによって、このコマンド発行に対するI/O処理の影響を無くしています

*6:slabアロケータを使うのはkmalloc()だけではないのですが、それについては割愛します

*7:たとえば前者から後者の間に16コミットあれば、最初は8個目のコミットで問題が起きるか確認して、問題が起きなければ次は12個目のコミットで確認…というように容疑者を一度に半分づつ減らしていく

QAエンジニアのAgile Testing vol.1

$
0
0

初めまして! 松山品質保証部の北地と申します。どうぞよろしくお願いします。

私が所属しているチーム、SPITz(Software Process Improvement in Test の略)は品質保証部内のQA全般のカイゼン支援を行っています。SPITzでは当面のテーマを「QAエンジニアのAgile Testing」として活動しています。この記事で、どういったカイゼン活動を進めているのかをご紹介します。

タイトルに「vol.1」とつけたのは、Agile Testingの深堀を継続し、定期的に社外へアウトプットしていこうというチームの総意です。 なお、SPITzの活動内容については別の記事でも紹介していますので、こちらも是非ご覧ください。 blog.cybozu.io

なぜAgile Testing?

サイボウズではAgile開発の導入がどんどん進んでいます。 Agile開発の浸透と共に、当社のQAエンジニアからはいくつかの意見が挙がるようになりました。

  • QAエンジニアとして、Agile開発にうまく適応できていない気がする
  • 開発プロセスはスプリントを重ねるごとに良くなっているものの、製品チームごとに最適化されていて、分断が進んでいる
  • 他のチームのやり方を知る機会が少ないが、上手いやり方があるなら取り入れたい
  • ある製品チームではAgile開発を取り入れることが難しいと感じ、採用していない

SPITzではチーム間の情報共有にカイゼンの余地があると考えました。

ゴール

短期目標はこうです。 f:id:cybozuinsideout:20180827164344j:plain

私たちの最終的なゴールは「すべての製品開発チームでAgile開発に適したテストを行う」です。 そのために必要な土台として、Agile Testingに関する情報を、チームを横断して共有する環境を作りたいと考えています。

具体的な活動内容

SPITzのメンバーで検討を重ねた結果、2つの案が出てきました。

LT/パネルディスカッションの開催

製品チームのQAエンジニアにLTやパネルディスカッションをしてもらう案です。SPITzが主催し、それぞれのチームのベストプラクティスを共有してもらうイメージです。LTが良いのか、パネルディスカッション形式にするのかは、まだ細かいところを詰めていません。いずれにしても、当人からノウハウを説明してもらうことで理解が進むと考えます。 リアルなイベント開催だと、参加者が刺激を受けやすく、カイゼンへのモチベーションにもつながりやすいことも期待しています。
反面、登壇者に事前準備をお願いすることになり、やや心苦しい気もします。 また、なるべく多くのQAエンジニアが参加できるような日程の調整は、早い時期の告知が肝になりそうです。

事例集作成

SPITzのメンバーが製品チームのQAエンジニアにヒアリングし、その内容を社内向けに事例集としてまとめる案です。記事の作成は業務の合間にコツコツとできそうです。QAエンジニアには、ヒアリングする際に時間をとってもらうことになりますが、LT用のスライドを作成してもらったりする工数よりは軽いと見込んでいます。また人前でしゃべるのが苦手な人からもノウハウを吸い上げやすいのでは、とも考えています。
ただしSPITzのメンバーの工数負担(ヒアリング → 記事作成 → 共有)と、そのスピード感が気になるところです。細かい点が漏れないように丁寧な記事を書く必要がありそうです。 情報をプッシュしていくイベント開催に比べると、QAエンジニアに対する影響力が少ないかも?


どちらを採用するかで、SPITzメンバーでの議論は紛糾しました。

  • どちらが高いコストパフォーマンスを見込めるか
  • 1回限りに終わらず継続できるか
  • できればQAエンジニアの負担は抑えたい

などなど。悩ましいところですね。

結局、私たちは「LT/パネルディスカッションの開催」を採用することにしました。チームを横断して情報を共有する(し続ける)ために、リアルなイベント開催は効果があるという判断です。

次回予告

「LT/パネルディスカッション形式で扱うテーマ」について記事を書きます! みなさんの周りでもAgile開発が徐々に浸透していると思いますし、そのやり方についての想いや悩みをお持ちなのではないでしょうか。私たちのカイゼン活動が、何かのヒントになると良いなぁと思っています。

We are hiring!!

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

キャリア採用 QAエンジニア | サイボウズ 採用情報(新卒・キャリア)
キャリア採用 QAエンジニア(ミドルウェア) | サイボウズ 採用情報(新卒・キャリア)
キャリア採用 テストエンジニア | サイボウズ 採用情報(新卒・キャリア)

ぜひ私たちと一緒に働きましょう!

Viewing all 681 articles
Browse latest View live