Classi開発者ブログ

教育プラットフォーム「Classi」を開発・運営するClassi株式会社の開発者ブログです。

QAチームが取り組む「品質の見える化」への挑戦

こんにちは、QAチームの牛木です。

QAチームでは今期「品質の見える化」に挑戦しています。 この記事では、QAチームが取り組む「品質の見える化」の歩みをお話します。

なぜ「品質の見える化」をやるのか

昨今のソフトウェア開発手法の主流が「ウォーターフォール開発」から「アジャイル開発」(アジャイル開発の中でもスクラム開発)へ変化しつつあります。

アジャイル開発においてもテスト業務は依然として必要ですが、包括的なドキュメントではなく、動くソフトウェアや対話の中でのテスト設計、開発サイクル全体を見通した素早いテスト実施、開発から運用まで含めたプロセスの中での継続的なフィードバックの獲得も求められます。 こういった体制を「DevOps」とも捉えていますが、QAが継続的なテスト、継続的なフィードバックを提供する足掛かりとして「品質の見える化」に取り組んでいます。

「品質の見える化」とは何か

「見える化」とは、”「可視化」された対象を「モニタリング(監視)」し、「課題を抽出・分析」、フィードバックに基く「改善活動を行う」ことである” とQAチームでは考えます。 「見える化」という言葉は、トヨタ自動車による業務改善の観点において初めて登場した言葉です。

可視化の対象となる「品質」は、現在以下のデータに絞っています。

  • QAチームが検出した不具合データ
  • QAチームが担当したテスト業務データ

ソフトウェアにおける「品質」の定義は、企業によって多種多様であり、品質の対象もさまざまです。

品質の定義から、ISO/IEC9126の品質特製(プロセス品質、内部品質、外部品質、利用時の品質)を思い浮かべる人もいれば、狩野モデルの品質要素(魅力品質、一元的品質、当たり前品質)を思い浮かべる人もいるのではないのでしょうか。 この全ての品質をスコープとすると膨大なデータと、そのデータを蓄積するための運用整備が必要となります。そのため、まずはミニマムスコープとしてQAチームの管理下で蓄積・運用されているデータを対象としています。

 今何をやっているのか

「品質の見える化」のプロセスとして、以下ステップを目標に活動を進めています。

  1. 品質データの”蓄積”
  2. 品質データの”可視化”
  3. 品質”課題抽出・分析”
  4. 品質”フィードバック”
  5. 1~4のサイクルの”運用”
  6. 他チームへの横展開

f:id:cku0301:20210913164859p:plain
社内に展開した「品質の見える化」のサイクル

上記ステップのうち、1”蓄積”と2”可視化”に現在取り組んでいます。 その活動の詳細についてお話しします。

 1. 品質データの蓄積

前述した品質データは、タスク管理ツール「Asana」を使い、QAチームがチケット管理をしています。 各データには以下のようなラベリングをしています。

  • 不具合データ:不具合分類、不具合レベル、不具合混入工程、不具合見逃し理由、不具合検出手法など
  • テスト業務データ:案件名、開発チーム、テスト工程、テスト項目数、実績工数など
    f:id:cku0301:20210913165114p:plain
    QAチームが管理するAsana

各チケット(データ)はQAチームが各プロダクトチーム統括でClose作業をしており、ラベルの未入力チェックや、不具合のステータスチェック(例えば、案件がCloseしているのにも関わらず、案件に紐づく不具合がOpen中である場合には確認を入れる等)のチェック運用を行っています。

 2. 品質データの可視化

BIツール「Tableau」を使い、「Asana」に蓄積された品質データのダッシュボードを作成し、社内に展開しています。 社内の役割によって気になる観点が異なるため、「全社向け」と「プロダクトチーム向け」に分けてダッシュボードを作成しています。

「全社向け」では、Classiサービスの品質状態が良いのか悪いのかを端的に伝えるために、Classiサービス全体の平均不具合件数や不具合密度の推移、不具合の改修状況などを表示しています。

f:id:cku0301:20210913165238p:plainf:id:cku0301:20210913165324p:plain
全社向けのダッシュボード一部抜粋

「プロダクトチーム向け」には、機能毎に不具合データのラベリング別の割合や、推移を載せています。各チームが、不具合の傾向から原因や課題を拾いやすくすることを目的としています。

f:id:cku0301:20210913165646p:plainf:id:cku0301:20210913165655p:plain
プロダクトチーム向けのダッシュボード一部抜粋

現在取り組んでいるステップとして「1.品質データの蓄積」と「2.品質データの可視化」をお伝えしました。

可視化のステップは次に見据えている”品質課題抽出・分析”のための活動でしたが、可視化をして最初に見えてきた課題は蓄積ステップの運用課題でした。 例えば、ステータスの中で新規起票状態のチケットが多いことが分かったのですが、これは実際に新規起票チケットが多いのではなく、ステータス更新が適切に行われていないことが原因でした。 他にも不具合レベルがあるレベルに偏っておりレベルの細分化・再定義が必要であったり、ある項目の未入力が多くフローがうまく機能がされていないことが判明したりと、可視化をすることで前ステップの改善にも繋げることができました。

蓄積されたデータが正確でなければ品質課題の抽出もできません。蓄積と可視化を反復しながら品質データが適切に蓄積されることが見える化における初めの一歩になると思います。

 今後何をやるのか

今後のステップとして、蓄積/可視化の管理・運用を推進しつつ、「3.品質課題抽出・分析」と「4.品質フィードバック」の取り組みを開始しています。

品質データの「蓄積」「可視化」では、品質データの対象をQA管理下のデータに絞り込んだこともあり、QAチーム主体で活動を進めることができました。 しかし、品質「課題抽出・分析」「フィードバック」では、プロダクトチームを巻き込んだ活動でないと価値を出すことは難しいと考えています。

