こんにちは、スタンバイのTech blogの運営担当の青山です。 あっという間に年末年始休みも終わり、気持ち新たに仕事初めを迎えられましたでしょうか。 みなさま、本年もスタンバイをよろしくお願いいたします。 さて、2023年の1回目のTech Blogの記事ということで、今回は、スタンバイの技術的軌跡をまとめた弊社CTO明石の 「2022年スタンバイアドベントカレンダー」の12月25日の記事 を、こちらでもご紹介します。 はじめに (※本記事は、「Stanby Advent Calendar2022」の再編集記事です。また、執筆時2023年1月時点の情報です。) タイトルにあるように、なぜ「2+1年」なのかは、 インタビュー記事(事業成功にフォーカスした、エンジニアのプロ集団を作っていきたい) を参照するのが良いのですが、読むのが面倒な方のために簡単に説明すると、スタンバイへJoinする1年前の2019年12月末に代表の南と邂逅し、ヤフーの明石としてスタンバイの検索精度向上の取り組みを始めたのが2020年1月、スタンバイにJoinしたのが、2021年1月ですので、2+1年の軌跡になります。 技術負債との闘い スタンバイでのエンジニアリングの取り組みを例えると技術負債との闘いのひとことで締めくくれるかと思います。 株式会社スタンバイは、2019年12月にZホールディングス株式会社とビジョナル株式会社(当時、株式会社ビズリーチ)のJoint Ventureとして設立された企業ですが、いわゆるスタンバイサービスは、ビズリーチの一事業を継承したものであるため既に4-5年の歳月を積み重ねてきたいわゆる年代物のサービスです。 4-5年間サービスを継続し続けるというのは、それはそれで一つの尊敬できるものであり、スタートアップの中でも数少ない生き残りともいえ、その実績と成果があってのJoint Ventureであるのも頷けるのですが、4-5年の歴史というのは、それなりにしょっぱい技術負債を樽の中に詰め込んで居るものなのです。 検索エンジン 最初に手をつけたのが、このサービスの心臓であり、最大の差別化ポイントの検索エンジンでした。ちなみにスタンバイの検索エンジンには、Organic(無料掲載求人)とAd(有料掲載求人)の2種類の検索エンジンがあり、GoogleやヤフーなどのWeb検索と同様に有料掲載求人については、Organicの結果の上位、中位、下位の固定枠に表示されます。 インタビュー記事(事業成功にフォーカスした、エンジニアのプロ集団を作っていきたい) で語ったように、ヤフーvsスタンバイでOrganic検索のABテストコンペを実施し、その結果、ヤフーの方式が採用されることになりました。いまだから話せますが、当時のスタンバイでは、この結果はわかっていたことでしたが、あなたたちは検索エンジンのことを理解していないと外の人間がいきなりやってきて話しても、だれも納得しないのでこのイベントを実施し、ヤフーとの協業という形をスムーズに進めることができたかと思います。 結果として、 Organic検索エンジンにはYahoo!ABYSSという検索プラットフォーム Ad検索エンジンにはElasticsearch を採用。 これは、Yahoo!ABYSSを利用し、ヤフーエンジニアとの協業を進めることにより、ヤフーの持つ検索Knowledgeをスタンバイに吸収し、スタンバイの検索技術の底上げを図ることを目的とし、その一方で、Yahoo!ABYSSで培った知識を自社検索エンジンに取り込むことにより、スタンバイ内での検索エンジンの知識や運用経験を養うためにこの2エンジン体制をとることにしました。 ヤフーの検索エンジンをお借りすることは非常にメリットの多いことなのですが、これは、諸刃の剣であり、ヤフー内の方針でこのエンジンの提供をいつ止められてもスタンバイが死なないようにこの体制にしました。これは、ヤフーを非難する訳ではなく、ソフトウェアにはライフサイクルもあり、ヤフーだけでなく、AWSなどクラウドそして、僕自身ヤフーのCTOなどを経験して、実際にこのような外部提供機能を様々な事情により断念した記憶があるからです。あと、そのほうがエンジニアのモチベーションも維持できますからね笑 Organic検索 Organic検索エンジンの一番の負債は、スタンバイサービス開発初期に作られた学習モデルが更新されずに改善されていなかったことですが、この取り組みによって大きく前進、さらに定期的な新モデルでのテストも実施されるようになり、改善サイクルがまわるようになりました。 Stanby Tech Blog スタンバイの検索の仕組み にてご確認ください。 Ad検索 一方、Ad検索エンジンについては、エンジンの切り替えをしなかったのと、多くの意味不明なアルゴリズムが随所に蓄積されていたためこんなに簡単ではありませんでした。 最初の取り組みとして、以下を実施しました。 1.新モデル適応と GSP 導入 2.Second Phase Ranking の導入 3.アルゴリズムとモデルの統一 Stanby Tech Blog スタンバイの広告表示におけるロジックについて あたりを参考にされると良いかと思います。 1.新モデル適応とGPS導入 GSPっぽいものが入っていたが、GSPは、Auction理論を応用して、価格を上限適正値に引き上げていくものなのですが、GSPっぽいものは、価格を中央適正値に寄せるようなもので、これだと価格適正化が行われそうもありませんでした。モデルは、Organicと同様で、サービス開発初期に作成されたものでした笑 2.Second Phase Rankingの導入 Adにおける、Second Pahse Rankingの重要性については、期待収益額順にRe-Rankingして掲載順を決定するのですが 例: 期待収益額 = 入札額 × 予測CTR このときに予測CTRがものすごく低く、入札額が高い不適切な広告が上位に来てしまうことが発生します。これを避けるために、First Phaseで適合性の高い広告順(上述では、予測CTR順)でRankingした上で、上位n本をSecond Phaseに渡し、期待収益順にRe-Rankingすることにより、適切な広告間でのAuctionを実現する必要があります。ElasticsearchのRe-Rank Plug-inである、 Learning to Rank がそれにあたるのですが、パフォーマンスが出ない、Yahoo!ABYSSのKnowledgeを取り入れるためには、拡張性に欠けるということで独自開発する必要がありました。 3.アルゴリズムとモデルの統一 なんとなくそれぞれの背景は想像ついたのですが、検索条件とか出面によってアルゴリズムが異なる。ここでは、詳細には触れたくないほどの負債でした。 取り組んだ結果 4-5年の歴史と 運用型広告 とはすごいもので、新モデル適応と[GSP]の取り組みだけでは、旧アルゴリズムには勝てませんでした。Second Phase Ranking の導入を並行して進めたことと、社内のMLチームの努力もあって、今年の4月に文句ない状態で1〜3を達成することができました。 結果として、4月〜12月までで求人企業様のROIを向上しつつ、広告収益性は2倍近く向上できるような体制になってきました。 先述のインタビュー記事では、ここまでを1〜3までを1年でと考えていたので結果2年掛かってしまいました。 求人データ取り込み 皆さんご承知かと思いますが、スタンバイは、求人検索エンジンです。求人検索エンジンで重要なものをふたつあげてと言われれば、先ほど、紹介した、検索エンジンとその検索エンジンのコンテンツである求人票です。 スタンバイの求人票は、3つの方法で取り込まれています。 1.データフィード 2.クロール 3.CMSからの入稿 このうち、1.と2.については、ジョブデータコアグループの開発する仕組みで実現されていますが、この仕組みでは、求人票の収集、分析、蓄積、管理、(分類)などが責務があり、なによりフレッシュネスをいかに担保するかが大きな課題です。Daily約2000万前後の求人の取り込みと、規約確認、同一求人などの分析や判別、そして、管理、求人票判定器によって判定された結果の反映などが実施され、結果としてスタンバイに掲載される求人票は約1200万(まとめ求人を考慮すると約800万)程度を処理する仕組みとなっています。 この求人取り組みも大きな課題を抱えており、2021年11月頃から大幅なリプレースを実施しました。 Stanby Tech Blog スタンバイの求人情報取込の仕組みを作り直した話 〜序章〜 Stanby Tech Blog 求人取り込み周りのリプレイスについて Advent Calender 2022 クローラー(求人取り込みシステム)のリアーキ対応について この件については、Tech Blogにしっかり記述されているので特筆しなくても良さそうですね。 結果として、法律対応や求人票拡充のためのカラムの追加など柔軟に対応でき始めています。 最近の取り組みとしては、クロールデータのフレッシュネスを担保するための終了求人チェックを如何に効率よく行うかと言う課題に取り組んでいるところです。 Stanby-API Stanby Tech Blog プロダクト開発体制のこれまでとこれから に紹介されているとおり、2021年4月の組織変更で、技術ドメインに基づいたチーム体制に変更しているのですが、その弊害として、複数チームがメンテナーとなっているのが、このStanby-APIといサービスです。 スタンバイは、 マイクロサービスアーキテクチャ を採用しているのですが、マイクロサービスって進めていくと、マイクロサービス群をいい感じにマッシュアップしてくれたり、ドメインの責務から外れるようなアルゴリズムなどもすべて集約されるいわゆるかゆいところに手が届く的なサービスができあがるのですが、いわゆるこれがそれにあたります笑 結果として、何か機能改修が必要な際には必ずメンテナンスを余儀なくされ、ほとんどのチームがメンテナーとなっているので、開発効率を著しく下げています。 詳細については、このプロジェクトが終わった頃、担当エンジニアが書くかもしれないので割愛しますが、モジュラモノリス化により、この課題に取り組んでいます。 Geo-API 求人サービスとして、Geographic情報の把握と理解というのは、重要な課題の一つなのですが、全スタンバイエンジニアがヤバいなーと思いながら見て見ぬふりをし続けたのがこのGeo-API。 Geo-APIにはいくつかの機能があり、 ●基本機能 ○住所のNormalization (住所文字列の正規化) ○Geo-Coder (住所から緯度経度情報への変換) ○Reverse Geo-Coder (緯度経度情報から住所への変換) ●その他 ○駅などの地点情報の判定 ○地点情報の住所の特定 みんなが言うには、 誰も仕様を理解していない どの機能がどこで使われているのかも分からない コードめちゃくちゃなので触りたくない と、カオスですね笑 このパンドラの箱をとうとうこじ開けました。Phase-1は、春には目にすることができそうです。 QAC(Query-Auto-Completion) Advent Calender 2022 日本語クエリオートコンプリーションの実現 でも紹介された、いわゆる検索サジェスト機能です。 これも、開発当初から一切メンテされていない、効果測定もしたことないからよくわからない、という誰も触れなくなったモジュール。 Lightな機能だからこそElasticsearchやSolrの機能を使わずに、Luceneで開発して、検索技術の理解を深めようというのが今のスタンバイのエンジニアらしい取り組みですね。 時がたち社内のエンジニアのレベルが維持できなかったり、環境が変わったときに負債化するので、もっと、汎用的な仕組みにすべきでは?という意見が聞こえてきそうですが、今は検索技術の理解のほうが重要だと思ってます。 これは、そろそろ、第一弾のABテストが始まるのかな。 他にもご紹介したことはたくさんありますが、今回はこのあたりにしたいと思います。この記事を書きながらも、新規リリース目白押しで、楽しみな年になりそうです。 スタンバイは、まだまだ、これからです! 最後に 2023年も新規リリースが目白押しとのことです。Tech Blogでも新規リリースにフォーカスした記事も投稿していきたいなと思っています。リリースもTech Blogの記事も楽しみにしていてくださいね! スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 www.wantedly.com
はじめに ジョブデータコアグループに所属している池田です。 ジョブデータコアグループでは、求人情報の取り込み、求人情報の管理、検索エンジンまでのインデックスを行っております。 我々のチームでは2020年11月からスタンバイのクローリングシステムをリアーキテクト・リプレイスしたのですが、 今回はその時の一部のプロダクトについて課題と実際に2年間運用してどうだったのか振り返りを書いていきます。 課題と背景 課題背景は過去のブログ「 スタンバイの求人情報取込の仕組みを作り直した話 〜序章〜 」でも記載しましたので詳細は割愛しますが 当時求人取り込みの現場で以下のような問題が発生しておりました。 求人取込から広告掲載まで最大6-7時間かかる 求人のフィールドを変更するのに1ヶ月かかった 求人情報の論理的な破損が発生後、復旧までに1ヶ月かかった 大きな問題として 「求人・広告データが1箇所で保管されていることで、データの柔軟性、拡張性に問題が発生している」 という技術的負債があったのでリアーキテクトを行っていったのですが、 その中でレガシーコード化が進んでいるものがありました。 それが es書き込みバッチ というシステムです。 ※1 レガシーコードとは (理由はなんであれ) 修正、拡張、作業が非常に難しいコードのことを指しています。 (「レガシーコードからの脱却」より) es書き込みバッチについて es書き込みバッチ は クローリングした求人から、求人の情報を抽出し、正規化して、検索エンジンへインデクシングするという責務を担っております。 図の es書き込みバッチ となっているシステムになります。 2015年から開発されて、 Scala と Apache Spark (オープンソースの分散処理システム)で作られておりました。 システムの処理の流れとしてクローリングデータ(文字列のjsonのデータ)を受け取り、以下のパイプライン処理を行います。 クローリングデータ(jsonデータ)から求人のフィールドの抽出 フォーマットチェック 求人情報の正規化、 検索エンジンへの書き込み しかし処理の過程の型が Option[Map[String, Option[Any]]] という型で どのような変更が行われ、最終的にどの項目が書き込まれているのか、ということがコードからすぐには理解できない状態でした。 各パイプラインの返り値の型 // jsonから求人データの抽出した後の型 Map[ String , Option[Any]] // フォーマットチェック後の型 Option[Map[ String , Option[Any]]] // 求人情報の正規化後の型 Option[Map[ String , Option[Any]]] Optionは値がその名の通り値がオプショナルである型で、値があれば値を返し、なければ値を返しません。 Mapは、「キー」と対応する「値」の2つの要素をペアにして格納するデータ構造です。 Scalaの世界のAny型はもっとも汎用的な型になり、どの型の値も入れることができます。 簡単な例ですが、 Map[String, Option[Any]] は以下のようなデータを入れることができます。 val sample = Map[ String , Option[Any]]( "jobTitle" -> Option( "バックエンドエンジニア" ), "jobContent" -> Option( "スタンバイのバックエンド開発業務" ), "jobType" -> Option( Array [ String ]( "正社員" )), "siteName" -> Option( "スタンバイ" ), "salary" -> Option( Map( "displayString" -> None ) ) ) アウトプット先が Elasticsearch なので書き込みはできるのですが(スキーマレスに書き込みができるため)、 求人の項目をMapで表現しているためコード上で求人の各項目の管理難しく、さらに検索エンジンで使われていない 項目のフィールドも存在しておりました。 一方でUnitテストがしっかり書かれていたので、Unitテストを通してコードを拡張し保守運用ができている状態でした。 またパイプライン処理が型で抽象化されており、全体的なシステムの構造として理解しやすい作りになっておりました。 おかげで5年経っても全体的なコードの見通しは良い状態でした。 /** * パイプライン処理で使用する1つの処理を表すトレイト。 * * @tparam IN 入力パラメータの型 * @tparam OUT 出力パラメータの型 */ trait Stage[IN, OUT] { /** * このステージでの処理をこのメソッドに実装します。 * * @param in 入力パラメータ * @return 出力パラメータ */ def process(in: IN): OUT /** * このステージの次に別のステージを連結してパイプラインを構成します。 * * @param nextStage 次のステージ * @tparam T 次のステージの出力パラメータの型 * @return パイプライン */ def |[T](nextStage: Stage[OUT, T]) /** * このステージの次に別のステージを連結してパイプラインを構成します。 * このメソッドで追加されたステージは | で連結されたステージとは異なり、 * 前のステージで例外が発生した場合でも必ず呼び出されます。 * * @param nextStage 次のステージ * @tparam T 次のステージの出力パラメータの型 * @return パイプライン */ def >[T](nextStage: Stage[Either[ExtractorThrowable, OUT], T]) } どのようなプロセスで進めたのか? 以下の手順でシステムの作り直しを行いました。 既存のコードをチーム全員でコードリーディングし、何をやっているのか?何のためにあるのか?必要なのか?を議論してコード上での共通認識をもつ 求人データで使われている項目(必須、非必須)、使われていない項目の整理 ドメインモデルを定義する パイプライン処理のフローで型を定義する 既存のドメインロジックで使えるコードを移植する Unitテストを書く 実際に検証環境で運用して想定してなかった型がきていないかなどの確認 各段階の詳細は省きますが、1の項目は既存のコードを熟知しているメンバーから機能の必要性を的確に判断してもらい そしてコード上の知見、課題がチーム全員に共有されたのでよかったなと思っています。 また、 es書き込みバッチ ではUnitテストがしっかり書かれていたのでコードの移植もスムーズに行えました。 メソッドのアウトプットに対して型定義する リファクタリングで心に残っているものについて紹介します。 es書き込みバッチ では、 JSON形式の設定情報によって、様々なデータの形式を抽出する汎用的なメソッドがありました。 protected def applyScript(property: ExtractProperty, rawdata: Map[ String , String ])(value: String ): Option[Any] 上記のメソッドではInt型、String型、Seq型、給与を表す型、JavaのList型など様々な型のアウトプットを返し、その結果返り値が Option[Any] の型で、どんな値でも取れるような作りになっておりました。 しかしアウトプットの型がブラックボックス化して、コードが追えなくなるため返り値に対して型定義を行いました。 // 結果を表すトレイト trait ScriptResult sealed abstract class MVELResult extends ScriptResult object MVELResult { case class StringResult(value: String ) extends MVELResult case class IntResult(value: Int) extends MVELResult case class SeqResult(value: Seq[ String ]) extends MVELResult case class JListResult(value: java.util.List[ String ]) extends MVELResult case class ArrayResult(value: Array [ String ]) extends MVELResult case class SalaryResult(value: Option[Salary]) extends MVELResult } //ScriptResultはMVELResult以外の型も返すことがあるため、MVELResultとScriptResultでわけております。 //ScriptResultをミックスインした結果の型定義を行うことで、他の文脈の型もとることができます。 上記は、コードの一部になります。 リファクタリングしたメソッドのシグニチャ private def extractProperties( extractDefinition: ExtractDefinition, crawlingJob: CrawlingJob, parser: Parser ): Map[PropertyName, Option[ScriptResult]] これでメソッドの結果の型が明確になり、コードが追いやすくなります。 リプレイスを行った結果 今回の es書き込みバッチ は2ヶ月ほどでリプレイスを行うことができました。(コードの変更が1ヶ月、インフラの変更からリリースまで1ヶ月ほどでした。) またメソッドに限らず、ドメインモデルを定義することで、処置中のコードも以下のような型が定義されて、各パイプラインでのデータの変化がわかるようになりました。 ドメインモデルを定義 // クローリングデータの求人情報 case class CrawlingJob( documentId: DocumentId, crawledResult: CrawledResult // 省略 ) // ETLを行った後の求人のクラス case class ExtractedJob( jobId: JobId, jobTitle: JobTitle // 省略 ) リプレイス後の各パイプラインの返り値の型 // jsonから求人データの抽出した後の型 Either[Exception, CrawlingJob] // フォーマットチェック後の型 Either[ExtractStageException, Job] 型ない状態だと実際のデータにどういった型が入っているのかを常に考えながら実装する必要があり、非常に頭を使います。 一方で型としてコードに落とし込めると、型情報を見ながら進められるので考えることを減らすことができます。 他にもリプレイスを通して以下のことを行いました。 これまで依存している外部ライブラリの都合でScalaのバージョンが2.11系だったので、リプレイスを機にメンテナンスされていない外部ライブラリを切り離しScalaバージョンを2.13にあげることができました。 フォーマットチェックでは Validated を使って簡潔にエラーの蓄積を実装できました。 これまでEC2上にSparkのクラスターを作り運用していましたが、 AWS EMR を使いクラスター管理がマネージドになりました。 EMRクラスターの構築、ログ収集基盤の作成などは難易度が高かったのですが、マネージドになったことで特に手を加えることなく、クラスターを動かしているマシンが入れ替わっているので運用自体は楽になりました。 求人情報の正規化処理を別モジュールに分けて、求人を抽出するアプリケーションとしての責務に変更しました。 振り返り 5年前のコードであってもバージョンのアップグレードを行ったり、ドメインモデルを再定義することで既存のUnitTestを再活用することができ、コードを再生できました。 またこのリプレイスから2年ほど運用しましたが、ドメインモデルや処理の過程を型で定義することで求人項目の追加、削除しやすくなりました。以後も追加の開発を行えております。 Masterのcommitの状況 2021年以降から活発に開発も行われて、リプレイス後に開発の頻度が上がっていることがわかります。 スキーマレスなデータストアに書き込む仕様だと、型情報がなくても実現できるのですが、時間の経過に伴いメンテナンスが難しくなります。 その結果コードがレガシー化することにつながりやすいです。 もちろんまだまだ技術的な負債は存在している状態ですが、今回ドメインモデルとして型を定義して、責務を分解し、Unitテストを書くことで 過去のシステムの資産を使いながらコードを拡張できる状態にできました。 継続的な開発を行っていくために改めて、型定義、責務の見直し、Unitテストが重要であるということを認識しました。 スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 www.wantedly.com
こんにちは、スタンバイでデザイナーをしている清水と申します。 この記事では「スクラムをやっていたら、なぜかデザイン作業の生産性が上がった..!」という驚きを共有させてください。 スクラムとの出会い 私はウェブ業界で、かれこれ 10 年ほどデザイナーやフロントエンドエンジニアのような業務をしております。 その期間のほとんどではスクラムやアジャイルといった手法は使わず、ウォーターフォール寄りに仕事をしていました。 そんな私ですが、スタンバイに入って突然スクラムに放り込まれました。 最初はカタカナ語の多さや、スプリントの短さに面食らう場面も多かったですが、続けているうちに徐々に良さが分かってきた気がします。 特に、スクラムがデザイン作業に与える良い影響を実感しており、本記事では「スクラムでデザイン作業の効率性が上がった!」というテーマに絞ってお話しします。 デザイン作業の生産性を高めるスクラムのプラクティス 「スクラムのすべてのプラクティスがデザイン作業の生産性を高める」といっても過言ではない可能性がありますが、特に影響が大きいと感じるものを主に紹介していきます。 作業量の見積もり まずは作業量の見積りについてです。 私たちのチームではストーリーポイントを使い、スクラムチームの全員が集まって作業を相対見積もりしています。 このストーリーポイント付けを通して、デザイン作業の生産性向上につながる情報を得られます。 以前の私は「このデザインなら実装そんなに大変じゃないかな」と思って、デザインを作成することがよくありました。 しかし、実際にその作業にどれぐらいの工数がかかったかは確認できていませんでした。 ストーリーポイントづけをメンバー全員でやると、この状況が変わります。 エンジニアがストーリーポイントを付ける場面を一緒に見るため、事前に想定していたエンジニアの作業量と、実際に必要な作業量の乖離に気づけるのです。 この気づきが積み重なると「こういうデザインは実装の負荷が高いから、ここはちょっと変えておこう」などの工夫がしやすくなっていきます。 そうなってくると、デザイン作成の精度も上がり、エンジニアから喜ばれるデザインを作りやすくなっていくでしょう。 ユーザーストーリーの書き方 私たちのチームでは、ユーザーがやりたい最小のアクション、ビジネス価値がある最小の単位でユーザーストーリーを記述し、チケットで管理しています。 その際は、推奨されているプラクティスを踏襲し、タイトルを「{ユーザーの種類}として、{達成したいゴール}をしたい。なぜなら{理由}だからだ」のように書いています。 例えば「はじめてアルバイトを探す大学生として、未経験 OK なバイトを探したい。なぜなら、初めてのバイトで不安だからだ。」などです。 この「フォーマットに従ってストーリーのタイトルを記述する」という工夫によって、デザイン作業の生産性が向上します。 フォーマットに則ってタイトルを書き、そのタイトルを意識しながらデザインをすると、ターゲットや目的を改めて意識して検討を進められます。 その結果として、適切な解決策を提案できたり、そもそもの課題に対応した解決策が見つかったりする可能性が高まるのです。 ターゲットや目的を考えてデザインを検討するのは当たり前ですが、忙しい業務の中では忘れてしまう場面も多いのではないでしょうか。 スクラムはこの基本をしっかり続けるのに適したフレームワークです。 そしてスクラムを継続すると、ターゲットや目的を考えることが無意識にできるようになり、結果としてデザインスキルの向上にもつながると感じています。 スプリントレトロスペクティブ スクラムではスプリントごとに、レトロスペクティブ(振り返りのようなもの)を実施します。 レトロスペクティブでは、人・関係・プロセス・ツールの観点、うまくいったこと・改善が必要なことなど幅広い観点で振り返りを実施します。 私がスクラムではない現場で経験していた振り返りは、頻度が少なく(四半期に 1 回や、プロジェクトの終了後だけなど)、細かいプロセスの改善につながる内容は出づらかった印象です。 スクラムのレトロスペクティブは頻度が多いこともあり、細かいプロセスの改善につながる意見も多く出ます。 また、職種横断でチームを組んでいるため、幅広い観点で意見が出ます。 普通に仕事をしていると、デザイナー以外の職種から、デザインデータの作り方などに関するフィードバックをもらえる機会は多くありません。 一方、スクラムではフィードバックをもらえる機会がとても多く、生産性の向上、自身の成長に繋がっていると感じます。 最後に 上記で紹介した以外にも、スクラムの様々な特徴やプラクティスはデザイン作業の生産性を高めてくれます。 この事実に気がついた時は不思議に思いましたが、スクラムは「価値のあるソフトウェアを早く継続的に提供する」ことに主眼を置いたフレームワークなので、その目的のためにデザイン作業の効率が向上するのも当たり前と言える気がします。 この記事を読み、スクラムなチームでデザインに取り組んでみたい!と思ってもらえたり、スタンバイでのデザイナーの働き方などに興味を持ってもらえたりしましたら幸いです。 スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 www.wantedly.com
こんにちは。スタンバイで Engineering Manager(以下EM)を担当している高原です。 今回はスタンバイのプロダクト開発がとっている組織体制とそのねらいをご紹介させていただきます。 そして、現実に生じている課題、講じている対策についても触れさせていただくことで、よりリアルな開発組織の現状と今後の展望を共有させていただこうと思います。 スタンバイのプロダクト開発体制 Product Drivenを実現するための組織ビジョン スタンバイでは会社全体として“Product Driven”を掲げており、開発組織はプロダクトロードマップの実行に責任を持ち日々開発に取り組んでいます。 プロダクトロードマップを達成するための各テーマ毎にチームを構成、役割・責任・権限を明確化することで、それぞれのチームが自らミッションや目標を定義し、メンバーが一丸となって目標に向かって取り組む体制で開発を進めています。 また、開発組織ビジョンに掲げている「自立型組織」として各チームが動けるよう、開発プロセスにおいても、は各チームで最適な手法を追求することを推奨しています。 開発組織ビジョン「自立型組織」 なお、「自立型組織」は「自律」のミスタイプではなく、「自立」で合っています! 広辞苑からそれぞれの意味を引用させていただくと次のようになります。 じりつ【自律】 自分の行為を主体的に規制すること。外部からの支配や制御から脱して、 自身の立てた規範に従って行動すること。 じりつ【自立】 他の援助や支配を受けず、自分の力で判断したり身を立てたりすること。ひとりだち。「経済的に―する」 CTO明石の言葉を借りれば、「自律」は『成人や社会人として、あたりまえの観点』という位置づけで、そのうえ更に『ひとりだち』している状態を目指すという意図を表しています。 「自立型組織」に添えてある「個々人が事業を深く理解し、個々の専門性を発揮し、自らの裁量を持って実行できている組織」は、その『ひとりだち』が実現した状態を言い換えた文章になっています。 開発組織ビジョンの図で特に該当するところを青字にしたもの 導入している組織体制と仕組み ドメイン構成に沿ったチーム体制 ところで、現在の組織体制に変える以前は、プロダクト開発全体でLeSS(Large-Scale Scrum:大規模スクラム)を実践していました。 当時は、大半の開発メンバーが3〜5チームに分かれて所属し、全チームがスタンバイの全てのサービスの開発・運用に携われる状態(スタンバイ全体を対象範囲とするフィーチャーチーム)を目指す体制をとっていました。 しかしながら、スタンバイが提供する検索サービスはビジネスドメインも技術ドメインも広範囲に跨がっているため(下図参照)、これらを包括的に開発・運用できるフィーチャーチームの実現というのは、かなり難易度が高い状態を目指していたと言えます。 技術ドメインの構成イメージ そのため、実際にはどのチームにも得意な技術ドメイン・サブシステムがあるいっぽうで、いつまでも触ることができない技術ドメイン・サブシステムもあるという状態を抜け出せていませんでした。いま思えば、これらの技術ドメイン全体をカバーするフィーチャーチームを目指すことは(例の“チームトポロジー”でも指摘されている)「認知負荷」が相当高い組織体制を目指していたと言うことができます。 2021年1月に現CTOの明石が就任後、この問題に着目して組織再編に着手し、新年度を迎えた2021年4月から技術ドメインに基づいたチーム体制へと移行しました。 チーム横断コミュニケーション施策 同時にこれらのチームを横断するコミュニケーション設計を行って、チームの細分化と裁量の付与によって起こりがちなサイロ化や意志決定の遅延を防止するために、相互に状況を把握したり共通の課題に対応したりするための基盤となる仕組みを導入しました。 具体的には次のようなもので、現在も(都度調整を加えながら)運用しています。 最低限のルール整備と情報のフルオープン化 ・各チームのSlackチャンネルはpublicで作成、原則全員参加 ・横断会議体のオープン化(参加自由、議事録開示) ・全OKRを可視化 チーム横断案件の相談先とアジェンダの透明化 ・Monthlyでプロダクト開発全体に関わる情報共有や表彰などを行う会議を開催 ・Weeklyで全チームのOKR進捗会議を開催 ・DailyでプロダクトマネージャとCTOといった意志決定者が揃う(議題持ち込み制の)相談時間を確保 根幹となるプロダクト開発行動指針 そして、コミュニケーションの共通言語を作るべく、プロダクト開発における行動指針を作成しました。 これは個人とチームの行動とマインドセットに関する期待を言語化したもので、「START」の5つの頭文字で構成しています(「S・T・A・R・T」それぞれが示す内容については、以前の記事『 スタンバイのプロダクト本部の行動指針「START」と「Engineering Belt」とは? 』をお読みいただけると嬉しいです)。 この行動指針「START」が会話の端々に出るレベルに浸透するため、月次で行っている表彰の選考観点や、採用・評価・育成・人材配置の観点にしています。そして、このTech Blogなど社外に情報を発信する際の規範とするなど、常にプロダクト開発活動を照らす鏡として使うようにしています。 プロダクト開発行動指針「START」 なかでも、横断コミュニケーションの基盤として=サイロ化や意志決定の遅延を防ぐものとして次のように行動指針が対応しています。 Transactive memory(どこの誰が何に詳しいかの理解)を活用して協力を仰ぎながら Scientific(科学的)な数字・データを共通言語として使うことで、様々な立場のメンバー間の判断のブレを無くしてコミュニケーションロスを減らし、 Relevantに(自分ごと化して)染み出して行動できるようにする 以上でご紹介させていただいたような仕組みを基盤として、各チームが担当ドメインにおけるプロフェッショナルへと進化し、それが更に個々人のレベルまで落とし込まれた状態の「自立型組織」を目指して、2021年の4月に現体制のスタートを切りました。 2021年4月に掲げた「目指す開発組織の姿」 生じた課題、講じている対策 このような開発体制と仕組みを導入しましたが、さすがに「全てが順調に自立型組織に向かっています!」というわけではありません。 当初「目指す開発組織の姿」で掲げていた「1年後の状態」に対して、既に1年半が経過している現状はどうか?という観点で振り返って、生じてきた課題を飾らずに書き留めておきたいと思います。 横断案件の難航 複数チームが協力して実現する必要がある開発案件において、関係メンバーが皆、自チームの開発案件と掛け持ちするかたちになり、「船頭不在」状態に陥った。 (それでも自分ごと化して先頭に立ってくれるメンバーがいたものの)担当外ドメインの要件定義やアーキテクチャ設計が手探りになったり、意志決定機関が不明瞭でスタックしたりした。 異なる時間軸で開発するチームが集まって協働する開発プロセスの確立や、共通理解をとるコミュニケーションにスピード感が出なかった。 これらの課題に対して、既に対策を講じているものをいくつかご紹介します。 プロダクトマネージャ直下にプロダクトマネジメントオフィス(PdMO)という独自組織を設置して、チーム横断の課題管理を行うことにした チーム横断イシューをバックログ化して、各チームのバックログのEpicレベルや各チームのOKRと紐付けられるよう可視化し、全体整合性をとるようにした チーム横断イシューの責任者(いわゆるプロジェクトリーダー)を指名して、複数チームで連携する開発スケジュールの見積りや進捗状況の集約する役割を明確化した 生じた課題と既に講じた対策 もちろん、これで十分とは考えていませんし、今講じている対策も含めて振り返り、UPDATEし続ける必要があると考えています。 今後に向けてまだまだ先にある目指す組織の姿に向けて加速していくために、当面は次のような課題に向き合っていかなければと考えています。 チーム間で共用できる「型」の導入 各チームの専門性を活かした提案や指摘を結集して要件定義や開発計画を行える開発プロセスの前半を再定義するとともに、要件や背景の透明性と相互理解度を高める共通フォーマットを導入する そうすることで、結果的にチーム間のコミュニケーションコストを下げ、そのぶん仮説検証のスピードを高めたい 事業とプロダクトの未来像の透明化 CxOやプロダクトマネージャらが描く事業やプロダクトの未来像の透明性(⇒メンバーの理解度と納得度)を高める仕組みと、それによって質が高まることが期待されるボトムアップ型の提案と議論のハードルを下げる仕組みを構築する そうすることで、いま対策で入れている「横断イシューのバックログ」を廃止したい(バックログがあると、そこに書いてあるwhatを見がちになり、whyを見ての革新的な提案やトライアルが出にくくなるリスクを感じているため) こうやって(改めて・再び)、チーム横断で行うトップダウン的なマネジメントを減らして、チームの自立度を高めていきたいと思います。 いま少し引いて眺めてみると、共通の「型」を持たないまま独立性の高いチーム体制で走り始めた後、チーム間で連携が必要になった際に、コミュニケーション・インタフェースをいちいち構築する必要が発生しているというのが現状のように思われます。 そのために最小限の「型」を入れようとしていますが、決してトップダウンで物事を決めたいわけではないので、今後「型」の機能レベルが高まっていけば、集中管理的な仕組みをどんどん外していくべきだと考えています。 そして、権限を分散させ、集合知を最大限に活かす、すなわち「自立型組織」に、どんどん近づけていきたいと考えています。 このようにまだまだ成長途上にあるスタンバイのプロダクト開発組織です。 これからも、ものづくり・事業づくりの効果を高めるための仕組みづくりを考え続け、トライアルし続けていきたいと考えていますので しばらく走って・・・また経過報告をさせていただければと思います! 以上、ここまでお読みいただきありがとうございました。 スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 www.wantedly.com
この記事を書いた目的 初めまして、上野と申します。求人検索エンジンを開発している株式会社スタンバイにて、BtoCプロダクトのスクラム開発チームでプロダクトオーナー(Product Owner:PO)を担っています。キャリア的には広告代理店が長く、直近までスタートアップ支援をしてきており、事業会社でスクラムでのプロダクト開発経験は初めての経験でした。 あまり知られてないのですが、実はスタンバイは歴史的にスクラムに関する教育や開発の環境が非常に整っています。僕自身もありがたいことに Odd-e社 様の 認定スクラムプロダクトオーナー(CSPO®︎)トレーニング や同社CTO 貝瀬様 のコーチングも数ヶ月にわたって受けることができ、自分なりのプロダクトオーナー像が見えてきた頃でした。そこへちょうど今回のようなチャンスがあり、本記事執筆に至っています。 この記事で伝えたいこと 基本的にはスクラムにおけるプロダクトオーナーで僕が常日頃心がけていることやtipsを経験からお伝えします。どちらかというとマインド部分が多いのと、なるべく読了後にすぐ実践できることをコンパクトに書いているつもりです。 欲を言えば、スタンバイに興味を持ってくれる人がいたら嬉しいなと思っています。 逆に書かないこと スクラムの教科書的な用語解説や小難しいことは書きません。「スクラムとは?」や個別のスクラムイベント、HOWなどについても割愛します(今後記事化する予定です)。ほかで体型的にまとめられている方もたくさんおりますし、僕自身がそうなのですが、小難しいことって、学んでも実は現場に活きづらかったりしますよね。結構この記事を読んでプロダクトオーナーとして明日からどう生かすか、の方がお役に立てるのではと思い、使えそうなことを中心にまとめてみました。 僕の考えるPO(プロダクトオーナー)十則 いきなりですが、僕の考えるプロダクトオーナーが守るべき「十則」をご紹介します。 PO(プロダクトオーナー)十則 1. 四六時中ROIを意識していること 2. 長期、短期それぞれでROIを意識できていること 3. ROI最大化のための仕組み作りができていること 4. 明確な判断基準と毅然としたリーダーシップがあること 5. 目的&目標を実現するためには手段を厭わないオリジナリティと度胸があること 6. 合理的で否定されないロジックを持ち続けられていること 7. 適材適所、且つ楽しく気持ちよく周りが働けるような環境作りができていること 8. 適切なフィードバックが実現できていること 9. 常に自身を高め続けられていること 10. どんな状況でも1歩でも前に進めるような言動ができていること 僕が1年近くプロダクトオーナーをやって思った結果がこれです。 正直、プロダクトオーナーは誰でも名乗ることができます。ただ、役割を全うするには弛まぬ努力が必要だと言うことを痛感しました。 ROIを最大化するためには手段を選ばない プロダクトオーナーとは、スクラムチームから生まれるプロダクトの価値を最大化することの結果に成果を持つ人、つまりは「 プロダクトのROIを最大化する 」ことがミッションである人のことです。 「スクラムを始める際には、まずプロダクトオーナーを任命すること」と言われているほどスクラムチームには重要な存在であり、プロダクトバックログを左右する最終意思決定者のため、複数人いてはならず、必ず1スクラムチームに対して1名です。開発者やスクラムマスターなどとは利害関係が異なるので、兼務も非推奨と言われています。 ROIを最大化するための決まった手段はありません。つまり、あらゆる手段を講じることができる、もっと言えば、そのくらいオリジナリティを発揮すべき役割であると考えています。他社や他チームのノウハウも積極的に参考にすべきですし、人が足りなければ他チームに交渉するし、ROI的に、今はチームを休ませるべき!と判断すれば1か月休ませたって良いでしょう。 「ROI」と一口に言っても、取り扱う際は注意が必要です。 -Investmentはプロダクトオーナーが直接コントロールできる? -何に対してのReturn? -どの時間軸でのInvestmentとReturn? 予算追加や人員増、ツール購入、計画など時間やお金を投資すべき対象はさまざまですが、Investmentはプロダクトオーナーがコントロールできるものでなくてはなりません。 直接Investすることで返ってくるReturnを最大化するのです。そのReturnは必ずしも売上等の定量的な事業インパクトに限らず、教育や文化形成など、社内向けの定性的なReturnがあることも忘れてはいけません。 見落としがちなのが、いつ発生するReturnなのか?です。次のスプリントで反映されるんだっけ?数ヶ月はたまた数年後のReturnなんだっけ?という点はROI最大化を考える上で非常に重要なポイントです。 また、ROI最大化に向けて、僕自身プロダクトオーナーとして心がけていることの1つは、多少の強引さです(笑)。スクラムチームメンバー全員に意見が賛同されなくても 「否定されない」程度のロジックを詰めて進めていく 、くらいの心構えも必要だなと感じています。とはいえ、やりすぎると単なる「わがまま」なので、伝え方や一貫した戦略はセットにはなりますが。サッカーでいうと、点さえ取っていればさほど文句を言われない、そんな世界に住んでいるのがプロダクトオーナーなのではないかな、と考えています。 つまるところ、プロダクトオーナーはROI最大化に向けて一歩でも二歩でもスクラムチームを前に進め続けること。転んでもタダでは起きないこと、本気でROI最大化を突き詰めるのであれば、そういったスタンスが必要とされていると感じています。 プロダクトオーナーとしてやらないこと 逆に、唯一僕が「 やらない 」と心がけているのは HOW(どうやるか?)まで踏み込むこと です。プロダクトオーナーはWHAT(何をやるか?)とWHY(なぜやるか?)のセットでスクラムチームを動かす方が何かとうまくいくと言われています。 つまりは、何を、なぜ実現したいか?というストーリーをプロダクトオーナーが考え、語り、どのように実現したいか?は開発者に委ねるということです。チームによっては歓迎されることもあるのでしょうが、プロダクトオーナーが開発にまで介入してしまうと、開発者の心情的には「言われたことだけをやっている」感が増すことが多いと言われています。経験的にも僕は控えてきました。 上記を徹底するために実践しているのは以下です。 1. PBI(Product Backlog Item)にユーザーストーリー(WHAT)を記載すること 2. 目でも耳でも背景(WHY)を徹底的に共有すること 1つ目は、PBIは必ずユーザーストーリーにしていることです。タスクを書くことはまさにHOWまで入り込んだ行為です。実現したいこと(Tobe)さえクリアであればスプリント内に最速で実現する方法(To-do)は開発者が検討すれば良いのです。 ちなみに、ACで実現したいことを書くだけでは開発者が判断しかねるケースも出てくるので、「やらなくても良いこと」を敢えて記載することもあります(AC1つに対して「やらなくても良いこと」2つが黄金比らしいです)。 2つ目は、実現したいユーザーストーリーの背景(なぜそれを実現したいか?)も、しっかりPBIに記載しつつ、納得いくまで説明することです。プロダクトオーナーがPBIを書くこともあるので、PBIは記載内容を型化するのも良いでしょう。 ただ、PBIの記載やスクラムイベントだけではどうしても伝えきれないストーリーの背景もあるはずです。ユーザーインタビュー結果のまとめやログ分析結果、競合情報など、共有したいんだけど、どこで話せばよいかわからない、といった類の情報も多い気がしています。 そのため、僕のチームでは、オンラインワークであることをうまく利用し、discordで「POラジオ」なるものを実践しています。週に30分ほど、プロダクトオーナーとプランナーがマーケットやユーザー動向、中長期で実現したいことなどの自由な雑談をただ単純に聞いてもらうという会です。任意参加なのですが、少々複雑な取り組みにおいても意思疎通がしやすくなってきている実感はあります。開発者からも継続希望の声が多く、数か月続けてこれています。 バックログ管理とその先にある大事なコト ここでスクラムガイド書かれているプロダクトオーナーの責務に触れておきます。「プロダクトバックログ管理」です。 “『プロダクトオーナーは、効果的なプロダクトバックログ管理にも責任を持つ』” PBI自体は誰が作っても並び替えても問題ありませんが、最終的に優先順位の意思決定はプロダクトオーナーに委ねられています。その優先順位の基準はROIが最大化されること、且つ、そのロジックはブレることなく、常に一貫させることが必要です。 ただ、個人的に、バックログの優先順位付けよりも重要だなと感じているのは、その先にある 開発者とのコミュニケーション です。優先順位が付けられたとしても、開発者が動けなければ意味がありません。プロダクトオーナーは何も生み出せないのです。プロダクトを前に進めるため、開発者が気持ちよく働ける環境を作っていくこともプロダクトオーナーにとって重要な役割であると考えます。 開発者の人柄や働く上での「やるべき」「やらないべき」ことをを知り、適切なコミュニケーションを取るよう心がけています。仕事とは言え、1人の人間です。プライベートな問題でどうしても気持ちが乗らないときだってあるでしょう。そうした心の変化も週次や隔週で1on1やオフライン出社を設けてコミュニケーションが取れる機会を作り、開発における障壁を極力無くすように心がけています。少し気持ちが乗っていなさそうだなということであれば負荷を減らしたり、逆に乗っている方には少々負荷を上げたりしています。 内容によって伝え方も気を付けています。例えば、良いニュースやポジティブなフィードバックは全体の場で盛大に話しますし、ネガティブなフィードバックが必要な時はなるべく個別で対面で話すようにしています。 DMを非推奨にしている企業さんもいらっしゃるようですが、全ケースに当てはめる必要はないと思っています。人によっては全体で語られることが嫌な人もいるはずで、特性に合わせて対応すべきでしょう。 ちなみに僕のチームでは四半期の最終日は全員で休暇を取るようにするなど、チーム独自のルールを設けたりもしています。 プロダクトオーナーとして意識していることとtips集 他にも書きたいことはたくさんあるので別記事にするつもりですが、今回お伝えしたかったのは、プロダクトオーナーを全うし続ける大変さとその反面自由度が高いゆえのやりがい、という点です。 つまるところ、プロダクトオーナーには特別なスキルや資格は必要なく、ROIへの執着、そして、プロダクトに対する熱い想いとチームメンバーへのリスペクトがあれば誰でもプロダクトオーナーを名乗れると思っています。 誰でも名乗れる、は裏を返せば、自分だけにしか出せない価値を常に問われていることに他なりません。「 プロダクトオーナーとしてアナタだけにしかできないことってなんなんですか? 」という呪いの言葉を常に視界の横にちらつかせながら活動していかなくてはならないのです。 オリジナリティを出し続けることは至難の技だとしても、少なくともプロダクトオーナーとしての自覚は持ち続けようと、僕がいつも戒めのためにPCの横に貼っておこうと考えたのが以下プロダクトオーナーの十則というわけでした。 PO(プロダクトオーナー)十則 1. 四六時中ROIを意識していること 2. 長期、短期それぞれでROIを意識できていること 3. ROI最大化のための仕組み作りができていること 4. 明確な判断基準と毅然としたリーダーシップがあること 5. 目的&目標を実現するためには手段を厭わないオリジナリティと度胸があること 6. 合理的で否定されないロジックを持ち続けられていること 7. 適材適所、且つ楽しく気持ちよく周りが働けるような環境作りができていること 8. 適切なフィードバックが実現できていること 9. 常に自身を高め続けられていること 10. どんな状況でも1歩でも前に進めるような言動ができていること 上記はあくまで個人的な「十則」ではありますが、読者のPOの皆さんにとっての十則をまとめてみると、自由度が高い職務の中でも軸がブレずに取り組めるかもしれません( Twitterで見つけ次第 スタンバイアカウントがコメントするかもしれません)。 ちなみに、Odd-e社様が「 プロダクトオーナーチェックリスト 」というチェックリストを外部公開しているのでご興味あればぜひご確認ください。 最後に、本文でも「プロダクトオーナーとしてやらないこと」で触れた、僕がプロダクトオーナーを担う中で上手くいったtipsを少しだけ紹介します。 反響次第では関連記事を公開していこうと思いますので、感想等ぜひお待ちしております。 まとめ:スクラムtips 課題感 開発者にとって、POやプランナーが考えていることがわからないので、開発にアイデアを活かしづらい。 1.POラジオ 解決策 任意で毎週2回30分、discordでラジオ番組風トークを開催。 開発チームは耳だけ参加でOK。POやプランナーは直近のプロダクト課題やロードマップ、分析結果など、普段ビジネス側で会話するような雑談を準備なしに実施。 以下プロダクトオーナーtimesでつぶやいたことを拾って話したりもしている。 結果 比較的課題の目線合わせがしやすくなったと感じる。 開発者からも評判は良く、継続を求める声が多い。 2.プロダクトオーナーtimes 解決策 slackに公開チャンネルを作り、プロダクトオーナーとして考えていることをTwitter感覚でslackにひたすら呟く活動。市場や気になるニュース、SEO、ユーザー体験、ログ分析結果、ほか業務以外の雑多なトピックなど、1日1投稿以上は心がけている。 他チームのPOらも参画。 結果 表にされづらい自グループのPOの考えを開発者や他メンバーが追いやすくなり、コミュニケーションにおける前提説明が減った(メンバーから「この前timesに投げてたアレのことですよね?」などで話をつなげやすくなったり)。 開発者との有益な会話に発展しチケット化につながるだけでなく、他グループと連携して施策につながるような副次的効果も。 スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 www.wantedly.com
こんにちは。スタンバイで Engineering Manager を担当しているステファンと高原です。 今回は、先日開催された「スクラムフェス仙台」で紹介させていただいた私たちの活動についてお伝えしたいと思います。 登壇の背景 2022年8月26日〜27日に開催されたスクラムフェス仙台のキーノートスピーチにOdd-e Japan代表の江端様が登壇するにあたって「スタンバイの事例も紹介したい」とのお声掛けをいただき、私たちもゲスト登壇してお話しさせていただきました。 Odd-e Japan社にはスタンバイのアジャイルコーチ・アドバイザーをお願いしています。 代表の江端様は本稿執筆時点で日本でただ1人、国内に限定されない Certified Scrum Trainer® として認定されているアジャイルコーチで、スタンバイのスクラム導入にも手厚いご指導をいただいておりまして、その熱さは当時を知るメンバーの語り草になっているほどです。 今回の江端様のキーノートスピーチのタイトルは「Various Scrum Teams」。 主催者さんからの「ソフトウェア開発以外での活用事例、活用方法を話してほしい」という要望に応えて数社のスクラム活用事例を紹介するものでした。その一社として参加させていただいたかたちです。 スクラムフェス仙台 www.scrumfestsendai.org スクラムフェス仙台2022 - 江端様キーノートスピーチ概要 confengine.com 事例紹介の概要:私たちCTO室〜EM室の業務をスクラムで進めています そのなかで私たちが紹介したのは、日々私たちが所属チームで行っている業務の進め方に関してでした。そんな話の何が面白いとされたのか?まず私たちのチームの立ち位置や業務についてご説明したいと思います。 チーム名称 CTO室(2021年4月〜)→現在は EM室(2022年4月〜) チームの立ち位置 私たちのプロダクト開発組織では、プロダクトロードマップを達成するための各テーマ=技術ドメインごとに開発チームを構成しています。 図:プロダクト開発組織の構成イメージ 私たちのEM室はこれらの開発チームを横断的に支援する存在です。 チームのミッション エンジニアリングパフォーマンス発揮のための環境整備・組織運営施策・技術課題の解決促進、人材成長促進などを実施する 業務内容の例 ・OKR導入支援(既に手離れしています) ・エンジニア行動基準「Engineering Belt」の定義・導入・改定( 関連記事 ) ・プロダクト組織のアセスメント ・開発チームのプロセス改善支援 ・プロダクト開発組織共通会議体の運営(OKR進捗会議、TechLead会議、マネージャ会議、表彰イベント 等) そんな私たちのチームは、一般的なスクラムチームと次の2点が異なると言えそうです。 ・プロダクト開発ではなく、組織開発をミッションとするチーム ・プロダクトオーナー(Product Owner:PO)含めメンバー全員が100%専任じゃないチーム 補足:私たちはEM室のミッションとは別に、EMとして得意分野や担当範囲ごとに開発チームの支援(例えばメンバーとの1on1、開発チームのTechLeadとの協働、開発チームのPOやマネージャーとの協働など)を行っています。 私たちがスクラムを導入した背景 現在の組織体制になる以前、スタンバイでは大規模スクラム(LeSS)を採用していて、高原を含むスクラムマスター3名体制で取り組んでいました。それぞれに所属チームも持ちつつ、LeSS全体もみていて、POもサポートするというかたちでした。その当時、次のような理由で、スクラムマスター3名もスクラムチームとして活動することにしました。 ・スクラムマスター間で協力して対応するべき課題も相当数あり、課題管理・タスク管理などチームワークが求められる ・自分たち自身もスクラムを実践してカイゼンを繰り返すことで、開発チームの支援に繫がるような事例や知見を蓄積したい その後、上記スクラムマスター2名を加えて発足したCTO室(現EM室)でも、この流れを汲んでスクラムを継続しました。 私たちのスクラムで行っている工夫 このような流れで現在に至り、足かけ2年以上スクラムを続けていますが、やはり問題も生じて、カイゼンを加えてきました。私たちがカイゼンしてきたポイントを二つご紹介したいと思います。 1.ステークホルダーの考慮 2.Scientificなふりかえり それぞれを簡単にご紹介したいと思います。 1.ステークホルダーの考慮 RefinementとPlanningを中心に、ステークホルダーを考慮して行ってきたことがいくつかあります。 POのアサインはやはり最重要だった CTOが多忙だったため、当初それに配慮するかたちで、当初はEMの合議制でPOを代行するという体制を取りました。ところが、課題の背景を捉え切れていなかったり、ゴールに対する優先順位判断が不十分だったり、レビューを受けても個別断片的になったりと問題が噴出しました。この時点では、私たちの業務の司令塔であるCTOが、チーム外のステークホルダーと変わらない状態でした。 その反省から、改めてスケジュール調整をお願いするなどして、(ちゃんと)CTOをPOに迎えてチームを再始動しました。その後はベロシティがカイゼンしました。 複眼でのPlanning・Refinementの確保 チームのふりかえりで「毎スプリント積み残しが続いている」問題について話し合ったところ、「検討不足と指摘を受けたところは別のEMが見たら気付けていたはず」とか「ステークホルダーとの会議設定が遅れて確認が間に合わなかった」などの、いわゆる段取り不足が理由で完了できなかったという共通要因が浮かび上がってきました。言い換えると、「完成の定義を満たすために必要十分なサブタスクを作成できていない」という状況が見えてきたのです。 そこで、割り込み対応が入りがちで揃いにくかったEM間で「Planningを揃って行えるよう時間を確保する」、「Refinementは関係するEM2人以上で時間を合わせて行う」というWorking Agreementを追加しました。以降、完了率が格段にカイゼンしています。 相対見積りの観点の共通認識 先に会議設定が遅れた例を書きましたが、私たちが価値を提供するステークホルダーは、各開発チームのマネージャ・PO・Tech Lead、プロダクトマネージャ、部長陣、CTO、COO、さらに人事や広報なども加わることがあり、バックログアイテムごとにステークホルダーが大きく異なりします。また、プロダクト開発全体がスコープの課題では全開発チームのリーダーとコミュニケーションが必要だったりするので、相当な人数になるケースも多いです。 そこで、私たちの相対見積もりでは「ステークホルダーの数」が一つの軸として定着しています。プロダクト開発の相対見積りでは「技術の不確実性」と「要求の不確実性」とを軸に行なっているのですが、ステークホルダーの数は後者に該当するという整理をしています。 最後のポイントはCTO室・EM室という私たちのチームならではの特徴的なものかもしれませんが、同じように社内に対してサービスを提供するチームでスクラムを実践されていたら、似たエピソードがあるかもしれませんので、情報交換してみたいところです。 それ以外の2つは、どんなスクラムチームでも共通して当てはまる話だろうと思います。 2.Scientificなふりかえり 私たちは、月一回、客観的データを使ったふりかえりを行って、チーム活動をカイゼンしようとしています。 図:ふりかえりで使っているデータのイメージ 元々は、1でも挙げた「完了率が低い」という課題に向き合うためだったり、兼務メンバーばかりなので作業時間の認識をメンバー間で合わせておく必要があったり、といったきっかけで始めた取り組みです。 データを使うことで、次のようなメリットがあると考えています。 ・客観的事実を振り返ることができる バイアスを少なくできる ・全員が同じ対象に対して振り返ることができる 同じ対象をみて別の気付きが出たときは、他人の視点を知ることができる データの解釈は在籍経験やキャリアに依存しないので、誰でも気付きを挙げられる この「Scientificなふりかえり」については、登壇時に複数の方からご質問をいただいたり、その後のネットワーキングタイムでも何人もの方が話しに来てくださるなど、多くの反響をいただいたため、補足情報として共有させていただこうと思います。 fact を使ったふりかえり! 科学的なふりかえり、良い響きだ スクラムマスターが自動化されそう>Scientificなふりかえり Q:Scientificなふりかえり。どんな指標をお使いですか? A:コミットしたストーリーポイント/完了したストーリーポイントの差分、価値提供できるユーザーストーリー/そうじゃないタスクとのアイテム比率、などをみています。 Q:的を射たデータを取るためにしている工夫は?どうやって指標を選定した? A:Jiraのレポートで自動的に出ているようなものから始めて、自分達が課題を感じたものをどう測るか?で次のふりかえりまでに指標を作ったりもする。ふりかえりのふりかえりで、気付きが出ないデータは一旦お休みするなどして入れ替えも試みています。 Q:全部が全部 fact ベースになるとちょっと窮屈になるのでは? A:ご指摘のとおりだと思います。先ず、実際には私たちも、各自の「もやもや」を書き出すスペースとかも設けていて、Hybridで使っています。 また、データを使うふりかえりは実施間隔を少し空けて、それ以外のふりかえりと組み合わせて行うのがよいと考えています。 ちなみに、私たちは「月1回」データで振り返っています。というのも、毎スプリントのレトロスペクティブでデータの推移をみても大きく変化しないので、傾向が変わる可能性がある3-4スプリントくらい空けてみるくらいが、適切なサイクルではと考えています。 また、このように「先に事実から問題認識をしてからその時の感情を思い出す」という流れも、逆に「先に感情を認識してから問題となる事実を思い出す」という流れも、どちらもふりかえりとして有効だと思っていますし、以降の「要因を掘り下げて→対策を考えて→TRYする」という流れは共通すると考えています。 今後について というわけで、今回ありがたい機会を得て、自分たちの活動のふりかえりと整理にも繫がりました。今後に向けた野心(will)を書いて締めくくりたいと思います。 開発チームのScientificなふりかえりを支援したい 各チームの状況や扱うテーマによって見るべきデータも異なると思われるので、それぞれのチーム目標や開発スタイルなどに寄り添って、示唆や気付きを得られそうな指標やデータを探すところから伴走していければと考えています。 開発チームの good practice を紹介し合い、生かし合える機会や文化を作りたい 今回、私たち自身が事例を紹介させていただいたことで、社内でもチーム活動の工夫を共有しあえる場を作っていければ、と思いました。 私たちは各チームの「自立型組織」化を目指しているので、画一的ではない様々な取り組みがTRYされています。それらの「こんなことをやってみた」「やってみてどうだった」をお互いに共有して学び合う意義があるように感じます。 定期的に、俯瞰的な組織分析も行いたい 私たちはPO的に自分たちで仮説立ても行うべき立場なので、バックログに並んでいる既存の課題だけに視野を固定されてしまわないよう、半期や四半期ごとに組織全体をふりかえって俯瞰的に分析する機会を設けたいと考えています。 ひとまず、そのこと自体をバックログに積んでおいて、実施をリマインドされるようにしておこうと思います(笑)。 以上、お読みいただきありがとうございました。 スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 www.wantedly.com
はじめに 求人検索エンジンスタンバイでは、広告枠が存在しています。 広告主様より広告枠に出したい求人票に対して入札額を求人に設定いただき、広告が表示されるようになっております。スタンバイではこの広告のクリックに対して収益が上がる仕組みを採用しています。 スタンバイのように検索に連動して広告を出すものを検索連動広告と呼びます。このような検索連動広告においては、検索の精度を担保する必要がありますが、同時に広告としての収益を確保する必要があります。 この記事ではスタンバイにおけるランキングと入札の仕組みについて紹介させていただきます。 ※ 入札額: 広告主様が設定した広告に対する最大予算 ※ 落札額: 1clickに課金される広告費 広告ランキング スタンバイでは落札額の決定とランキングにGSPを利用しています。 GSP(Generalized Second Price Auction)はセカンドプライスオークションの一種であり、ランキングと課金方式の決定します。 フィルター 検索条件に一致しない広告を表示してもユーザーに興味を持っていただけないため、検索にマッチした求人票だけがオークションに参加できるように絞り込みを行なっています。 ランキング フィルターに残った求人票の中でもより求職者にマッチした求人票を上位に出すためスタンバイではユーザーのクリック率を機械学習により予測しています。 予測したクリック率と、入札額等を利用して期待収益額を計算してランキングの上位に出す求人票を決定しています。これによりユーザーのニーズにあった求人票をランキング上位に挙げつつオークションを実現しています。 期待収益額 = 入札額 × 予測CTR 落札額の決定 スタンバイで採用しているGSPは、セカンドプライスオークションの一種であるため入札額がそのまま落札額になりません。 自分よりランキングが1つ下の求人票の期待収益額と同額となるように、落札額を決定します。 計算例 順位 入札額 予測CTR 期待収益額 落札額 1 35 0.30 10.50 34 2 25 0.40 10.00 23 3 20 0.45 9.00 20 落札額計算ロジック: 順位:1の落札額計算すると、10.00 / 0.30 = 33.33 となるので、落札額を34とする 順位:2の落札額計算すると、9.00 / 0.40 = 22.5 となるので、落札額は23とする まとめ この記事ではスタンバイ広告の検索精度を担保しつつ収益性を担保するための仕組みについて紹介させていただきました。他にもスタンバイで行われている技術などを紹介していますので興味を持っていただけたのであれば、他の記事もご覧ください。 スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 www.wantedly.com
こんにちは。StanbyのDataPlatformグループでデータエンジニアをやっている陳です。 今回は、スタンバイ社が独自に開発しているリアルタイム分析プラットフォームのStanby Analyticsを紹介します。 Stanby Analyticsとは 何故Stanby Analyticsを作る事になったのか GAの廃止 Stanbyはサービス開始時からGoogle Analytics(ユニバーサル アナリティクス、以下UA)を使っており、UAをベースとした分析結果で事業判断を行なってきました。 しかし、Google社から2023年のUAサポート終了と、Google Analytics4 (以下GA4)の移行が案内されたことをきっかけに、社内でStanby Analyticsプロジェクトが開始されました。 GA4の金額が高い 全ての分析要件をGA4で満たすプランを検討した結果、スタンバイのユーザー数増加に伴って、全てのイベントログをGA4で取得するには高額になる為、内製化でコストダウンを期待できる事がわかりました。 GA4のカスタマイズ性が低い Google Analyticsは完成度の高いプロダクトですが、GA4単体ではStanbyの分析要件を満たせません。何か特殊な処理をしたい時、カスタマイズ性の低さがUA時代から課題になっていました。 データ基盤を統一したい スタンバイではAWSをベースに開発をしており、Data Platformグループが開発している基盤は全てAWS上にあります。GAはデータをGCPのBig Queryに保存するので、データ基盤が分断されている状態でした。GAのデータを他のデータと一緒に分析したい場合、Big Queryからデータを持って来なければならなく、工数がかかっていた事が問題となっていました。 Stanby Analyticsのアーキテクチャー アプリケーション層 フロントエンドにJSを埋め込み、フロントで発火した全てのイベントをログとしてサーバーサイドに飛ばしています Data Pipeline層 フロントエンドから来たイベントログは、API Gatewayを通しKinesis Streamに送られます。 Kinesis StreamはメッセージQueueの役割を果たしており、ここからバッチ処理とリアルタイム処理のパイプラインに分かれて行きます。 バッチ処理の場合 生データを簡単な分類をしてKinesis Firehoseを通しS3に書き込んでいきます。 リアルタイム処理の場合 Kinesis Analyticsを通して必要なビジネスロジックを全てonlineで処理をしていきます。例えばセッション周りのKPIを処理する場合、FlinkのSession Window Functionを用いて同一Sessionで発生した全てのイベントをAggregateし計算した後Opensearchにデータを書き込みます。なので指標によってmsレベルのラグと、ビジネスロジックに応じて数分 ~ 数十分ラグのパイプラインが同時に走り処理を行なっています。 下の画像はFlinkのパイプラインの1つですが、Pipeline上で色んな処理や集計を行なった上でデータの書き込みを行なっている図になります。 Data Storage & Computing層 バッチ処理の場合 S3に書き込まれたデータはAWS Glueで定期的に加工され、datalake(S3)に保存されます。分析者は基本的にAthenaを使いdatalake上のデータを分析する様になっています。 リアルタイム処理の場合 基本的に加工ロジックは全て前処理のパイプライン上で終了しているので、Opensearchの役割は簡単なフィルタリングと、書き込まれたデータの検索となっています。結果として複雑なQueryが実行される事はほぼなく、ニアリアルタイムな分析が可能となっています。 Visualization層 バッチ処理の場合 RedashやTableauを使用しており、データアナリストの方々に作成して頂いたダッシュボードでアプリケーションの状態を可視化し日々分析を続けています。 リアルタイム処理の場合 OpenSearchに付属したKibanaで可視化しており、今は一番基本的な指標のみを可視化しています。こちらは分析用途以上に、異常検知が目的となっています。 リアルタイム処理の異常検知 Stanby Analyticsにおけるリアルタイム処理の最大の利点が異常検知になります。 過去スタンバイでは、サービスリリース後異常値を検知する仕組みが整っていなく、ユーザー様に大きなご迷惑をおかけしてしまった経験があります。このような事態を防止する為、Stanby Analyticsが活用されています。 アラート閾値の設置が難しい スタンバイは求人検索エンジンのサービスであり、基本的に24時間365日の稼働をしています。当然ながら、1日の中ではアクセスが集中する時間帯、閑散する時間帯があります。ピーク時のアクセス数はオフピーク時の約10倍程になります。更に求人が活発になる季節性や、CM等による一時的なアクセスの急騰 もあります。下の画像では一日内の指標の変化になりますが、ピーク時とそうでない時間帯でかなりの落差がある事を示しています。 そんな中、アクセス数やクリック数の様な動的な指標の異常値を従来の固定閾値を設定する方法で検知するのはとても難しくなります。リアルタイムの異常検知において、ただ単に0, 1の様な死活監視だけでなく、急激なKPIの変化をいち早く検知し、アクションを起こす事が求められています。 KibanaのAnomaly Detection 上記の課題を解決する為、今回Stanby Analyticsで採用したのはKibanaのAnomaly detection機能です。 https://www.elastic.co/guide/en/kibana/current/xpack-ml-anomalies.html 注: AWSのOpenSearchサービスでは、7.10バージョンのKibanaしか提供していない為、Stanbyでは7.10のKibanaを使用しています。 アルゴリズムに関する具体的な説明は割愛させて頂きますが、KibanaではRandom Cut Forestを使用しており、教師なし学習を用いて直近のWindowサイズ内のデータと比較する事により高速に異常値の発見を可能としています。 https://aws.amazon.com/jp/blogs/big-data/using-random-cut-forests-for-real-time-anomaly-detection-in-amazon-opensearch-service/ Anomaly Detectionのパラメータ数は多くなく、簡単な閾値の設定だけで始められるお手軽な機械学習機能となっています。 Anomaly Detectionの運用 結論から申し上げますと、現状プロダクション環境で異常検知が出来てるかはわかりません。 理由はまだリリースして数ヶ月程度なので、プロダクションレベルでの異常が発生していないからです。(いいこと) なので今回はプロダクション環境で意図的に異常値を発生させた時の結果となっています。 19:00にOpenSearchへ書き込むデータ量を半分にした所、数分でAnomaly Gradeが上がり、20分程度で 異常レベルが70%を超え、アラートが発火されます。今回はリリース作業等で一時的な異常を無視する為、敢えて20分以上異常が続く場合のみ発火させるようにチューニングしています。 その後もデータ量を半分のまま暫く流し続けると、徐々に新しいデータ量に対しての学習が進んで行き、数時間後には正常値とみなしてアラートが止まっている事がわかります。 Stanby Analyticsの展望 以上がStanby Analyticsの現在となっていますが、まだまだ開発途中のプロダクトで、日々チームメンバーと議論しながら改善を行なっています。 Stanby Analyticsの将来像: Flink SQLの実装 現在のKinesis AnalyticsはScalaのアプリケーションで書かれていますが、作ってみたら意外とFlinkSQLで代替できそうなコードが多かったです。FlinkSQLで作る事により、Batch処理と同じコードでロジックのメンテナンスが可能です。 LakeHouseの導入 現在のBatchシステムはHourlyの集計しか行なっていません。Athenaで他のデータと分析する際には1時間のラグが発生します。HudiやIcebergといった仕組みを用いて、Batch集計のニアリアルタイム化を目指しています。 データ分析の民主化 現在、新指標の追加やダッシュボード作成はエンジニアが主体で動いています。データドリブンな文化を目指すには、誰でもデータを扱える民主的な環境提供が必要不可欠です。データプラットフォームのエンジニアだけでなく、誰もが簡単にデータ分析を始められるプラットフォームを目指しています。 最後に Stanby Analyticsの開発によって、UAでは解決困難だった分析やコスト面の課題をクリアし、更にリアルタイムなアノマリー検知の仕組みにより、より迅速且つ柔軟な異常検知が可能となりました。 Data PlatformチームではStanby Analyticsの開発、運用だけでなく、社内のデータ基盤の改善やデータパイプラインの構築等沢山の業務に関われます。スタンバイ社のデータ活用支援やそれ関係する技術領域に興味関心がある方は、是非一度お気軽にご相談ください。 スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 www.wantedly.com
1 はじめに はじめまして、スタンバイのSearchAdvertisingCoreGroup(検索・広告コアグループ、以降SACG)でAPI・インフラ周りの開発を行なっている金正です。 この記事では、スタンバイにおける検索への取り組みを紹介します。 2 一般的な検索システムに関して まず一般的な検索改善の取り組みを紹介します。 以下の図のように一般的な検索システムは大きく分けて2つのコンポーネントに分けられます。 2.1 クエリプリプロセス ユーザーが入力したクエリをより検索マッチしやすく加工したり、 ユーザーの検索理解をする、いわゆる「クエリアンダースタンディング」と一般的には呼ばれているコンポーネントもこのクエリプリプロセスに含まれます。 そもそも検索システムに使い慣れているユーザーなら、クエリアンダースタンディングは必要ありません。 検索窓されあれば自分で意図通りの検索クエリを入力し構築できるからです。 しかし全てのユーザーが慣れているとは限りません、そこで必要になってくるのがユーザーの検索行為を助ける役割であるクエリアンダースタンディングです。 query understanding query classification(分類) query correction(訂正) query expansion(拡張) query relaxation(緩和) query suggestion(提案) query segmentation(セグメンテーション) query scoping(スコーピング) 上リストのようにクエリアンダースタンディングは主に7つのコンポーネントから構成されています。 それぞれのコンポーネントの役割を紹介します。 2.1.1 classification(クエリ分類) ユーザーが入力したクエリを分類するコンポーネントになります。 2.1.2 correction(クエリ訂正) ユーザーが入力したクエリの誤字脱字などを訂正するコンポーネントになります。 例えば、googleで「エンジア」と検索すると、「エンジニア」として検索された結果が返ってきます。 ゼロマッチ等の再現率 ※2の低下に対して有効です。 2.1.3 expansion(クエリ拡張) ユーザーが入力したクエリに対してタームを追加するコンポーネントになります。 検索対象のドキュメントを増やすためにクエリを書き換えます。 例えば、「メガネ バイト」に対して、「(メガネ or サングラス) and (バイト or アルバイト)」のようにクエリを拡張することで検索対象のドキュメントを増やすことができます。 2.1.4 relaxation(クエリ緩和) ユーザーが入力したクエリに対してより検索マッチさせるためにクエリを書き換えます。 簡単にできることはストップワードの削除です。 例えば、「バイト 東京都周辺」のようなクエリに対して、 「周辺」を削除してあげることで 検索対象のドキュメントを増やすことができます。 2.1.5 suggestion(クエリ提案) ユーザーが入力したクエリに対して他のクエリを提案するコンポーネントになります。 ここにはクエリオートコンプリートや関連キーワードの表示などがある。 クエリオートコンプリートでは、以下のようにユーザー入力クエリに対して、 以下の例では、enと打つと「エンジニア、園芸」などのような検索クエリを提案しています。 また、関連キーワードもこのコンポーネントの一部です。 ユーザーが再度検索クエリを入力する必要がなくなり、運営目線で言うと離脱を防げる可能性があります。 2.1.6 segmentation(クエリセグメンテーション) ユーザーが入力したクエリに対して、意味的にまとまりのある単語に分割してより意図通りの検索結果を返すようにします。 例えば、New Yorkという単語に対して空白で分割すると全く意味が変わってくるので、分割はせずにNew Yorkという1つの単語としてみなした方が良いでしょう。 2.1.7 scoping(クエリスコーピング) ユーザー入力のクエリやセグメントされたクエリに対して、ジャンルを割り当ててより適合率 ※1を高めることをします。 ユーザーが畜産業のバイトを探しており「豚 飼育 バイト」と検索する例で考えます。 このクエリに対して「豚飼育= 畜産業」のジャンルだと認識することで、豚を飼育している動物園の求人情報も出る可能性があるところ 「畜産業」ジャンルのラベルがついたドキュメントのみ検索結果として表示されるようになります。 以上がクエリプリプロセスを構成するクエリアンダースタンディングの各コンポーネントの紹介でした。 2.2 クエリポストプロセス 検索結果の加工・ランキング等を行うコンポーネントです。 検索結果のランキングではクエリに合致したドキュメントを何かしらの基準で並び替えることをします。 例えば、ドキュメントの登録日順に並べてより新鮮なドキュメントを上位に並び替えたり、機械学習モデルを用いてより複雑な要素を組み合わせてドキュメントを並び替えたり、様々な手法があります。 これ以上はこの記事の範囲を超えるので触れませんが、気になる方はぜひ論文等読んでみてください。 以上のように、一概に検索といっても様々なコンポーネントから構成されていることが分かりました。 スタンバイの検索システムに関しては別記事で詳しく紹介していますので、ぜひご覧ください。 https://techblog.stanby.co.jp/entry/stanby_search ※1 適合率 検索結果の中でクエリにマッチしたドキュメントの割合 ※2 再現率 クエリに対して返すべきドキュメントの中で、どれだけ返せたのかの割合 3 スタンバイにおける検索改善への取り組み ここからは、実際にスタンバイで行っている検索改善取り組みをご紹介します。 主に、前章の内容にそって具体例を載せながら紹介します。 まず、検索周りの大まかなシステム構成は下図のようになっています。 3.1クエリアンダースタンディングの実施 3.1.1 suggest-apiの説明 suggestionを行うapiです。ユーザーの過去検索リクエストからsuggestワードを抽出し、検索エンジンにインデックスしています。 検索窓にユーザーが文字を入力するとその文字にマッチしたsuggestワードを表示しています。 例えば「エンジニア」と入力する最中の「えn」という文字に対してsuggestワードを検索できないといけません。 この場合検索エンジンにsuggestワードをインデックスする際に、「enjinia, enzinia」のようなローマ字読みを追加したドキュメントをインデックスしておきます。 さらにユーザークエリに対しても、「えn => en」のようにローマ字読みに変換します。 その後、変換後のユーザークエリでprefix searchをすることで suggestワードを結果として取得しています。 3.1.2 query-handling-apiの説明 このapiでは、クエリアンダースタンディングの以下コンポーネントを実装しています。 relaxationとexpansionを実現しています。現状主に、ルールベースでクエリアンダースタンディングの各コンポーネントを実現しています。 それぞれの具体例を紹介していきます。 実際のクエリ判定方法は以下の記事を参照してください。 具体的な実装方法などを詳しく紹介しています。 https://techblog.stanby.co.jp/entry/label 3.2.1 relaxation 主にストップワードの削除を行なっています。 例えば、「募集・仕事」のような文字をユーザー入力クエリから削除しています。 スタンバイでは求人情報を扱っているので、「募集・仕事」など明示的にドキュメントに記載する必要がないからです。 これらの単語を削除することにより、 再現率を高めることができています。 expansion スタンバイでは、エンジニア・看護師などのキーワードの他に、東京都・神奈川のように勤務地検索にも対応しています。(検索窓が2つ用意されている) そこで首都圏が入力されたときには、「首都圏 => 東京都、神奈川県」に変換し、or検索にしています。 スタンバイの求人票では具体的な都道府県が記載されていることが多いので、 このような変換処理を入れることで再現率を高めることができています。 機械学習ランキングモデルの説明 スタンバイではYahoo! Japanで独自開発しているSolrのプラグインを利用して、機械学習モデルによるランキングを実現しています。 ランキングモデルの特徴量としては、求人票自体の特徴や検索ログから集計した求人票ごとのクリック数などの実績データも使用しています。 ABテストを重ねて日々モデル改善に取り組んでいます。 まとめ この記事では、スタンバイにおける検索改善の一例を紹介しました。 検索改善と言っても課題に対して様々なアプローチが存在します。 現在スタンバイでは、この記事で紹介した様々なアプローチをとり課題解決に向けて日々取り組んでいます。 スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 参考文献 検索システム 実務者のための開発改善ガイドブック: https://www.lambdanote.com/products/ir-system-ebook query understanding: https://queryunderstanding.com/ query relaxation: https://queryunderstanding.com/query-relaxation-342bc37ad425 query expansion: https://queryunderstanding.com/query-expansion-2d68d47cf9c8 query segmentation: https://queryunderstanding.com/query-segmentation-2cf860ade503 query correction: https://queryunderstanding.com/spelling-correction-471f71b19880 スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 www.wantedly.com
スタンバイでは、様々な技術勉強会やイベントに登壇をしております。 本記事では、これまで登壇した勉強会やイベントでお話した内容や資料をまとめさせていただきました。 スタンバイにおけるECS on FargateからEKS on Fargateへ移行した話/CloudNative Days 2021 by吉田 芳弘 マイクロサービスアーキテクチャな組織、システムにSLOを導入している話/Observability Conference 2022 by小林良太郎 自然言語処理スタートアップに学ぶ実践事例/ML Study #4 by 北川 淳一郎 スタンバイにおけるSLOの、これまでとこれから/【スタンバイ× hey】急成長スタートアップSREのリアル by小林良太郎 スタンバイにおけるECS on FargateからEKS on Fargateへ移行した話/CloudNative Days 2021 by吉田 芳弘 スタンバイでは、2021年5月から求人検索エンジン「スタンバイ」を EKS on Fargateへ移行するプロジェクト を進めてきました。 講演では、移行の計画、EKS on Fargateを選んだ理由、既存システムとの結合、切替方法、今後の展望などについてお話しました。 speakerdeck.com マイクロサービスアーキテクチャな組織、システムにSLOを導入している話/Observability Conference 2022 by小林良太郎 求人検索エンジン『スタンバイ』はマイクロサービスアーキテクチャを採用しており、組織的にも機能毎に分かれて開発・運用をしております。そういった組織にSLOを導入をしていく中で、いくつかの課題が見えてきておりました。 講演では、課題に対して、SRE、開発エンジニアたちはどのように立ち向かったのか。横断的なSLOルールの策定やDatadogを使ったSLO運用環境の統合といった取り組みを、組織・技術面合わせて紹介しました。 SLOのオーナーシップ、SLO違反時の対応フロー、ユーザーの行動に寄り添ったSLOなどを、どのように設計・導入・改善してきたのか。 『100年続く会社ではなく、100回変わる会社』を目指すスタンバイの軌跡を、皆様のSLO導入に関するヒントとしてお使いいただけますと幸いです。 speakerdeck.com 講演内容は下記で視聴可能です。 event.cloudnativedays.jp ※本講演はthinkitへも取り上げていただきました。 thinkit.co.jp 自然言語処理スタートアップに学ぶ実践事例/ML Study #4 by 北川 淳一郎 スタンバイは全国の仕事情報からニーズにあった最適な求人を探すことができる求人検索エンジンであり、 日々、検索精度を向上させるために様々な施策をおこなっています。 講演では、それらの施策のうち、自然言語処理を利用したもの及びこれから利用しようとしているものの紹介をさせていただきました。 最後の質問会では、とても多くの質問をいただき、ご参加いただいた皆さんの自然言語処理への関心が高く窺えました。 speakerdeck.com 講演内容は下記で視聴可能です。 www.youtube.com 検索精度に関する他施策として、ラベル情報での検索については、下記ブログでも紹介しています。 techblog.stanby.co.jp スタンバイにおけるSLOの、これまでとこれから/【スタンバイ× hey】急成長スタートアップSREのリアル by小林良太郎 SREあるあるに対して、スタンバイ社でどのように取り組んでいるのかを発表しました。SLO(Service Level Objective)をどのように考え、取り組んでいるのか、デプロイに関するツールや課題など、日々の開発にいかせる観点でお話しさせていただきました。 speakerdeck.com スタンバイでは、求人検索エンジンを開発運用しているからこそ、またスタンバイだからこそ発信できる内容を、積極的に勉強会やセミナーを通して、発信してまいります。 スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 www.wantedly.com
(本記事は、執筆時2022年7月時点の情報です) 株式会社スタンバイは、日本最大級の求人検索エンジン「スタンバイ」の開発・運営を手掛けています。 “UPDATE WORKSTYLES 「はたらく」にもっと彩を”をミッションに掲げ、テクノロジーの力で世の中の「はたらく」をアップデートしていくことを目指す弊社ですが、どんな技術を用いてどこを目指しているのか。 本記事では、元ヤフーCTOとして日本の検索/広告技術を牽引、2021年1月より弊社CTOを務める明石信之より、その全容について語っていただきました。 【profile】 株式会社スタンバイ CTO 明石 信之 システム開発会社を経て、2000年にヤフー株式会社入社。Yahoo! JAPANの広告配信システムなどの開発に従事。2009年に同社CTOに、2013年にシニアフェローに就任。2014年に株式会社フリークアウト執行役員CTO就任。IPO後を見据えたエンジニアリング組織作りに携わる。2017年1月、フリークアウト・ホールディングス執行役員就任。その後、ユアマイスター、インフォステラ、フリークルなどベンチャー企業の技術顧問就任。2019年10月、東京都チーフデジタルサービスフェロー委嘱し、東京都のDX推進および構造改革のためのアドバイザーを務めている。2021年1月スタンバイCTO就任。 ヤフー元CTOが挑む新たな検索エンジンづくり ーまずはじめに「スタンバイ」について教えてください。 スタンバイは、求人(仕事探し)に特化した検索エンジンです。 求人企業が良い人材の採用が出来ること、求職者がより良い企業に就職出来ることを最終目的としており、その課題を技術を用いて解決しようとしています。 主な技術としては、検索技術/広告技術を活用しています。 広告は2000年、検索は2008年からと私自身もう20年近く携わっている領域ですが、この間の進化も目覚ましく、いまだに新しい発見があるなど、非常に奥が深く一筋縄でいかない面白い領域ですね。 ーこれまで明石さんが関わられてきた検索エンジンと、スタンバイはどんな違いがあるのですか? 検索エンジンとは、ユーザーの意図を汲み取り、複数のドキュメントをランキング化したものを提供するサービスです。言葉にすると簡単ですがこれを実現することが、難しくもあり楽しい世界です。 ユーザー意図を汲み取るにしても、ユーザー自身が明確な意図を持たれていないケースもあるので、サービスに蓄積されたログによる統計やクラスタリングやユーザの属性などを利用して、検索文字予測(インテント)など周辺技術を活用しながらユーザー意図をより正確に導きだす必要があります。 ランキングにおいても、どんな順位でランキングするのが最適なのかという問いに答えはありません。一番オーソドックスな方法としては対象ドキュメントに含まれるターム数でランキングを出すものですが、それだけでは不十分なことも多く、機械学習を始めとする様々な技術を組み合わせてKPI沿った予測精度の改善を行っていることが多いですね。 そんな中でも、スタンバイが取り組んでいる求人検索は、特有の難しさがあると考えています。 例えば、通常のWEB検索などではユーザーのほとんどの検索は探したいものが明確で、的確なキーワードを入れて検索いただけることが多いです。しかし求人検索では、ユーザー自身が「働きたい」というニーズはあるものの、どんな仕事を探しているか不鮮明なことが多く、検索をしながらより多くの検索結果をみて探す傾向があります。例として「正社員」や「都道府県名」のみといったビックワードでの検索では、何を優先してランキングするか、検索結果だけでは補えないユーザーの意図をどのような技術で導くか非常に難しい問題だと考えています。 また、数字や記号など検索エンジンで当てづらい主観的なキーワードやストップワードの検索が非常に多いのも、求人検索の特徴です。EC検索でも”かわいい” “おしゃれ”などの主観的なキーワード、商品番号、型番などのストップワードは多いのですが、何を買いたいかのニーズを絞りやすいので予測しやすく精度を高めやすいですが、求人検索はこれに加えて就業先など位置情報などさまざまな条件を複合的に加味して解決しないものも多くあります。 そのため、通常の全文技術だけでは解決しないことも多く、後でもお話しますが、様々な技術を利用して、より求人を理解し、よりユーザのニーズに応えられるよう、最適な方法を日々考えながら改善に取り組む必要があるのが特徴ですね。 ー非常に奥が深い検索エンジンづくりですが、スタンバイで取り組んできたことについて簡単に教えてください。 今までやってきたことを順にお話をすると、オーソドックスに検索キーワードに対して全文検索エンジンを用い、いかにCTR最適化を行いランキングしそれらを継続的に向上させ、提供しつづけるいうことに注力しました。これは閲覧(クリック)されやすい求人はユーザニーズに合致しているであろうという仮説を前提にした取り組みであり、社内での改善サイクルの確立と全文検索エンジンのknowledgeの蓄積、KPIであるCTR改善においても一定の成果をあげることができました。 しかし、全文検索エンジンだけのon the flyで適切な検索結果を返すことの改善に限界が見えてきたため、次の取り組みとして求人票判定機の開発を開始しました。これは、検索エンジンにインデックスする前に、求人票を判定機にかけて、その求人がどのような求人なのかを事前にラベル付けするものです。 例えばコンビニ店員の仕事を探すために〈コンビニ〉で検索をすると、通常の全文検索だけではコンビニ店員の求人も、コンビニが近くにある施設ではたらく求人も一緒に出てきてしまいます。それらを事前に求人票判定器を通すことにより、コンビニ店員の求人なのか、コンビニが近くの異なる求人なのかを判定器を通してラベル付けすることで、よりユーザーが求めている結果を表示することができます。 実は、検索結果のCTRのみをKPIとして追求すると、検索結果に必要最低限の情報のみを表示して詳細を見ないとわからないようにすることで改善することはたやすいのですが、私たちの目的は最初に説明したとおり、求人企業が良い人材の採用が出来ること、求職者がより良い企業に就職出来ることです。ですので、ユーザのニーズにあった求人票で且つより質の良い求人票というものを追求しながらランキングできるように日々改善を続けています。 検索エンジンづくりの先に、見据える世界とは?「検索させない検索エンジン」 ー奥深い検索エンジンづくりですが、検索技術の現状についても教えてください ひと昔前に比べて便利なソフトウェアが数多く生まれ、検索エンジンは多くの方が触れる身近な技術となりました。広告技術や推薦技術など現在でも活用される技術も検索で培ったknowledgeやエンジンなどの技術から派生しており、WEB業界に大きく寄与している技術領域といえるでしょう。 簡単に検索機能をサービスに投入するサービスが増えた一方で、検索を主要技術として真面目に開発に取り組んでいる企業は少ないと感じています。実際にビックテック以外で検索技術の開発に取り組んでいる企業は稀少な存在であり、検索技術の発展が一部会社とアカデミックに閉じられてしまっていますが、我々は、まさにそこに対して事業の成長という成果とともに挑戦していきたいと考えています。 私たちの技術はいわゆるビックテックと言われる企業から比べると大きくビハインドしていますが、スタンバイが取り組む求人検索は、現時点では非常にローカルに根付いた発展途上な領域です。競合含め求人検索エンジンでの明確な正解に辿り着いているプレイヤーはいないと考えているので、より挑戦しがいがありますね。 ー今後のスタンバイの展望はどう考えているのでしょうか? 現在は、求人の閲覧に対して最適化を行っていますが、今後、求人への応募や、果ては就職や採用というところまでこの技術を昇華させていきたいと考えています。 そのために、現在はユーザーニーズをより深めるための検索機能を多角的に開発しています。 今後検索エンジンのデータが溜まっていけば、ユーザーのデモグラフィックの情報を元に、よりユーザーに適した求人を表示することが可能となります。検索しているユーザーの環境を把握し、より合致している求人を提案する取り組みなど実施していきたいですね。これらの精度が上がれば、アプリのプッシュ通知などにも使えるようになります。これらはIR(Infomation Retrieval)の中でも推薦技術の領域となりますが、それらを活用しながら、よりユーザーのニーズにより合致する検索エンジンに仕立て上げていきたいです。 ただ、前述の通り、求人検索においてはユーザー自身、ニーズが曖昧なケースも多いため、今のような検索窓を置きユーザーに検索いただくモデルでは、本質的な課題解決にはならないと考えています。 将来的には、ユーザーによる検索ではなく、こちらからユーザーの意図を汲み取り、求人を提案できるサービスになることが必要です。ここまでユーザーの検索に対してより適した結果を出すかという取り組みについて話してきましたが、まさに目指すゴールは「検索させない検索エンジン」ですね。 今は正直まだやりたいことの半分も実現出来ているわけではないですが、一つずつ着実に階段を登っています。やればやるほど成果に繋がる実感は持てているので、まさに一番楽しい時期ですね。 ー最後にこれを読んでくださる方に一言お願いします。 色々なお話をしましたが、実際には、日々地道な技術の積み重ねと挑戦の繰り返しです。 スタンバイで取り組んだ技術の挑戦については、社内に閉じずに積極的に外に多く発信をしていきたいと考えています。検索技術や広告技術に興味がある方と繋がりたいと思っていますし、発信や交流などをして、より技術力を高めていきたいと思っています。 ーStanby Tech Blogにも通じますね。少しずつではありますが、今後もスタンバイの取り組みをブログを通じて発信していきたいと思います。本日はありがとうございました。 スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 www.wantedly.com
MLOps導入でAmazon SageMaker PipelineによりMLワークフロー構築の話 はじめに はじめまして、スタンバイのSearchAdvertisingCoreGroup(検索・広告コアグループ、以降SACG)で機械学習関連の開発をやっている王です。今回はAmazon SageMaker PipelineでMLワークフローを構築する取り組みを紹介します。 MLOpsとは 私が所属しているSACGは機械学習モデルを用いて改善施策をオフラインで効果検証して、A/Bテストで仮説を確かめることでユーザーの検索体験を継続的に改善しています。 従来Group内ではAWSのマネージド機械学習基盤Amazon SageMakerを利用してPoC(Proof of Concept)、機械学習モデルの構築、トレーニングなどを行っていました。 過去PoCの結果を振り返りにくい、もしメンバーが居なくなった際の引き継ぎなど問題がありました。 このような理由から、MLOpsの導入検討を始めました。 MLOpsについて明確な定義はなくて、DevOpsからの派生で機械学習のワークフローを効率化できることを目指しています(個人的見解)。よく参考にされるのはGoogleがプロセスの自動化のレベル別でMLワークフローを定義しています. - MLOps レベル 0: 手動プロセス - MLパイプラインは自動化を含まない - MLOps レベル 1: MLパイプラインの自動化 - MLパイプライン自体を自動化する - MLOps レベル 2: CI/CDパイプラインの自動化 - MLパイプライン自体及びCI/CDを自動化する 出典: MLOps: 機械学習における継続的デリバリーと自動化のパイプライン 上記ワークフローを構築するために、本番適用の時手間がかかる、実験管理しにくいなどの課題が出て、MLOpsはこのような課題を解決することが期待されています。 Group内で存在する課題を整理して、優先度をつけて段階的にレベルアップを目指しています。私達は、MLOps導入を最初の推進ゴールとして「MLOps レベル 1」を部分的に実現すること、具体的には「データの前処理、モデルトレーニング、モデル評価をパイプライン化する」を進めていきたいと考えています。 Group内の課題整理 MLOpsを導入する前に、Group内で実際にモデリングを担当しているメンバーにヒアリングして出てきた機械学習プロジェクトを進める中でよくある課題を紹介します。 後で実験が再現しにくい 「先月のABテストをした時の、その施策結果を今のデータにもう1回確認したい」、「その時のモデルの前処理はどんな処理でしだっけ?」などの要望がよくあります。ABテスト終了の数ヶ月後に実験を再現したい時に、当初の学習、前処理ソースコード、学習時と評価時に使うデータ、モデルのハイパーパラメータなどを全部用意しないと再現しにくいです。再現できない時に過去の知恵を活用できなくなって機会損失になりました。 モデル生成の手間 MLOpsを導入する前に、ABテストを行う際にモデルを作成にあたって、データ取り込む、データ前処理、モデル学習、モデル評価を全部手動で実行する必要がありました。特に頻繁にABテストを回す時に、このような手順の手動実行は時間もかかるし、ヒューマンエラーも起きやすい状態でした。 またモデル開発を担当するメンバーが用事で休みになった時、他のメンバーに引き継ぎも上記のプロセスを実行するときコードやパラメータなどを全部伝わないといけないので、引き継ぎもしにくい状態でした。 MLパイプラインの構築 MLOps導入最初の推進ゴールに当たって、Group内整理した課題「後で実験が再現しにくい」「モデル生成の手間」に対して、実験管理できる機械学習パイプラインの導入を考えています。 今回はGroup内利用する機械学習プラットフォームAmazon SageMakerとの連携や実験管理をライトに始められることから、Amazon Sageaker Pipelineの導入を決定しました。 Amazon Sagemaker Pipelineとは、機械学習ワークフローを管理するCI/CDサービスです。機械学習プロセスの各ステップをワークフローに組み込んで、JSON形式のDAG(Directed Acyclic Graph)としてワークフローを定義して管理や再利用できます。ワークフローを実行するときに全てのステップの詳細をログに記録して後から自動追跡できます。 CTR予測モデルを例にして、ワークフローの流れを簡単に紹介します。全体の構成は下記の図のイメージとなります。 CodeBuildから対象のpipelineを実行します。まず「SageMaker Processing Job」を起動してS3に置くデータに対する前処理を行います。処理されたデータを学習評価用のデータとしてS3に保存します。 次に「SageMaker Training Job」を起動して、前処理されたデータを用いてモデルを学習します。学習されたモデルをS3に置きます。最後に「SageMaker Processing Job」を起動して、テストデータとモデルを使ってモデルを評価して、オフライン評価結果をS3に保存します。 実行の詳細はSageMaker Studioで確認することできます。各ステップをダブルクリックするとステップの入力、出力、パラメータなどの情報を確認できます。 導入の効果としては、 - 過去の実行結果およびパラメータ、データなどを追跡できて、再実行が可能になる - データ収集からモデル評価まで1つのパイプラインでまとめてOne-Clickで実行できる まとめ この記事では、Group内で取り込んだMLOps導入の経緯、施策を紹介しました。MLOps導入によって機械学習プロジェクトの運用を自動化できて、手間を省き、実験に多くの時間を割くことができるようになりました。今後も機械学習施策をどんどん検証してユーザーに価値を提供できるように仮説検証のサイクルを回していきたいと考えています。 スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 www.wantedly.com
求人検索エンジンで使用するラベル付与の話 はじめに スタンバイでは求人検索エンジンにラベル情報での検索を可能にしています。 ラベルとは求人情報や検索キーワードの特徴的な情報に対するTag付けと考えていただければイメージしやすいかと。 本記事ではRuleによるラベル付けをテーマとしています。 ラベルの使い所 例として「住吉」という駅の求人を検索する場合を挙げます。住吉という駅は全国に下記の数存在します。 東京都 住吉駅 大阪府 住吉駅 熊本県 住吉駅 長崎県 住吉駅 兵庫県 住吉駅(JR西日本) 兵庫県 住吉駅(阪神電鉄) 「住吉駅」という単語のみで検索する際は上記全ての駅の求人データが対象となりますが、「半蔵門線 住吉駅」の場合は「半蔵門線」は東京にある路線なので、1の「東京都 住吉駅」のみが対象となって欲しいところです。しかし、「東京都 住吉」の求人データに「半蔵門線」の記述がない場合には絞り込むことができません。そこで駅名周辺情報から同一駅名でも別物と扱える仕組みとしてラベルを導入しています。 複数の同一駅名から絞り込む情報として、 都道府県 、 路線名 を使用しています。東京都、大阪府の住吉駅であれば以下の絞り込み情報との組み合わせが挙げられます。 このように同じ「住吉駅」であっても周辺情報を活用し、ラベル付与すれば区別できます。 区別できればこのラベル情報を用いて、より意図に近いものを検索することが可能になります。 ラベル付けを行うにはMLかRuleか ラベル付けを行うにあたり考えられる手法は大きく分けてRuleベースと機械学習の2つが挙げられます。 2つの手法のメリット・デメリットをまとめると以下ようになります。 メリット デメリット Ruleベース 高速 学習データ作成不要 Ruleで表現できないものは対応不可 機械学習 Ruleで表現できないものに対応可能 学習データ作成負荷が高い どちらを採用するかというとHybridな形で行うのがBetterかと考えます。 単語の有無で決定できるようなラベル付けまで機械学習を使う必要はありません。 しかし、Ruleで表現できないものもあるので機械学習を使用しないという選択肢もありません。 まずはRuleベースでラベル付けを実施、その後まかない切れない部分を機械学習で補う形としています。 Ruleベースの機能定義 Ruleベースには下記の5つの機能を実装しました。 単語の有無Rule機能 指定単語の有無によりラベルを付与、削除する 単語の組み合わせRule機能 2つ以上の単語の組み合わせの有無によりラベルを付与、削除する ラベルと単語の組み合わせRule機能 付与したラベルと単語の組み合わせによりラベルを付与、削除する Rule適用順序機能 Ruleの適用に順序を設け、ラベルを付与する順序を制御する 例: 順序1. 東京 に対して 駅 ラベルを付与 順序2. 東京都 の場合には 駅 ラベルを削除 ラベル付与先の指定機能 ラベルを付与する形態素を制御する 例: 都営新宿線 住吉駅 の 住吉駅 にのみラベルを付与する場合に使用 都営新宿線 , 住吉駅 ともにRule条件だが、駅ラベルを付与する場合には路線名には付与したくない場合がある 構成 開発言語 : Rust 構成Module : 形態素解析 Rust製の Lindera を使用しています。社内にLinderaのMaintainerがいることもあり、採用しました。 DoubleArray RuleEngine DoubleArray Ruleを検索するのにDoubleArrayを使用しています。DoubleArrayの詳しい説明はここでは割愛します。 DoubleArray の概要はこちらのページが簡潔にまとまっていて理解しやすいです。 取り立てて特別なことはしていませんが、DoubleArrayの検索状態を維持しながら検索を行えるようにしています。 例としては下記になります。 DoubleArrayに追加する情報 ・国際フォーラム ・国際展示場 検索する文字列 ・国際展示場駅 検索時の形態素解析された[国際 展示 場 駅]で、下記のように文字列を作成して検索するのは非効率 ・国際 ・国際展示 ・国際展示場 ・国際展示場駅 DoubleArrayのbase、check、tailの状態を保持しておけば前回の検索結果の続きから検索することが可能 RuleEngine 上記の「住吉駅」のRuleをjsonで表現すると下記のようになります。 1001 : 東京都の住吉駅 1002 : 大阪府の住吉駅 {"order":1, "add_label":[[1, [1001, 1002]]], "word_rule":[{"word":"住吉駅"}]} {"order":2, "del_label":[[1, [1002]]], "word_rule":[{"word":"東京都"}, {"word":"住吉駅"}]} {"order":2, "del_label":[[1, [1002]]], "word_rule":[{"word":"新宿線"}, {"word":"住吉駅"}]} {"order":2, "del_label":[[1, [1002]]], "word_rule":[{"word":"半蔵門線"}, {"word":"住吉駅"}]} {"order":2, "del_label":[[1, [1001]]], "word_rule":[{"word":"大阪府"}, {"word":"住吉駅"}]} {"order":2, "del_label":[[1, [1001]]], "word_rule":[{"word":"上町線"}, {"word":"住吉駅"}]} {"order":2, "del_label":[[1, [1001]]], "word_rule":[{"word":"阪堺線"}, {"word":"住吉駅"}]} jsonの各項目は以下の内容を表しています。 order : Ruleの適用順を表す。小さい値のRuleから順にRuleを適用する。 add_label : ラベルを付与するword_rule上の形態素Indexとそのラベル名。 del_label : ラベルを削除するword_rule上の形態素Indexとそのラベル名。 word_rule : 構成要素の「word」と「labels」が解析文章と一致すれば、「add_label」, 「del_label」を適用する。 word : 形態素表記と同一の文字列が解析文章に存在すれば一致とする。 複数ある場合は、記述されいてる順序もRule条件となる。 上記の "word_rule":[{"word":"東京都"}, {"word":"住吉駅"}] では、以下のようになる。 解析文章:「東京都の住吉駅」 Rule一致 解析文章:「住吉駅 東京都」 Rule不一致 labels : 形態素に付与されたラベルが解析文章に存在すれば一致とする。 配列となっておりラベルが複数記述されている場合はAND条件として処理する。 上記のRuleに対して 「半蔵門線の住吉駅」 を解析するとRule適用は以下の流れで行われます。 order:1のRuleが適用され下記の状態となる 半蔵門線 [] の [] 住吉 [1001, 1002] 駅 [1001, 1002] order:2のRuleが適用され下記の結果となる 半蔵門線 [] の [] 住吉 [1001] 駅 [1001] Rule検索の全体の流れとしては下記のイメージとなります。 まとめ Ruleベースを導入することで単純なものならば機械学習を導入しなくとも事足りることを紹介しました。 機械学習は手法の一つであって目的ではないので、最適な手法を採用することが重要と考えます。 スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 www.wantedly.com
※本内容は Visional Designer Blog の転載です。 UI/UXデザイナーの早川です。 今回は、求人検索エンジン「スタンバイ」でABテストを進めるなかで出てきた「デザインの部分最適」や「デザインの指針がない」課題に、デザインガイドラインやペルソナ、カスタマージャーニーマップを作成して、ユーザー体験の質を保ち、向上し続けるための環境・体制を整えた取り組みを紹介します。 単にガイドラインなどを作成したプロセスを紹介するのではなく、事業指標とユーザーの体験バランスをとるためにそれぞれの打ち手に至った経緯と、途中で打ち手を変えた紆余曲折も含めて赤裸々にご紹介できればと思います。 事業指標とデザインの折り合いの付け方に悩んでいる方 定量的な指標が重要な環境で、デザイン負債が起きるのではと思っている方 共通のユーザー像やデザイン指針がなく、開発メンバーとの思考のすり合わせに悩んでいる方 こういったモヤモヤを抱えている方に読んでいただき、少しでも開発チームでの動き方を考えるきっかけになれば嬉しいです。 グロースを進める事業とデザインの間で起きた2つの課題 「スタンバイ」は、求人検索エンジンサービスです。 インターネット上に公開されている正社員からアルバイトなど、全国のさまざまな求人情報の中から、キーワードで最適な求人情報を一括で探すことができます。 求人検索エンジン「スタンバイ」 サービスグロースのため、およそ週6本のABテストを実施しており、その結果を主な判断軸にして施策を進めています。 例えば、求人情報が掲載されたカードUIのデザインを少し変えるだけで、事業の重要指標に大きな影響が出るため、デザインや機能の仕様もABテストから方向性を見出していました。 検索結果ページのカードUI こうした環境で、ABテストで判断しながらグロースを進めるなかで、以下の2つの課題が出てきました。 1. デザインの部分最適化 同じUIや情報設計でも、ページごとにABテストの結果が異なることがありました。事業指標を優先してABテストの勝ちパターンを取り入れていったことでデザインの部分最適が進行。サービス全体でデザインの一貫性が失われ、挙動や仕様も異なる箇所が出てきたことでユーザー体験自体も問題視されるようになりました。 2. デザインの指針がない デザインの指針やルールが定まっておらず、デザインの選択肢が多くなっていました。そうすると、ABテストのパターン検討に時間がかかったり、デザインを検討するためのABテストの量が増えてしまうため、費用対効果の観点でデザインの指針を決める優先度が上がりませんでした。 「部分最適化」の課題にはデザインガイドラインを作成、「デザインの指針がない」課題にはペルソナやカスタマージャーニーマップを作成する打ち手を取りました。 ここからは、それぞれの打ち手に至るまでの経緯を含め、ガイドラインやペルソナなどの策定プロセスを紹介します。 「デザインの部分最適化」の課題には、デザインガイドラインを作成 デザインの部分最適化について、具体的な例を紹介します。 スタンバイでは、「検索結果ページ」「求人詳細ページ」のページに求人情報が掲載されたカードUIがあります。ABテストをする中で事業指標を優先した結果、ページごとの情報設計やUIが異なっていました。 中には仕様や挙動なども部分最適になり、サービス全体のユーザー体験がおざなりになっていると感じていました。 このようなデザインの部分最適に歯止めをかけるため、ガイドラインを作成することにしました。 現状を可視化するも、新たな問題が発生 まず、フォントサイズ、カラー、UIパーツがどのように使われているかを整理して、使用ルールが曖昧なものを可視化します。グレーカラーの使用ルールが決まっていなかったり、ボタンの大きさや色がバラバラだったりして、想像以上にデザインが揃っておらず、並べてみると改めてガイドラインの必要性を感じました。 次に、これらのルールを整えます。 開発に関わり始めたばかりの人でも、統一されたアウトプットを作れるようカラーやタイポグラフィー、アイコン、コンポーネントらのデザインを構成する最小要素と、その使用ルールをガイドラインとしてまとめました。 ガイドラインを整えても、事業指標にマイナスの影響を与えるわけにはいきません。 そのため、ガイドラインを本番環境に反映する前に、ABテストでガイドラインに沿ったデザインに変更しても、重要指標に悪影響を与えないかを確かめていきました。 しかし、都度ABテストで確認する進め方では新たに2つの問題が出てきました。 1つは、ガイドラインを反映したデザインがABテストで負けてしまい、ガイドラインで統一しきれないケースが出てきてしまったこと。 もう1つは、重要指標への影響を確認するABテストに時間がかかってしまうことです。 ABテストはサービスグロースのためにも実施します。グロースのためのテストが優先されるので、ガイドラインの整備が思うように進まずにいました。 事業の重要指標とデザインの統一に折り合いをどうつけるか このタイミングで、事業の重要指標とデザインの統一、どちらを優先すべきかを議論しました。 「デザインの一貫性がないことは課題なので、デザインを揃えることは賛成」「ただし、重要指標に影響がないことは担保したい」といった考えをプロダクトオーナーやプランナーから聞き出し、着地点を探っていきます。 結果、プロダクトオーナー、プランナー、デザイナーの三者間でレビューしたガイドラインはテストを行わずに反映し、事業指標に強く影響しそうな場所に限りABテストを行うことで合意しました。 テストの優先度などを調整しながら、ガイドラインのVer.1が完成。部分最適が進んでいたページにガイドラインを反映し、サービス全体の統一感を担保する基盤を整えました。 「デザインの指針がない」課題には、ペルソナとカスタマージャーニーマップを作成 次に「デザインの指針がない」課題に、ペルソナとカスタマージャーニーマップを作成した取り組みです。 実際は最初からペルソナなどを作成したわけではなく、はじめは課題に適切ではない手法をとってしまいました。そのしくじりも含めて紹介します。 チーム全員を巻き込んで、サービスのイメージをまとめようとするも失敗 当時、所属していた求職者向けWebプロダクトの開発チーム全員を巻き込み、「サービスのイメージ」を集約して、そこからデザインの方向性を見つけようとしました。 そこで「サービス人格」をみんなで考えるワークショップを行い、グループに分かれて「スタンバイサービスといえば〇〇」というキーワードを出して、デザインの方向性をとりまとめようと試みました。 実際にワークを進めると、たくさんのキーワードが出たり、グループごとにキーワードの質が違ったりして、方向性をまとめきれなかったので、キーワードから合う色を検討し、どの方向性が相応しいかABテストで決めようと考えました。 しかし、ここでもガイドラインと同じく、1つ1つのキーワードから出たそれぞれの色でテストを行うと膨大な工数がかかってしまいます。 また、自分たちが現在抱いているサービスのイメージ起点ではなく、今後どういうサービスにしていきたいかのビジョンの観点。さらに、現在利用いただいているユーザーに加えて、将来的にこんなユーザーにも使ってもらいたいといったユーザーの観点も反映すべきではという考えにいたりました。 そこで「どんなサービスでありたいか、どんなユーザーに使ってもらって、どんな気持ちになって欲しいか」をペルソナやカスタマージャーにマップで固めてから、デザインの方向性を見出す進め方にシフトしました。 スモールチームでの再挑戦 サービス人格を考えるワークショップの反省点は、多くのメンバーを巻き込みすぎて意見をまとめるのに苦戦したこと、作業時間を奪ってしまったことでした。 これらを踏まえて、ペルソナなどを考える際は、開発チーム全員ではなく、デザイナーとプランナー、自ら手を挙げてくれたメンバーの少人数でたたきを作成。その後、チーム全員にフィードバックをもらう形で進めることにしました。 まずはじめにターゲットとなるユーザー層をマトリクスで可視化します。さまざまなユーザーの「仕事探しで大切にしている軸」があり、競合サービスを含む他のサービスと一緒に整理して、「市場の中でスタンバイはこの辺りだよね」という相対的なポジションとユーザー層を決めました。 市場のポジションとターゲット層を決めたところで、ペルソナの作成にはいります。 さまざまなセグメントのユーザーをカバーするため2種類のペルソナを作成しました。 ペルソナには、仕事を探すための行動やそのきっかけ、仕事探しの条件になりそうな要素(例えば、子供が生活の中心にある子育て中の方は、時間に融通が効くかを気にしている)、普段使っているアプリなどの項目を入れます。 2種類のペルソナと大まかな仕事を探すまでの行動が整理できたところで、実際にペルソナを活用するとなった時に、どちらを使うべきか迷うのではという課題が出ました。 そこで、メンバーからの提案で、メインペルソナとサブペルソナのように優先順位をつけることに。カスタマージャーニーマップの作成を見越して、メインペルソナをより詳細にしていきました。 カスタマージャーニーマップには、仕事を探すきっかけから仕事に就業した後までの各段階でどのような行動をとっているのか、どのような思考なのか、などの要素を入れ、特に各段階でのペインを意識的に出しました。 カスタマージャーニーマップを作り込んでいくと、ユーザーのペインが「仕事を探す中での困り事」と「仕事をすることへの不安」に分けられ、それぞれサービスの機能として提供するべき価値と、デザインの方向性に反映できそうな情緒的な価値に分類できることに気付きました。 こうして、どんなサービスでありたいか、どんなユーザーに使ってもらいたいか、2つの観点を踏まえたペルソナとカスタマージャーニーマップが完成。これらを拠り所にして、今後カラーやアイコンなどの検討を進めていく予定です。 結果|重要指標が向上、これまでできなかった取り組みにも挑戦できる環境に 「デザインの部分最適」や「デザインの指針がない」という課題に、ガイドライン、ペルソナ、カスタマージャーニーマップを作成して、ユーザー体験の質を担保し、向上し続けるための環境・体制が整いました。 施策を進めながら、グロース目的のABテストは継続しており、事業の重要指標を半年で40%以上引き上げる成果にもつながりました。 今後はユーザーヒアリングなどにも力を入れ、ペルソナなどは作って終わりではなく、ブラッシュアップし続けます。 また、プロダクト全チームへ展開する機会があり、チーム間の連携に発展することに。今までは、それぞれのチームで開発を進めることが多かったですが、ペルソナやカスタマージャーニーマップがサービス全体の軸になり、Webからアプリへの流入施策など、これからはスタンバイ全体の体験設計にも活用できそうと感じています。 おわりに|作るだけでなく、メンバーにユーザー体験のことを問い続けられるデザイナーに 紹介したペルソナやカスタマージャーニーマップのようにデザインに限らずさまざまなフレームワークがありますが、ただ闇雲に実施すればいいというわけではありません。 開発を進めるにあたってチームが何に困っているのかの「課題」、今やる必要があるのかの「優先順位」、その中で「適切な手法」が何かを考え、推進していくことも開発チームのイチメンバーとして大事な役割だと感じています。 今回、自分の小さな悩みや困り事が、実はチームやプロダクト全体の課題になっていて、積極的に問題提起や提案することの重要性を実感しました。実際、開発メンバーもUXデザインに興味があり、巻き込むと積極的に参加してくれました。 ビジネスの指標も大事にしながら、事業とユーザーのバランスをとって、ユーザー体験の重要性をチームに問い続けることはデザイナーの役目だと感じています。メンバーが納得感を持って開発できるように、デザイナーの独りよがりにはならず、メンバーを巻き込んでチームを引っ張っていけるデザイナーでありたいです。 デザイナーから積極的に声を上げて、メンバーを巻き込み、良いユーザー体験を目指すべく一緒にがんばっていきましょう! スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 www.wantedly.com
EKSのCoreDNSを安定させるための取り組み こんにちは。StanbyのProductPlatformグループでSREをやっている小林です。 今回はEKS環境のCoreDNSを安定稼働させるために取り組んだことを紹介します。 何が起きたのか まず前提として、当社サービスの多くはECS/Fargateで運用しており、現在EKS/FargateへFrontから段階的に移行しています。 移行についての経緯や方法については、 CloudNative Days Tokyo2021 での 登壇資料 をご覧ください。 本題に戻します。ある日、EKSで稼働しているシステムでエラーが多発しました。5xxのHTTPステータスコードが大量発生し、Web閲覧障害が断続的に発生していました。 FrontからAPIの通信で5xxが発生しているようで、Pod内で稼働しているコンテナを調査すると、名前解決で失敗していました。 EKSが内部の名前解決やサービスディスカバリに使っている CoreDNS Podの数を倍に増やすとエラーが解決したので原因はCoreDNSと判断し、原因調査と再発防止への取り組みが必要になりました。 CoreDNSの負荷状況を見る 障害が起きたときはCoreDNSの監視をしていなかったので、検証環境に負荷をかけて事象を再現してCoreDNSの状態を監視することを試みました。 弊社は監視にDatadogを使っております。なのでCoreDNSをDatadogで監視するために、CoreDNSのPodに対してサイドカーコンテナとしてDatadogコンテナを追加し、DatadogコンテナからDatadogにメトリクスを送信しようとしました。 しかしEKS/Fargateのせいなのか我々の設定が悪いのか、Datadogにメトリクスを送信できませんでした。 AWSやDatadogのサポートともやり取りしたのですが解決に時間がかかりそうで、また一方でコンテナ基盤がFaragateではなくEC2だとメトリクスが取得できることを確認できました。 なので、CoreDNSが動いているkube-systemというnamespaceだけをEC2で動かす、Hybrid EKS環境を構築しました。 仕組みとしては、特定のnamespaceを使うFargate profileを作成し、namespaceの指定がない場合はdefaultのmanaged node groupにdeployしています。 これでメトリクスが取得でき、 Webへのリクエストに応じてCoreDNSへのリクストも増加すること 負荷が一定状になると、CoreDNSのPodが再起動を起こすこと が確認できました。 CoreDNS安定化させるためにやったこと [導入に至らず]ノードにDNSキャッシュを持たせる 検証はしたけど導入には至らなかった対策として、 NodeLocal DNSキャッシュ を紹介します。 これは、クラスターノード上でDNSキャッシュエージェントをDaemonSetで稼働させることで、クラスターのDNSパフォーマンスを向上させる機能です。 ただアプリケーション環境のノードはFargateなので、DaemonSetを動かすことができず、断念しました。 ndots設定を変更 名前解決のために使う resolve.conf には ndots という、名前解決するときにローカルドメインをどのくらい優先させるかを設定できるパラメータがあります。 デフォルトは 5 で、EKSの場合だと以下のようになっています。 nameserver 10.100.0.10 search default.svc.cluster.local svc.cluster.local cluster.local ec2.internal options ndots:5 ndots:5 だとドットが5以下の場合はFQDNと見做さず、以下の順で名前解決を試みます。 この状態で example.com への名前解決を試みた場合の挙動は以下の順序で、ローカルドメインを自分で補完して名前解決を試みます。 example.com.default.svc.cluster.local. example.com.svc.cluster.local. example.com.cluster.local. example.com.ec2.internal. example.com. これだと名前解決にコストがかかりすぎるのでndotsを1に設定し、corednsへの負荷を軽減させました。 詳細は AWSの公式記事 を参照してください。 CoreDNSリリース時の切断軽減 CoreDNSへの設定変更を反映する際、ローリングデプロイしてるにも関わらずリリース時に接続が不安定になり、アプリケーションPodが全てALB TargetGroupから切り離される事象が発生しました。 CoreDNS Podが処理中のリクエストがあるにも関わらずPodがterminateされているようで、終了を待機させる必要がありました。 CoreDNSコンテナにはsleepコマンドがないので、 lameduckオプション を入れて終了を待機させました。 実際のcorefileは以下のようになります。これは終了を20秒待機させる設定です。 .:53 { errors health { lameduck 20s } } EKS1.21環境のCoreDNSのpod配置戦略を検討する Hybrid EKSになり、CoreDNSがEC2ノード上で動くので、EC2ノードで障害が起きた時のこと考慮する必要があります。 特にEC2ノードが突然停止した場合にサービスへの影響を最小限に抑える必要があります。 また、ノードを増減させた場合もPodが偏らないようにする工夫が必要になります。 topologySpreadConstraints を使ってPodの配置を調整し、 descheduler を使ってノードが増減した時にPodを再配置させるようにしました。 topologySpreadConstraints とは topologySpreadConstraints は、Podをregion、zone、node、およびユーザが定義したドメインでPodを分散させ、高い可用性と効率的なリソース利用を実現できる仕組みです。 Podをデプロイするときの動きとなり、ノードを追加しても再配置は行われません、 実際にCoreDNS Podに設定した値を元に解説します。 "topologySpreadConstraints": [ "maxSkew": 5, "topologyKey": "kubernetes.io/hostname", "whenUnsatisfiable": "DoNotSchedule" } ] topologyKey は分散させる単位です。 kubernetes.io/hostname だとノード単位でPodを分散させます。 maxSkew はtopologykey同士を比較したときに、配置pod数の最大と最小の差分の許容範囲です。この設定だと、ノード間のPod数差分を5まで許容するということです。 whenUnsatisfiable は、 maxSkew を満たせるようなノードがない場合の挙動を制御します。 DoNotSchedule はデフォルトの設定で、 maxSkew を満たすようなPodのスケジュールを行いません。(実際の挙動を見ると、均等に配置してるようでした) これでPodのデプロイ時にノード間でPodが均等に配置されるようになりました。 descheduler とは Descheduler は、ノードの状態を見て移動可能なPodを見つけ出しそれらを退避させます。現在の実装ではdeschedulerは、退去させられたPodの交換をスケジュールせず、退去させらたPodの配置はデフォルトのスケジューラに依存します。 実際にCoreDNS Podに設定した値を元に解説します。 apiVersion: v1 kind: ConfigMap metadata: name: descheduler-policy-configmap namespace: kube-system data: policy.yaml: | apiVersion: "descheduler/v1alpha1" kind: "DeschedulerPolicy" evictSystemCriticalPods: true evictLocalStoragePods: true maxNoOfPodsToEvictPerNode: 1 nodeSelector: node-type=default strategies: "RemovePodsViolatingTopologySpreadConstraint": enabled: true params: includeSoftConstraints: false namespaces: include: - "kube-system" CoreDNSは kube-system にあるので、 evictSystemCriticalPods: true が必要でした。 strategies: RemovePodsViolatingTopologySpreadConstraint を設定することで、先ほど設定したtopologySpreadConstraintsが退去させるポリシーになります。 あとはDeploymentの descheduling-interval に設定した間隔で、 topologySpreadConstraints に違反したPodが退去させられます。 公式 にあるサンプルは以下のようになっています。 apiVersion: apps/v1 kind: Deployment metadata: name: descheduler namespace: kube-system labels: app: descheduler spec: replicas: 1 selector: matchLabels: app: descheduler template: metadata: labels: app: descheduler spec: priorityClassName: system-cluster-critical serviceAccountName: descheduler-sa containers: - name: descheduler image: k8s.gcr.io/descheduler/descheduler:v0.23.1 imagePullPolicy: IfNotPresent command: - "/bin/descheduler" args: - "--policy-config-file" - "/policy-dir/policy.yaml" - "--descheduling-interval" - "5m" - "--v" - "4" これでKubernetesのノードを運用する上で問題になりがちな、Podが偏る問題にもある程度対応できました。 Datadogで監視する CoreDNSのメトリクスが取れるようになったので、Datadogで監視を始めました。 DatadogのBlog記事 を参考に、以下のメトリクスを監視しています。 カッコ内はDatadogで取得できるCoreDNSのメトリクスです。 corednsのresponseの数(coredns.forward_response_rcode_count) corednsのエラーコード(coredns.response_code_count{rcode:serverfail}) corednsのリクエストタイム(coredns.request_duration.seconds.sum / coredns.request_duration.seconds.count) corednsのforwardリクエストタイム(coredns.forward_request_duration.seconds.sum / coredns.forward_request_duration.seconds.count) ダッシュボードも作成し、負荷の変化を追えるようにしました。 まとめ CoreDNSの負荷低減を行ったおかげでエラー数が激減し安定稼働するようになりました。Hybrid EKSはチーム内での議論や負荷試験を行い本番導入し、現在は本番環境で安定稼働しております。 現状に満足することなく他にも様々な取り組みを行なっているので、またの機会に公開し、皆様の日々の運用へのヒントになったら嬉しいです。 最後までお読みいただき有難う御座いました。 スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 www.wantedly.com
スタンバイの求人情報取込の仕組みを作り直した話 〜序章〜 求人情報を取り扱うスタンバイでは、2020年から半年がかりで求人情報取込の仕組みを更改しました。本記事では概要をお話をして、詳細についてはテーマ別に別記事にて掲載させていただきます。 スタンバイにおける求人情報取込とは? 当社サービスは全国の仕事情報から求職者のニーズにあった求人を提供するものであり、求人情報は全国の企業、求人メディア、サイトから収集をしております。「 求人情報取込 」は社外に存在する求職情報の収集、保管・整形、検索エンジンへのindexingの工程全体を指します。 我々にとっては、求人情報は「商品」、求人情報の取込は「仕入れ」に当たるものであり、「求人情報取込」とはプロダクトの生命線に当たる重要な仕組みであります。 また広告(有料求人枠)により売上を立てており、広告への求人反映が利益の生命線になっております。 概要 2020年、今後のビジネス展開を踏まえたプロダクトマイルストーンを作成をするにあたり、現状システムプロダクトに存在するいくつかの負債が、ビジネス展開の律速になっていることが明確になりました。 すでに具体的な兆候が発生しておりました 求人取込から広告掲載まで最大6-7時間かかる 求人のフィールドを変更するのに1ヶ月かかった 求人情報の論理的な破損が発生後、復旧までに1ヶ月かかった これらの事例、システム構成、データ構造から導き出された最大の負債は、「求人・広告データが1箇所で保管されていることで、データの柔軟性、拡張性に問題が発生している」という点でした。言い換えると、求人、広告データが密結合しており、変更作業に対する影響範囲が計りしれず、変更にコスト・時間が非常にかかる状態になっておりました。 シンプルなんだけど、拡張性がほぼなかったのが難点 私達が下した決断は、「求人取込直後の求人情報を構造化データとしてもつことで、目的別(広告、検索、分析、生データなど)用途に合わせたデータソースを持てるようにする」ということであります。 最初は全部盛りで書いたけど、ここからいくつかの機能は断舎離されていきます これを実現するため、本題でもある「求人データストア化PJT」が開始されました。 PJTメンバーとして寄せ集められた6名のチームビルド、方向性のすり合わせから始まり、約4ヶ月で求人データの構造化を行い(job-store PJT)、後に3ヶ月かけて広告配信の仕組みを全面更改しました(ad-store PJT)。 PJT完了後も、重複排除や審査機能の追加など、数々の機能を追加しており、成長可能なプロダクトとして生まれ変わることが出来ました。 本編では、以下の章立てを考えており、可能な限り詳細に求人情報取込のお話をさせていただきたいと思っております。 Part.1 課題感の確認から計画策定 Part.2 開発からリリースへ Part.3 リリース後の話 積極採用中です スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 www.wantedly.com
Cats MTL のご紹介 はじめに スタンバイではシステム開発に主に Scala を使用しています。またその一部では、モナドやエフェクトを使用して型安全で堅牢なシステムを構築しているシステムもあります。 モナドやエフェクトの合成においては、それらを書きやすい形でラップアップした Eff などがありますが、一方で Scala の関数型ライブラリである Cats とシームレスに統合可能である Cats MTL というライブラリがあり、書きやすさの面でも過去のバージョンと比較してかなり向上しています。 ここでは、 Cats MTL をアプリケーションにどのように取り入れるのかをサンプルコードを元に紹介していきます。 本文中のサンプルコードで使用した各ライブラリのバージョンは以下の通りです。 scala : 3.1.0 cats : 2.6.1 cats-mtl : 1.2.1 cats-effect : 3.3.1 また、Scala のコンパイルオプションは kind projector を有効にするため -Ykind-projector を加える必要があります。詳細な情報は下記URLを参照してください。 https://docs.scala-lang.org/scala3/guides/migration/plugin-kind-projector.html Cats MTL とは Cats MTL (Monad Transformer Library) はその名の通りScalaの関数型ライブラリである Cats の Applicative などに対応するモナドトランスフォーマー用の型クラスを提供するライブラリです。 例えば、下記のようにモナドトランスフォーマーを複数使用する場合、ネストしたモナドトランスフォーマーをそのまま使用するとコードが非常に複雑になってしまいます。 import cats.data._ def checkState: EitherT[StateT[List, Int, ?], Exception, String ] = for { currentState <- EitherT.liftF(StateT.get[List, Int]) result <- if (currentState > 10 ) EitherT.leftT[StateT[List, Int, ?], String ]( new Exception( "Too large" )) else EitherT.rightT[StateT[List, Int, ?], Exception]( "All good" ) } yield result Cats MTL では、このような問題を解決しモナドトランスフォーマーをハンドリングするコードを抑えることで、コードの記述性や可読性を高めます。 import cats.MonadError import cats.syntax.all._ import cats.mtl.Stateful def checkState[F[_]](implicit S: Stateful[F, Int], E: MonadError[F, Exception]): F[ String ] = for { currentState <- S.get result <- if (currentState > 10 ) E.raiseError( new Exception( "Too large" )) else E.pure( "All good" ) } yield result 同じ内容のコードが非常に見やすくなりました。 ユースケース Cats MTL で提供される型クラスの内、アプリケーション内でよく利用される Ask と Raise についてサンプルコードを交えて紹介していきます。 ここでは、IDに一致するユーザー情報を取得し、特定の条件に適合すればユーザー情報をそのまま返し、適合しなければエラーを返すという簡単な ユースーケースを例として考えてみます。 Ask は Kleisli#ask を型クラスとして表したもので、何かしらの値を保持し、それを読み出して利用するために使われます。 この型クラスを利用することで、処理の中で動的にアプリケーションの設定を読み込んだり、依存するインスタンスを注入(DI)したりできます。 import cats._ import cats.syntax.all._ import cats.mtl._ object UserUseCase: def user[F[_]: Monad]( id: UserId )(implicit A: Ask[F, UserAlgebra] ): F[IdentifiedUser] = for { alg <- A.ask user <- alg.user[F](id) } yield user Raise は Functor に対して例外やエラーの質を表す型,例えば Either[E, A] を発生させる機能を提供するものです。 ApplicativeError#mapError と同等の機能を提供しますが、型は ApplicativeError である必要はありません。 この型クラスを利用することで、処理条件によってエラーとして Left を生成できます。 特定の条件に適合しない場合にエラーを返す、という仕様を Raise を使用して実装してみましょう。 import cats._ import cats.syntax.all._ import cats.mtl._ object UserUseCase: def user[F[_]: Monad]( id: UserId )(implicit R: Raise[F, UserError], A: Ask[F, UserAlgebra] ): F[IdentifiedUser] = for { alg <- A.ask user <- alg.user[F](id) identified <- if (user.invalid) R.raise(UserError(id)) else user } yield identified 上記で作成したプログラムを実行します。実行結果を 実行するメソッドの型引数には型クラスのインスタンスである EitherT と Kleisli を指定します。(実際には型引数は別途 type として宣言しておくと見やすくなります。) 実行結果として取得できた EitherT[Kleisli[...]] に対して EitherT#value および Kleisli#run を行い、最終的に Either[UserError, IdentifiedUser] を取得します。 import cats._ import cats.data._ import cats.effect.IO object Main: def main(args: List[ String ]) = UserUseCase.user[EitherT[Kleisli[IO, UserAlgebra, _], UserError, _]] (UserId(args( 0 ))) .value .run(UserAlgebra) おわりに Cats MTLを使用することで、コードの記述と実行をうまく分離しつつ、アプリケーションを構築するために必要となる機能(エラー処理や依存性の注入など)を簡易なコードで実現できました。 また モナドトランスフォーマーの lift を行わずとも、平坦な for 文のみでUseCase を記述することが出来、記述性、可読性がともに大きく向上しました。 モナドの性質や使い方を学習していくにはそれなりにコストがかかりますが、Cats MTL の様なライブラリを導入することであまりモナドやモナドトランスフォーマーに対する造詣が無くてもアプリケーションの 機能や記述性を損なうこと無く適切な制約を持たせた強いコードを書くことができます。 参照 Cats MTL: https://typelevel.org/cats-mtl/ 積極採用中です スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 www.wantedly.com
この記事では、QAチームが現在進行形で取り組んでいる不具合チケット棚卸の取り組みについてお話しします。 はじめに こんにちシステム開発において、バグを追跡・管理・分析することは非常に重要です。 そのため、JIRAやBacklog、RedmineなどBTS(バグ・トラッキング・システム)の運用は必要不可欠です。 チケットを起票したり編集したり、皆さんも経験があると思います。 その中で、こんな状況に出くわしたことはないでしょうか? TOPページに表示される、夥しい数のオープンなチケット X年前に起票されていまだ残っているが、 担当者がもういなくて迷子のチケット 『いつか直す』のまま一生動かないチケット スタンバイも例外ではありません。 2021年8月時点でおよそ4割のチケットが手つかずのまま、放置されていました。 開発が進むにつれ、チケットは増え続けます。 半年後、数年後、五年後、十年後はどうなってしまうのでしょうか? どうにかしたい どうしてやろうと思ったの? プロジェクトの風通しを良くしたいからです。具体的には、『現存するバグがいくつで、どの程度の影響があるのか瞬時に判別できる状態』を理想としています。これにより、QAのみならず全てのメンバーが不具合の現状を正確に把握でき、共通の問題意識を持てると考えています。 どうしたい? 『直す予定のあるチケットだけが残っている状態』にしたい。 できるだけそこに近づけたい。 そこで、いろいろやりました 1. みんなで棚卸ミーティング PO(Product Owner)、エンジニア、QAの各チームからメンバーを募り、オープン中のチケットの処遇を決めるMTGを実施しました。 確かに処遇は決められたのですが、30分で4件が限度だったため、残っているチケットすべてに実施するのは負担が大きすぎました。また、チケットの処遇はPO主導で決めることが多く、全員の意見を募る必要性が薄い=MTGである意義がない、ということに気づきました。 2. 棚卸リスト作成 上記の反省を活かし、非同期コミュニケーションでの棚卸に切り替えました。 対象とするチケットのリストを作り、個々のチケットにおけるQAの懸念事項(不具合が残っていることによるユーザーへの悪影響)を記載しました。 POは懸念事項を基に『改修予定』『リスク許容』の判断を下し、『リスク許容』とされたチケットはQA側でクローズできるようになりました。 やるときの考え方 チケットはいつ閉じる? チケットができる条件はハッキリしています。『不具合を見つけた時』です。 一方、チケットが閉じる条件はいくつかあります。 『不具合が直ったことを確認した時』『不具合を改修しないことを決めた時』などなど。 前者は明白ですが、後者は誰が・いつ決めるのでしょう? スタンバイではPOが主導して随時決定するため、POへの連絡に重点を置いています。 『いつか』は来ない 開発・リリースを止めて既存不具合の改修のみ行うタイミングがあるならともかく、大半の現場で『いつか直す』はいつまでも直りません。また、期限や重要度が決まらないチケットはそもそも優先順位が極端に低い=改修する意義が薄い、とも言えます。このようなチケットが生まれる背景として、QAが過剰品質な考え方をしている場合もあると思います。 逐一声を上げて判断を仰ぐか、『改修しないことを決めて閉じる』ようにします。 迷ったら閉じる 部屋の掃除をしてる時に、ありませんか? 「捨てようかな……でも、いつか使うかもしれないし残そう!」 ⇒使いません。 チケットも同じです。『使うかもしれない』は使いません。 迷ったら=オープンにしている理由が言えないならクローズしましょう。 幸い、チケットはモノと違って後から再オープンできます。 『いつか直す』の『いつか』が来た場合も、当初とは状況が違うかもしれないので新しくチケットを作ってもよいでしょう。 いずれにせよ、残している理由がはっきりしないチケットは閉じましょう。 ただし、原則的に『直った』『直さないと決めた』のいずれかで閉じるべきです。 後者の場合、不具合を改修していないというリスクがある旨をきちんと共有しましょう。 チケットを閉じたいあまり、QAが不具合を黙殺してしまっては本末転倒です。 やってみた結果 活動を開始した結果、不具合(チケット)のクローズ率は順調に向上し、4か月後には80%以上を達成しました。 この高い水準を維持するためには、継続的な取り組みが必要です。 おわりに 『チケットの棚卸で、PJの見通しを良くしよう』というモットーのもと、ある程度の成果を出すことができました。大事なのは『POを巻き込む姿勢』と『継続的な取り組み』です。 これからもQAが主体となって取り組んでいきます。 スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 www.wantedly.com
はじめに 求人 検索エンジン のスタンバイでは全国の仕事情報から自分のニーズにあった最適な求人を探すことができます。 この記事では、スタンバイの検索の仕組みを紹介します。 検索エンジン の概要 一般的に 検索エンジン は複数のフェーズから成り立つシステムです。 以下の図は 検索エンジン を構成するフェーズを表しています。 検索エンジン には、大きく分けてデータの収集、データのインデクシング、検索の3つのフェーズがあります。 データの収集フェーズでは検索対象とするデータをさまざまなソースから集めてきます。スタンバイの場合、取り扱うデータは求人票です。 データのインデクシングフェースでは、取得してきたデータで 検索エンジン のインデックスを構築します。 インデックスとは索引という意味で、検索のために事前準備しておくデータです。 検索エンジン はインデックスを活用することで高速に検索結果を返すことができます。 検索フェーズでは、 検索エンジン がユーザからリク エス トを受け取った際に実際に検索を実施するフェーズです。 検索フェーズはさらにクエリハンドリング、マッチング、ランキングという3つのステップに分かれます。 クエリハンドリングではユーザが 検索エンジン に入力したクエリの分析をおこない、検索に適した形へクエリを変換します。 マッチングでは 検索エンジン の全てのデータの中からクエリに合うデータを絞りこみ検索結果として取得します。 最後のランキングでは、取得したデータをユーザのニーズにあった順番へ並び替えます。 以降では、 検索エンジン のそれぞれフェーズについて、スタンバイではどのような仕組みになっているかを紹介します。 スタンバイの 検索エンジン の仕組み データの収集 スタンバイで扱う求人票はさまざまなところから取得しています。取得元の種類としては、 クローリング、 XML フィード、スタンバイで求人票を作成する、などがあります。 クローリングでは、 クローラー というプログラムが求人票を掲載しているサイトのHTMLを取得・解析し、データを抽出して求人票を集めてきます。 サイトごとにページの作りが異なるので、 クローラー はページのどの部分を見て情報を抽出するかを正しく判定し、データを抽出する必要があります。 XML フィードでは、求人票掲載元の企業のサーバーにあるデータを取得したり、スタンバイのサーバーに求人票データを置いてもらったりすることで求人票データを取得します。この方法で取得するデータは XML ファイル形式で扱います。 また、スタンバイが提供している求人票を作成する機能を使うことも可能です。 この場合は直接求人票データをスタンバイ内のデータベースに保存するため、そこからデータを取得します。 データのインデクシング 上記のようにして集めてきたデータを検索システムのインデックスに登録するのですが、 データの形式や求人票の書き方がそれぞれで異なるため、データをインデクシングする前に内部で扱いやすい形式に変換しています。 これにより、例えば、給与を数値型に変換することで時給○円以上の求人票だけ絞り込むといったことをできるようにします。 また、求人票ごとに検索で使用するための追加の情報の付与も行います。 具体的には、求人票が一定期間に何回クリックされたかといった実績データや、求人票のデータから推測した求人のカテゴリなどです。 これは、マッチングの精度の改善やランキングの改善のために、後述する 機械学習 ランキングモデルの特徴量データとして使用されます。 このようにして、求人票データのインデクシングを行っているのですが、 求人票データの特徴として更新頻度が高いということにも注意しなければなりません。 求人票は、企業が求職者を採用できた時点で無効になってしまいます。 このため、終了した求人を削除し検索結果へ表示されないようにしないといけません。 また、求人票の情報が更新された場合、インデックスにも素早く反映させる必要があります。 このように、大量の求人票データを素早く処理する必要があるため、 インデクシングの実装ではストリーミング処理を行うなど様々な工夫をしています。 検索 ここまでは、検索を実施する前のデータを準備するフェーズを説明しました。 ここからは、実際にユーザが求人票を検索を実施する際に行われる処理を説明していきます。 クエリハンドリング ユーザが入力したクエリはまず最初にバックエンドの API に渡されます。 ここで、受け取ったクエリを検索システムで使用する形式に変換します。 また、単に変換するだけではなく、検索品質を改善するためにさまざまな処理を実施しています。 例えば、クエリに会社名が含まれている場合、その部分だけ抽出してインデックスの会社名のフィールドへマッチさせるようにします。また、雇用形態に関する文字列が含まれている場合、内部的なコード表現に変換することで、文字列が完全に一致していない場合でも同じ意味の雇用形態の求人票を取得できるようにしています。このように、ユーザが入力したクエリを分析し・適切な処理を施すことは検索結果の品質を向上させるために非常に重要です。 マッチング ここからは、検索システムの内部で実施される処理になります。 API で処理されたクエリが検索システムに入力されると、 検索システム内部では、検索結果の候補となる求人票を取得します。 ここでは、できるだけ高速にユーザが求めているものにマッチするものだけを過不足なく取得することが求められます。 スタンバイでは検索システムに Yahoo! Japan が開発している ABYSS を使用しています。 ABYSSは Yahoo! Japan のさまざまなサービスで利用されており、ABYSSを利用することで、これまで Yahoo! Japan の中で培われてきたさまざまな検索技術のノウハウを検索品質の改善に取り入れることができています。また、後述する検索用の独自 プラグイン を利用することで 機械学習 を用いたランキングの改善ができるようになっています。 ランキング マッチングの後に取得した結果を並べ替えます。 検索エンジン はユーザが入力したクエリに対して、マッチした結果をユーザのニーズにあった順番で返すことが必要です。なぜなら、検索にヒットした結果がたくさんあってもユーザは全てを見ることができないからです。そのため、検索結果のランキングの改善も非常に重要になります。 ランキングの改善方法として、 機械学習 を使って検索結果を並び替える ランクキング学習 という手法がよく知られています。ランクキング学習では、 機械学習 モデルを検索結果の並び順が最適になるように学習させます。検索時にはこの学習済みモデルを使用して、検索結果を並べ替えます。 スタンバイでは Yahoo! Japan で独自開発しているSolrの プラグイン を利用して、 機械学習 モデルによるランキングを実現しています。ランキングモデルの特徴量としては、求人票自体の特徴に加えて、インデクシングフェーズの説明で述べたように、ログから集計した求人票ごとのクリック数などの実績データも使用しています。 まとめ この記事では、スタンバイの検索の仕組みを紹介しました。 検索エンジン は複数のフェーズから構成されるシステムであり、 それぞれのフェーズで、さまざまな技術的課題が存在します。 スタンバイはそれらの課題に対して専門のチームが複数存在しており、 日々解決に取り組んでいます。各チームがどのようなことをやっているかは、 それぞれのチームの記事をご確認ください! スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 www.wantedly.com
※この情報は2021/12/17現在のものです。 はじめに 本記事は、これから Github Actionsの導入を検討している方に向けて記載しております。 最近業務で GitHub Actionsを使うようになりましたので、その中で勉強してわかったことをつらつらとまとめます。 詳細な仕様につきましては GitHub公式サイト のドキュメントが充実しておりますのでそちらをご確認ください。 背景 弊社スタンバイでは現在、CI/CDツールとしてJenkinsと AWS CodeBuildをメインに利用しており、JenkinsからCodeBuildをキックする構成としております。 しかし、この構成だと以下の問題点がございました。 ビルドの設定がJenkinsと AWS CodeBuildで分かれるため、運用コストがかかる GitHub をトリガーとしてJenkinsジョブを起動する場合、Webhookを作成する必要がある Serverless Framework などをJenkinsから実行するためには、JenkinsのslaveサーバにNode.js/ python などをインストールする必要があり、環境競合発生の可能性がある これらの問題があるため、 Github Actionsの導入を検討しておりました。 現在、Serverless Frameworkのデプロイ及び一部のサービスのビルドにつきましては Github Actionsを実際に導入しております。 GitHub Actionsとは GIthub Actionsとは、 GitHub が提供するCI/CDシステムです。 ランナーとして提供されるOSは Linux だけでなく、 Windows / macOS も提供されています。 セルフホストランナーを自前環境に構築して GitHub Actionsを実行できますのでオンプレ環境でも使えます。 料金 料金は GitHubのサイト に詳細が書かれています。 パブリック リポジトリ 及びセルフホストランナーの場合は無料、プライベー トリポジ トリの場合でも契約しているプランに応じて無料枠があるため、 GitHub を既に利用している場合使い始めやすいのではないでしょうか。 GitHub Actionsの始め方 リポジトリ 直下の . github /workflows ディレクト リに任意の名称のymlファイルを作成することによって、ワークフローを作成できます。 サンプルのワークフローを定義して実行結果を確認してみます。 .github/workflows/learn-github-actions.yml name : learn-github-actions on : [ push ] jobs : check-bats-version : runs-on : ubuntu-latest steps : - uses : actions/checkout@v2 - uses : actions/setup-node@v2 with : node-version : '14' - run : npm install -g bats - run : bats -v GitHub にpushします。 git add .github/workflows/learn-github-actions.yml git commit -m " first github actions workflow " git push origin main Actionsのタブを見ると、pushしたワークフローを確認できます。 今回サンプルで追加したワークフローは リポジトリ へのpushをトリガーとして動くため、既に動いていることが確認できます。 グリーンのチェックが実行成功のマークです クリックして中身を確認すると、実行ログを確認できます。 例では全てのブランチに対してのpushをトリガーとして動くワークフローを作成してみましたが、他にも色々な トリガー があります。 もちろん特定のブランチに対するpushで動くワークフローも作成可能です。 GitHub と高度に統合されており、様々な操作をトリガーとしてワークフローを起動できるのは GitHub Actionsの強みではないでしょうか。 AWS との連携 Github Actionsのランナー環境には AWS CLI など主要な CLI やモジュールがインストールされているので、わざわざインストールするステップを入れることなく AWS の各種リソースを操作できます。 https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu2004-README.md クレデンシャルの設定やECRログインなども、 AWS 公式から提供されている GitHub Actions用テンプレート があるため、1ステップで簡単に行う事ができます。 クレデンシャルはアクセスキーだけではなく、OIDCにも対応しています。 今回はアクセスキーでの設定例を書きます。 .github/workflows/aws-actions-sample.yml name : aws-actions-sample on : workflow_dispatch : jobs : test : name : aws test runs-on : ubuntu-latest steps : - name : Checkout uses : actions/checkout@v2 - name : Configure AWS credentials uses : aws-actions/configure-aws-credentials@v1 with : aws-access-key-id : ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key : ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region : ap-northeast-1 - name : s3 list run : | aws s3 ls AWS のアクセスキーとシークレットキーは GitHub の Settings→Secretsで設定できる Repository secret に入れておきます。 今回はトリガーを workflow_dispatch としているので、Actionsのタブから手動で実行します。 実行できていることが確認できます。(今回の例ではs3 lsコマンドが成功しています) また、 AWS アカウントが開発と本番で分かれているなど、環境が分かれているケースでは Environment secrets が利用できます。 ymlに environment の記述(とついでにワークフロー実行時引数)を追加します。 .github/workflows/aws-actions-sample-environment.yml name : aws-actions-environment-sample on : workflow_dispatch : # ワークフロー実行時の引数 inputs : env : type : choice options : - dev - prod description : '環境' jobs : test : # environment設定を追加 # 値はワークフロー実行時の引数を設定 environment : ${{ github.event.inputs.env }} name : aws test runs-on : ubuntu-latest steps : - name : Checkout uses : actions/checkout@v2 - name : Configure AWS credentials uses : aws-actions/configure-aws-credentials@v1 with : aws-access-key-id : ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key : ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region : ap-northeast-1 - name : s3 list run : | aws s3 ls Settings→Environmentsからenvironmentを作成し、その中に AWS のアクセスキーとシークレットキーを登録します。 今回はdevとprodのenvironmentを作成し、それぞれに AWS_ACCESS_KEY_ID と AWS_SECRET_ACCESS_KEY を設定しました。 なお、 Repository secret にも同名のキーが設定されていた場合、 Environment secrets が優先されます。 ワークフロー実行時にenvを指定すると、指定したenvironmentからシークレットを取得して AWS CLI を実行できます。 なお、 workflow_dispatch をトリガーとした場合、UIからの実行以外に、 API からも実行が可能です。 # OWNER: ワークフローを定義しているリポジトリのownerを指定 # REPO: ワークフローを定義しているリポジトリの名前を指定 curl -f \ -X POST \ -H "Accept: application/vnd.github.v3+json" \ -H "Authorization: Bearer ${GITHUB_TOKEN}" \ https://api.github.com/repos/${OWNER}/${REPO}/actions/workflows/aws-actions-sample-environment.yml/dispatches \ -d "{\"ref\": \"main\", \"inputs\": {\"env\": \"prod\"}}" Composite Action ワークフローを書いていると、セットアップなどで同一ステップを複数のワークフローに書くことが増えてきます。 複数のステップをまとめて再利用可能なActionに定義できる機能として、 Composite Action が提供されています。 Composite Actionのファイル名は action.yml である必要があります。 Composite Actionは利用する側と同じ リポジトリ に定義することも、別 リポジトリ に定義もできます。 今回の例では利用する側と同じ リポジトリ に定義しています。 composite-action/init/action.yml name : "setup library" inputs : node-version : default : '14.x' required : false description : 'version of nodejs' runs : using : "composite" steps : - name : Set up Node.js uses : actions/setup-node@v1 with : node-version : ${{ inputs.node-version }} Composite Action利用側は uses で呼び出します。 .github/workflows/composite-action-sample.yml name : composite-action-sample on : workflow_dispatch : jobs : build : runs-on : ubuntu-latest steps : - name : Checkout uses : actions/checkout@v2 - name : Initialize # Composite Actionを相対パスで指定 # Composite Actionが別リポジトリにある場合は{owner}/{repo}/{path}@{ref} # owner: Composite Actionを定義しているリポジトリのownerを指定 # repo: Composite Actionを定義しているリポジトリの名前を指定 # path: Composite Actionを定義しているパスを指定 # ref: Composite Actionのブランチまたはタグを指定(mainなど) uses : ./composite-action/init with : node-version : 16.x 注意点 Composite Actionを利用側とは別 リポジトリ にする場合、その リポジトリ は パブリック リポジトリ にする必要があります。 プライベー トリポジ トリにする場合は、Composite Actionと利用側が同じ リポジトリ にいる必要があります。 (この点が不便で、プライベー トリポジ トリにComposite Actionを集めたいケースもありますが、今のところそれができないです) Composite Action側のログは1つのステップにまとめられて表示されるため、地味に見づらいです。 利用側で設定した 環境変数 はComposite Action側でも参照できますが、シークレットは参照できないのでinputで渡す必要があります。 なお、Composite Actionにシークレットを渡した場合でも、Composite Action側のログはちゃんとマスクされます。 Reusable Workflow ワークフロー内のステップを共 通化 する方法としてComposite Actionを紹介しましたが、もう1つ共 通化 する方法として Resuable Workflow があります。 こちらはワークフロー自体を他のワークフローから呼び出せる機能となります。 Composite Actionは一部処理を共 通化 するものですが、ビルド→デプロイなど一連のフローを共 通化 する際に利用できます。 また、Composite Actionと比べて処理内でcheckoutを実行すると以下の違いがあります。 Composite Action Composite Actionを定義している側の リポジトリ のリソースをcheckout Reusable Workflow Reusable Workflowを利用している側の リポジトリ のリソースをcheckout なのでcheckoutも含めて共 通化 して別 リポジトリ に定義する場合はReusable Workflowの方が使いやすいです。 実際にResuable Workflowを書いてみます。 Resuable Workflowはワークフローの一種なので、 . github /workflows の中に作る必要があります。 .github/workflows/reusable-workflow.yml name : Reusable workflow example on : # Reusable Workflowはworkflow_call workflow_call : inputs : command : required : true type : string env : required : true type : string # secretの設定 secrets : access_key_id : required : true secret_access_key : required : true jobs : example_job : # Environment Secretを使う場合はReusable Workflow側で設定 environment : ${{ inputs.env }} name : exec aws command runs-on : ubuntu-latest steps : - name : Configure AWS credentials uses : aws-actions/configure-aws-credentials@v1 with : aws-access-key-id : ${{ secrets.access_key_id }} aws-secret-access-key : ${{ secrets.secret_access_key }} aws-region : ap-northeast-1 - name : aws ${{ inputs.command }} run : | aws ${{ inputs.command }} .github/workflows/call-reusable-workflow.yml name : Call a reusable workflow on : workflow_dispatch : inputs : env : type : choice options : - dev - prod description : '環境' jobs : call-workflow : # owner: Reusable Workflowを定義しているリポジトリのownerを指定 # repo: Reusable Workflowを定義しているリポジトリの名前を指定 # path: Reusable Workflowを定義しているパスを指定 # filename: Reusable Workflowのファイルの名称を指定 # ref: Reusable Workflowのブランチまたはタグを指定(mainなど) uses : { owner } /{repo}/{path}/{filename}@{ref} with : env : ${{ github.event.inputs.env }} command : s3 ls # Resuable Workflowで使うsecretを渡す secrets : access_key_id : ${{ secrets.AWS_ACCESS_KEY_ID }} secret_access_key : ${{ secrets.AWS_SECRET_ACCESS_KEY }} Reusable Workflow側のログはそれぞれのステップごとに分かれるので見やすいです。 注意点 Composite Actionと同じく利用側とは別 リポジトリ にする場合、その リポジトリ は パブリック リポジトリ にする必要があります。 Resuable Workflow呼び出し側で設定した 環境変数 はResuable Workflow側には反映されません。 なので引数でResuable Workflow側に渡してあげる必要があります。 ワークフローの配置戦略 Composite Action/Reusable Workflowの注意点に記載した通り、別 リポジトリ に定義する場合はパブリック リポジトリ にする必要があります。 弊社では全ての リポジトリ をプライベー トリポジ トリとして運用しているため、パブリック リポジトリ とすることは許容できません。 なので、弊社では1つの リポジトリ に主要なワークフローを集める方針としました。 GitHub Actionsのワークフロー用 リポジトリ を作成 そこにデプロイなどのワークフローを作成 ワークフロー用 リポジトリ に定義した各ワークフローで使う共通処理(ビルドなど)をComposite Actionとして同 リポジトリ に定義 各ワークフローでは各アプリケーション リポジトリ をcheckoutしてビルドを実行 各アプリケーションの リポジトリ には、必要に応じてPRのマージなどをトリガーとして起動するワークフローを作成 ワークフロー用 リポジトリ に定義されたワークフローを API でcall これで各アプリケーション リポジトリ に同じワークフローが分散することを防ぎつつ、Composite Actionで共 通化 ができます。 他にも、そもそも同じワークフローが必要なアプリケーションは1つの リポジトリ にまとめてしまうなどの案もありましたが、移行 工数 などの観点から断念しました。 その他Tips デバッグ ロギング https://docs.github.com/ja/actions/monitoring-and-troubleshooting-workflows/enabling-debug-logging Repository secret に ACTIONS_STEP_DEBUG をtrueで設定すれば詳細なログを出力できます。 ローカルでワークフローを実行する https://github.com/nektos/act GitHub の公式ツールではありませんが、上記のようなツールがあります。 まとめ GitHub Actionsはテンプレートも充実しており、簡単に、かつパブリック リポジトリ なら無料で実装できるのでかなり便利だと思いました。 ただ、Composite Action/Reusable Workflowのパブリック リポジトリ にしか配置できないなど、若干痒いところに手が届かない的な部分もまだまだあります。 どんどん新しい機能が追加されて使いやすくなっていってるようですので、是非皆さんも試してみてください。 本記事が、これから GitHub Actionsを始めようとされている方の理解の一助になれば幸いです。 スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 www.wantedly.com