というのも、Classiのプロダクトチームは複数存在しており、チーム毎に開発スタイル、リリースサイクル、人数、担当機能などが異なります。チームによってデータ結果の背景・原因は異なりますし、重視している観点も異なります。

「可視化」したところで...

  • 可視化したデータをそもそも見てもらえない
  • データを見ても、「それで?」となる
  • データ結果が実際の業務と紐付けられない
  • なぜこのような結果になっているのかの根本的な背景や原因が分からないから活用できない

などなど...

Classiサービス全体としてデータの結果を表示できても、チームに寄り添った分析とフィードバックを行えなければ、「見える化」できているとは言えません。

そこで現在は、プロダクトチームに参画し、品質データを生かしたフィードバックをどう推進していくのが有効か?の情報収集をしている段階です。他チームへの展開も視野に入れて、まずは特定チームで「品質の見える化」のサイクルを確立することを目指して活動をしています。

 おわりに

「QA」と聞くと「テスト業務」が連想されると思いますが、QAはQuality Assuranceであり、「品質保証」業務全般を差します。最近はQAの担当領域も広がってきましたが、品質目標やQAが担う領域は、企業によってさまざまであると感じています。わたしも勉強中の身ですが、Classiにおける「品質保証とは?」をつきつめられるように精進していきます。

Classi QAの品質向上への道

こんにちは、Classi QAチームの竹林です。 Classi QAチームでは、単にテストをするだけのチームではなく品質向上をミッションとするチームとして、品質向上に向けた施策を実施してきました。

この記事は、Classi QAチームの品質向上に向けた歩みの共有になりますので、QAの進め方について検討している方の参考になれば嬉しいです。

「品質の見える化」で始める

Classi QAの品質向上に向けた取り組みは「品質の見える化」から始めています。 品質向上のための課題抽出や施策提案を、現状に最適な優先度や内容で進めるために、まずは現状を把握する必要性があるから、との考えからです。 また現状把握だけの利用でなく、今後の品質向上PDCAサイクルなど、QAの中長期的な取り組みのベースにすることを想定しています。

「品質の見える化」の実装は、バグなどの不具合発生時に「どんな事象が?」「どこで混入した?」「なぜ混入した?」などの情報を残していく方式で進めました。 この方式は、開発者の協力がないと成り立たないのですが、Classiの開発メンバーは品質意識が高くとても協力してくれるので、スムーズに進めることができました。(改めてこの場で感謝します。)

f:id:classi_qa:20210913141816p:plain

     

「品質データの活用(第一弾)」を始める

「品質の見える化」で見えてきた品質データは、「テスト結果レポート」を作るために活用しています。 「テスト結果レポート」は、テストから得られた情報を基にしてリリースリスクを把握できるため、「リリース品質の向上」に繋げることが期待できます。   

  • 【リリースリスクを把握するための、テスト結果レポートの項目(例)】
    • テストの進捗状況
    • 不具合の発生・収束状況
    • 不具合の内訳(バグ?デグレード?仕様?など)
    • 不具合の対応状況・残存状況など
    • リリース見解(総合的なリスク判断)

f:id:classi_qa:20210913141902p:plain

     

「開発とテストの専業化」をトライアル

「開発作業と検証作業を専業化するかどうか」は意見の別れる事案ですが、Classi開発では「専業化」をトライアルでスタートしています。 開発スピードが企業戦略的にも重要な要素になっている現在では、専任メンバーがテストを専業することで得られる「開発メンバーの負担軽減」は大きなメリットです。

一方で、専業化によるデメリットとして「開発者のテスト意識やテストスキルが低下する懸念」などがあり、今後はメリットとデメリットのバランスを観察しながら、随時ブラッシュアップしていく予定です。

f:id:classi_qa:20210913141956p:plain

     

「QA効果の最大化」を始める

「上流でのバグ検出」を目的として導入されることが多い「QAの上流参加(仕様レビューなど)」ですが、Classi QAでも始めています。 「上流でのバグ検出」を目的とする理由は、「品質のV字モデル」で提唱されるように開発後期(下流工程)で検出したバグは手戻りが多いため、できるだけ開発前期(上流工程)で検出したいとの考えからです。

f:id:classi_qa:20210913142009p:plain

     

「テストの自動化」を始める

開発スピードがUPしたりリリース頻度が高まることで懸念されるのがデグレード発生リスクですが、その対策として利用されているのがテストの自動化で、Classi QAでも導入を開始しています。 かなり以前(10年以上前)からある自動化施策ですが、ここ数年は特に盛り上がりを感じており、多くの開発現場でデグレードに苦労していることが分かります。

f:id:classi_qa:20210913142028p:plain

     

「品質データの活用(第二弾)」を始める

品質データの活用(第一弾)では『リリース品質の向上』に活用しましたが、(第二弾)では課題や改善のヒントなどの見えた情報を開発工程にフィードバックして、『開発品質の向上』に活用します。

この(第二弾)は非常に難しい施策で、本施策の実現に必須となるPDCAサイクルを「継続運用できている」という事例を私はあまり聞いたことがありませんが、Classi QAでは挑戦を始めています。

本施策については、当開発者ブログの別記事【QAチームが取り組む「品質の見える化」への挑戦】に投稿を予定していますので、併せてお読みいただけると嬉しいです。

f:id:classi_qa:20210913142048p:plain tech.classi.jp    

おわりに

Classi QAチームの品質向上施策をフェーズ分けすると、現在は後期に入ったあたりです。

f:id:classi_qa:20210913142109p:plain

     

ただ、後期フェーズに入ったとはいえ、やることが少なくなったわけではありません。

今後更にスピードアップが求められるソフトウェア開発や、日々進化する開発技術に対応するために、新しい施策の提案や実施済みの施策のブラッシュアップなど、タスクは尽きません。

今後もClassi QAチームの品質向上への道は続きます。

Angular+Storybookで画像回帰テストを小さくはじめる

Classiのフロントエンドエキスパートチームlacolacoです。最近AngularのプロジェクトでStorybookをベースにしたUIコンポーネントの画像回帰テストをはじめたので、この記事ではそのなかで学んだことを共有します。

画像回帰テスト

画像回帰テスト(visual regression testing)は、UIの見た目のバグを防ぐためのテストです。見た目はユニットテストのようにコードでテストするのが難しいため、画像回帰テストではその名の通りテストに画像を使用します。実際に描画されたUIを画像としてキャプチャして、ソースコードの変更前後での画像の差分から「見た目が変わっていないか」をテストできます。

最近では画像回帰テストをサポートするツールやSaaSなどさまざま出てきていますが、画像回帰テストをはじめるにあたっての問いはシンプルに次の2つだけです。

  1. どのように画像をキャプチャするか
  2. どのように画像を比較するか

どのように画像をキャプチャするか

最初の問いは、そもそもどのようにUIを画像化するかということですが、今回はStorybookと、そのプラグインであるreg-viz/storycapを選択しました。

Classiでは社内で利用するAngularのUIコンポーネントをライブラリとして開発しています。そのライブラリにはもともとドキュメンテーションを目的としてStorybookを導入していました。すでに見た目の確認のためのStoryを定義しているので、これをそのまま画像にできれば一石二鳥です。

別の選択肢として、Cypressをベースにすることも検討しました。Cypressには画像回帰テストをまるごとサポートするプラグインがあったのですが、Cypressを新規に導入する手間や、すでにあるStorybook資産を再利用したいことなどを考えた結果、最終的にStorycapを選びました。

どのように画像を比較するか

次の問いが、キャプチャした画像をどのように比較して回帰テストするかということです。極端にいえば2枚の画像を1ピクセルずつ比較して差異を探せばよさそうですが、実際はそこまで単純ではありません。ブラウザのスクリーンショットという手段で画像化する過程で、わずかな誤差が生じる可能性があります。差分があることだけわかっても「どの部分がどう変わったか」を人の目で見つけるのは大変なので、テスト結果の視覚的な支援も重要です。

というような観点で検討した結果、今回はreg-viz/reg-cliを使用しました。reg-cliは機能面で要求を満たしていましたし、Storycapと同じ開発元ということで親和性という点でも安心して使えました。

f:id:lacolaco:20210909104917p:plain
差分が出たときのレポートの様子。フォントサイズが変わって文字がずれていることが視覚的にわかる

小さくはじめるための工夫

今回はすでにあるプロジェクトへ後から画像回帰テストを導入し、有効性を検証することが第一の目的であったため、なるべく最小限の変化ではじめられることを重視しました。そのためにいくつかの工夫をしています。

対象を絞る

最初からすべてのコンポーネントに対して有効にせず、仕様が比較的安定している限られたコンポーネントだけを対象にしました。対象を絞ること自体はStorycapの skip オプションや exclude オプションなどを使用して簡単に実現できました。

今後対象コンポーネントを広げていく上でも、すべてのStoryを回帰テスト対象にはしないだろうと考えています。対象が増えるごとにキャプチャの時間的コストが開発体験上で問題になることが予想できます。コスパのバランスを考えて適用範囲を広げていく必要がありそうです。

ローカルでスナップショットを更新する

今回の導入では、開発者がUIを変更したらローカルでキャプチャした画像を新たなスナップショットとしてGit pushします。巷の画像回帰テストツールでは、スナップショット画像はGit管理せずクラウドストレージに保存することが多いようです。これは画像回帰テストの実行には時間がかかることが主な理由のようです。画像回帰テストはCI環境だけで実行し、結果をクラウドストレージに保存することでローカルの開発者体験を損なわないようにされています。

確かに大規模に画像回帰テストを導入するとなるとその部分がボトルネックになることは想像できますが、今回は小さくはじめることを目的に簡略化しました。本格化するにあたっては、Storycapやreg-cliと同じ開発元のreg-suitやSaaSのPercyなど、このあたりのツールを検討する見込みです。

ちなみに、ローカルとCIでは環境が違って同じChromeでも描画結果が変わるため、StorycapはDocker上で実行するようにしました。特にフォントレンダリングで差が出ます。最初はどうにかしてやろうと闘いましたが、途中で徒労だと悟ってDockerに切り替えました。これも実際にやってみたらあっという間だったので、はじめからこうしておけばよかったと反省しました。

導入後の学び

まだ小規模な導入なので著しい変化はありませんが、それでも適用対象のソースコードに関しては画像回帰テストがされていることでリファクタリングへの安心感が高まりました。社内で横断的に使われるので、ライブラリのアップデートが意図しない破壊的変更を含まないことを検証する仕組みとして、やはりとても重要だと感じています。

既存のStorybookの運用についても、画像回帰テストしたいパターンのStoryが作られていないことに気づいたり、コンポーネントの仕様上の問題に気づいたりと、新たな視点でコンポーネントを見ることで改善の種が見つかることもありました。

まとめ

この記事の内容自体は特に目新しいものではないと思います。画像回帰テストは普及してきつつありますが、しかし「知っているし導入したいと思ってるができていない」という状態のプロジェクトがまだまだ多いと感じています。思ってたより簡単だったり逆に大変だったり、実際にやってみないとわからないことは多いので、この記事がこれからはじめたいと思ってる人の後押しになれば幸いです。 ここでは語れていないこともたくさんあるので、もっと詳しい話をしながら意見交換していただける方は lacolaco 宛にDMなどいただけると喜びます。 それではまた!

Hardening Drivers Conference 2021でCSIRTの受援力についてパネルディスカッションをしました

f:id:nomizooon:20210902111309j:plain

こんにちは、サイバーセキュリティ推進部の野溝(@nomizooone)です。普段はClassiサービスの脆弱性診断や顧客対応などを担当しています。

先日、Hardening Projectにより開催された/dev/hardening – Hardening Drivers Conference 2021 のセッション「CSIRTの受援力」にて、私を含むClassiサイバーセキュリティ推進部の3名がパネラーとしてお話をさせていただきました。

「Hardening Project」(ハードニングプロジェクト)とはウェブサイトの安全性を追求する技術の啓蒙と人材の育成や、技術の社会的認知の向上による健全なネット社会への進歩に貢献することを目的としたセキュリティ界隈の皆さんにはおなじみのコミュニティです。

インシデント情報公開が登壇のきっかけに

Classiは昨年度、大規模な個人情報漏えい事件をおこしてしまいました。そして、その件に関して、2021/5/11にあらためてお客様向けに精緻な情報公開を行いました。

corp.classi.jp

セキュリティインシデントを起こしてしまった会社の報告というのは、様々な配慮や事情から、一般的にはぼかした表現になりがちです。

そんな中で、インシデントの発生から1年という時間を経て「判明している限りの情報を時系列で精緻に報告を行なった」という点で、Classiの業界への貢献を評価いただいたことがセッション登壇のきっかけになりました。

インシデントの情報公開や、普段からどのように備えておくべきかについて、Classiの他にもセキュリティベンダや保険会社の方も含めて90分ディスカッションしてきました。 アーカイブ動画の公開範囲はイベント(有料)の申込者の方に限られているので、この記事ではお話した内容をピックアップしてお伝えします。

テーマは「CSIRTの受援力」

みなさんは「受援力」(じゅえんりょく)という言葉をご存知でしょうか?

恥ずかしながら、私は今までこの言葉を知りませんでした。簡単にいうと「 "助けて" と言える力」だそうです。(特にセキュリティに限定した言葉というわけではなく、災害のときとかによく使う言葉みたいです)

また、CSIRT(シーサート)というのは、「Computer Security Incident Response Team」の略語で、名前の通り、セキュリティインシデントに関する報告を受け取り、調査などの対応活動を行う組織体のことです。社内のセキュリティ消防団みたいな感じですかね(組織によって位置付けは違うことがあります)

私はサイバーセキュリティ推進部という、セキュリティを専門に扱う部署に所属していますが、会社やサービスのセキュリティを守るためには、専門部隊である部署の中の力だけではなく、エンジニアや営業や法務や広報などなど…いろんな人達に助けてもらう必要があります。

そしてもちろん、社内だけではなく、社外の専門会社や関係機関との連携も会社としての対応をしていくためには欠かせません。

いざというとき、あるいは普段から、どのように周りを巻き込んで助けてもらいながら組織やプロダクトのセキュリティを高めていくか? 言葉にすると些細なことに感じますが、とても重要で深淵なテーマです。

己を知ることがセキュリティの第一歩

そのテーマに対して、インシデントの発生から対応を振り返ってセキュリティの受援力を発揮するためにまず必要なのは「自組織の弱い部分を把握すること」というお話をしました。

Classiでは、セキュリティアセスメントを事前に行い、組織として弱い部分を把握し、外部ベンダと支援のたてつけを合意しておいたことがいざというときのスムーズな対応につながったと分析しています。

セキュリティアセスメントとは、会社の中にどのようなセキュリティリスクが存在するのか調査して洗い出し、それぞれについて影響度を評価して、対応をきめていくことです。

保険の考え方と同じですが、たとえばリスクが発生する可能性は低いが、いざ発生すると損害が大きすぎて自社で対応しきれない、と想定される場合は、外部の専門業者にあらかじめ助けてもらえるように準備をして、いざというときに備えるなどの対応を行います。

自力に限界があることを認め、弱い部分を把握することではじめて、人に助けてもらう範囲を決めることができるってことですね。 そう考えるとアセスメントはとても大事です。

f:id:nomizooon:20210902110315j:plain
時系列と巻き込んだ組織の整理

改めて振り返ると、昨年のセキュリティインシデントのときには、本当にさまざまな人に助けていただきました。ほんの一両日の間にグループ内を含めた社内関係者はもちろん、社外関係者の協力を仰ぐことができたのは、事前の準備と、協力範囲の設計の賜物だと言えると思います。

情報をいつどこまで公開するべきか問題

セキュリティインシデントの中でも、発生した事象が個人情報の情報漏えい事故である場合、その事故を起こしてしまった会社は、適切に情報公開することが求められます。 (2022年の個人情報保護法の改正で義務にもなりますね)

しかし、起こった事故の性質によっては、まだわからないことがあったり配慮して伝えないといけないことがあったりして、ぼかした報告をせざるをえないことが往々にしてあるのではないかと思います。

かくいうClassiでも、インシデント発生直後に行った報告では、事故の詳細を載せることができていませんでした。その時点ではっきりしていないこともありましたし、グループ会社やお客様である学校の先生方への影響などを心配したからです。

f:id:nomizooon:20210902110413j:plain
インシデント情報の公開時の最大リスク

しかし、インシデント終息後このままで良いのか?本当に「お客様のために」なる情報公開とはどのようなものか? ということを考えて、議論を続けました。

約1年間かかりましたが、いったんこのインシデントにおいての結論として、前述の情報公開に踏み切ったという経緯です。

f:id:nomizooon:20210902110809j:plain
インシデント情報公開に関するClassiの結論

基本方針は「お客様本位、公明正大を前提としてお客様に利益を届ける」ことです。 目的は「お客様にこれからのClassiをより安心して使っていただくこと」で、あくまでお客様本位の情報公開が必要である、ということを強調してお伝えしました。

なんだかとても綺麗な話として綴ってしまいましたが、実際は1年の間、さまざまな方との意見交換や調整を続けたClassiのメンバーの泥臭い努力と、周りの理解があって実現したことです。

今回Classiとしてはこのような結論を出しましたが、当然、それぞれの会社の状況やご意見もあるので何でもかんでも透明にして情報公開を行えばいいかというと、そう簡単ではないことも多いと思います。

ただ今後、世の中で起きるセキュリティインシデントの情報公開が、少しでも利用者の利益になる方向に進むことを願っています。

おわりに

今回オンラインでの登壇だったので、時間や場所の制約なくたくさんの方に見ていただけて嬉しかったです。登壇中のチャット欄も盛り上がっていて、リアルイベントよりも参加者の方のワイガヤがダイレクトに伝わってきたのが印象的でした。

セキュリティはあまり正解といえるものがない分野ですが、他の視点を持った立場の方々とのディスカッションを通じて、みんなそれぞれ悩みながら先に進んでいることを知り、勇気をもらうことができました。 また、Classiは本当に色んな人に助けていただいて成り立っていると、あらためて実感する機会にもなりました。

今回のセキュリティインシデントに関しては、お客様向けの発信の他に当開発者ブログでエンジニア観点の発信も行っています。よろしければ併せてご覧いただけると嬉しいです。

tech.classi.jp

tech.classi.jp

最後に、私に貴重な機会をあたえてくださったHardeningProjectの皆様、インシデント対応にご協力いただいたClassiやほかの関係者の皆様に、この場を借りてお礼申し上げます。

本当にありがとうございました!

Airflowの処理の一部をdbtに移行しようとして断念した話

こんにちは、データプラットフォームチームでデータエンジニアをやっている滑川(@tomoyanamekawa)です。

以前紹介したデータ分析基盤であるソクラテスの改善のためにCloud Composer(Airflow)で行っている処理のdbtへの置き換えを検討しましたが、導入を見送りました。 調べてみてdbtに対するわかりみも深まったので、その供養のために検討内容を公開します。 同じように検討している方の参考になれば幸いです。

続きを読む

Data Engineering StudyでClassiのデータ組織の歩みと題して発表しました

f:id:tetsuro-ito:20210810181549p:plain みなさん、こんにちは!データAI部の部長をしている徹郎(id:tetsuro-ito)です。

先日開催されたforkwellさんとprimeNumberさんが主催されているイベント、Data Engineering Study #9「企業規模別に見る、データエンジニア組織の作り方」にて、Classiのデータ組織の歩みについて発表してきました。

Data Engineering Studyはこれまで8回開催されていて、データエンジニアリングの話題を中心にデータ分析に関する話題やデータ基盤に関する話題、それを扱う組織における話題を取り上げたりと、人気の勉強会です。

9回目の勉強会では「企業規模別に見る、データエンジニア組織の作り方」というトピックで、それぞれ従業員数が100人・300人・3000人と企業規模の異なる3社が下記のようなトピックを発表する趣旨として開催されました。

  • 企業の課題と、データ分析の目的
  • データ分析に関する組織構成と、各組織のメンバー構成
  • データエンジニア職の採用をどのように行っているか

Classiはこのうち、100人規模の組織の事例発表ということで招待され、300人規模の事例として弁護士ドットコムさん、3000人規模としてLINEさんが事例講演をされました。

当日の発表資料

当日発表した資料がこちらです。Classiの会社紹介とデータ組織の紹介を行ったのちに、データAI部が取り組んでいるプラクティスを中心にご紹介させていただきました。

データAI部の前身であるAI室が創設されたのが2018年の6月ですが、約3年間の歩みをわりと赤裸々にご紹介させていただいたつもりです。

また、YouTubeにて当日の録画配信も公開されているので、興味がある方はぜひご覧ください

当日いただいた質問

また、イベントではkoibumiというサービスを通して、視聴者のみなさんからのご質問もいただきました。いただいた質問と回答は下記のとおりです

  • Q:データエンジニアはGCPをよく使うことから、管理者的な役割を期待されることも多く、困っているのですが、そういったことはありますか?どう対応されていますか?

    • A : 発表の中でもあった通り、データエンジニアがadminとしての役割を担っていることからも、そういったことはあります。理想的には専門組織を作り、そうしたことを担ってもらうのが望ましいですが、現状では主管部署が責任を持って管理し、他の専門的に見ている部署と連携してるのが現状です。
  • Q : 部長さんということですが、部下の方のキャリアアップなどのプランは練られていますか?

    • A : ちょうど今年度、評価の基準をアップデートしているところです。その評価基準にグレードがセットになっていて、そのグレードをあげることでできることも増えたり影響力が上がるような設計にしています。そういう観点では、プランは練っています。
  • Q : 採用はフルリモート可ですか?

    • A : Classiではコロナ禍以降、基本的にフルリモートで業務を行なっています。例えば、コロナ禍以降に入社したメンバーの中には、一度もまだオフィスに来たことがない方もいて、会社全体がフルリモートワークに対応しているといえます。今後については、議論しながらClassiにとって一番良い方法を検討していきますが、リモートワークがメインになることを軸にオフィスをコンパクトにしたため、「再び毎日出社」にはしないと考えています。
  • Q : 一人のデータエンジニア がどの程度の業務を担当されているのでしょうか?また人員は足りていますか?

    • A : 発表の中でデータ基盤の構成図をお見せしましたが、メンバーの中でも得意な技術領域にグラデーションがあります。エンジニアリングに強いメンバーはよりソフトウェアエンジニアリングに近いデータエンジニアリングに重心を置き、ビジネス活用に近いスタンスのメンバーはDWHやDMの部分に重心を置いています。最後に募集をさせていただいた通り、人員は足りていません(笑)

おわりに

以上のように、オンラインでのイベントでもあったにもかかわらず、多くの方に参加をいただき、質問も色々していただけました。 ClassiのデータAI部の取り組みについて知っていただけたり、自分たちの取り組んでいることを聞いていただいて、新しい発見や気づきにつながれば幸いです。

最後に、ClassiのデータAI部ではデータ関連職を3職種募集しています。 もし、ご興味を持っていただいたら、こちらの募集要項を読んで、ぜひご応募してください!お待ちしています!

Flask 2.0.xのアップデート項目紹介

こんにちは、データAI部でPythonエンジニアをしている平田(@JesseTetsuya)です。普段は、PoCとデータをもってくる、というところ以外全部やる、というスタンスで開発業務を行っています。

日頃は、Flask1.1.4を利用していましたが、2021年5月11日にFlask2.0へのメジャーバージョンアップがありました。

メジャーバージョンアップということもあり、多くのアップデート項目がありました。そこで、特に日頃の業務に関わりそうなアップデートについて当記事にまとめていこうと思います。

Flaskとは?

Flaskは、PythonistaのArmin Ronachertによって2010年に初回リリースされました。いまでは、 Armin Ronacherを筆頭にPalletプロジェクトと言う名前でFlaskを含む、Flaskに関連する各ライブラリのメンテナンスがPalletプロジェクトメンバーによって行われています。

Flaskは、WSGI準拠のPythonマイクロWebフレームワークです。Djangoのようなフルスタックフレームワークとは違い、決まったディレクトリ構成もなければデフォルトでのデータベース機能やアドミン画面もありません。

GitHubのスター数も多く、JetBrain社の「Python Developers Survey 2020 Results」調査結果で人気ナンバーワンになっているそうです。

f:id:JesseTetsuya:20210707105027p:plain

軽量のフレームワークであるため、手軽にWEB アプリケーション開発でもAPI開発でも利用しやすくなっています。WEBアプリケーション開発未経験者やPython初学者にとってとっつきやすいフレームワークだと思います。

データサイエンスの界隈では機械学習モデルを構築し、その結果を返すREST APIをFlaskで実装するというユースケースがよく聞かれます。

データサイエンスを担う人が手軽にREST APIを作るニーズが高まりをみせ、最近では、Fast APIというASGI準拠の非同期処理を得意とするStarletteをラッピングしたフレームワークの人気も台頭してきました。

Fast APIにあってFlaskにまだ実装されていない機能を実装したのが今回のFlask 2.0へのversion upに垣間みることができます。

では、Flask 2.0の各アップデート項目をみていきます。

Flask 2.0.xのアップデート項目

ざっと、日頃の私の業務で目にとまった項目をピックアップしました。その他のアップデートはこちらのリンクでご確認ください。

  • Werkzeug 2.0, Jinja 3.0, Click 8.0, ItsDangerous 2.0 MarkupSafe 2.0へのアップデート
  • Python 3.5以下のversionがサポート対象外に
  • Blueprintのネスト記法対応
  • Routingがよりシンプルにかけるように
  • Configファイルの読み込み方法の変更
  • タイプヒント対応
  • 非同期実装が対応
  • Werkzeug 2.0のmultipart/form-data の改善により、大きいファイルのアップロードが15倍速に

Flask 2.0.1とFlask 1.1.4の書き方の比較確認

まずは、Flask 2.0.1とFlask 1.1.4でのRouting、Blueprint、config.from_json()、async defの書き方について比較しながら確認してきましょう。

全て一つのapp.pyモジュールとして実行できるようになっています。

初めてFlaskにふれる方は、下記のコマンドを実行し、各スクリプトをapp.pyにコピペして挙動を確認してみてください。

$ touch app.py
$ pip install Flask
$ pip install "Flask[async]"
$ export Flask_APP=app.py
$ flask run

Routingの確認

from flask import Flask, Blueprint

app = Flask(__name__)

# 日本語文字化け対策
app.config["JSON_AS_ASCII"] = False

api = Blueprint("api", __name__, url_prefix="/flask")

# flask 2.0.x
@api.get("/v2")
@api.post("/v2")
def flask2():
    return "こちらは、Flask 2.0.1です。"


# flask 1.x
@api.route("/v1", methods=["GET", "POST"])
def flask1():
    return "こちらは、Flask 1.1.4です。"


app.register_blueprint(api)

わざわざ、httpメソッドを引数内に指定する必要がなくなりました。このRoutingの書き方は、Fast APIと同様です。


Nested Blueprintの確認

# flask 2.0.x
from flask import Flask, Blueprint

app = Flask(__name__)

parent_api = Blueprint("api", __name__, url_prefix="/parent")
child_api = Blueprint("api", __name__, url_prefix="/child")


@parent_api.get("/")
def parent_flask2():
    return "Hello Parent Flask 2.0.x"


@child_api.get("/")
def child_flask2():
    return "Hello Child Flask 2.0.x"


parent_api.register_blueprint(child_api)
app.register_blueprint(parent_api)

このアプリケーションを起動してhttp://127.0.0.1:5000/parent/http://127.0.0.1:5000/parent/child/にアクセスしてそれぞれ確認してみてください。 Blueprintは、もともと大きいアプリケーションを開発する際にファイル分割、ディレクトリ分割をして各モジュール間の疎結合状態をたもつための機能でした。

Blueprintのネスト化の対応により、各モジュールを各役割・責務ごとに束ねてルーティングをする際、更にもう1階層上のレイヤーでの各役割・責務ごとに各モジュールを実装し、ルーティングの紐付けがしやすくなりました。

# flask 1.x
from flask import Flask, Blueprint

app = Flask(__name__)

parent_api = Blueprint("api", __name__, url_prefix="/parent")


@parent_api.route("/", methods=["GET", "POST"])
def parent_flask1():
    return "Hello Parent Flask 1.x"


@parent_api.route("/child/", methods=["GET", "POST"])
def child_flask1():
    return "Hello Child Flask 1.x"


app.register_blueprint(parent_api)

flask 1.1.4で同じコードを書いてみると、違いが明らかにわかりますね。

一方で、ただでさえ、Flaskの場合はディレクトリ構成に悩むことが多く、これでさらに考えうるディレクトリ構成のパターンが増え悩むことが増えてきますね。


config.from_json()の確認

...
...
# flask 2.0.x
app.config.from_file("config.json", load=json.load)
app.config.from_file("config.toml", load=toml.load)

# flask 1.x
app.config.from_json("config.json")
...
...

たまに書くことがあり、自分で間違えそうということでメモ感覚で追記しておきます。 ふーん、こうなったんだ、という感じで大丈夫かと思います。


非同期処理の確認

最後に非同期処理の確認です。Python自体は、Python3.5以降からネイティブなコルーチンが実装されており、非同期処理の実装が可能でした。

しかし、Flask 1.xでは、フレームワークとしては対応しておらず、ルーティングにコルーチン関数を書いて実行しても対応していないためエラーがでておわるだけでした。

Flaskが非同期処理に対応していないため、FastAPIを選択した方も多いのではないでしょうか。

それが、今回のFlask2.0へのversion upで対応されました。

色んな書き方があるかとおもいますが、一番わかりやすい書き方でみていきます。

# flask 2.0.x
from flask import Flask, Blueprint
import time
import asyncio


app = Flask(__name__)

async_api = Blueprint("async_api", __name__)


async def async_get_data(name, sec):
    print(f"{name}: started")
    await asyncio.sleep(sec)
    print(f"{name}: finished")
    return f"{name}:{sec}sec"


@async_api.get("/")
async def flask_async():
    start = time.time()

    results = await asyncio.gather(
        async_get_data("TaskA", 1),
        async_get_data("TaskB", 3),
        async_get_data("TaskC", 2),
        async_get_data("TaskD", 1),
        async_get_data("TaskE", 2),
    )
    print(results)
    print(f"process time: {time.time() - start}")
    return "All async tasks are finised !!"


app.register_blueprint(async_api)

出力結果

$ flask run
 * Serving Flask app 'app.py' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
TaskA: started
TaskB: started
TaskC: started
TaskD: started
TaskE: started
TaskA: finished
TaskD: finished
TaskC: finished
TaskE: finished
TaskB: finished
['TaskA:1sec', 'TaskB:3sec', 'TaskC:2sec', 'TaskD:1sec', 'TaskE:2sec']
process time: 3.00414776802063

Task A ~ Eまで順番に非同期で実行され、並列処理されているため、Task A、Task D、TaskC、Task E、Task Bの順番で処理が終わっています。

本来、同期処理で一つ一つ処理が終わるまで待って処理していると、全てのタスクが終了するのに9秒かかります。しかし、上記の非同期実装により、マックスでかかる3秒以内で全てのタスクが終了しています。これで非同期処理の挙動確認ができました。

次は、version間でパフォーマンスの違いがあるかどうかを念の為確認しておきます。

Flask 2.0.xのパフォーマンス確認

日頃は、Python製の負荷テストツールであるLOCUSTを利用していますが、まだFlask 2.0.xに対応していないため、Golang製の負荷テストツールVegetaを利用して軽く負荷をかけてどんなものか比較してみます。

先に結論を言うと、大きな差分はありませんでした。 とりわけ、フレームワーク全体のパフォーマンス改善のアップデート項目が明記されていたわけではなかったので悪くなっていなくてよかったという感じです。

Vegetaの使い方をみながら出力結果をみていきます。

Vegetaは、CLIコマンドで軽く負荷試験を実施するのに便利なのでどんなものか軽く知っておくだけでもいつか役に立つときがくるかもしれません。

では、パフォーマンス確認していきましょう。

f:id:JesseTetsuya:20210707104908j:plain

画像は権利関係上使えなかったので、筆者が手書きしました。ベジータ様を書いたつもりです。

「カカロットォォォ、いくぞーーーー!おりゃーーーー!」

事前準備

*Macを前提にしています。

vegetaのインストール

$ brew update && brew install vegeta
$ go get -u github.com/tsenart/vegeta

下記では、Flask 2.0.1とFlask 1.1.4を比較するために2つのvenv環境が必要になります。 Flask 2.0.1用のディレクトリを作成し、下記のコマンドを実行してvenv環境を作成してください。

$ python3 -m venv venv
$ python3 venv/bin/activate
$ pip install Flask==2.0.1

Flask 1.1.4用のディレクトリを作成し、下記のコマンドを実行してvenv環境を作成してください。

$ python3 -m venv venv
$ python3 venv/bin/activate
$ pip install Flask==1.1.4

Flask 2.0.1の検証用コード準備

from flask import Flask, Blueprint
import asyncio

app = Flask(__name__)
api = Blueprint("api", __name__)

    
@api.get("/flask_v2")
def flask2():
    return "Hello Flask 2.0"

app.register_blueprint(api)

Flask 1.1.4の検証用コード準備

from flask import Flask, Blueprint
import asyncio

app = Flask(__name__)
api = Blueprint("api", __name__)

@api.route("/flask_v1", methods=["GET"])
def flask1():
    return "Hello Flask 1.x"
    

app.register_blueprint(api)

Flask 2.0.1とFlask 1.1.4 のパフォーマンス計測結果

負荷試験実行コマンド*

$ echo "GET http://127.0.0.1:5000/flask_v2" | vegeta attack -rate=500 -duration=5s | tee result.bin

$ echo "GET http://127.0.0.1:5000/flask_v1" | vegeta attack -rate=500 -duration=5s | tee result.bin

*5秒間500RPSで負荷をかけるコマンド (rate: Request Per Second (RPS), s: second)

負荷テスト実行結果レポート確認

レポート作成コマンド

$ vegeta report result.bin

Flask 2.0.1の出力結果

Requests      [total, rate, throughput]         2500, 500.22, 338.90
Duration      [total, attack, wait]             6.81s, 4.998s, 1.812s
Latencies     [min, mean, 50, 90, 95, 99, max]  368.671µs, 369.698ms, 296.596ms, 569.892ms, 798.921ms, 2.191s, 4.202s
Bytes In      [total, mean]                     34620, 13.85
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           92.32%
Status Codes  [code:count]                      0:192  200:2308  
Error Set:

Flask 1.1.4の出力結果

Requests      [total, rate, throughput]         2500, 500.25, 364.51
Duration      [total, attack, wait]             6.581s, 4.997s, 1.584s
Latencies     [min, mean, 50, 90, 95, 99, max]  370.878µs, 296.992ms, 276.982ms, 458.868ms, 595.851ms, 1.038s, 4.02s
Bytes In      [total, mean]                     35985, 14.39
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           95.96%
Status Codes  [code:count]                      0:101  200:2399  
Error Set:

各メトリックスのみかたは、下記。

- Requests
    - total … 全実行回数
    - rate … 秒間の実行回数
- Duration
    - total … 負荷をかけるのに要した時間(attack+wait)
    - attack … 全リクエストを実行するのに要した時間(total - wait)
    - wait … レスポンスを待っている時間
- Latencies … それぞれ、平均、50%、95%、99%パーセンタイル、最大値
- Bytes In/Bytes Out … リクエスト/レスポンスの送受信のバイト数
- Success … リクエストの成功率(なお、200と400がエラーとしてカウントされない)
- Status Codes … ステータスコードのヒストグラム(0は、失敗)
- Error Set … 失敗したリクエストとその内容

「Flask 2.0よ、そんなものか、そんなにかわらんではないかっ、貴様っ!!」とベジータ様が申しております。

負荷テスト実行結果をヒストグラムで確認

さて、次はヒストグラムでみてみます。

ヒストグラムレポート作成コマンド

$ cat result.bin | vegeta report -type='hist[0,100ms,200ms,300ms,400ms,500ms]'

Flask 2.0.1の出力結果

Bucket           #    %       Histogram
[0s,     100ms]  159  6.36%   ####
[100ms,  200ms]  397  15.88%  ###########
[200ms,  300ms]  728  29.12%  #####################
[300ms,  400ms]  505  20.20%  ###############
[400ms,  500ms]  336  13.44%  ##########
[500ms,  +Inf]   375  15.00%  ###########

Flask 1.1.4の出力結果

Bucket           #    %       Histogram
[0s,     100ms]  188  7.52%   #####
[100ms,  200ms]  478  19.12%  ##############
[200ms,  300ms]  976  39.04%  #############################
[300ms,  400ms]  440  17.60%  #############
[400ms,  500ms]  191  7.64%   #####
[500ms,  +Inf]   227  9.08%   ######

「なにっ?!Flask 1.1.4のほうが若干安定してるようにみえるではないか、貴様っ!!」とベジータ様が申しております。

負荷テスト実行結果を時系列グラフで確認

時系列グラフのhtml生成コマンド

$ cat result.bin | vegeta plot > plot.html
$ open plot.html

Flask 2.0.1の出力結果

f:id:JesseTetsuya:20210707110159p:plain

Flask 1.1.4の出力結果

f:id:JesseTetsuya:20210707110225p:plain

「はやくしろっ!!!! 間にあわなくなってもしらんぞーっ!!!」とベジータ様が申しております。

最後に

このようにベジータ様がおっしゃっていますが、実際のところ大きな差分はありませんでした。

Flask 2.0.xへversion upするか否かは、利用したいライブラリが対応しているか否か、非同期実装したいか否か、Blueprintをネストしたいか否か、で決めればいいと思いました。

しかし、まだ確認できていない箇所や気になる項目もあり、全ての項目について当ブログ記事にて書ききることができませんでした。

というわけで、私のFuture Workとして箇条書きにて下記に残しておきます。

  • Fast APIとの書き方の比較をみる
  • 非同期処理のパフォーマンスをFast APIと比較してみる
  • multipart/form-dataの15倍速が本当にそうなったかをみる
  • Vegetaで分散アタックする
  • 今年度、どこかの国のPythonカンファレンスで上記の検証結果まとめを発表します
  • Flask全般の使い方については、チュートリアルを今年度、どこかの媒体でまとめて発表します

データAI部では、PoCとデータをもってくる、というところ以外全部やるPythonエンジニアを募集しています。

全部とは?!が気になる方は、下記URLを確認して頂きカジュアル面談の応募お待ちしています!!

hrmos.co

Flask 2.0.xへのメジャーバージョンアップ関連資料

© 2020 Classi Corp.