自己紹介 こんにちは。タイミーのデータエンジニアリング部 データサイエンスグループ所属の吉川です。 1年前にタイミーに入社し、データサイエンティストとして日々業務に取り組んでいます。 前職では大手ゲーム会社で全社のCRMや離脱予測のモデル構築を担当していましたが、縁あってタイミーに入社することになりました。 まず入社後のミッションは「データを活用したプロジェクトの立ち上げ」でした。 立ち上げにあたり、何に取り組むか、社内の問題を探索し、下記のようなビジネス面とデータ活用面の2軸で取り組む領域やテーマの選定を行いました。 ビジネス面: ポテンシャルがあり、ビジネスインパクトが大きいか データ活用面: データ量が多く予測モデルの活用などデータ駆動型のアプローチが取りやすいか 具体的な取り組みとして、顧客のスコアリングモデルを構築し、そのスコアを基に顧客フォローを実施するなど、仕組みの構築を行っています。 この記事の目的 ちょうど1年前にタイミーに入社したのですが、応募や内定承諾の意思決定をする時にこんな情報があったら欲しかったと思ったことを2つ、この記事で書きたいと思います。同じようにタイミーのデータサイエンティスト職への応募や内定承諾を迷われている方がいらっしゃれば、その方の一助になれば幸いです。 1. フルリモートで働けるのか? 入社時はフルリモートでしたが、あくまで一時的な状態で、そのうち出社にならないかという不安がありました。 私の転職理由は、前職は素晴らしい職場だったのですが、フル出社体制で片道1時間30分の通勤を強いられていたことが転職理由の一つでした。朝、子どもがまだ寝ている時間に出社しなければならず、子どもとの時間を確保するために転職を決意しました。そのため、新しい職場でもフル出社になれば、同じ問題の繰り返しになってしまうという懸念がありました。 実際に入社して1年経った今でも、入社直後と変わらずフルリモートで勤務できています。データサイエンスグループでは地方にお住まいの方もいらっしゃるので、フル出社になるということは、よほどの大きな経営方針がない限り今のところはなさそうです。 また、タイミーのビジョン、ミッションと照らし合わせても、この柔軟な働き方は今後も続いていくと考えています。 2. 転職をして、人間関係の資産がゼロの中、リモートで業務を進めるのは大変なのでは? 対面での関係性がある状態でのリモートワーク経験しかなかったため、上記のような不安を持ちました。 入社して1年経ちますが、データサイエンスグループをはじめ協働している他部門の心理的安全性が高く、入社前に抱いていた不安は杞憂に終わりました。 プロジェクトの実例 特に他部門との関係性がない中で多くの関係者と合意形成を図りながら業務を進めるのは、リモートだと大変なのではと考えていました。しかし、タイミーの組織はフラットで階層が少なく、コミュニケーションコストが低いため、リモートであってもプロジェクトマネジメントに大きな困難はありませんでした。 例えば、現在他部門と進めているスコアリングのプロジェクトでは、調整に必要な関係者も多くなく、MTGの場で意思決定できるため、ストレスなくプロジェクトを進行できます。 前々職で同じようなプロジェクトを経験しましたが、階層型組織にありがちな「MTGで方針決定後、さらに上位レイヤーに確認する」ということを何度も繰り返しており、意思決定から実行までにかなりの時間がかかっていました。その時との体感差でいえば、同じことが1/3くらいの期間でできたのではないかと思います。 リモート前提の組織設計 また、タイミーはリモート前提で組織が円滑に回るように仕組み化されています。従来の対面を基本とした働き方前提でリモート環境を組み込むと、情報共有の不足やコミュニケーションの断絶など、さまざまな無理が生じがちです。しかし、タイミーは最初からリモート前提での業務遂行ができるようにさまざまな制度設計がされているので、そのような問題を最小限に抑えられているように感じています。 具体的には、MTGはオンライン・オフラインどちらでも参加が可能だったり、ドキュメントを残して情報共有したりする文化が徹底されています。これにより、物理的な距離に関係なく、チーム全体で一貫性のある業務遂行が可能となっています。 データ基盤の整備 さらに、データ部門に限っていえば、タイミーではデータ基盤が整備されているため、BigQueryを叩けば必要なデータを即時に取得でき、これにより、データ利活用におけるコミュニケーションコストが抑えられていると感じています。 自律的な働き方と組織の成長 こうした環境下なので、自発的に課題を見つけ、自律的に動ける人にとってはとてもやりがいのある職場だと思います。階層が少ないことで意思決定が迅速に行われ、社員一人ひとりの意見やアイデアが直接反映されやすい。だからこそ、データサイエンティストは自身の専門知識と創造力を最大限に活かし、新しい取り組みを通じて組織全体の成長に寄与することができます。 加えて、タイミーでは最新のテクノロジーの活用にも積極的で、生成AIなどの取り組みも始まっています。社内GPTなども利用可能で、そうしたものを活用しつつ、業務効率化を進めています。実際、この記事の作成においても、誤字脱字のチェックに社内の生成AIツールを活用しました。 タイミーはデータサイエンティストにとって技術的な挑戦が可能で、組織的な柔軟性もある非常に魅力的な環境だと思います。 以上、1年前の入社時に、当時知りたかった2つのことを記事にまとめてみました。 今、タイミーへの応募や入社を検討中の方にとって、少しでも参考になれば幸いです。 We’re Hiring! タイミーのデータエンジニアリング部・データアナリティクス部では、ともに働くメンバーを募集しています!! 現在募集中のポジションは こちら です! 「話を聞きたい」と思われた方は、是非一度 カジュアル面談 でお話ししましょう!
スクラムマスターの吉野です! 株式会社タイミーは LeSS Yoake ASIA 2024 のスポンサーとして参加し、私も招待チケットを利用して現地に行ってきました! 今回の記事はDay5のレポートです! Day1のレポートは こちら セッション : 企業変革の現実から考える「組織レベルのアジャイル」への道のり (ABeam Cunsulting) 1つのチームから複数チームへ拡大するパターン 組織改革の形で、大規模な組織にアジャイルを適応するパターン 2軸にて、組織へアジャイルを広めていくストーリーが聞けました! 前者については、スクラムが成功したチームがあっても、2つ目以降のチームに同じ形で導入しようとしても、簡単にうまくいくわけではない、というお話でした。 ふりかえりの質が悪い、Doneの定義が形骸化している、頻発するメンバーの入れ替えに伴い開発が安定しない、というスクラムチーム立ち上げ期に経験する問題。スクラムマスターがチームの「自分ゴト化」をリードし、その後、見守る形へ移行していく流れで各問題の解決に向けて進められていました。 個人的な感想となりますが、かなり共感できたのは「スクラムマスターが背中を見せる」という点です。 スクラムマスターがリーダーシップを発揮するところがスタート地点だと考えています。なぜなら、まずはスクラムマスターがリードし、チームを成功に導き、その後適切にデリゲーションを行なっていく流れが大切だと考えています。 いきなり「xxした方がいいよ」「xxはやめた方がいい」とアドバイス中心になっても、チームおよび個人は受け止めることに心理的なハードルを感じてしまう可能性があります。 そのため、まずはリーダーシップを発揮することが、序盤においては大切だと考えています。 2つ目のパターンについては、大規模アジャイルの導入を成功させたい企業に対し、支援および現場に入ってリードしたお話でした。 経営層もどうすれば良いのかわからない、もしくは、経営層が考える実行計画がある状態からどうやって成功させるかを考えます。 実際にできる人はいるのか?試しに作ってみたら、できる可能性は見出せるのか?という形で、想像と小さく実験することにより、アジャイルの必要性を徐々に知ってもらうとのことでした。 ポイントをいくつか説明する中で、印象的だったのは“アジャイルという言葉を使わない”です。 アジャイルという言葉にはさまざまな印象が紐づいてしまっていたり、言葉に引っ張られてしまっったりで、本当に考えるべきことにフォーカスが当たらない可能性があるとのこと。これは私が好きな考え方でした!私もチームへスクラムを適応していく際には、「気がついたらスクラムをやっている」というパターンを選んだりします(必ずその形というわけではありませんが)。スクラムガイドを読み込み、チームがスクラムを形式から体現するのは大切だと思うのですが、「スクラムをガイド通りに行う」が目的になってしまうケースをいくつか経験してきました。アジャイルやスクラムはあくまで考え方や実行する上での選択肢であり、本当にやるべきことは顧客に届ける価値をいかに高められるか、そして事業をいかに成功させるかにフォーカスすることを忘れないため、あえて初期段階ではスクラムを意識させない形でリーダーシップを見せていくことも行っています。 クロージングキーノート : How to adopt LeSS (Bas氏) LeSSを導入する前からLeSS導入後について、 どのような準備が必要か 最序盤の動きは何が必要か どのような改善点があるのか を、Bas氏の経験を交えて話されていました。 意思決定はとても重要とのことです。 いくつかをピックアップすると、 私たちのプロダクトの定義はなにか? 内部的なことや小さなプロダクトを含めて大きく定義する必要がある。それは、顧客が思っているよりも小さく定義してしまうと顧客との対話が難しくなる プロダクトオーナーはだれか? 真のプロダクトオーナーは、プロダクトにまつわるお金を辿っていくとわかる。プロダクトの予算の決定権を持っている人はだれなのか 同一拠点で、リアルで顔を合わせながら仕事をする プロダクトの開発が複数拠点を跨いでいたり、チームメンバー同士が顔を合わせることがなく仕事をすると、協働がしづらくさまざまなデメリットの要因となる というお話が印象的でした。 LeSSだからLeSSのやり方があるわけではなく、他の形式や概念を採り入れた開発をしていても重要とすべきことは同じだと感じました。 自分たちのプロダクトを自分たちが一番理解し、必要な意思決定者を巻き込み、チームがコラボレーションしやすい環境でアウトカム最大化のためにアウトプットを最大化させられる働き方をする大切さの再確認ができました! Bas氏の苦悩が伝わったエピソードとしては、「マネージャーや経営層が変わった場合、そこまで積み重ねたものがすべてひっくり返ってしまう可能性がある。決して短くない数年間が、経営者の一声ですべて無くなってしまった経験もした。しかし、私はその事象に対して、どうすれば良いのかの答えをまだ持てていない」というお話でした。絶対的な力に抗えない状況は、苦しいですね...。もちろん私も答えを持っていませんが(笑)、一度LeSSを実施できた組織は横断的にノウハウを獲得できていると思うので、再挑戦に向かえるのではないか、と思っています。 以上がDay5のレポートとなります! 直接お話をしたことがなかったCraigさん、Basさんと直接お話ができ、私にとっては貴重な体験ができたカンファレンスでした。 LeSS Yoake ASIA 2024は初回ということもあり、次回以降はさまざまなところでカイゼンが行われると思います!それも、次の楽しみになりそうな印象でした! カンファレンスを運営されたみなさま、貴重なお時間をありがとうございました! (お弁当は好きなプレートをチョイスできる形式だったのですが、炭水化物を2つ選んでしまい、お腹いっぱいになりました)
こんにちは、タイミーでデータサイエンティストとして働いている小栗です。 今回は、機械学習モデルの予測の不確実性を定量化する手法であるConformal Predictionについてご紹介します。 Conformal Predictionとは 機械学習モデルの予測値がどの程度信頼できるか知りたい場面は多いと思います。 医療診断のように誤った予測が重大な問題につながる状況でモデルを使用する場合、予測の不確実性を定量化してそれを元に判断できると嬉しいです。 Conformal Prediction(以下CP)はUncertainty Quantification(不確実性の定量化。以下UQ)のパラダイムの1つであり、モデルの予測値の集合/区間を統計的に厳密に作成します。 Conformal Predictionで生成される予測集合の例。出典: Angelopoulos, Bates (2022) CPの利点 UQ手法には、分類モデルが出力する確信度を用いる方法、複数のモデルから算出した予測値の分散を用いる方法、学習サンプルと予測サンプル間の距離を用いる手法など、さまざまなものがあります。 これらのUQ手法に対するCPの優位性として、以下の点があります。 予測集合の中に真の結果を含むことをユーザーが指定したエラー率で保証できる モデルに依存しない手法であり、かつ計算コストが比較的低い 予測誤差の分布に関する仮定は要求されず、入力Xと出力Yのペアが独立同一分布(i.i.d.)であることのみを仮定する 他のUQ手法で算出した不確実性は1について保証できない場合があるため、1がCPの最も優れた特徴かと思います。 2に関しても、事前学習モデルの有効性が明らかになってきた昨今の状況を考慮に入れると大変ありがたい性質です。 また3についても、現実世界では予測誤差が正規分布でないケースがありますし、i.i.d.は依然として強い仮定ではあるものの機械学習では標準的な仮定であるため、CPの実用性につながっていると感じます。 予測の不確実性を算出する手順 CPにて予測の不確実性を算出するワークフローを簡単にご紹介します。 Conformal Predictionの手順(分類での例)。出典: Angelopoulos, Bates (2022) 以下の手順でキャリブレーションデータのConformal scoreを計算する データセットを学習データ、キャリブレーションデータ、テストデータに分ける 学習データを使ってモデルを訓練する ユーザーが許容できるエラー率αを設定する(例えば5%) キャリブレーションデータの各サンプルに対してConformal scoreを計算する。Conformal scoreは値が大きいほど予測が外れていることを表現できるものにする。scoreはユーザーがカスタマイズ可能。上図の例ではモデルの予測値であるsoftmax出力値を1から引いた値が利用されている キャリブレーションデータのscoreが全体の1-αとなる分位点を求める テストデータの各サンプルに対し、分位点より大きい予測値を持つクラスをピックアップし、予測集合とする Conformal score自体も不確実性を表現したスコアですが、その尺度をさらに厳密に較正するためにキャリブレーションデータを用いる、というのがざっくりとしたイメージです。 結果として、ユーザーが定めたエラー率が厳密に保証された予測集合が得られます。 CPの利用方法とコツ CPを利用したい場合、scikit-learn-contribプロジェクトとして開発されているPythonライブラリ” mapie ”を使えば、scikit-learn likeにサクッと実装できます。 回帰の場合 from mapie.regression import MapieRegressor mapie_regressor = MapieRegressor(regressor) mapie_regressor.fit(X_train, y_train) # エラー率αを設定(複数設定することもできる) alpha = [ 0.05 , 0.32 ] # 予測値と予測区間の上限/下限値を計算 y_pred, y_pis = mapie_regressor.predict(X_test, alpha=alpha) # 可視化に関するコードは省略 # ↓α=0.05の予測区間は点線、α=0.32の予測区間は色付きの帯で示されている 出典: https://mapie.readthedocs.io/en/latest/quick_start.html 分類の場合 from mapie.classification import MapieClassifier mapie_classifier = MapieClassifier(estimator=classifier, method= 'score' , cv= 5 ) mapie_classifier = mapie_classifier.fit(X_train, y_train) # 予測値と予測集合を計算 y_pred, y_pis = mapie_classifier.predict(X_test, alpha= 0.05 ) # 可視化に関するコードは省略 # ↓α=0.05での各クラスの領域が示されている 出典: https://mapie.readthedocs.io/en/latest/quick_start.html CPを行う際に意識したいこととして、Conformal scoreとアルゴリズムの選択があります。 CPではエラー率の保証は常に成り立ちますが、得られた予測集合が有益かどうかは主にConformal scoreによって決まります。 scoreによって予測誤差の大きさに応じて入力Xを正しくランクづけできる場合、予測が簡単な入力には小さな予測集合を、難しい入力には大きな予測集合が得られます。 逆に、ランクづけを正確に反映できないscoreを選択した場合、すべてのクラスが予測集合に含まれるなど、意味のない予測集合が得られてしまうことに注意が必要です。 アルゴリズムについては、データを学習データとキャリブレーションデータに分割するSplit conformal predictionをはじめとし、すべてのデータをモデルの訓練とキャリブレーションの両方に使用するCV+やJackknife+といったものがあります。 基本的なConformal scoreやアルゴリズムはmapieで実装されているため、気になる方は 公式ドキュメント をご参照ください。 CPは有用な手法である反面、前述のとおりi.i.d.の仮定が満たされない状況で出力された結果は信頼できないことに注意が必要です。 学習・評価時のデータと推論時のデータの分布が異なる状況は現実世界でしばしば存在するため、そのようなケースで得た予測結果を過信しないよう気をつける必要があります。 おわりに 本記事ではConformal Predictionの概要と利用方法についてご紹介しました。 CPのアイデアは1990年代にすでに提唱されていましたが、特にここ数年で機械学習コミュニティで人気が出てきたようです。 必要な仮定が少ない手法であり、mapieなどのライブラリも充実してきたことから、非常に実用的なUQ手法だと感じています。 すでに英語・日本語でいくつか有益な文献・記事が存在していますので、気になる方はそちらもご参照ください *1 *2 *3 *4 We’re Hiring! タイミーのデータエンジニアリング部・データアナリティクス部では、ともに働くメンバーを募集しています!! 現在募集中のポジションは こちら です! 「話を聞きたい」と思われた方は、是非一度 カジュアル面談 でお話ししましょう! *1 : "A Gentle Introduction to Conformal Prediction and Distribution-Free Uncertainty Quantification" *2 : "Conformal Prediction" *3 : 『直感的に理解するConformal Prediction』 *4 : 『モデル予測に自信がないときに、素直にわからないと答えConformal Predictionに助けてもらおう』
こんにちは! スクラムマスターを担当している吉野です! 株式会社タイミーは LeSS Yoake ASIA 2024 のスポンサーとして参加し、私も招待チケットを利用して現地に行ってきました! 本記事ではDay1のコンテンツのいくつかをレポートします! Opening Session オープニングキーノートは、Craig Larmanさんによる "AI & HR: Evolving Organizations in an AI World" というタイトルで、AIが私たちにもたらしてくれる変化を、AIができることの紹介、AIの変化に合わせて個人の役割の変わり方からチームのあり方の変化、と、徐々にスコープを広げて話されていました。 特に印象的だったのは、一部の専門性をAIが担ってくれる未来はそう遠くないというお話でした。 現在は各スペシャリストで構成されています。 それは、各メンバーがバリューを個々の専門性の領域で発揮しているからとなります。 チームメンバーの専門性が特別必要な領域や、ルーティンワークを必要とする業務は、AIにお任せすることで、個人の時間に余裕を持たせることができます。余裕ができた時間で何をするのか? それは、学習です。それも自身の専門性を深める学習ではなく、他の領域についても学ぶことが大切とのことでした。 そうすることで、個々が専門性を高めるチームではなく、様々なことに関わることのできるメンバーでチーム内の協働を促進し、よりチームの透明性を高められるということでした。 クロスファンクショナルなチームを作ることに苦労されているお話をよくお聞きしますが、このようにAIを使うことで、チーム組成を進めることができるとのこと。 (GLAD Developer = Generative-AI & LLM-Assisted Development) そして、将来は一人の開発者と複数のAI Agentsの可能性も… AIが専門性を担ってくれる可能性と、チームの形の可能性を一つ知れたセッションでした。 スポンサーセッション(Red Hat) Red Hatさんによるスポンサーセッションでは、いくつかのチームでの議論を通しつつ、チームのスクラム/LeSSをより促進させるためには、組織変革へ目を向ける必要があることをメインに話されていました。 また、その組織変革に必要な要素を表す「スターモデル」を通して戦略/構造/プロセス/リワード/人材の考え方について、具体的な例を通しての説明がありました。 実際に使用し、方針を現場で立てるには、各項目のつながりとWhyとなる軸は説明できる必要があると思いますが、観点が簡潔にまとまっているモデルで、とっかかりに使えそうな印象を受けました。 また、その組織を変えることができる人物は「スクラムマスター」とのことです。 現在のタイミーでは「Agile Practice Team」というスクラムマスターで構成される、組織に向き合うチームがあります。 私も所属しているチームで、この発表はチームの存在意義を言語化してくれている!という気持ちになりました。 その他にも、ディスカッションの時間もあり、チームで多くの意見を出し合いました。 1時間とは思えない密度のセッションでした! The LeSS Company CEOによるトークセッション フルスタックCEOというパワフルな自己紹介をされていた、The LeSS Company CEOのBastiaan氏によるセッションでした。 LeSSの広がりやどのような場で活用されているのか、世界レベルでのお話を聞くことができました。 また、本セッションテーマについて、次のセッション時間に直接お話を伺いしました。 Bastiaanさん、Basさん、Akiさんとの廊下トーク 本来であれば、LeSS Morning 主催のワークショップの時間だったのですが、偶然にも廊下で3人のお姿を見つけて、話しかけてみました。 お話しした内容のうち、いくつかを以下に記載します。 最適なスケールフレームワークを選択するには? A. やりたいことをマネージャーなどに伝えるのではなく、まずは経営層の困っていることなどに寄り添うこと。システムモデリング(思考)などを使用し、困っていることを明確にする。 その中で、いくつかのフレームワークを当てこんだ時にうまくいくのか、うまくいかないのか、どのようなデメリットがあるのかを明確にする。 まずは、経営層の困っていることやプロダクトの課題を見える化することが大切。 特に、昨今はチームの認知負荷を減らすことが重要視されている傾向がある。多くの経営層の悩みにつながっており、あなたの場でも関係する可能性があるので見てみると良い。 Stage of Agileのデータから、LeSSの割合が少なくなっていて、SaFEの割合が多くなっている。これはなぜなのか? A. ガードナーに伺ったところ、実際のLeSS利用率は増えている。ガードナーは7000人にヒアリングしている。State of Agileの方は1000人しかデータを取っていないので、どのような人に向けてのアンケートかで、内容が変わってしまう。また、その人の意思とは反するデータになっているケースもある。例えば、ボスが変わってしまったことで、利用しているフレームワークがガラッと変わってしまうなど。なので、データの信憑性としてはそこまで高いものではないため、ある程度参考値ぐらいに考えた方が良い。 (名刺もいただきまして、「困ったらいつでも連絡して欲しい!」とオープンに接してくれたのがめちゃくちゃ嬉しかったです!) スポンサーセッション : LINEとヤフーそれぞれの組織でのLeSS実践奮闘記 LINEヤフーより、nakoさん( @nako418 )の登壇でした。 LeSSに挑戦し、難しい点に当たりつつもLeSSの実践を進めていったお話でした。 印象的だったのは、常に何ができるのかという選択肢を考え、一番最適な形を選択し続ける動きをされていたことです。 例えば… フィーチャーチームとコンポーネントチームのどちらが良いのか?(理想と現時点) オーバーオールイベントの参加者はどのような人を集めるのか? など、要所要所の考える必要があるポイントでしっかりと選択肢を洗い出し、都度最適と思えるものを理由と合わせて選択して実践していくことは、とても参考になりました。 また、困ったお話についても、バックログの整理方法など多くの人が向き合うことになりそうな問題をピックアップされていて、すごく共感が多かったセッションでした! (整理は、ひとまずエピックレベルで並び替え、必要になる上位のものに一旦フォーカスしていくとのことでした) Q&A Craigさん / Basさんに質問Time 一度業務で抜けたため終盤のみの参加でしたが、それでも濃い質問と鋭い返答が多い印象でした。 例えば… 質問 : マネージャーがいない組織における、責任の所在は?生産性の可視化ができていない、となった場合の問題は誰が責任を負うのか?マネージャーがいない組織において、どのように解決していくのか。 回答 : チームが責任を持つことが大切 スクラムマスターがそれを促す 人数の少ないマネージャーができるのであれば、人数の多いチームメンバーもできるのではないか? “マネージャーでないとできない” という制約を個人の中に持ってしまっていたことに気がついた瞬間でした。大切なのは、“なぜチームが気にしないのか” ということを考え、システムとシステム構造を変えることが大切と話されていました。 ディナー (写真を撮っていなくて残念…) 8種類ぐらいのビールと美味しそうな夕食が並んでいて、ビュッフェスタイルの懇親会でした。 ビールを何回もおかわりしつつ、多くの方とお話しして回りました。 そのため、夕食にはほとんど手をつけず… イベント全体で時間が押していたため、1時間と短めの懇親会でした。 (もっと多くの方と交流したかった…!!) 終わりに 以上でDay1のレポートとなります! 運営の方にお話を伺ったところ、初めての大規模開催とのことでした。 機材トラブルや時間が押してしまった等、いくつか大変そうなシーンもありましたがあの規模のカンファレンスを開催されていたのはめちゃくちゃすごかったです! (同じ規模のイベントをいつか私も開催したい) まだDay5がありますので、引き続き楽しみつつ学んでいきたいと思います!
こんにちは! データアナリストの@takahideです。 タイミーではプロダクト向き合いの分析業務を行なっています。 先日、東京都立大学の経済経営学部にてデータアナリストの講義を行わせて頂きました。 データアナリストの魅力やアカデミックな知の重要性に改めて向き合うことができたので、ご紹介できたらと思います。 内容は以下になります。 講義の背景 講義内容 事業と組織に関して データアナリストの業務内容 アカデミックな知の活用 講義の御礼 最後に We’re Hiring! 講義の背景 講義に至った背景は、知り合いでもある 米田泰隆准教授 とお話しした際に、データアナリストの業務内容が学生の皆さんの興味と合致するのでは、と意気投合したことでした。 今回お伺いしたのはゼミ形式の講義で、計量経済学から最新のデータサイエンスまで幅広い知見に基づいたデータ分析の授業が行われていて、業務内容との整合性も高いと感じました。 講義内容 講義は大きく下記テーマを扱いました。本ブログでは、1、3、4を中心にお話します。 1.タイミーの事業と組織に関して 2.データアナリストになるまで 3.データアナリストの業務 4.アカデミックな知との繋がり 事業と組織に関して まずは、タイミーの事業説明を行いました。 タイミーはワーカーと企業の双方が出会うツー・サイド・プラットフォームであり、分析対象となるデータも「ワーカー」「企業」「マッチング」に関するものが中心となります。 ビジネスモデルは一見単純に見えますが、ワーカーと企業の複雑な意思決定を読み解く必要があり、データアナリストとして非常にやりがいがあります。 次に、タイミーのデータアナリストの組織に関してお話ししました。 大きくは「プロダクトアナリティクス」と「ビジネス&マーケティングアナリティクス」という2つのグループに分かれていて、各グループが、担当領域に対して分析で得られたインサイトを提供しています。 例えば、プロダクトアナリティクスグループであれば、顧客に提供する価値体験ごとにチームが組成され、各チームにデータアナリストがアサイン、チームの責任者と協働して分析業務に携わります。 また、チーム単位の分析とは別に、会社横断での分析も行っていて、こちらはプロダクト✖︎マーケティングのようにグループ横断のものも多いです。 データアナリストの業務内容 こちらのパートでは、データアナリストの業務内容を説明しました。 タイミーのデータアナリストは、プロダクト、マーケティング、ビジネスのメンバーと伴走しながら、意思決定をサポートしています。 データアナリストは、データを用いた分析をもとに、全社戦略や事業に貢献することが期待されています。自分が行った分析がきっかけで、顧客に価値を届けられた瞬間は非常に大きな達成感を得られます。 では、「データを用いた分析」を進める上で必要になるものとは何になるのか? 「データを用いた分析」なので複雑な分析スキルが重要と思われがちですが、個人的には「仮説構築」が最も大切だと感じています(もちろん高度な分析スキルが不要というわけでは全くないです)。 「分析」を上流工程から下流工程へのプロセスと考えると、上流工程で作られた仮説に基づいて様々な定量・定性分析が行われます。そのため、仮説が間違っていると、定量・定性分析自体も間違った結論を出すことになってしまいます。 仮説の精度を上げる手法は色々ありますが、重要なのが「言語化」だと考えています。 仮説構築を「何気ない問いを、定義を確認し、抽象化して解像度を上げる一連の流れ」と考えると、「個人的な問い」をメンバー間で納得できる「共通言語」に仕立てていくことが大切になります。 アカデミックな知の活用 最後は、データアナリストとアカデミックな知の繋がりに関してお話ししました。 アカデミックな知は先ほどの「言語化」を考える上で非常に重要だと考えています。 個人的に、データアナリストの業務を長く続けるにつれて、アカデミックな知の価値を強く感じることが多くなってきました。 向き合う課題が複雑であるほど、それを解くためのアイディアも多様な切り口が求められますが、アカデミックな知はそうした切り口の宝庫になっていると感じます。 具体例をいくつか挙げてみたいと思います。 経済学 - 経済学の一分野であるマーケットデザインはマッチングの最適化を考える上で参考になります。 統計学 - プロダクト開発では、施策の効果判断を行う際にランダム化比較試験を行います。この背景には統計学の知見があります。 人類学 - ユーザーの声を知ることはプロダクト開発において非常に重要ですが、定性調査の各種手法の背景には人類学や社会学の知見があります。 論理学 - 仮説を構築する上で、演繹的推論、帰納的推論といった古典的な推論が必要になります。また、仮説的推論は創造的な思考を生み出すヒントになります。 これらの知見は、必要な時に使える便利なツールではないですが、様々な観点でものごとを捉えられるほど「言語化」が捗り、結果として仮説構築など分析の重要なプロセスを推進しやすくなります。 講義の御礼 改めて、今回データアナリストの講義を行わせて頂きありがとうございました! データに対する理解度が高い方が多く、また、ご自身で起業されていて分析の勘所を備えているなど、お話しして非常に大きな刺激を受けました。 大学で学べることを羨ましく思いつつ、アカデミックな知と業務との繋がりをこれからも作っていきたいと思いました。 最後に 今回は、データアナリストの外部活動として、大学での講義に関してお話ししましたが、きっかけは「大学で講義を行いたい」という提案でした。 タイミーでは、メンバーの意見を真摯に聞いて、サポートしてくれる文化があります。 年次に関わらず分析プロジェクトの提案ができ、新しいことにチャレンジしたい方にとって非常に魅力的な環境だと思います。 We’re Hiring! 私たちは、ともに働くメンバーを募集しています!! カジュアル面談 も行っていますので、少しでも興味がありましたら気軽にご連絡ください。 hrmos.co
エンジニアリング本部 プラットフォームエンジニアリングチームの徳富です。我々のチームでは、CIパイプラインの効率化と開発体験の向上を目指し、CircleCIからGitHub Actionsへの移行を進めてきました。移行によってテスト・静的解析(以降CIと記載する)の実行時間をp95で9分短縮しましたが、この移行にはいくつかの課題もありました。今回は移行の背景や移行時の苦労について紹介します。 本記事のまとめ CircleCIからGitHub Actionsに移行 し、CI実行時間をp95で 9分短縮 並列実行やテスト結果の連携 における課題を解決し、効率化を実現 テスト結果のPR通知や分割テスト の仕組みを工夫し、開発体験を向上 CircleCIからGitHub Actionsへ移行した背景 以前利用していたCircleCIのPerformanceプランでは、並列実行できるジョブ数が80に制限されていました。1回のCI実行に40の並列ジョブを使用していたため、同時にCIを実行できる開発者は最大2名まで。この状況では、今後開発者が増加するにつれて、 CIの待ち時間が長くなるリスクを抱えていました。 プランのアップグレードも検討しましたが、以下の理由から GitHub Actionsへの移行 を決断しました。 理由1: 学習コストの削減と効率化 弊社では基本的にGitHub Actionsを利用していますが、RSpecテストと静的解析のみにCircleCIを使用していました。これをGitHub Actionsに統合することで、 CircleCIとGitHub Actionsの両方を学ぶ必要がなくなり 、開発者の 学習コストが削減 されます。また、以前はCircleCIでのCI完了後に、デプロイをGitHub Actionsで行うワークフローでした。CIの実行基盤をGitHub Actionsに移行することで、テストもデプロイもGitHub Actionsに統一できるようになります。これにより、デプロイフローがよりシンプルになり、メンテナンス性も向上しています。 理由2: セキュリティリスクと管理の効率化 CIプロバイダーをGitHub Actionsに一本化することで、複数のプロバイダーを管理する必要がなくなり、 情報流出のリスクが低減 し、一貫したセキュリティ対策を実施できます。さらに、管理するツールが減ることで 管理業務が簡素化 され、運用が効率的になります。 移行する上での課題となっていたポイント テスト実行時間ベースでテスト分割する仕組みがGitHub Actionsにない テストの失敗した情報をわかりやすく参照できるUIがない matrix strategy を使って並列実行をすると 後続のjobに結果を送れない Flakyテストを一覧で参照できない 1. テスト実行時間ベースでテスト分割する仕組みがGitHub Actionsにない CircleCIには、 CircleCI CLI を使用してテストファイルを実行時間ベースで分割する機能 がありましたが、GitHub Actionsには同様の機能がありませんでした。 そこで、代替として mtsmfm/split-test ライブラリを導入し、 RSpecテストを均等に分割 する仕組みを整えました。このライブラリは、過去のテスト実行時間をもとに、実行時間が均一になるようにテストファイルを分割します。そのためデフォルトブランチのテスト結果をS3に保存し、その結果を利用してテストを効率的に分割・実行するフローを構築しました。 2. テストの失敗した情報をわかりやすく参照できるUIがない CircleCIには、 テスト結果をわかりやすく表示する機能 が標準で備わっており、開発者はテストの成否や詳細を簡単に確認できました。しかし、GitHub Actionsにはそのような標準機能がなく、開発者がテスト結果を確認するのに手間がかかってしまうという課題がありました。 そこで、 SonicGarden / rspec-report-action にパッチを適用し、 テスト結果をPRにコメントする仕組み を導入しました。この改善により、CircleCIにログインせずにPR内で直接テスト結果を確認できるようになり、作業効率が向上しました。 3. matrix strategy を使って並列実行をすると 後続のjobに結果を送れない GitHub Actionsでは、 matrix strategy を使うことで、並列実行ができます。しかし、この並列実行には問題があり、 後続のジョブにテスト結果を渡せない という課題がありました。 最初は、各マトリックスで生成されたテスト結果をアーティファクトとしてアップロードし、後続のジョブでダウンロード・集計する方針を検討しました。しかし、25並列で実行していたため、アーティファクトのアップロード頻度が増加し、すぐに GitHub Actionsのレート制限 に達してしまいました。 この問題に対処するため、テスト結果をGitHub Actionsのアーティファクトではなく、 S3にアップロード する方法に切り替えました。これにより、レート制限の問題を回避でき、後続のジョブでテストの結果をPRに投稿する仕組みが無事に機能するようになりました。 4. Flakyテストを一覧で参照できない CircleCIではテストインサイトを使ってFlakyテストを確認できましたが、GitHub Actionsにはそのような機能がありませんでした。当初は CodecovのTest Analytics を使ってFlakyテストを確認していましたが、絞り込み機能が不十分で、ページの表示に時間がかかるという問題がありました。 そこで、現在は Datadog Test Visibility を利用してFlakyテストを確認しています。Datadogを使用することで、テストのTrace情報などの詳細なデータを収集できるだけでなく、不安定なテストが検出された際にアラートを設定することも可能になりました。 完成したワークフローの一例 ここで、最終的に完成したワークフローの一部をご紹介します。GitHub Actionsへの移行を検討している方にとって、少しでも参考になれば幸いです。 name : ci on : push : branches : - '**' tags-ignore : - '*' concurrency : group : ${{ github.workflow }}-${{ github.ref }} cancel-in-progress : true permissions : id-token : write contents : read pull-requests : write jobs : generate-matrix : runs-on : ubuntu-latest timeout-minutes : 1 outputs : parallelism : ${{ steps.set-matrix.outputs.parallelism }} ids : ${{ steps.set-matrix.outputs.ids }} steps : - id : set-matrix run : | parallelism=25 # テストの並列数 ids=$(seq 0 $((parallelism - 1)) | jq -s | jq -c) echo "parallelism=[$parallelism]" echo "ids=$ids" echo "parallelism=[$parallelism]" >> "$GITHUB_OUTPUT" echo "ids=$ids" >> "$GITHUB_OUTPUT" rspec : needs : [ generate-matrix ] runs-on : ubuntu-latest timeout-minutes : 20 env : DD_CIVISIBILITY_AGENTLESS_ENABLED : true # Datadog Test Visibility用 DD_API_KEY : ${{ secrets.DATADOG_API_KEY }} # Datadog Test Visibility用 DD_ENV : ci # Datadog Test Visibility用 strategy : fail-fast : false matrix : parallelism : ${{ fromJson(needs.generate-matrix.outputs.parallelism) }} id : ${{ fromJson(needs.generate-matrix.outputs.ids) }} services : # rspec実行に必要なコンテナを起動 steps : - name : Generate token id : generate_token uses : actions/create-github-app-token@v1 with : app-id : app idを指定 private-key : キーを指定 - name : Checkout uses : actions/checkout@v4 with : token : ${{ steps.generate_token.outputs.token }} - name : Setup Ruby with caching uses : ruby/setup-ruby@v1 with : ruby-version : 3.3 bundler-cache : true - name : Configure AWS credentials uses : aws-actions/configure-aws-credentials@v4 with : role-to-assume : # S3にアクセスできる権限をもつロールを指定 aws-region : # リージョン名 # NOTE : デフォルトブランチで記録された各RSpecテストの実行時間データをダウンロード - name : Download rspec junit run : aws s3 cp s3://デフォルトブランチでのテスト結果(JUnit XML)が保存されているS3を指定 tmp/rspec_junit --recursive # NOTE : テストファイルを分割するためのツールをダウンロードし、後続ステップでテストファイルをダウンロードして分割する。 # これにより、マトリックスを使用して並列で実行されるRSpecテストが均等に分割され、 # すべてのテストジョブがほぼ同時に完了するようにする。 - name : Split test file run : | curl -L --output split-test https://github.com/mtsmfm/split-test/releases/download/v1.0.0/split-test-x86_64-unknown-linux-gnu chmod +x split-test - name : RSpec run : | # テストファイルを分割しテストを実行する bundle exec rails db:create db:schema:load ./split-test --junit-xml-report-dir tmp/rspec_junit \ --node-index ${{ matrix.id }} \ --node-total ${{ matrix.parallelism }} \ --tests-glob "spec/**/*_spec.rb" \ --tests-glob "packs/*/spec/**/*_spec.rb" | xargs bundle exec rspec \ --format progress \ --format RspecJunitFormatter \ --out report/rspec_junit/${{ matrix.id }}.xml \ -f j -o report/results/${{ matrix.id }}.json \ -f p # テストが完了したら、テスト結果をS3にアップロードし、後続のジョブで利用できるようにする - name : Upload test report if : always() run : | aws s3 cp report s3://テスト結果が保存されているS3を指定 --recursive rspec-status : runs-on : ubuntu-latest timeout-minutes : 1 needs : [ generate-matrix, rspec ] if : ${{ !cancelled() && github.event_name == 'push' }} steps : - name : Check previous job status run : | if [ "${{ needs.rspec.result }}" == "success" ] ; then echo "テスト成功" else echo "テスト失敗" exit 1 fi upload-rspec-junit : runs-on : ubuntu-latest timeout-minutes : 2 needs : [ generate-matrix, rspec ] if : github.ref == 'refs/heads/main' # デフォルトブランチを指定 steps : - name : Configure AWS credentials uses : aws-actions/configure-aws-credentials@v4 with : role-to-assume : # S3にアクセスできる権限をもつロールを指定 aws-region : # リージョン名 - name : Download rspec junit run : aws s3 cp s3://各マトリックスでのテスト結果が保存されているS3を指定 rspec_junit --recursive - name : Upload rspec junit run : aws s3 cp rspec_junit s3://各テストのJUnit XMLをアップロードするS3を指定 --recursive pr-comment : needs : [ generate-matrix, rspec ] if : ${{ !cancelled() }} runs-on : ubuntu-latest timeout-minutes : 2 steps : - name : Generate token id : generate_token uses : actions/create-github-app-token@v1 with : app-id : app idを指定 private-key : キーを指定 - name : Checkout uses : actions/checkout@v4 with : token : ${{ steps.generate_token.outputs.token }} - name : Configure AWS credentials uses : aws-actions/configure-aws-credentials@v4 with : role-to-assume : # S3にアクセスできる権限をもつロールを指定 aws-region : # リージョン名 - name : Download RSpec reports run : aws s3 cp s3://各マトリックスでのテスト結果が保存されているS3を指定 report --recursive - name : RSpec Report if : steps.find_pr.outcome == 'success' uses : SonicGarden/rspec-report-actionアクションに一部パッチを適用したアクションを指定 with : token : ${{ steps.generate_token.outputs.token }} json-path : report/results/*.json comment : "${{ github.event_name == 'push' }}" # PRにコメントするかどうか pull-request-id : ${{ fromJson(steps.find_pr.outputs.pr_json).number }} hideFooterLink : true 得られた成果 テスト時間の短縮による開発効率の向上 GitHub Actionsへの移行により、 p95でテスト実行時間が9分短縮 されました。並列実行の制限がなくなったことで、CI/CDのパフォーマンスが大幅に向上しました。この改善により、開発者の作業効率が向上し、より早くフィードバックを得られる環境が整いました。 CircleCIの時はp95で15分 GitHub Actionsに移行後はp95でテストが6分程度で終わるようになっているになっている 移行のまとめと今後の課題 CircleCIからGitHub Actionsへの移行により、 テスト実行時間の大幅な短縮 という大きな成果を得られました。しかし、移行に伴う課題も少なくなく、とくに テスト結果の連携 や テストの分割 には工夫が必要でした。 今後は、GitHub Actionsの新機能やさらなる最適化手法を活用し、テストやデプロイのより一層の効率化を目指していきます。本記事が、同様の課題に直面している方々の参考になれば幸いです。
こんにちは。okodoooonです🌵 ラスベガスに来てから、同行しているメンバーと手分けをしてセッションを聞いて、知見をまとめる作業に追われており、なかなかハードな日々を過ごしておりました。 今回は初日に聞いた5つのセッションがどれも知見に溢れるものだったので、そちら紹介していきたいと思います💪 スライドが公開されたり他メンバー担当分の記事が上がり次第リンクなど追って貼っていきます。(2日目以降のやつも頑張って書いております) 目次 Data alone is not enough(訳:データだけでは不十分である) 概要 セッション内容の紹介 感想 Breaking the mold: A smarter approach to data testing(型破り:データテストに対するより賢明なアプローチ) 概要 セッション内容の紹介 感想 How dbt transformed FinOps cost analysis at Workday(訳:dbtがWorkday社のFinOpsコスト分析をどのように変革したか) 概要 感想 Surfing the LLM wave: We can't opt out and neither can you(訳:LLMの波に乗る:私たちも、あなたも避けることはできない) 概要 セッション内容の紹介 感想 Semantic layers: The next data revolution or just overrated hype? (訳:セマンティックレイヤー:次のデータ革命か、それとも過大評価された流行か?) 概要 セッション内容の紹介 感想 Data alone is not enough(訳:データだけでは不十分である) 発表者:Preston Wong (Analytics Engineer @ Settle) 概要 データを提供できる組織や環境を作るのではなく、洞察を提供できる組織や環境を作るべきで、 理想の組織に改善されるために、Settleが取り組んでいるJTBDというフレームワークの紹介をしていました セッション内容の紹介 以下のような画像と こちらブログ の引用ともに レポーティング業務はデータ組織にとってゲートウェイドラッグのようなものだ 。 これらのレポートは本当に意味のあるものなのか?変化を促しているのか?実際に使用されているのか?誰かがそれを見て、2秒で終わるために7時間も費やすのはなぜだろう? 最近のデータイニシアチブは、情報へのアクセスを民主化することに重点を置きすぎており、ビジネスへの影響を促進することに十分に注力されていない。どんなに多くの情報があっても、それだけでは組織の変革を促すことはできない。 という強烈なメッセージを発信していました。 とても耳が痛いですね。 「 ビジネスインパクトをいかに生み出すか 」という観点なしに、ただデータを出力し続けるチームになってしまうことへの懸念を紹介してくれました。 それらの懸念を紹介した後に「データチームがどのようにしてビジネスのデータが持つ潜在能力を最大限に引き出せるのかを考える必要がある」と続き、 レポーティング業務から抜け出せない理由はコンテキストを持っていないことが多いからと説明していました。 良い例として、Q3の新規アクティブ顧客数が安定して増加しており、最近の月では20%増加したことが挙げられます。データ担当者がこれに気づいたとしても、直接的な文脈を持っているとは限りません。このデータには、最近の企業買収があったことや、その顧客基盤を取得したことが含まれておらず、それが20%増加の理由です。この説明はデータ自体では行えず、他の誰かが説明する必要があります。つまり、なぜそうなったのかを答えるのは難しいのです。 Settleではデータチームを適切にステップアップさせていくためにデータ組織の JTBD(JobToBeDone) を設定しており、その運用によってレポーティング組織から適切に脱却しつつあると紹介しています。 JTBD of Modern Data Team データの活用 メトリクス管理 プロアクティブなインサイト 実験のドライブ データとのインターフェース 紹介されていた各要素ごとの課題例のproblemとimpactがこちらです(ちょっと文量が多いので畳んでいます)。 データの活用(クリックすると展開) データの活用 オペレーショナルデータを必要とするチームに提供すること ex. マーケティング部門からのリクエストで「ハブスポットで解約した顧客に自動メールを送信し、タッチポイントとアウトリーチを行いたい」 解決したHow 多様なソースからのデータを、主に内部データを含めて、dbtモデルに統合。このデータマーケットは、ビジネスの包括的な表現として機能し、意思決定に不可欠な業務属性、将来のフラグ、メトリクスおよびSSoTを提供。 リバースETLと組み合わせることで、各チームの好みや主要なSaaSツール(SalesforceやHubSpot、Intercomなど)にシームレスに同期されたSSoTを持つことが可能となり、効率的なデータ活用とリアルタイムのインサイトが実現可能。 インパクト このワークフローを合理化し、データを取得して作業を行うのに何時間も費やす代わりに、数分で済むようになった。 このアプローチにより、彼らはツールセットを簡素化し、結果として効率的なセルフサービスモデルを実現。 マーケティングチームは最新の関連データに直接アクセスでき、タイムリーかつ正確に実行できるようになった。 メトリクス管理(クリックすると展開) メトリクス管理 共有された定義と重要な事実の基準を作成すること ex. もしファイナンスのステークホルダーに尋ねれば「アクティブカスタマー」とは、自分の銀行口座を接続した人だと言うでしょう。マーケティングに聞けば「アクティブカスタマー」は、完全なオンボーディングフローを完了した顧客だと言います。しかし、プロダクトに尋ねると「アクティブカスタマー」は、最近プラットフォーム上で活動があった顧客だと言います。では、どれが本当に正しいのでしょうか? 解決したHow セマンティックレイヤーを活用することで、これらのメトリクスを明確に定義し、文書化。 インパクト Hexのようなツールや、ExcelやGoogle Sheetsを好むステークホルダーがアドホックレポートを作成する際にも使用可能に。 指標に関する煩わしい問い合わせ対応が大幅に減少。 プロアクティブなインサイト(クリックすると展開) プロアクティブなインサイト プロアクティブ(=先手を取れる)ようなデータの提供 (= 先行指標を提供) ex. 顧客の解約が若干増えているが、どのデータが私たちが顧客を維持するためによりプロアクティブになる手助けをしてくれるか? 解決したHow 戦略的リーダーを特定、ビジネスユニットと密接に連携 「アシュアランススコア」を開発 顧客がサービスを解約する可能性を定量化し、チームが迅速に行動を起こすためのアラートを受け取れるようにする インパクト 。このデータ駆動型のアプローチにより、リスクのある顧客を早期に検出し、ターゲットを絞った介入措置を講じることが可能に。 顧客のライフタイムバリューを向上させ、会社の長期的な成功と持続可能な成長において重要な役割を果たすことに成功。 実験のドライブ(クリックすると展開) 実験のドライブ プロアクティブなインサイトの発見と連携しながら、ビジネスに対して測定可能な影響を与えるための実験を推進すること ex. 現在使用している機能に問題が発生しました。リスクを高めたり、ビジネスへの影響を評価したりするために利用できるデータポイントはあるか? 解決したHow 組織内のステークホルダーと協力して、実験を展開 インパクト 全体のスコアを向上させることができ、非常に洞察に富んだ重要な結果を得ることに成功 データのインターフェース(クリックすると展開) データのインターフェース ビジネス全体にわたるチームメンバーに必要な情報や結論を提供し、彼らがそれを活用できるようにすること ex. この収益はどこから来ているのか?モデルでどのように定義されているのか?この数字を週次レポートに使用しても良いのか? 解決したHow 彼らが自分自身で質問を探求し、答えを見つけるために必要なツールを提供できるようにする description生成をAIに実行してもらいその内容をチェックするプロセスを実行する dbtとmetabaseを接合して、metabase上でdbt exploreと同様のメタデータを閲覧可能とする インパクト 最終的にデータチームのレポーティング作業負担を軽減 JTBDのマトリクスは最終的にこのような形になります。 Settle社による所見 生のデータ出力だけでは不十分であり、JTBDフレームワークは、ステークホルダーのコンテキスト、動機、および具体的なニーズを理解することが、データを実行可能なインサイトに変換し、ビジネスを推進するために重要である。 私たちのステークホルダーを支援するために、データの活用を推進し、より効率的なセルフサービスモデルを実現するべきである。これにより、ステークホルダーは自身の専門分野で最も得意なことに集中でき、データの整理に関する煩わしさから解放される。 感想 事業会社のデータ部署に勤めている僕としては、めちゃくちゃ刺さりました。 JTBDを弊社なりに構築しつつ、もっとアナリストやDSと協働して、意思決定に刺さる指標がビジネスに素早く届けられるような状態を構築しなきゃなあ。 なぜ早くデータを出せるようにするのか、なぜメタデータを拡充するのか、なぜこのようなインターフェースを構築するのか、などをしっかりとビジネスインパクトと紐づけた上で、今後意思決定をしていけるようになれる気がしています! Breaking the mold: A smarter approach to data testing(型破り:データテストに対するより賢明なアプローチ) 発表者:Anton Heikinheimo (Senior Data Engineer @ Aiven) , Emiel Verkade (Senior Analytics Engineer @ Aiven) 概要 僕たちはdbt testを書きまくっているけれど、デリバリーが遅くなることの方がデータ品質において深刻なケースが多いよね。 warningの利用やWHERE句による代替で不要なテストを消してデータ品質を向上させていこう セッション内容の紹介 朝にSlackを開いて、dbtパイプラインのfailedの通知を確認して、トリアージを決めて、ステークホルダーと合意形成して、解決策を実装・テストしてデプロイする。そんな朝がたくさんあるのはおかしい。dbtのデータテストのベストプラクティスに従っているはずなのに そんな言葉から始まった本セッションでは、以下のようなMEMEでテストを書くこととデリバリー品質の向上が一致しないことに対する課題感を説明していました。 現状のdbtテストを単純に実装していくと、以下のような課題点があると説明しています。 テストが落ちるとその下流のビルドがすべて落ちるので、データの品質が落ちる。 特定のモデルが更新される一方で、他のモデルが更新されないことがありロジックによっては致命的である。 buildコマンドではテストは落ちるが、run → testの順番なのでrunによって不正なテーブルが出力される。 データテストが何であるか、そして典型的なデータセットアップがどのようなものか を以下の図を用いて説明していました。 この図における右と左のアサーションテスト(正常な場合に予測されるものと一致していることを確認するテスト)の役割を整理すると、以下のように記述できます。 左側のアサーションテスト ソースデータに関する仮定をテストするのに役立ちます。 このデータは、外部および内部のソースから来る可能性があり、データコントラクトやQAフレームワークが組み込まれていないこともあります。 右側のアサーションテスト 私たちが適用したロジックに関する仮定をテストするためのもの。 すでにソースデータをテストし、それが期待に合致していることを確認していると仮定しているもの。 入力データが検証された後、出力データをテストすることで、私たちはロジックの整合性を確認できる。つまり、dbtプロジェクトで行ったすべての変換が、期待した結果をもたらすことを確実にするためのテスト。 例えば左側のアサーションテストで落ちた場合は図中の Data と記載されたテーブルに悪いデータが留まり、左のアサーションテストが解決されるまで更新されない ショートサーキット が発生します。 ただし、テストの重要性が不明瞭で、モデルのテストに対して厳しい要件を持つことでプロセスを標準化しようとしたり、テスターなどのメトリクスを持つことで対処しようとしたりする場合、テストが品質に与える影響を無視しているとも言えます。 上流のプロダクトやSaasの変化や変更は加速しており、テストは 「そのテスト作成時点のアサーションでしかない」 という主張です。なのでよく落ちるテストに対しては削除することが合理的な場合があり、落ちるケースに対しては以下のような対応をしてしまうとのことでした! <= before | after => このようにフィルターとして扱ってしまうことで、テストによるデータ鮮度の品質を落とさず、そもそも間違ったデータが渡らないようにしてしまおうという発想のものです。 テストはデータ基盤のしなやかさを構築するためのものでもあり、そのためにSettleのチームではテストのデフォルトの 重要度を「警告」に変更 したとのことでした。確かにこのようにすれば、上流の変化に“しなやかに”対応しつつデータ鮮度の品質は落とさずにいられますね。 また、このテストではなくフィルターを記載する取り組みをスケールさせるために、マクロを使用していると話していました。 <= before | after => ↑こんな感じでマクロを呼び出せるようにして、外部キー制約やaccepted_valueなどをフィルターとして表現可能にしているとのことです! 最終的に以下のようにテストを減らしつつ、しなやかな構成に変更できたと締めくくられています! 感想 弊社もdbt + Lookerで構築されたセマンティックレイヤーがかなりのオペレーションに活用されており、こちらのパイプラインの停止がビジネスの停滞に繋がりうるため、とても参考になる発表でした! 外部に公開する数字とか請求金額とか絶対に間違えちゃいけないもの以外は、実は多少ずれていても意思決定に大きく影響しないものもありますもんね! 主キー制約とかまでフィルターでやっちゃうのは執念が感じられて笑っちゃったのと、弊社だとSalesforceみたいなデータソースの自由入力や意図しないデータ入力にパイプライン全体が影響を受けていたりするので、すぐにでも実践を検討したいです! How dbt transformed FinOps cost analysis at Workday(訳:dbtがWorkday社のFinOpsコスト分析をどのように変革したか) 発表者:Eric PuSpeaker (Senior Software Engineer @ Workday), Pattabhi Nanduri ( FinOps Data Engineer @ Workday ) 概要 DBTとJinjaマクロを活用してAWSやGCPを中心としたプラットフォーム上でのコスト要因を把握 DBTマクロやテンプレートを用いることで、ユーザーの負担を軽減し、データモデルの構築から実装までを自動化 モデル定義を通じて、ドキュメンテーションの自動生成やテストの自動実行 Workday社でのビジネス上の課題 コストとコストドライバーの理解: AWSやGCPから発生するコストやその要因を把握。 コスト最適化の機会の特定: コスト削減のための最適化ポイントを特定。 コストの割り当て:アプリケーションサービス、顧客、テナントごとにコストを適切に割り当て。 コストの予測:未来のコストを見積もり、予測する。 レポートとセルフサービス機能: 自己サービス型のレポート作成、チーム開発、行レベルアクセスやオブジェクトレベルアクセスなど、SOXコンプライアンスに対応したアクセス制御。 作成したダッシュボード(写真撮り忘れました) 今日の作業全体が表示され、コンピューティング、ストレージ、データベースなど、さまざまな支出の詳細が表示されているもの。同様に、月ごとのコンピューティングトレンドも表示されているもの。 コンピュータに関連する支出の詳細が表示されているもの。現在の四半期での使用状況を見ることができ、プロセッサやインターネット利用料などがわかるもの。 ダッシュボードのDimension 階層型の製品Dimension 最初のレベルでAWS製品とGCP製品といった大分類で区切り、7つのレベルにブレークダウンする形で分割しているもの。 階層型のプロジェクトDimension マーケットや人、組織に関する情報が含まれる。人と組織の情報は、プラットフォームの所有者や責任者に関連している。 全体のデータ構造はこんな感じです。 ユーザーが早い段階で次元カラムとファクトカラムを明確に定義するようにして、カラムのタイプを一度だけ定義すれば、その情報がデータマートの最終段階まで引き継がれるようにしているとのことです。 モデルの概要を宣言するだけで、残りのコードが自動的に生成され、モデルが正しく効率的に動作するように設計されています。 こちら紹介されていたサンプルクエリ。 lightdashを活用しているので、その辺りのアクセス制御もマクロを活用して行っているとのこと。 動的なユーザーフィルター sql_filter: {account_project_id in [select account_project_id from cpus.flops.access_control_bridge where login_user=${lightdash.attributes.user_name}]} ユーザーアクセスコントロール - name : effective_discount_column data_type : numeric meta : dimension : required_attributes : is_opus_super_user : "true" - name : aws_cur_table meta : required_attributes : is_opus_super_user : "true" このような構成にしたメリット ユーザーに多くの教育を必要としない。 パーティショニングが自動で切られてテストも自動で追加されるため気にしなくても良くなる。 統一された生成方法なのでドキュメンテーションが自動的に生成される仕組みを導入できた。 情報をリネージグラフと組み合わせて、実行計画を作成し、それをAirflowに送信、実行計画に基づいて、Airflowはクラスタの動的スケーリングを行い、各ステージのワークロードに応じた最適なリソースの割り当てが可能。 感想 クラウド料金を探索できるデータモデリングをする際のDimensionの切り方や、主要なコストはそれ単体をFactとして切り出して名前をつけてしまっているところなんかが地味に参考になりました! マクロ化には賛否色々ありそうですが、テストが自動で設定されるようになっていたり、DBTが生成するドキュメントに自動的に反映するような仕組みになっていたりして、ここまでやりきってしまえるならメリットが大きそうだなあと思いました! Surfing the LLM wave: We can't opt out and neither can you(訳:LLMの波に乗る:私たちも、あなたも避けることはできない) 発表者:Amanda Fioritto(Senior Analytics Engineer @ Hex), Erika Pullum(Analytics Engineer @ Hex Technologies) 概要 データ分析&BI統合ツールのHexにできたmagicと呼ばれるLLMによるサポート機能をHex社自身が使い倒して LLMによるクエリ生成の精度をどうしたらどのくらい向上させることができるか試行錯誤したレポートです セッション内容の紹介 Hex社にはドッグフーディングの文化があるので、リリースした magic というLLMによるクエリサポート機能を自社でどこまで活用できるか常に使用してきたそうで、社内のmagic機能に「パトリック」という名前をつけているそうです! パトリックの精度は論文やLLMモデルの公式発表などによると90%であるとのこと。 パトリックをオンボーディングする時の仮説 ⇒ 人間の同僚とそれほど違わないかもしれない。 ⇒ 人間のステークホルダーにとってデータが役立つように工夫することが、パトリックが新しい役割で成功する手助けにもなるかもしれない。 ⇒ データ組織の役割はステークホルダーにとって使いやすいデータ資産を作ることであり、現在ステークホルダーにはパトリックも含めている。 パトリックの評価の比較対象は spider というtext-to-sqlのオープンソースとのことです。 パトリックの評価を比較可能にするために「前四半期に予約された会議の数はいくつですか?」という質問を投げることにしたが dim_dates というテーブルがJOINできていなかった。 会議の日を特定するカラムを指定できていない。 など人間のステークホルダーからもよく寄せられる質問のようなミスをした。 「MMセグメントにいる顧客数は何人ですか?」という質問にパトリックが答えられるためには「顧客」とは何か、「セグメント」とは何を意味するのか、「MM」が何を指すのかを理解する必要がある。 パトリックのチューニング方法 「エンフォースメント」というツールを使ってDWH内のアクセス範囲を拡げる、または狭める。 ドキュメンテーションへのアクセスを許可してカンニングペーパーありの状態にする。 アクセス範囲のチューニングの実験結果 アクセス範囲を全snowflakeテーブルに拡大 ⇒ 正答率10% アクセス範囲をHex社内のテーブルに限定 ⇒ 正答率38% アクセス範囲をHex社内のテーブルに限定して、よく使われるテーブルにフラグをつける ⇒ 正答率46% ドキュメンテーションの実験をする際に、ドキュメントの品質にも差をつけた。 低品質: segment_type は Enterprise や Mid-Market などの値を含むことができる。 高品質: 低品質なものに加えて同義語も提供します。例えば、「Small Midsize(中小企業)」は「SMB」とも呼ばれるし、「Mid-Market」は別の呼び方もある。 ドキュメントの品質によるチューニングの実験結果 低品質のドキュメント ⇒ 正答率33% 高品質のドキュメント ⇒ 正答率51% セマンティックレイヤーを使った場合の実験結果 正答率75~88% 実験結果を受けてHex社の見解 MMをMidMarketと変換できるような人なら、パトリックのサポートをうまく活用し、自分たちの作業を進められるかもしれません。 データチームにとって、これらのツールがどのように、そしてなぜ機能するのか、またそれが質問に答えようとする人たちにとって役立っているのかを理解することが重要です。 データウェアハウスの整理やモデルのドキュメント化やコンテキストの整理は必要です。 重要なテーブルをマーキングする作業と高品質のドキュメント整備がそこまで差異がなかったのは驚異的でした。 LLMをうまくクエリビルダーとしてワークさせたいならセマンティックレイヤーの導入が必要そう。 感想 LLMをクエリビルダーとして使う想像は誰もがしたと思いますが、(僕も去年試行錯誤しました https://speakerdeck.com/okodoon/slackkarazi-you-yan-yu-deshe-nei-zhi-biao-wowen-ihe-waserarerubotwozuo-ritaka-tuta )実際にこれをすると何%改善するのかというところまで定量的に示してくれている実例は滅多にないので素晴らしい発表だと思いました! セマンティックレイヤーを作っていくことで、社内のクエリコスト低下だけでなくLLM活用まで見据えられそうなことが明確に示唆されたので、セマンティックレイヤー整備を引き続き頑張ります! LLMをステークホルダーと捉えてマシンリーダブルなデータ基盤に寄せていくって発想は今後のデータ基盤にきっと求められていく要素なんだろうなあと思いました! Semantic layers: The next data revolution or just overrated hype? (訳:セマンティックレイヤー:次のデータ革命か、それとも過大評価された流行か?) 発表者:Katie Hindson(Head of Product and Data @ Lightdash) 概要 dbtによってトランスフォーメーションが容易になったが課題が多く、数字の不整合の解消、指標の統一、LLMの活用といった課題を解決するためにセマンティックレイヤーが有用であると思われる。 ユニバーサルなセマンティックレイヤーを参照できるツール(Lightdash)がセマンティックレイヤーの活用方針としては望ましい セッション内容の紹介 セマンティックレイヤー不在における課題点を以下のようなスライドで紹介しています。 クエリごとに同じ指標を出しているはずなのにバラバラになってしまう。 指標の定義が社内で噛み合わない(アクティブユーザーのアクティブって何?「使用」の定義は?) LLMをデータ基盤で活用するためには同じ質問に対して同じ答えが得られることが重要。 そして、今日のセマンティックレイヤーは以下のようにスタンドアロン型かバンドル型かに分類されるとの主張をしていました。 スタンドアロン型 ex. ATSCALE, Minerva, cube, dbt Semantic Layer インテグレーションが不足しており、ビジネスユースケース足り得ない。 バンドル型 ex. Lightdash, MicroStrategy, Looker, SAP BIツールに統合されたセマンティックレイヤー。他のモダンデータスタックとのインテグレーションが不足している。 セマンティックレイヤーの使用体験として必要なもの メトリクスファーストの探索(テーブルを意識しないで指標名だけで思考が完結するような体験)。 データカタログでメトリクスの意味が確認できること。 データカタログから、簡単にメトリクスの探索ができること。 これらの体験を満たしているセマンティックレイヤーとして、Airbnb社が内製しているMinervaがとても優れていると述べていました。( Minervaに関するAirbnb社の記事はこちら ) セマンティックレイヤーのユニバーサル性として必要なもの スタック全体に適用できるほど汎用的 必要なすべてのツールと統合可能 今後セマンティックレイヤーがデータ基盤の中心となるため、あらゆるデータスタックと接続可能な状態を構築する必要があるとのことでした! 感想 結構ポジショントークみが強く「ユニバーサルって言葉をLightdashにだけ使うのは言葉として強すぎるのでは?」という指摘をQAで受けていて少し面白かったです。 Looker上にセマンティックレイヤーを構築している弊社としては、データカタログからの滑らかな探索体験みたいなところは実現が難しそうで、Lightdashも全然候補になってくるなと思いました! Gemini on Lookerを超えるセマンティックレイヤー✖️LLMの体験を創出できるのか、これからもWatchし続けていきたいです。
イベント概要 2024年9月18日に「GENBA #4 〜データサイエンティストの現場〜」と題してタイミー、ビットキー、AbemaTVの3社でデータサイエンスに関する合同勉強会を開催しました。 今回はそちらの勉強会からタイミーのデータサイエンティストである小関さん(@ozeshun_)の発表をイベントレポート形式でお伝えします。 自己紹介 まず、自己紹介をさせていただきます。私は2022年にタイミーにデータサイエンティストとして入社し、現在は機械学習モデルの改善、機械学習パイプラインの構築、そして推薦API基盤の運用を担当しています。最近では、検索機能にも携わっており、Elasticsearchのキャッチアップしながら頑張っています。趣味は野球で、ロッテやレンジャースの試合を観るのが好きです。 本日は、以下のような内容でお話ししようと思います。まず最初に、タイミーのサービス紹介を簡単に行います。その後、私が担当しているレコメンド機能について、A/Bテストをどのように運用しているのか、具体的なアーキテクチャやプロセスを説明します。また、A/Bテストを実施する際の指標のモニタリング方法についても触れ、その結果をどのように管理しているのかを順を追ってご紹介していきます。 サービス紹介 前提となる部分なんですが、タイミーはなんぞやというと働きたい時間と働いてほしい時間をマッチングする「スキマバイト」サービスです。 アプリを開くと仕事のリストが表示され、ワーカーは気軽に仕事を申し込むことができます。一方、クライアント側も求人情報を簡単に掲載でき、効率的に働き手を見つけることが可能です。 レコメンド機能におけるABテスト レコメンド機能におけるA/Bテストについて説明しますが、まずはタイミーのアプリ上でこのレコメンド機能がどのように実装されているのか、そしてどこを注視すべきかをお話しします。 タイミーのレコメンド機能は、アプリを開いた際に、トップ画面の「探す」タブや「お気に入り」タブに表示される「あなたのおすすめの仕事」バナーをタップすると、パーソナライズされた仕事のリストが表示される仕組みです。たとえば、探すタブの真ん中あたりにあるバナーや、お気に入りタブの上部に表示されるバナーをタップすると、あなたにおすすめの仕事が一覧として表示されます。このように、ユーザーごとにパーソナライズされた結果として仕事が表示される形で実装されています。 ユーザーが「あなたのおすすめのお仕事」画面でお気に入り登録を行うと、推薦がうまく機能していると判断でき、その後の申し込みに繋がれば、さらに効果的だったと見なせます。このように、推薦機能がどの程度成功しているかを確認するために、まずはお気に入り登録、その次に申し込みの動向を追っています。 レコメンド機能におけるA/Bテストについては、非常にシンプルな方法を取っています。基本的に、既存のアルゴリズムと新しいアルゴリズムを対決させる形でテストを行っています。導入初期では、レコメンド機能を表示するグループと非表示のグループで比較した結果、表示グループのKPIが圧倒的に良かったため、レコメンド機能の導入を決定しました。 現在では、既存アルゴリズムと新規アルゴリズムを日々比較し、どちらがより効果的かを評価しながら運用しています。 タイミーのレコメンド機能では、オフラインでの検証からオンラインにデプロイするまでのプロセスが非常に迅速である点が特徴です。このスピード感のある運用についても、具体的にどのように実施しているのか、説明していきたいと思います。 最後に、A/Bテストで計測している指標についてお話しします。先ほども少し触れましたが、まず重要なのは「お気に入り数」です。レコメンド機能を通じてお気に入りに登録される数が増えると、アルゴリズムが正しく機能していると判断できます。これが先行指標として、アルゴリズムの改善を測る基準になります。 次に遅行指標として、申し込み数も重要です。レコメンド画面から直接申し込む場合もありますし、レコメンド経由でお気に入りに追加した後に通知を受け取って申し込むパターンもあります。このように、申し込み数が遅れて増えることを確認できると、さらに効果的な結果となります。 また、全体の指標も見ていく必要があります。お気に入り数については、レコメンド経由のお気に入り数とそれ以外のお気に入り数の合計で構成されています。ただし、レコメンド経由のお気に入り数が増えたとしても、それ以外の部分が変わらないというわけではなく、時には減少することもあります。特に申し込みに関しては、ワーカーの時間が限られており、タイミーのアプリでは1日に1回しか働けないため、レコメンド経由での申し込みが増えると、それ以外の申し込みが減少するというトレードオフが発生することがあります。こうした点を注意深くモニタリングしています。 なお、他にもマッチングスピードなども指標として確認していたりなど、さまざまな指標を計測しながら運用を続けています。 ABテストの設定方法 A/Bテストの設定方法についてですが、タイミーでは非常に高速にデプロイできる仕組みが整っていますので、そのアーキテクチャについて説明します。 レコメンドシステムのアーキテクチャを簡単にご紹介します。左側がアプリ本体の基盤がある場所で、これはAWS上に構築されています。一方で、私が所属しているデータエンジニアリング部が主に操作するデータ基盤は、Google Cloud側にあります。 つまり、アプリの基盤はAWS、データの基盤はGoogle Cloudといった形で役割分担がされています。私たちが管理する部分は基本的にGoogle Cloud側が多いため、特に高速なデプロイが必要なときには、Google Cloud上にサービスがあると非常に効率的です。この点を念頭に置いた設計を採用しています。 次に、AWS上にあるRails APIがタイミー本体のAPIとなります。これに対して、右側のCloud Run上に構築したアイテム推薦APIがリクエストを受け取り、「推薦アイテムを返して」とリクエストが来ると、ロードバランサーを経由してその結果を返すといった構成になっています。 A/Bテストの振り分けについては、先ほども述べた通り、Google Cloud側で管理しています。Google Cloud上のアイテム推薦API内でA/Bテストの振り分けがすぐに設定できるような設計になっており、柔軟で迅速に対応可能なシステムとなっています。 実際にどのようにA/Bテストを設定しているかというと、とてもシンプルで、すべてYAMLファイルで管理しています。 具体的には、YAMLファイルの中にA/Bテストの設定を書き込んでいます。たとえば、アルゴリズムAとBを5対5で割り振るようなA/Bテストを設定する場合、durationをstart_atとend_atで指定し、A/Bテストに使用するキー名も設定します。その下には、variantsとしてアルゴリズム名を指定し、アルゴリズムAに対してウェイトを0.5、アルゴリズムBにも同様に0.5を割り振ります。また、表示するグループ(display)と非表示グループ(hidden)に対する割り振りもここで指定します。たとえば、表示群と非表示群に5対5で割り振るように設定することができます。 このYAMLファイルを記述するだけで、あとは推薦結果を正しく出力できるMLパイプラインが整備されていれば、すぐにA/Bテストが開始できる状態になります。 ABテストで計測する指標のモニタリング A/Bテストで計測する指標のモニタリングについてですが、弊社ではGoogle Cloud上のデータ基盤を活用し、Looker Studioを使ってさまざまな指標を可視化しています。具体的には、毎日モニタリングが必要な指標をグラフとして可視化し、日々確認しています。たとえば、テスト群とコントロール群で「お気に入りクリック数」を比較する場合、グラフ上で緑の線(テスト群)が上にくると良い結果と判断できます。 こういった重要なグラフは、Slackで毎日通知されるように設定しており、A/Bテストの結果をリアルタイムで確認できる体制を整えています。また、週に2回ほど、可視化された指標をもとに同期的にレビューを行い、モデルが意図通りに機能しているか、次にどのようなアクションを取るべきかなどを議論しています。 DWH(データウェアハウス)やデータマートのモニタリングについても、かなりしっかりと構築しています。まず、データレイク層にはアプリの生ログやレコメンドAPI側のログが保存されています。これらのログを直接スキャンすると、スキャン量が非常に大きくなってしまいます。皆さんもご存じの通り、これを毎回BIツールで呼び出すのは非常にコストがかかるため、データマートとデータウェアハウスを効率的に設計しています。 私が所属するデータサイエンスグループでは、これらのシステムのバッチ実行部分もしっかりと構築しています。毎日、ビジネスKPIやランキングメトリクスといった指標を集計し、それをデータマートに格納することで、BIツールで簡単に参照できるような仕組みを作っています。 現在、バッチツールとしてはCloud Composerを使用していますが、今後はdbtへの移行も検討しています。このように、効率的なデータ処理とモニタリング環境を日々改善しています。 ABテストの仮説と分析結果の管理方法 A/Bテストの仮説と分析結果の管理について説明します。私たちのチームでは、A/Bテストの仮説と結果をすべてNotionのデータベースで一元管理しています。アナリストチームがプロダクト全体で利用できるデータベースを構築しており、それを活用しています。 ドキュメントにはまず簡単にテスト内容をまとめ、その後「確かめたいこと」を記載します。これにより、テスト後に検証すべき内容が明確になり、スムーズに分析を進められます。次に「仕様」も詳細に記載し、他のメンバーが振り返りやすいようにしています。 A/Bテストの設計では、比較手法としてA/Bテストを行うか、因果推論的手法を使うかを事前に決めます。サンプルサイズも事前にシミュレーションし、実験期間を見積もりますが、期間が長すぎる場合はA/Bテストをやめて全ユーザーに公開し、因果推論的手法を使うこともあります。 メトリクスでは「お気に入り数」など重要な指標を設定し、ガードレールメトリクスも活用します。また、テストの条件とアクションを事前に設定することで、実験の目的を明確にしています。 テスト終了時にはドキュメントに結果と考察を書き込み、次の疑問や検証すべき内容を整理して次のステップに繋げるようにしています。 最後に、仮説の立て方についての具体例をお伝えしておきます。私たちは、確認したいポイントに対して「こう考えているので、この特徴量を加える」といった形で仮説を立てています。そして、その仮説に基づいて、意図通りに動いているかを分析します。また、仕様の部分には、これまでのアルゴリズムが抱えていた課題や、新しいアルゴリズムで行った変更を記載しておき、仮説に対応させることで、後からの検証がスムーズに進むようにしています。これらは、仮説検証を効率よく行うための例として紹介させていただきました。 まとめ 本日はタイミーのレコメンド機能におけるA/Bテストの運用についてお話しさせていただきました。まず初めにA/Bテストの概要をご説明し、次に、A/Bテストを素早く開始できる仕組みや、モニタリング体制についてお話ししました。最後に、A/Bテストの分析結果の管理方法や活用事例を紹介させていただきました。 本日はありがとうございました! お知らせ 現在、タイミーでは、データサイエンスやエンジニアリングの分野で、共に成長し、革新を推し進めてくれる新たなチームメンバーを積極的に探しています! また、気軽な雰囲気での カジュアル面談 も随時行っておりますので、ぜひお気軽にエントリーしてください。↓ product-recruit.timee.co.jp hrmos.co hrmos.co
こんにちは、タイミーのデータエンジニアリング部 データサイエンス(以下DS)グループ所属のYukitomoです。 DSグループではMLパイプラインとしてVertex AI Pipelinesを 利用 しており、その開発環境の継続的な効率化を進めていますが、今回はここ最近の変更点を紹介したいと思います! Vertex AI PipelinesについてはGoogle Cloudの 公式ページ や、 前回の記事 を参照ください。 モノレポ環境に移行 当初はパイプライン毎にレポジトリを用意していました。しかしながら新規でパイプラインを起こす度にレポジトリの作成から行うのは、 ちょっとした“作業”ではあるのですが気軽に行うには少し腰が重い cookie cutter を使って初期状態を揃えたり 前回の記事 のような標準化を行っても、異なるレポジトリを異なる開発者によりメンテナンスを行うと、それぞれが独自の進化をしたり属人化したりしがち といった問題を抱えていました。そこで、モノレポ化して1箇所に集積し、 CI/CDや、パイプラインビルド等の付随スクリプトを共用化、付随スクリプトのような非本質的な機能開発を効率化 フォルダ構成やファイルの命名規則などを統一、“隣のプロジェクト”を参照しやすくすることで、ベストプラクティスを共有化、パイプラインの機能そのものの開発を効率化 することを目指しました。同一レポジトリ傘下に収めることで従来よりも敷居が下がり、共通知化を進められていると感じています。 コンテナイメージの共通化 パイプラインを1箇所のレポジトリに集めた段階でパイプラインのコンポーネントは200個以上あり、パイプラインのコンポーネントそれぞれが独自にDockerfileやpyproject.tomlを持っていました。脆弱性対応や機能追加のための依存モジュールのアップデートはそれぞれのpyproject.tomlを更新することになりますが、ファイルの数が多いと更新に手間がかかってしまいます。そこで、同一パイプラインのコンポーネント間ではコンテナイメージを共用できるような形にアーキテクチャを改めました。 おおまかな方針は以下のとおりです。 コンポーネントの入出力を定義するyamlファイル(component.yaml)はそれぞれ名前を変え、1つのフォルダにまとめる。 コンポーネントの中のロジックを記述するpython コードも1箇所にまとめ、全体をコンテナイメージに複製。 単一コンテナだけでは処理しきれない場合を考慮し、複数のコンテナイメージを格納できるようコンテナイメージ用のフォルダは階層化。 従来のアーキテクチャ % tree PIPELINE_X ./PIPELINE_X ├── components │ ├── component_A # コンポーネント毎にフォルダを用意し、 │ │ ├── Dockerfile # Dockerfile/pyproject.tomlはそれぞれ独立に配置 │ │ ├── component.yaml │ │ ├── pyproject.toml │ │ └── src │ │ └── ... │ ├── component_B │ │ ├── Dockerfile │ │ ├── component.yaml │ │ ├── pyproject.toml │ │ ├── src │ │ └── ... │ └── component_C │ ├── Dockerfile │ ├── component.yaml │ ├── pyproject.toml │ ├── src │ └── ... └── pipelines └── main.py 新しいアーキテクチャ % tree ./PIPELINE_X ./PIPELINE_X ├── components │ ├── definitions # 1. component.yamlは1箇所に集約 │ │ ├── component_a.yaml │ │ ├── component_b.yaml │ │ └── component_c.yaml │ └── src # 2. src以下全てをコンテナイメージに複製. │ ├── component_a.py # component_*.yamlの設定で動作するpython fileを指定 │ ├── component_b.py # DockerfileのCMDを指定するイメージ │ └── component_c.py │ ├── containers # 3. 単一コンテナでは難しい場合に備え │ └── main # 複数のコンテナを利用できるようフォルダを階層化 │ ├── Dockerfile │ └── pyproject.toml └── pipelines └── main.py コンテナ数を減らすことで、 dependabot の運用は格段に楽になりました。コンテナが減ることで警告の数も下がり、警告の数が下がることで更新の初動も取りやすくなるという好循環のおかげで、2024年10月現在、dependapotからの警告は画面のスクロールが不要な範囲にはおさまるようになってきました。 また、コンテナを集約する段階で気がついたのですが、いくつかのDockerfileの中で利用するpoetryのバージョンが古いままだったり、マルチステージのビルドが正しく行われていなかったりするものも少なからずありました。Dockerfileに限らずコードライティング全般に言えることですが、記述量は可能な限り少なくする方が、このような小さな不具合の発生は抑制でき、安定したコードを供給できます。 CDにおけるビルドキャッシュの利用 タイミーDSグループにおいて、CI/CD環境は GitHub Actions 、クラウド環境は Google Cloud を利用しています。指定されたトリガー条件が発生した時にコンテナイメージをビルドするのですが、GitHub Actionsの場合、ジョブ単位でVMが異なるため、連続するGitHub Actionsの実行の場合でもdockerのビルドは一からやり直しになってしまいます。 そこで、 こちら や こちら の内容を参考に、ビルド結果をVMの外部、Artifact Registryにキャッシュ、次回実行時に再利用することでCI/CD の処理を高速化させました。なお、以下の設定ではbuildcacheは通常のコンテナ用Artifact Registry(下のコードで言うと${IMAGE_NAME})とは異なるRegistry( ${IMAGE_NAME}-buildcache )に保存しています。 GitHub Actions内での記述より抜粋 docker buildx create --name container --driver=docker-container docker buildx build \ --builder=container \ : --cache-from=type=registry,ref=${IMAGE_NAME}-buildcache \ --cache-to=type=registry,ref=${IMAGE_NAME}-buildcache,mode=max \ -t ${IMAGE_NAME}:${IMAGE_TAG} \ --load . docker push ${IMAGE_NAME}:${IMAGE_TAG} # -cache-from/-cache-toに指定するrefの値にsuffix '-buildcache'を付加し # 本来のイメージとキャッシュイメージの置き場所を分離しています。 パイプライン命名規則の工夫 MLパイプラインを開発していると、 あるパイプラインを少しだけ変えたパイプラインを実現したい Gitの別ブランチで管理すればいいんだけれど、比較しながらコード書きたい といったケースはよくあると思います。簡単にこれを実現しようと cp -r でパイプラインを丸ごとコピーしたとしても、従来のアーキテクチャでは様々な設定(パイプラインの名前、参照するコンテナイメージの名前、バージョン情報)を書き換える必要があります。そのため、煩わしい作業が発生していました。また、それらの設定方法も統一が取れておらず、“微妙に”パイプライン毎に異なっていました。そこで、それらのバージョン情報以外の情報を全てパイプラインが保存されているフォルダのパス情報から取得するよう統一し、 cp -r だけですぐにパイプラインの亜種が作成できるようにしました。 従来のアーキテクチャ # 1. パイプラインの名前はパイプラインのpyproject.toml内のname属性や環境変数(dotenv)を利用 # 2. コンテナイメージの名前は コンテナイメージのpyproject.toml内のname属性を利用 # 3. Version情報: # パイプライン -> パイプラインのpyproject.toml内のversionを利用 # コンテナイメージ -> コンテナイメージのpyproject.toml内のversionを利用 . PIPELINE_X ├── components : │ ├── containers │ └── main │ ├── Dockerfile │ └── pyproject.toml # 2 [tool.poetry].name -> パイプラインの名前 │ # 3 [tool.poetry].version -> パイプラインのversion ├── pipelines │ └── main.py ├── .env.* (prod/stg/dev..) # 1 パイプラインの名前は .envからやpyproject.tomlなど └── pyproject.toml # 各種のやり方が存在。 # 3 [tool.poetry].version -> コンテナイメージのversion # Compile されたpipeline config抜粋 { : "deploymentSpec" : { "executors" : { "exec-comp_a" : { "container" : { : "image" : "${GAR_REGISTRY_PREFIX}/blahblahblah:vX.Y.Z" # 2. コンテナイメージの名前"blahblahblah"はコンテナイメージのpyproject.tomlより # 3. コンテナイメージのVersion "vX.Y.Z"はコンテナイメージのpyproject.tomlより }..}..}..}, : "pipelineInfo" : { "name" : "arbitrary_string" # 1. パイプラインの名前は }, # 注: ${GAR_REGISTRY_PREFIX} は Artifact Registry のアドレス 新しいアーキテクチャ # 1. パイプラインの名前はRepository内のフォルダ位置を利用 (= ${SERVICE}-${PIPELINE} ) # 2. コンテナイメージの名前は "${パイプラインの名前}"-"${コンテナのフォルダ名}" # 3. Version情報: # パイプライン -> パイプラインのpyproject.toml内のversionを利用 # コンテナイメージ -> パイプラインのversionを利用 SERVICE/**/vertex_ai_pipelines/PIPELINE_X # 1 ├── components : │ ├── containers │ └── main # 2 │ ├── Dockerfile │ └── pyproject.toml ├── pipelines │ └── main.py ├── pyproject.toml └── .env.* (prod/stg/dev..) # Compile されたpipeline configの一部を抜粋 { : "deploymentSpec" : { "executors" : { "exec-comp_a" : { "container" : { : "image" : "${GAR_REGISTRY_PREFIX}/${SERVICE}-${PIPELINE_X}-main:vX.Y.Z" }..}..}..}, : "pipelineInfo" : { "name" : "${SERVICE}-${PIPELINE_X}" }, : # 注: ${GAR_REGISTRY_PREFIX} は Artifact Registry のアドレス ちょっとした変更ではあるのですが、新しいパイプラインを構築する際の初動を早くすること、また簡単にできることにより、新しい方式を試そうという心理的な敷居を下げることができていると考えています。 今回紹介した取り組み以外にも、Vertex AI Pipelinesに限らず効率化するための具体的なアイデアはいくつかあるのですが、プロダクションを動かしながら変更しており、障害の発生を抑えるためにも、一度に大きな変更は与えずステップを踏みながらMLOps基盤を理想の姿に近づける活動を続けています。 We’re Hiring! タイミーのデータエンジニアリング部・データアナリティクス部では、ともに働くメンバーを募集しています!! 現在募集中のポジションは こちら です! 「話を聞きたい」と思われた方は、是非一度 カジュアル面談 でお話ししましょう!
みなさんこんにちは。タイミーのデータエンジニアリング部 データサイエンスグループ所属の菊地と小関です。 2024年9月27日(金)、28日(土)に開催された PyCon JP 2024 に参加してきました。今回はPyCon JP 2024の雰囲気と、特に興味深かった&勉強になったトークセッションをいくつかピックアップしてお届けしようと思います! PyCon JPとは PyConJPは1年に1度開催されていて、今年はTOC有明コンベンションホールにて9月27日(金)、28日(土)の2日間にわたって開催されました。 概要については、 PyCon JP 2024 の「What is PyCon JP」をそのまま引用させていただきます。 PyCon JP は、Python ユーザーが集まり、Python や Python を使ったソフトウェアについて情報交換、交流をするためのカンファレンスです。 PyCon JP の開催を通じて、Python の使い手が一堂に集まり、Python にまつわる様々な分野の知識や情報を交換し、新たな友達やコミュニティとのつながり、仕事やビジネスチャンスを増やせる場所とすることが目標です。 当日のタイムテーブルは こちら を参照いただければと思います。 PyCon JPの雰囲気 今年の参加者数は600名超で、日本国内のみならず、海外からの参加者も多く、非常に活気がありました。昼食についても様々な文化圏の方に配慮して用意されていた印象です。 また、企業ブースも多数出展しており、トークセッションで気になった企業様の発表内容を更に詳しく伺うことができました。スタンプラリーや書籍販売、Pythonに関する求人が掲載されている等、ビジネスチャンスを増やせるよう、非常に配慮された設計だったと感じています。 個人(菊地)的には、企業ブースにいたGrooveX社の「 LOVOT 」がとてもかわいかったです(写真を撮っていないのが心残りです…)。 特に興味深かった、勉強になったトークセッション まず、小関が興味深かった&勉強になったトークセッションを紹介します。 FastAPIでのasync defとdefの使い分け speakerdeck.com このセッションでは、タイトル通りのFastAPIにおける実装方法の使い分けを、Pythonにおける並列処理・並行処理・非同期処理の性質を説明した上で解説しており、非常にわかりやすかったです。 要点は以下の通りでした。 マルチスレッド、非同期処理ともにI/Oバウンドな処理の場合に高速化が期待できるが、マルチスレッドはスレッドが増えるとスレッド切り替え分だけ処理速度が低下してしまう つまり、スレッドが多くなるような処理だと非同期処理の方が処理速度が速くなる場合がある Fast APIにおける def と async def の違いと使い分け def 並行処理 (マルチスレッド) 同期処理を行いたい場合はこちらを使う async def 非同期処理 マルチスレッドだとスレッドの切り替え時間がかさむぐらいのリクエスト量をさばきたい時に有効 低コストで実現する社内文書RAG機能を搭載したAIチャットボット開発 speakerdeck.com このセッションでは、RAG機能を用いたAIチャットボットの開発について、主に開発前の実装スコープの決め方や実装方法の詳細の観点からお話しされていました。 開発前にpros & consを言語化して、目的にあったHowとして今回のRAG + Chat botを選択した過程がわかりやすくまとまっていたり、システムのアーキテクチャ自体も丁寧に説明されていたりしたので、今すぐにでも社内で試せそうな感じでした。 弊社だとNotionのドキュメント量が膨大なので、indexingするドキュメントの選定基準をどう決めたのかが特に気になりました。Notionのページ階層が綺麗にまとまっている組織だとindexingする対象を選択する時の手助けになるのかなとも思いました。 LlamaIndex は未履修なので、近々個人で触ってみようと思います! 次に、菊地が興味深かった&勉強になったトークセッションをピックアップして紹介します。 PandasAI:生成AIがデータ分析業務にもたらすパラダイムシフト speakerdeck.com このセッションでは、機械学習・生成AI・データ分析の基礎の基礎を説明した後に、 PandasAI について紹介しています。 PandasAI とは、自然言語でデータ分析をしたり可視化を行ったりできるデータ分析のためのAIエージェントで、OSSとして公開されています。OpenAIのAPIキーがあれば使用できるとのことです。 また、SaaSとしても提供しており、登録・課金を行えばAPIキーがなくとも使用できるそうです。 データコネクタとしては、CSVやParquet, XLSX等はもちろん、PostgreSQL, MySQL, BigQuery, Databricks, Snowflakeといった各種クラウドサービスのデータソースへ接続できます。 内部の処理としては、自然言語をPythonコードやクエリに変換してデータを操作し、結果をユーザーへ返却しており、悪意のあるコードの実行に対する制御もできるそうです(ホワイトリストとして特定のモジュールのみ実行可能にする等)。 OSSとして提供されているので一度試してみたいなと思っているのですが、弊社でも導入しているLookerやその他BIツールに搭載されているAI機能とどのように差別化していくのかは気になりました。 データサイエンスのフルサイクル開発を実現する機械学習パイプライン speakerdeck.com このセッションでは、まずCARTA MARKETING FIRM社におけるデータサイエンティスト像「フルサイクルデータサイエンティスト」について紹介し、類似概念「フルスタック」との違いを説明しています。 その上で、理想状態とのギャップを課題として整理し、データサイエンティストがより本質的な価値創出ができる状態に向けて、これまでのデータ分析基盤の歴史(luigi → Amazon SageMaker → Prefect)が紹介されています。 タイミーのデータサイエンティストも「フルサイクルデータサイエンティスト」に近い働き方をしている点や、ML基盤の歴史が似通っていたり、チームの規模感や構成が近いなど、多くの共通点がありました。「わかる〜」と心の中でうなずきながら、セッションを聴かせていただいておりました。 あえてApache AirflowではなくPrefectを選定した経緯などは、機会があればぜひ伺ってみたいなと思いました。 終わりに いかがでしたか?PyCon JP 2024の雰囲気が少しでも伝わっていますと幸いです。次回は広島開催とのことで、ぜひ来年も参加させていただきたいなと思っています。 ちなみに、タイミーには「 TDE10 」という、プロダクト開発やデータ職種メンバーを対象とした成長支援制度があります。今回は「世界中で開催されているすべての技術カンファレンスに無制限で参加できる『 KaigiPass 』」を利用してPyConJPに参加しました。 今後もこのような機会があれば、積極的に技術カンファレンスに参加していきたいと考えておりますし、外部登壇も積極的に行っていきたいです。 We’re hiring!!! 現在、タイミーでは、データサイエンスやエンジニアリングの分野で、共に成長し、革新を推し進めてくれる新たなチームメンバーを積極的に探しています! また、気軽な雰囲気での カジュアル面談 も随時行っておりますので、ぜひお気軽にエントリーしてください。↓ product-recruit.timee.co.jp hrmos.co hrmos.co hrmos.co hrmos.co
こんにちは、株式会社タイミーでデータサイエンティストをしている貝出です。直近はカスタマーサポートの業務改善に向けたモデルやシステムの開発を行っております。 新規プロジェクトを始めるにあたって、予測や検知の機能といった複雑な課題に取り組む必要がある際、機械学習モデルを使うことを検討される方も多いと思います。しかし、Google の定めた Rules of Machine Learning: Best Practices for ML Engineering でも記載されているように、必ずしも最初から機械学習モデルを使うことが最適とは限りません。データの不足や問題の不明確さから、機械学習が効果的に機能しない場合もあります。 その一方で、 ルールベースなどのシンプルなアプローチ を用いることで、早期にプロダクトやサービスを開発し、ユーザーの反応を得られます。この段階でデータサイエンティストや機械学習エンジニアが関与することで、後々の機械学習モデルの導入をスムーズに進めることが可能です。 本記事では、新規プロジェクトでのルールベースの活用方法と、その段階でデータサイエンティストができることについて書きたいと思います。 新規プロジェクトにおける早期フィードバックと早期成果 構築・計測・学習のフィードバックループ 新しいプロダクトやサービスを開発する際には、できるだけ早く最小限の機能を持つ製品(MVP:Minimum Viable Product)を作成し、ユーザーからのフィードバックを得ることが重要です。これは、エリック・リースの『 リーン・スタートアップ 』で提唱されている手法で、迅速なサイクルで仮説検証を行うことで、無駄を最小限に抑えながら製品を改善していく考え方です。 早期にユーザーの反応を得ることで、問題設定や解決策が適切かを迅速に判断できます。もしユーザーに使われなかったり、期待した効果が得られなかったりする場合は、素早く方向転換(ピボット)することが可能です。このように、早期フィードバックはプロジェクトの成功確率を高める重要な要素となります。 具体的な成果物を早く提供することで、ステークホルダーからの信頼を得やすくなり、追加のリソース確保やプロジェクトの優先順位向上にもつながります。 機械学習モデル構築の課題 機械学習モデル(今回は教師あり学習の場合に限定します)を構築する際には、データの質と量が極めて重要です。しかし、新規プロジェクトでは利用可能なデータが限られていたり、ラベリングルールが明確でなかったりすることも多く、高性能なモデルを開発するのが困難である場合があります。 例えば、数千から数万件のラベル付きデータが必要な場合、データの収集とラベリングには数週間から数ヶ月、さらにはそれ以上の時間と多大なコストがかかることがあります。また、専門知識が必要なラベリング作業では、ラベルの一貫性を保つために教育や管理も必要です。 データが少ない場合、モデルは訓練データに過度に適合し、未知のデータに対しては性能が低下する「 過適合(オーバーフィッティング) 」が起こりやすくなります。逆に、データのパターンを十分に学習できず、訓練データでも性能が低い「 過小適合(アンダーフィッティング) 」の状態になることもあります。 このような状況では、データの追加収集やラベリングにかかる時間とコストを考慮し、新規プロジェクトではまずシンプルなルールベースの方法を検討することが有効です。 ルールベースと機械学習ベースの違い ルールベースと機械学習ベースのアプローチの違いを簡単に整理します。 ルールベース 機械学習ベース 定義 人間が設定したルールで動作 データからパターンを学習 メリット ・簡易性 ・迅速な開発 ・解釈性 ・高い精度 ・適応性 ・高度なパターン認識 デメリット ・柔軟性の欠如 ・拡張性の限界 ・開発コスト ・解釈の難しさ ルールベース 定義 :人間が設定したルールや条件に基づいて動作する メリット : 簡易性 :実装が比較的容易で、結果が理解しやすい 迅速な開発 :短期間での開発・デプロイが可能 解釈性 :なぜその結果になったのか説明しやすい デメリット : 柔軟性の欠如 :複雑なパターンや例外への対応が難しい 拡張性の限界 :ルールが増えると管理が煩雑になる 機械学習ベース 定義 :データからパターンを学習し、予測や分類を行う メリット : 高い精度 :大量のデータから複雑なパターンを学習可能 適応性 :新しいデータや状況にも柔軟に対応 デメリット : 開発コスト :データ収集やモデル構築に時間とリソースが必要 解釈の難しさ :モデルがブラックボックス化しやすく、結果の説明が難しい場合がある 事例:スパムメールのフィルタリング ルールベースの場合 :特定のキーワードや送信元をブロックリストで管理。実装が簡単で明確な基準で判定可能。 機械学習モデルの場合 :メールの内容を解析し、ナイーブベイズや深層学習モデルでスパムを分類。大量のデータで高精度を実現。 ルールベースモデルの活用 迅速な実装とデプロイ ルールベースモデルはシンプルなため、 短期間で実装・デプロイ が可能です。これにより、ユーザーからのフィードバックを早期に得られ、製品の改善に活かせます。 ルールベースモデルにおけるデータサイエンティストの役割 データサイエンティストは、ルールベースのアプローチでも以下のような重要な役割を果たせます。 データ分析によるルール策定支援 :データの傾向やパターンを分析し、効果的なルールの策定をサポートする 性能評価とモニタリング :モデルの性能を定期的に評価し、改善点を提案する エラー分析とルール改善 :誤判定の原因を特定し、ルールの精度向上に貢献する 将来の機械学習モデルへの布石 :データの蓄積とラベリング戦略を立案し、スムーズな機械学習モデルの導入を支援する 段階的な開発プロセス 段階的な開発プロセスの一例として、以下のような進め方が考えられます。 段階的な開発プロセスのイメージ ルールベースでの MVP 開発 : シンプルなルールを用いて基本機能を実装 迅速なデプロイを目指す フィードバックの収集 : ユーザーや領域専門家(ドメインエキスパート)から意見を収集 ルールの有効性や不足点を把握 評価と改善の繰り返し : 性能指標を用いてモデルを評価 エラー分析を行い、ルールを最適化 機械学習モデルの導入検討 : ルールベースの限界が見えた段階で機械学習の導入を検討 データの蓄積状況やビジネス価値を総合的に判断 まとめ ルールベースと機械学習モデルの双方にメリットとデメリットがあります。新規プロジェクトにおいて、データが十分に揃っていない初期段階では、 ルールベースなどのシンプルなアプローチ を活用することで迅速に価値を提供し、フィードバックを得られます。 データサイエンティストが早期から関与し、評価やエラー分析、データ品質の向上に努めることで、プロダクトの質を高めつつ、将来的な機械学習モデルの導入をスムーズに進めることが可能となります。 ビジネス価値が確認できた段階で機械学習モデルを導入することで、より高度な機能や精度の向上を実現できます。この段階的なアプローチは、リソースの有効活用とプロジェクトの成功に寄与することが期待されます。 おわりに 今回はルールベースのアプローチの活用を中心に紹介しましたが、技術的にルールベースでは実現が難しいケースもあります。例えば、画像内の文字を機械が読み取れる形式に変換する光学文字認識(OCR)は、ルールベースでの実装が困難です。そのようなケースでは、ルールベースではなく、既存の機械学習モデルや Google の Cloud Vision API などの SaaS を最初のアプローチとして利用することになるかもしれません。 新規プロジェクトでは、限られたリソースや時間の中でいかに迅速にユーザーに価値を提供するかが重要です。シンプルなアプローチから始めて、段階的に機械学習モデルを導入することで、プロジェクトのリスクを最小限に抑えつつ、より高度な機能を実現できます。 私はタイミーにおけるデータサイエンティストとして、このプロセスに積極的に関与し、ビジネスと技術の橋渡し役を目指しています。 現在、タイミーでは、データサイエンスやエンジニアリングの分野で、共に成長し、革新を推し進めてくれる新たなチームメンバーを積極的に探しています! product-recruit.timee.co.jp また、気軽な雰囲気での カジュアル面談 も随時行っておりますので、ぜひお気軽にエントリーしてください。↓ hrmos.co hrmos.co
2024年10月7日から10日(現地時間)にかけて、dbt Coalesce 2024がラスベガスで開催されています。 私たち株式会社タイミーからは、4名が現地参加しました (※)。 今回は、カンファレンスの最初のKeynoteについてご紹介したいと思います。 このKeynoteでは、dbtのビジョンや今後の新機能リリースに関する熱いトピックが多く取り上げられ、非常に刺激的な内容となりました。 以下がアジェンダです。 イントロ 新機能の紹介 dbt Copilot Visual Editing Experience Advanced CI Data health tileの埋め込み dbtセマンティックレイヤーのコネクタ追加 クロスプラットフォームのためのdbt Mesh まとめ References ここからはKeynoteのレポートをお届けします。 発表内容とその他を切り分けるため弊社メンバーの今回の発表に対するコメントや確認した内容はこのマークアップスタイルで表記します。 イントロ 2024年のdbt Coalesceには、世界中から2000人以上の参加者がラスベガスに集まり、オンラインでは8000人もの方が参加していました。分析エンジニアやデータエンジニアはもちろん、アナリスト、CDO、財務、マーケティング、プロダクト担当者など、さまざまな役割を持つ人々が一堂に会し、dbtのコミュニティの成長を感じました。 既に5万以上のチームが本番環境でdbtを利用しており、毎日1350万回もの実行が行われ、過去1年間で49億ものモデルが構築されているそうです。 データ変換の課題が解決されつつある一方で、品質やオーナーシップ、そしてステークホルダーのリテラシーといった新たな課題が浮上しています。特に、データシステムをソフトウェアシステムと同様に扱う必要性がますます強調されていることが印象的でした。 dbtは、ソフトウェアエンジニアリングのベストプラクティスを取り入れた「ADLC(Analytics Development Lifecycle)」という新しい概念を提案し、分析の全過程をカバーするエンドツーエンドのワークフローを提供しています。このアプローチが、今後どのようにデータ活用を進化させていくのか、とても楽しみです。 画像は https://www.getdbt.com/resources/guides/the-analytics-development-lifecycle より抜粋 新機能の紹介 今回の Keynote では dbt Cloud の新機能として dbt Copilot、 Advanced CI、ビジュアルエディティング、クロスプラットフォーム連携などが紹介されました。 dbt Cloud はオーケストレーションやオブザーバビリティ、カタログやセマンティクスの機能を持った Data Control Plane と説明されていました。 今回発表された新機能も Data Control Plane の一部とのことで、抜粋しながら紹介していきたいと思います。 画像は https://www.getdbt.com/blog/coalesce-2024-product-announcements より抜粋 dbt Copilot LLMはコード作成に優れており、データワークを大幅に簡素化できる可能性がある。 dbt Copilot は、モデル、テスト、メトリクス、ドキュメントなどを支援してくれます。 ログ分析による根本原因分析、クエリプロファイルの分析と改善の提案によるコスト削減、ビジネス上の質問などを行うための自然言語インターフェースの提供など、さまざまな機能を持つチャット画面も追加されます。 OpenAIのAccesskeyを登録することで利用が可能になるそうです。 dbt開発に慣れ親しんでいないメンバーがこれを使ってすぐに開発者になることは難しそうですが、これまでdbt開発をしていたメンバーがアシスタントとして並走してもらう形で活用することで、開発スピードが向上させられそうです。 Visual Editing Experience NoCodeでブロックを繋げるだけでdbtモデルを作成可能となる機能が発表されました。 ドラッグ&ドロップ・インターフェースをベースとして、元データからブロックを繋げるだけで変換、計算、フィルターなど少し風雑なモデルも作ることができます。ビジュアル編集モデルはSQLに変換されgitによって管理されます。作ったモデルはdbt上の基本機能であるテストとドキュメントに接続できるため、dbt上と同じ信頼性を担保できます。ベータ期間に申し込むのは こちら から。 DataMesh的な思想で開発を分散可能になりうる部分は素晴らしいと感じましたが、ビジネスメンバーに機能を譲渡する前にしっかりガードレールを敷くような開発を行わないとカオスが発生しうると感じたため、機能利用可能時にはその辺りは深くチェックしたいです! 弊社はSQLに慣れ親しんでいないビジネスメンバーが大きなボリュームを占めているので、この機能がしっかりしたガードレールの元でハマることがあれば開発スピードを大きく高めることができそうです! Advanced CI これまでのdbt Cloudではslim CIの概念に則って、変更差分とその影響範囲をテストして開発速度を向上させてきましたが、「CIでテストが通ってビルドが通ること」と「期待した変更が正しくされたこと」が一致しないケースも多々あり、今回の advanced CI はそちらを解決する機能として紹介されていました。 そのPRで変更対象となったモデルの実行結果と、直近の本番ビルドの実行結果と比較して「作成されるモデルがこのPRでどのように変化したのか」を確認できるような新しいCIです! 以下の内容を本番とCIの差分として確認できます。 追加、変更、または削除された行または列 列内の値の変更 列のデータ型または列の順序の変更 主キーの変更または重複 データモデル全体と比較して、変更、追加、または削除された行の割合 紹介されている画面 dbt Cloud上のCI Job詳細画面 PullRequest上に投稿されるdbt Cloudからのコメント画面 こちらの機能によって以下のメリットがあると説明されています。 不正なデータを本番環境にデプロイされることを防止できることによるデータ品質の向上 行レベルの変更を確認可能となることで、行レベルテストなどが不要になることによる開発者の速度向上 本番環境の手前で消化活動が可能なため、事後対応が減ることによるコスト効率の向上 早速ちょっとやってみました。(色々な事情ありが成功版と中身をお見せできず、すみません)CI環境のJobで Run compare changes のオプションをONにします。 この機能をONにしないとCI Jobの画面で以下のような表示になります。 pull request上にadvaced CIのコメントがしっかりされました! (advanced CIに対するコメント) 全てのケースを網羅するテストを書くことは難しいため、コードやデータの変更を自動的に確認できるようになるこのCIは、「これこそ顧客が求めていた機能だ」と感じました。 ただ、本番環境のビルドに関してもCIを意識しなければならないため、ビルド戦略は少し複雑になるかもしれません。それでもビルドの信頼性の向上によって、より安定したリリースプロセスに近づくはずです。 Data health tileの埋め込み 自動公開とそれに対応するData health tileにより、信頼できるデータセットを保証できます。 Tableauワークブックやiframeで埋め込めるData health tileで信頼性を可視化することが可能です。 こちらTableauとの連携が主にフィーチャーされていましたが、こちらLookerStudioにも埋め込み可能なことを確認しました。 このようにdbt Cloud exploreからexposureを選択してiframeを取得 iframe内のtokenパラメータにdbtのBearerトークンを渡した状態で、LookerStudioに埋め込むことで以下のようにLookerStudio上に表示されることを確認できました! dbtセマンティックレイヤーのコネクタ追加 Tableau(updated)、Sigma、Power BIなど、さまざまなBIツールとの統合が強化されました。 クロスプラットフォームのためのdbt Mesh これまでの dbt では、複数のデータプラットフォームをまたいだプロジェクトの構成が困難でした。例えば、あるプロジェクトで Databricks を利用していて、後続のプロジェクトで Snowflake を使っていたとしても、後続のプロジェクトから source として Databricks のプロジェクトのテーブルを参照することはできませんでした。 今回の Keynote でクロスプラットフォーム dbt Mesh の機能が発表されて、プラットフォームをまたいだプロジェクトの構成が現実味を帯びてきました。 実現に至ったのは、 Apache Iceberg に対応したデータプラットフォームが増えたことがきっかけでした。 Apache Iceberg とは Open Table Format と呼ばれる、主にデータレイクハウスでの利用が想定されたオープンなテーブルフォーマットです。Apache Iceberg 自体の詳細の説明は割愛しますが、データレイクハウスにおいて Iceberg 形式でテーブルを作成することで、他のデータプラットフォームから読み取りが可能になる仕組みです。 2024年10月に入って dbt が Apache Iceberg のサポートを開始したことで、クロスプラットフォームでの連携が可能になりました。 弊社では BigQuery を利用しており Iceberg 互換でデータを保持することはできないため、残念ながら今の環境で試すことはできませんが、企業間のデータ連携などを見据えると触っておきたい機能だと思いました。 さらに Apache Iceberg 形式への対応に加えて、 Amazon Athena や Azure Synapse Analytics といったアダプターが新しくサポートされました。 プラットフォーム間の連携が容易になるとのことで、柔軟な形式でデータを保持できるデータレイクハウスの強みがこういった形で出てくるのは印象深いです。さらにアダプターの追加によって dbt とデータレイクハウスの相性がますます良くなったように感じました。 まとめ dbt Coalesce 2024のKeynote「Innovating with dbt」は、dbtのビジョンと共に新規機能がいち早く聞ける非常に刺激的な内容でした。特に、advanced CIやdbt Copilotによってさらに効率的に分析・開発が推進できそうですし、ビジュアルエディティングによって開発に関われるデータ関係者の幅が広がりそうで可能性を感じました。 セッション内で繰り返し強調される One dbt というワードの名の通り、dbtがカバーできる新しい領域が沢山できたと思える内容でした。 タイミーでも、こういった新機能を活用することでdbt開発の効率化や品質向上を図り、データ基盤の信頼性とアジリティをさらに高めていきたいです。 ※今回私たちは、タイミーの Kaigi Pass 制度を利用してdbt Coalesce 2024に参加できました。 References One dbt: the biggest features we announced at Coalesce 2024 https://www.getdbt.com/blog/coalesce-2024-product-announcements About dbt Copilot https://docs.getdbt.com/docs/cloud/dbt-copilot Build with speed and confidence https://www.getdbt.com/product/develop Advanced CI https://docs.getdbt.com/docs/deploy/advanced-ci Data health tile https://docs.getdbt.com/docs/collaborate/data-tile
株式会社タイミーでデータサイエンティストをしている渡邉です。 タイミーでは、スマートフォンへのプッシュ通知を利用してタイミーを利用されているワーカーの方にキャンペーン情報やおすすめのお仕事を通知しています。通知した情報をワーカーの方が開封することで初めて詳細な情報を確認することができるのですが、タイミーのアプリ以外のアプリでもプッシュ通知を利用されていると、様々なアプリから日々大量の通知を受け取っているという状況が発生しているかと思います。 このような状況下では、ワーカーの方々が重要な情報を見逃したり、通知の頻度が多すぎてストレスを感じたりする可能性があります。そのため、より効果的でユーザーフレンドリーな通知システムの構築が必要だと考えています。 そんな中でこの問題について有効と思われる手段を提案されている論文 「 Real-World Product Deployment of Adaptive Push Notification Scheduling on Smartphones 」[1] を見つけたので、本ブログではこの論文の内容を紹介したいと思います。 論文の概要 本論文では、プッシュ通知技術のアプローチである「適応型通知スケジューリング」について詳細に報告しています。慶應大学と Yahoo! JAPAN が実施した大規模研究(382,518人のユーザー、28日間)を通じて、この新しいアプローチの仕組みとその効果が検証されました。 まず従来のプッシュ通知システムの問題点を明らかにし、それらを解決するための適応型通知スケジューリングの手法を提案しています。この手法は、ユーザーの行動パターンを学習し、最適なタイミングで通知を配信することで、通知の効果を大幅に向上させることを目指しています。 システムの設計から実装、そして大規模な実験結果までが詳細に記述されており、この新しいアプローチがユーザーエンゲージメントにどのような影響を与えるかを明らかにしています。さらに、この技術の実用化に向けた課題や今後の展望についても議論されています。 従来のプッシュ通知手法の問題点 従来のプッシュ通知システムには、以下のような問題点があると述べられています。 ユーザーの状況を考慮しない一方的な通知配信 ユーザーが何をしているかに関係なく通知を送信するため、作業の中断や集中力の低下につながる可能性がある。 頻繁な割り込みによるユーザーの生産性低下とストレス増加 頻繁に作業を中断されることで、ユーザーにストレスや不満を与える可能性がある。 通知の無視や設定オフにつながるユーザーの疲弊 インタラプション過負荷の状態になると、ユーザーは通知を無視したり、通知設定をオフにしたりする可能性がある。これは、プッシュ通知本来の目的である情報伝達を阻害する要因となる。 クリック率や反応時間の低下によるマーケティング効果の減少 通知が頻繁に送られてくることで、ユーザーは通知に対して鈍感になり、クリック率や反応時間の低下につながる可能性がある。 これらの問題は、ユーザーエンゲージメントの低下と、プラットフォームの価値低下につながる危険性があります。 適応型通知スケジューリングの概要 適応型通知スケジューリングの大事なポイントは、ユーザーの「ブレークポイント」を検出することです。ブレークポイントとは、ユーザーが一つのタスクを終え、次のタスクに移る瞬間のことを指します。この瞬間は、ユーザーの認知負荷が低く、新しい情報を受け取るのに適しているとされています。 本論文では、スマートフォンのセンサーデータと機械学習を組み合わせて、リアルタイムでブレークポイントを検出するシステムについて述べられています。このシステムは、ユーザーの活動状態、時間帯、デバイスの状態などの多様なコンテキスト情報を活用しています。 具体的には、474 の特徴量を用いた分類モデルを構築し、ユーザーの状態をリアルタイムで評価します。 主な特徴量には以下が含まれます。 時間帯情報 デバイスの状態(充電状況、音量設定など) ユーザーの活動状態(Google Activity Recognitionを使用) アプリケーションの使用状況 モデルは線形回帰をもとにしており、日々のユーザーデータを用いて更新されます。また、平日と週末で異なる行動パターンに対応するため、コンテキストに応じた重み付けを導入しています。 そして、ブレークポイントが検出されるまで通知の配信を遅延させ、最適なタイミングで通知を表示します。 システムアーキテクチャ システムは主にクライアント側とサーバー側のコンポーネントで構成されています。 クライアント側: センサーデータの収集 特徴抽出 リアルタイムのブレークポイント検出 通知の遅延と表示 サーバー側: ログデータの収集と分析 日次モデルの更新 新しいモデルのクライアントへの配信 このアーキテクチャにより、ユーザーの行動パターンを日々学習し、モデルを継続的に改善することが可能となります。 実験と結果 382,518人の Android 利用ユーザーを対象に実験を実施しています。実験では、ユーザーを Test 群と Control 群にランダムに分割し、Test 群には新しい適応型通知システムを適用し、Control 群には従来の通知システムを使用しています。 主な結果は以下の通りです。 クリック率の平均が 23.3% 向上(最大 41.6%) 適応型通知スケジューリングを導入した結果、ユーザーのクリック率は平均で 23.3% 向上し、最大で 41.6% の向上率が確認されました。 ターゲット通知のクリック率の平均が 30.6% 向上(最大 60.7%) 特に、ユーザーの興味関心にもとづいてパーソナライズされたコンテンツを含むターゲット通知では、平均 30.6%、最大 60.7% と、より高いクリック率の向上が見られました。 一般通知のクリック率の平均が 11.1% 向上(最大 42.9%) 一方、すべてのユーザーに同じ内容が配信される一般通知でも、平均 11.1%、最大 42.9% のクリック率向上が確認されました。 通知配信後 120 秒以内のクリック率が 2.6〜2.86 倍に向上 適応型通知スケジューリングでは、通知配信後 120 秒以内のクリック率が、大幅に向上しました。 一般的な通知の場合:クリック率が2.60倍に向上 (実験群のクリック率 / 対照群のクリック率) ターゲット通知の場合:クリック率が2.86倍に向上 (実験群のクリック率 / 対照群のクリック率) これらの結果はユーザーのブレークポイントに合わせて通知が配信されることで、ユーザーの関心を高め、即座に反応しやすくなったためと考えられます。特にターゲット通知(パーソナライズされた内容)の場合、より大きな効果が見られました。この結果は、適切なタイミングとパーソナライズされた内容の組み合わせが、ユーザーエンゲージメントを大幅に向上させる可能性を示唆しています。 速報ニュースが多い日でも高いパフォーマンスを維持 速報ニュースが多い日でも、適応型通知スケジューリングは高いパフォーマンスを維持しました。これは、ユーザーのブレークポイント以外のタイミングで配信される速報ニュースが多い日でも、適応型通知は、ユーザーの適切なタイミングで配信されるため、クリック率への影響が少なかったと考えられます。 これらの結果は、適応型通知スケジューリングの有効性を強く示唆しています。 考察 実験結果から、以下の点が考察されます。 ユーザーの状況を考慮した通知は、大幅にエンゲージメントを向上させる パーソナライズされた内容(ターゲット通知)と適切なタイミングの組み合わせが最も効果的 適応型システムは、緊急時の通知と日常的な通知のバランスを効果的に管理できる ユーザーの即時反応性の向上は、情報のタイムリーな伝達に有効 これらの知見は、マッチングプラットフォームにおける通知戦略の最適化に大きく寄与すると考えられます。 今後の課題と展望 今後の主な課題と展望として、以下が挙げられています。 さらなるパーソナライゼーションの追求(通知内容と配信タイミングの最適化) クロスプラットフォーム対応(iOS, ウェブなど) リアルタイム処理の効率化 これらの課題に取り組むことで、より効果的でユーザーフレンドリーな通知システムの実現が期待されます。 まとめ 適応型通知スケジューリングは、ユーザーの状態を考慮したアプローチです。大規模実験の結果は、この手法が従来の問題点を大きく改善し、ユーザーエンゲージメントを向上させることを示しています。 マッチングプラットフォームにおいても、求職者と求人情報のマッチング精度の向上、ユーザー満足度の向上、そしてプラットフォーム全体の価値向上が期待できます。今後もこのような論文から得られた知見をもとに、タイミーアプリの継続的な改善を重ね、ユーザーとビジネスの双方に価値をもたらすことに貢献していきたいと考えています。 現在、タイミーでは、データサイエンスやエンジニアリングの分野で、共に成長し、革新を推し進めてくれる新たなチームメンバーを積極的に探しています! また、気軽な雰囲気での カジュアル面談 も随時行っておりますので、ぜひお気軽にエントリーしてください。↓ product-recruit.timee.co.jp hrmos.co hrmos.co 参考文献 [1] : Okoshi, T., Tsubouchi, K., & Tokuda, H. (2019). Real-World Product Deployment of Adaptive Push Notification Scheduling on Smartphones. In Proceedings of the 25th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining (KDD ’19) (pp. 2792-2800), DOI: 10.1145/3292500.3330732 (2019)
ふなち( https://x.com/_hunachi )です。 DroidKaigi 2024で登壇してきました! 初めてのDroidKaigiでの登壇はとても緊張しましたが、良い経験になりました。 登壇内容 タイムテーブル 2024.droidkaigi.jp 登壇資料 speakerdeck.com 登壇動画 www.youtube.com この内容で登壇した経緯 昨年から今年の頭にかけて、基本一人でもくもくPDFを表示するコードを書く機会がありました。 話はそれますが、iOSでPDFを表示させたいとなった時、公式のFrameworkであるPDFKit( https://developer.apple.com/documentation/pdfkit )を使うことができます。これが高性能で、リンクの表示や文字列検索など色々できてしまうのです。 しかしAndroidは… 調査した結果、公式から出ているAPI(PdfRenderer)では、Bitmap生成とページ数取得くらいしかできませんでした。 また、実装するときに気にすることも色々あり少し大変な道でした。Bitmapを大量に扱うので何も考えないで実装すると簡単にメモリリーク等させることができるのです。 しかもネット上の記事にも、(当時は特に)英語のもの含めPDFビュアーに関してメモリを考慮して実装したというようなものはありませんでした。 そんな困難に見舞われながらも実装することができたので一旦よかったのですが、一部悔しさはありました。 そんな時です。 Android 15のリリース予告記事でPDF周りのAPIがかなり強化されると発表されました🎉 記事を読んでみると、文字列検索などもできるらしく、革命だ!と思いました。 そのような中、Android 15(Vanilla Ice Cream)をエミュレーターで動かせるようになったタイミングで、休日も使い色々実装してみました。 ハッカソンで作ったようなコードと見た目なので今後も公開はしない予定ですが、PDFを要約する機能や、特定の文字列がある部分を枠で囲ってくれる機能、フォームの内容を変更できる機能を試すことができるアプリを作りました。 色々できるようになっていることを知れて、嬉しかったのを覚えています。 しかもこの時、運よくDroidKaigiのプロポーザル提出期間でした。 もともと緊張するタイプの私にとって、40分という時間のハードルはそこそこ高く、特に発表したいことがなければ提出するつもりはなかったのですが、PdfRendererの素晴らしい進化を伝えたいという気持ちに負けて、プロポーザルを提出しました。 その結果嬉しいことに、採択されました🙌 登壇準備 採択された後は、発表資料を作成するため、今までやってきた経験をもとに一から簡易的なPDF Viewerを作り、そのコードを使い発表資料を作成しました。 コードを書く方に時間を費やしすぎて、焦って資料作りすることになったので今後は気をつけようと思います。 資料を作る中で、やらかしたかも!と思う出来事もありました。 プロポーザルの編集期限が過ぎた後に、GDEの方のSNSでの発言により、androidx配下にpdf/pdf-viewerというディレクトリが爆誕していることに気がついたのです。プロポーザルに書き損ねてしまったことに後悔していたら、資料作成途中の8月頭に androidx.pdf:pdf-viewer の alpha がリリースされました。 そこで、プロポーザルに書いていないが話さないわけにはいかないなと思い、発表資料に androidx.pdf:pdf-viewer のことも組み込むことになりました。 この androidx.pdf:pdf-viewer の内部実装について紹介しようか迷いましたが、このライブラリと同じようなことをしたいのであればこのライブラリを使えばいいなと思い、重要な部分(プロセス分離の部分)を除いて内部実装については発表しないことにしました。 また同時通訳対象だったため、日英両方でスライドを書いたり、事前にスライドを提出したりする必要がありました。 大変な部分もありましたが、英語のチェックを社内の方や家族にしてもらえて助かりました🙇 発表数日前にはタイミーのメンバーの前で発表練習もさせてもらいました。 その際に色々と質問等をもらえたおかげで、ブラッシュアップすることができました。 忙しい中時間を作っていただき感謝です! 余談ですが、弊社には登壇や執筆をサポートする制度もあります。 productpr.timee.co.jp 今回私は入社前に登壇準備をすることになったため、あまり利用していませんが、今後はこちらの制度も活用したいです! 発表当日 発表では、めちゃくちゃ緊張していましたが、なんとか発表を無事終えることができました。 緊張で止まってしまったりすることもなく、発表時間もちょうどで終わったようでよかったです。 ただ、早く喋り過ぎたので同時通訳の方には負荷をかけただろうなと思い反省しています。次に同じような機会に恵まれた際には気をつけます。 少しニッチな内容なので、Ask the Speakerも暇になるかなと思っていましたが、 「今PDF関連のことを触っていて〜」や「ちょうど調べている内容なので助かりました!」と英語話者の方も含め質問していただいたり、声をかけていただいたりして嬉しかったです。 当日だけじゃなく後日含め、SNSやブログでもピックアップしてコメントをくださった方々もありがとうございます。 直接の反応はあまりできていませんが、見るたびにとても嬉しい気持ちになっています。 写真はDroidKaigiが公開しているアルバムから引用 引用元: https://x.com/DroidKaigi/status/1840692565538136507 発表以外の感想 実は今年もスタッフをしており、主にセッション進行のお手伝いを行なっていました。 また、スタッフ業務をしながら他の方のセッションを見ていたのですが、そこにも多くの学びがありました。 まだ全てのセッションを見ることができてないので時間を作って見てみようと思います。 アフターパーティなどでも他の登壇者の方や私の発表を聞いてくださった方、初めましての方や久しぶりにお会いした方々とお話しすることができ、とても楽しかったです。 まとめ 私の発表を聞いてくださった方々、登壇のサポートをしてくださった方々、スタッフの方々など、皆様に感謝しています。ありがとうございます! 40分の登壇を経験したことで、他の登壇している人たちをさらに尊敬できるようになりました。 とても良い経験でした。 これからも積極的にインプットとアウトプットをしていこうと思います! 余談 発表中緊張して途中で声が出なくなるといけないので、いざという時にお面をつけて落ち着こうと考えてました。お面は前日の夜に緊張具合を考慮し作成しました。 登壇直前のマイク確認でお面をつけて喋ってもマイクが通るかの確認もしました。 確認の結果、お面をしていてもマイクに音は乗ることがわかりました。 このいざという時の備えができた安心感も発表を乗り切れた理由の一つかもしれません。 お面参戦なら登壇できるかもという方は、来年以降プロポーザル提出チャレンジをしてみると良いと思います! また、他のメンバーによるDroidKaigi 2024参加レポートも公開しています。是非こちらも読んでみて下さい↓ tech.timee.co.jp
9/11~9/13 にかけて DroidKaigi 2024 が開催され、タイミーの Android アプリエンジニアチームが参加してきました。 はじめに 9月にジョインされた hunachi が登壇しています。Android の PDF Viewer に関する歴史や詳細な実装からライブラリの紹介まで PDF Viewer を網羅したセッションとなっているのでぜひアーカイブでご覧ください。 2024.droidkaigi.jp タイミーではブースも出しておりノベルティやタイミンの写真を撮りに来られる方など盛況でした。足を運んでいただいた皆様ありがとうございます! この DroidKaigi から配布するノベルティに新しく「マイクロファイバークロス」が追加されました。めちゃめちゃかわいいデザインになっているので手に入れた方はぜひ使ってみてください! また、ネイルブースの横に出していたので「せっかくだし」と初めてネイルを体験しましたが、とてもかわいくテンションも上がって最高でした。 今回は DroidKaigi 2024 に参加した Android アプリ開発メンバー( tick-taku , murata, haru, nashihara)が気になったセッションの感想などをレポートします。 セッション紹介 nashihara Day1: From 0 to 100 with Kotlin and Compose Multiplatform 2024.droidkaigi.jp Kotlin Multiplatform / Compose Multiplatform について、初学者向けにどうやって書いたらいいのかや platform ごとの書き分けの方法などが解説されていました。 Day1のセッションはこちらのみでワークショップ形式での講演でした。 ワークショップの内容は、platform ごとのコードを書く場所の説明から始まり、platform ごとに使えるAPIの種類(例えば kotlin api は全ての platform で使えるが android api は android platform でしか使えない)の説明やそれぞれの Lifecycle がどうなっているかなど、主にこれから KMP/CMP を触り始める人を対象とした内容でした。 自分は今回初めて触る内容が多く新鮮で面白かったです。 特にワークショップの課題として実際に Compose で書いたUIを web / desktop / android platform ごとに実行し確認する、という内容がありましたが、いつも android でUIを作る感覚でUIを書くだけで、 web / desktop アプリを作れてしまうのは、わかっていても感動しました。 Lifecycle に関しても、ちゃんとハンドリングできるようになっていて、web であれば focus → onResume、 blur → onPause 、ios であれば viewWillAppear → onStart、 viewDidDisapper → onStop となっており、 android アプリ開発者が見慣れた形で実装できる点がすごく良いです。 また、collectAsStateWithLifecycle を使っておけばいい感じになる、という便利メソッドも教えてもらいました。 初学者にとって、KMP / CMP の良い導入になるワークショップで楽しいセッションでした。 tick-taku デザインからアプリ実装まで一貫したデザインシステムを構築するベストプラクティス 2024.droidkaigi.jp デザインシステムにおけるFigma との連携やプラクティス、導入することのメリットや心構えなどが解説されています。 最近タイミーではデザインシステムを導入しました。その時にリードしてくれたデザイナーの方が話していた内容が、エンジニア目線から解説されていてより理解が深まりました。 個人的には F imga を中間言語として会話できるようになる ことにとても共感しています。 UI 実装においてデザインファイルがスナップショットになっており、実装との差が発生することでデザイナーとのコミュニケーションが都度発生し、開発スピードが遅くなったり心理的ハードルになったりしていました。デザインシステムを導入することで Figma を起点としてデザイン製作時にエンジニアとデザイナー双方のエッセンスが考慮されたコンポーネントで作られることになり、これらの課題のコスト軽減が期待できます。 また Figma の property と Composable の引数を同期させれば、 Figma 上に実装のために必要な情報が全て書かれています。それだけ見ればある種脳死で実装できるようになり、より統一感のある UI 実装がスピーディ & スムーズに行えるようになります。 Figma は素晴らしいツールですね! デザインシステム導入時に非常に勉強になるポイントがちりばめられたセッションでした。スライドもとても分かりやすく見やすかったです。 2024年のナビゲーション・フォーカス対応:Composeでキーボード・ナビゲーションをサポートしよう 2024.droidkaigi.jp Android のアクセシビリティにおける focus についての紹介や実装のポイントが解説されているセッションでした。 実装についてはアクセシビリティを意識したユースケースの紹介と実装や、xml を何年も書いてない人のために Compose での実装方法や tips も紹介されていました。 特に「宣言順序とレベルによって focus が流れる」という話は、なんとなくそうなんだろうなと思っていたところもあって納得できました。focus のことを考えるとやはり ConstraintLayout を多用するのはよくないのかも。 やっぱり Modifier 順序問題は難しいですね... また動作確認方法についても触れられていました。 特に Android Studio 上で物理デバイスのミラーリングができることは個人的に初耳で知れて非常に良かったです。 いつもスプリントレビューなどでチームに画面共有する際に、ブラウザと並べて表示するため scrcpy を使っていましたが IDE だけで済みそうです。 ソフトウェアキーボード体験改善の tips はまた今度とのことなのでそちらも楽しみに待っています。 haru Jetpack ComposeにおけるShared Element Transitionsの実例と導入方法 またその仕組み 2024.droidkaigi.jp Shared Element Transition, 皆さんは使っていますか? 私たちタイミーのアプリ内でもいくつかの場所で使用しており、Compose化するときに泣く泣くShared Element Transitionを使わない方法で実装し直したりしていましたが、ついにComposeでも取り入れられるようになってきました。 このセッションでは、実際にGoogle PhotosのようなUIをComposeで実装していくことによって実際の実装方法、つまづきポイント、その解決方法などを順番に紹介してもらうことができました。 Transitionにもいくつか種類があり、どちらの方が見え方が綺麗なのかといったところまで説明されていて実際に取り入れる際にもとても参考になるセッションでした。 murata Kotlin 2.0が与えるAndroid開発の進化 2024.droidkaigi.jp タイトル通り、Kotlin 2.0によって受けられる恩恵がこれでもかというくらい紹介されていたセッションでした! 数も多かったですが、特に個人的に刺さったものをピックアップして紹介します。 Power AssertをKotlinが正式にサポート 🎉 従来のUnitTestにおけるFailed Messageは非常にシンプルにExpectedとActualの値が表示されるだけのメッセージでした。 Expected : 0 Actual : 6 Power Assertを導入した場合は、以下のようにテストが失敗した理由を事細かに表示してくれます。※公式ページより引用 Incorrect length assert ( hello . length == world . substring ( 1 , 4 ) . length ) { "Incorrect length" } | | | | | | | | | | | 3 | | | | orl | | | world ! | | false | 5 Hello 注意点として、assert式の書き方をPower Assertを意識した書き方に少し変える必要はありそうです。 例えば、 assert(isValidName && isValidAge) のような変数のみをassert式に入れてしまうと以下のようにFailed Messageの情報量も減ってしまう為、先ほどの例のように変数をインライン化する必要があります。 Assertion failed assert ( isValidName && isValidAge ) | | | false true ですが、導入することでテストを用いた開発やCIが落ちた時の要因調査が捗ること間違い無しの素晴らしい機能ですね!! Jetpack Compose Strong Skip Mode enabled by default Kotlin 2.0.20 より、ComposeのStrong Skip ModeがデフォルトでONになりました。 具体的には、Unstableな引数を使用していても、同一instanceであればRecomposeされなくなります。 Strong Skip Modeについては既に各所で話題になっていましたが、さらにこのセクションで紹介されていた以下のポイントが個人的に刺さりました。 Stability Configuration File 使用する箇所とは遠いところでStableの指定を行う行為自体に懐疑的でしたが、セッションで紹介されていた「java.time.LocalDateのようなJavaのクラスをStableだと認識させる」ようなユースケースではかなり有用だと感じました。 Lambdaの再生成の条件 Lambdaの中で一般的なViewModelのようなUnstableな変数を参照していても再生成されなくなるといった点は非常に便利だなと感じました。onClickのコールバックにてViewModelのメソッドを呼ぶようなこと、よくありますもんね。 代わりに、Lambdaの再生成を前提にしていたようなケースでは @DontMemoize を新たに指定する必要が出てくる点にも地味に注意が必要だと感じました。たまによくありそう。 Object equalsとInstance equals 従来はListは常にUnstableだと見なされていた為、タイミーではComposableの引数にリストを指定する場合はListではなくkotlinx.collections.immutable.ImmutableList を利用するルールとしていました。 ただし、セッションで紹介されている通り、パフォーマンス観点において「Object equalsはO(n)に対してinstance equalsはO(1)」となります。 よって、件数が多く複雑なlistである場合には、ImmutableListではなくListを指定することでinstance equalsとした方が速くなるケースが考えられる為、Strong Skip ModeがONになった際にはどちらを使用するのか都度検討する必要がありそうです。 これらの他にもKotlin 2.0 の良きポイントを理解できる情報がたっぷりな、とても良いセッションでした!! 発表いただき本当にありがとうございました! まとめ 9月に新たにジョインされたメンバーも KaigiPass 制度を利用してみんなでワイワイ参加できました。カンファレンス後にチーム内でセッションのシェアやディスカッションを行うなど、非常に多くの学びやヒントを得ることができモチベーションへ繋がるカンファレンスとなりました。 ブースやアフターパーティーなどでさまざまな人と交流できたり、セッションのアーカイブのアップが早くその日に見直すこともできたり素晴らしい体験ができました。スタッフの皆様ありがとうございました! 次回の開催も楽しみにしています! 余談 今回タイミーはDroidKaigi 2024にゴールドスポンサーとして協賛し、冒頭のブース出展だけでなくDroidKaigiスカラーシップの活動もお手伝いさせていただきました。 そして、DroidKaigiスカラーシップでは企業訪問の取り組みに参加し、DroidKaigiに参加されていた学生さんたちをタイミーのオフィスにご招待しました! タイミー社員と一緒にランチをしたり、オフィス内を一緒に周り紹介したりする中で、熱心に活動されている学生のみなさんとお話できたことは、私たちにとっても楽しく、とても良い機会でした。 タイミーはこれからも技術コミュニティへの協賛、協力を通して一緒に盛り上げていきます!それではまたどこかの勉強会・カンファレンスでお会いしましょう👋
タイミーでバックエンドのテックリードをしている新谷( @euglena1215 )です。 タイミーでは RBS の活用を推進する取り組みを少しずつ進めています。意図は こちら メンバーと雑談していたときに「steep check でコケたときにその名前で調べても全然ヒットしないので型周りのキャッチアップが難しい」という話を聞きました。 いくつかのエラー名でググってみたところ、 Ruby::ArgumentTypeMismatch や Ruby::NoMethod など有名なエラーはヒットしますがほとんどのエラーはヒットせず、ヒットするのは Steep リポジトリの該当実装のみでした。 これでは確かにキャッチアップは難しいだろうと感じたので、Steep のエラーリファレンスを作ってみました。ググってヒットするのが目的なのでテックブログとして公開してインデックスされることを期待します。 各エラーの説明は以下のフォーマットで行います。 エラー名 説明: 簡単なエラーの説明 例: エラーが検出される Ruby コード steep check を実行して得られるエラーメッセージ severity : Steep のエラープリセットに対して、該当エラーの severity がどのように設定されているかの表 Ruby::ArgumentTypeMismatch 説明 : メソッドの型が一致しない場合に発生します。 違反例 : ' 1 ' + 1 test.rb:1:6: [error] Cannot pass a value of type `::Integer` as an argument of type `::string` │ ::Integer <: ::string │ ::Integer <: (::String | ::_ToStr) │ ::Integer <: ::String │ ::Numeric <: ::String │ ::Object <: ::String │ ::BasicObject <: ::String │ │ Diagnostic ID: Ruby::ArgumentTypeMismatch │ └ '1' + 1 ~ severity : all_error default strict lenient silent error error error information nil Ruby::BlockBodyTypeMismatch 説明 : ブロックの body の返り値の型が期待される型と一致しない場合に発生します。 違反例 : lambda {|x| x + 1 } #: ^(Integer) -> String test.rb:1:7: [error] Cannot allow block body have type `::Integer` because declared as type `::String` │ ::Integer <: ::String │ ::Numeric <: ::String │ ::Object <: ::String │ ::BasicObject <: ::String │ │ Diagnostic ID: Ruby::BlockBodyTypeMismatch │ └ lambda {|x| x + 1 } #: ^(Integer) -> String ~~~~~~~~~~~~ severity : all_error default strict lenient silent error warning error information nil Ruby::BlockTypeMismatch 説明 : ブロックの型が期待される型と一致しない場合に発生します。 違反例 : multi = ->(x, y) { x * y } #: ^(Integer, Integer) -> Integer [ 1 , 2 , 3 ].map(&multi) test.rb:2:14: [error] Cannot pass a value of type `^(::Integer, ::Integer) -> ::Integer` as a block-pass-argument of type `^(::Integer) -> U(1)` │ ^(::Integer, ::Integer) -> ::Integer <: ^(::Integer) -> U(1) │ (Params are incompatible) │ │ Diagnostic ID: Ruby::BlockTypeMismatch │ └ [1, 2, 3].map(&multi) ~~~~~~ severity : all_error default strict lenient silent error warning error information nil Ruby::BreakTypeMismatch 説明 : break の型が期待される型と一致しない場合に発生します。 違反例 : 123 .tap { break "" } test.rb:1:10: [error] Cannot break with a value of type `::String` because type `::Integer` is assumed │ ::String <: ::Integer │ ::Object <: ::Integer │ ::BasicObject <: ::Integer │ │ Diagnostic ID: Ruby::BreakTypeMismatch │ └ 123.tap { break "" } ~~~~~~~~ severity : all_error default strict lenient silent error hint error hint nil Ruby::DifferentMethodParameterKind 説明 : メソッドのパラメータの種類が一致しない場合に発生します。省略可能な引数の prefix に ? をつけ忘れることで発生することが多いです。 違反例 : # @type method bar: (name: String) -> void def bar ( name : " foo " ) end test.rb:2:8: [error] The method parameter has different kind from the declaration `(name: ::String) -> void` │ Diagnostic ID: Ruby::DifferentMethodParameterKind │ └ def bar(name: "foo") ~~~~~~~~~~~ severity : all_error default strict lenient silent error hint error nil nil Ruby::FallbackAny 説明 : 型が不明な場合に untyped が使用されることを示します。一度 [] で値を初期化したのちに再代入するような実装で発生することが多いです。 違反例 : a = [] a << 1 test.rb:1:4: [error] Cannot detect the type of the expression │ Diagnostic ID: Ruby::FallbackAny │ └ a = [] ~~ severity : all_error default strict lenient silent error hint warning nil nil Ruby::FalseAssertion 説明 : Steep の型アサーションが誤っている場合に発生します。 違反例 : array = [] #: Array[Integer] hash = array #: Hash[Symbol, String] test.rb:2:7: [error] Assertion cannot hold: no relationship between inferred type (`::Array[::Integer]`) and asserted type (`::Hash[::Symbol, ::String]`) │ Diagnostic ID: Ruby::FalseAssertion │ └ hash = array #: Hash[Symbol, String] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ severity : all_error default strict lenient silent error hint error nil nil Ruby::ImplicitBreakValueMismatch 説明 : 引数無し break の値( nil )がメソッドの返り値の期待される型と一致しない場合に発生します。 違反例 : class Foo # @rbs () { (String) -> Integer } -> String def foo '' end end Foo .new.foo do |x| break end test.rb:9:2: [error] Breaking without a value may result an error because a value of type `::String` is expected │ nil <: ::String │ │ Diagnostic ID: Ruby::ImplicitBreakValueMismatch │ └ break ~~~~~ severity : all_error default strict lenient silent error hint information nil nil Ruby::IncompatibleAnnotation 説明 : 型注釈が不適切または一致しない場合に発生します。 違反例 : a = [ 1 , 2 , 3 ] if _ = 1 # @type var a: String a + "" end test.rb:5:2: [error] Type annotation about `a` is incompatible since ::String <: ::Array[::Integer] doesn't hold │ ::String <: ::Array[::Integer] │ ::Object <: ::Array[::Integer] │ ::BasicObject <: ::Array[::Integer] │ │ Diagnostic ID: Ruby::IncompatibleAnnotation │ └ a + "" ~~~~~~ severity : all_error default strict lenient silent error hint error nil nil Ruby::IncompatibleArgumentForwarding 説明 : 引数に ... を使ってメソッドの引数を forward する際に、引数の型が一致しない場合に発生します。 違反例 : class Foo # @rbs (*Integer) -> void def foo (*args) end # @rbs (*String) -> void def bar (...) foo(...) end end test.rb:8:8: [error] Cannot forward arguments to `foo`: │ (*::Integer) <: (*::String) │ ::String <: ::Integer │ ::Object <: ::Integer │ │ Diagnostic ID: Ruby::IncompatibleArgumentForwarding │ └ foo(...) ~~~ severity : all_error default strict lenient silent error warning error information nil Ruby::IncompatibleAssignment 説明 : 代入の際の型が不適切または一致しない場合に発生します。 違反例 : # @type var x: Integer x = " string " test.rb:2:0: [error] Cannot assign a value of type `::String` to a variable of type `::Integer` │ ::String <: ::Integer │ ::Object <: ::Integer │ ::BasicObject <: ::Integer │ │ Diagnostic ID: Ruby::IncompatibleAssignment │ └ x = "string" ~~~~~~~~~~~~ severity : all_error default strict lenient silent error hint error hint nil Ruby::InsufficientKeywordArguments 説明 : キーワード引数が不足している場合に発生します。 違反例 : class Foo def foo ( a :, b :) end end Foo .new.foo( a : 1 ) test.rb:5:8: [error] More keyword arguments are required: b │ Diagnostic ID: Ruby::InsufficientKeywordArguments │ └ Foo.new.foo(a: 1) ~~~~~~~~~ severity : all_error default strict lenient silent error error error information nil Ruby::InsufficientPositionalArguments 説明 : 位置引数が不足している場合に発生します。 違反例 : class Foo def foo (a, b) end end Foo .new.foo( 1 ) test.rb:5:8: [error] More keyword arguments are required: b │ Diagnostic ID: Ruby::InsufficientKeywordArguments │ └ Foo.new.foo(a: 1) ~~~~~~~~~ severity : all_error default strict lenient silent error error error information nil Ruby::InsufficientTypeArgument 説明 : 型引数に対する型注釈が不足している場合に発生します。 違反例 : class Foo # @rbs [T, S] (T, S) -> [T, S] def foo (x, y) [x, y] end end Foo .new.foo( 1 , 2 ) #$ Integer test.rb:8:0: [error] Requires 2 types, but 1 given: `[T, S] (T, S) -> [T, S]` │ Diagnostic ID: Ruby::InsufficientTypeArgument │ └ Foo.new.foo(1, 2) #$ Integer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ severity : all_error default strict lenient silent error hint error nil nil Ruby::InvalidIgnoreComment 説明 : steep:ignore:start コメントはあるが steep:ignore:end コメントがないなど、無効なコメントが存在する場合に発生します。 違反例 : # steep:ignore:start test.rb:1:0: [error] Invalid ignore comment │ Diagnostic ID: Ruby::InvalidIgnoreComment │ └ # steep:ignore:start ~~~~~~~~~~~~~~~~~~~~ severity : all_error default strict lenient silent error warning warning warning nil Ruby::MethodArityMismatch 説明 : キーワード引数なのに順序引数としてメソッドの引数の型を記述しているなど、メソッドの引数の型が一致しない場合に発生します。 違反例 : class Foo # @rbs (Integer x) -> Integer def foo ( x :) x end end test.rb:3:9: [error] Method parameters are incompatible with declaration `(::Integer) -> ::Integer` │ Diagnostic ID: Ruby::MethodArityMismatch │ └ def foo(x:) ~~~~ severity : all_error default strict lenient silent error error error information nil Ruby::MethodBodyTypeMismatch 説明 : メソッドの返り値が期待される型と一致しない場合に発生します。 違反例 : class Foo # @rbs () -> String def foo 1 end end test.rb:3:6: [error] Cannot allow method body have type `::Integer` because declared as type `::String` │ ::Integer <: ::String │ ::Numeric <: ::String │ ::Object <: ::String │ ::BasicObject <: ::String │ │ Diagnostic ID: Ruby::MethodBodyTypeMismatch │ └ def foo ~~~ severity : all_error default strict lenient silent error error error warning nil Ruby::MethodDefinitionMissing 説明 : メソッドの型定義が存在するがメソッドの実装が欠落している場合に発生します。 違反例 : class Foo # @rbs! # def bar: () -> void end test.rb:1:6: [error] Cannot find implementation of method `::Foo#bar` │ Diagnostic ID: Ruby::MethodDefinitionMissing │ └ class Foo ~~~ severity : all_error default strict lenient silent error nil hint nil nil Ruby::MethodParameterMismatch 説明 : メソッドのパラメータの型が一致しない場合に発生します。 違反例 : class Foo # @rbs (Integer x) -> Integer def foo ( x :) x end end test.rb:3:10: [error] The method parameter is incompatible with the declaration `(::Integer) -> ::Integer` │ Diagnostic ID: Ruby::MethodParameterMismatch │ └ def foo(x:) ~~ severity : all_error default strict lenient silent error error error warning nil Ruby::MethodReturnTypeAnnotationMismatch 説明 : メソッドの戻り値の型注釈が期待される型と一致しない場合に発生します。 違反例 : class Foo # @rbs () -> String def foo # @type return: Integer 123 end end test.rb:3:2: [error] Annotation `@type return` specifies type `::Integer` where declared as type `::String` │ ::Integer <: ::String │ ::Numeric <: ::String │ ::Object <: ::String │ ::BasicObject <: ::String │ │ Diagnostic ID: Ruby::MethodReturnTypeAnnotationMismatch │ └ def foo ~~~~~~~ severity : all_error default strict lenient silent error hint error nil nil Ruby::MultipleAssignmentConversionError 説明 : 複数代入の変換に失敗した場合に発生します。 違反例 : class WithToAry # @rbs () -> Integer def to_ary 1 end end a, b = WithToAry .new() test.rb:8:8: [error] Cannot convert `::WithToAry` to Array or tuple (`#to_ary` returns `::Integer`) │ Diagnostic ID: Ruby::MultipleAssignmentConversionError │ └ (a, b = WithToAry.new()) ~~~~~~~~~~~~~~~ severity : all_error default strict lenient silent error hint error nil nil Ruby::NoMethod 説明 : 型定義が存在しないメソッドが呼び出された場合に発生します。 違反例 : "" .non_existent_method test.rb:1:3: [error] Type `::String` does not have method `non_existent_method` │ Diagnostic ID: Ruby::NoMethod │ └ "".non_existent_method ~~~~~~~~~~~~~~~~~~~ severity : all_error default strict lenient silent error error error information nil Ruby::ProcHintIgnored 説明 : Proc に関する型注釈が無視された場合に発生します。 違反例 : # @type var proc: (^(::Integer) -> ::String) | (^(::String, ::String) -> ::Integer) proc = -> (x) { x.to_s } test.rb:2:7: [error] The type hint given to the block is ignored: `(^(::Integer) -> ::String | ^(::String, ::String) -> ::Integer)` │ Diagnostic ID: Ruby::ProcHintIgnored │ └ proc = -> (x) { x.to_s } ~~~~~~~~~~~~~~~~~ severity : all_error default strict lenient silent error hint information nil nil Ruby::ProcTypeExpected 説明 : Proc 型が期待される場合に発生します。 違反例 : -> (&block) do # @type var block: Integer end test.rb:1:4: [error] Proc type is expected but `::Integer` is specified │ Diagnostic ID: Ruby::ProcTypeExpected │ └ -> (&block) do ~~~~~~ severity : all_error default strict lenient silent error hint error nil nil Ruby::RBSError 説明 : 型アサーションや型適用に書かれたRBS型がエラーを生じる場合に発生します。 違反例 : a = 1 #: Int test.rb:1:9: [error] Cannot find type `::Int` │ Diagnostic ID: Ruby::RBSError │ └ a = 1 #: Int ~~~ severity : all_error default strict lenient silent error information error information nil Ruby::RequiredBlockMissing 説明 : メソッド呼び出し時に必要な block が欠落している場合に発生します。 違反例 : class Foo # @rbs () { () -> void } -> void def foo yield end end Foo .new.foo test.rb:7:8: [error] The method cannot be called without a block │ Diagnostic ID: Ruby::RequiredBlockMissing │ └ Foo.new.foo ~~~ severity : all_error default strict lenient silent error error error hint nil Ruby::ReturnTypeMismatch 説明 : return の型とメソッドの戻り値の型が一致しない場合に発生します。 違反例 : # @type method foo: () -> Integer def foo return " string " end test.rb:3:2: [error] The method cannot return a value of type `::String` because declared as type `::Integer` │ ::String <: ::Integer │ ::Object <: ::Integer │ ::BasicObject <: ::Integer │ │ Diagnostic ID: Ruby::ReturnTypeMismatch │ └ return "string" ~~~~~~~~~~~~~~~ severity : all_error default strict lenient silent error error error warning nil Ruby::SetterBodyTypeMismatch 説明 : セッターメソッドの戻り値の型が期待される型と一致しない場合に発生します。 違反例 : class Foo # @rbs (String) -> String def foo= (value) 123 end end test.rb:3:6: [error] Setter method `foo=` cannot have type `::Integer` because declared as type `::String` │ ::Integer <: ::String │ ::Numeric <: ::String │ ::Object <: ::String │ ::BasicObject <: ::String │ │ Diagnostic ID: Ruby::SetterBodyTypeMismatch │ └ def foo=(value) ~~~~ severity : all_error default strict lenient silent error information error nil nil Ruby::SetterReturnTypeMismatch 説明 : セッターメソッドの return の型が期待される型と一致しない場合に発生します。 違反例 : class Foo # @rbs (String) -> String def foo= (value) return 123 end end test.rb:4:4: [error] The setter method `foo=` cannot return a value of type `::Integer` because declared as type `::String` │ ::Integer <: ::String │ ::Numeric <: ::String │ ::Object <: ::String │ ::BasicObject <: ::String │ │ Diagnostic ID: Ruby::SetterReturnTypeMismatch │ └ return 123 ~~~~~~~~~~ severity : all_error default strict lenient silent error information error nil nil Ruby::SyntaxError 説明 : Ruby の構文エラーが発生した場合に発生します。 違反例 : if x == 1 puts " Hello " test.rb:2:14: [error] SyntaxError: unexpected token $end │ Diagnostic ID: Ruby::SyntaxError │ └ puts "Hello" severity : all_error default strict lenient silent error hint hint hint nil Ruby::TypeArgumentMismatchError 説明 : 型引数が期待される型と一致しない場合に発生します。 違反例 : class Foo # @rbs [T < Numeric] (T) -> T def foo (x) x end end Foo .new.foo( "" ) #$ String test.rb:7:19: [error] Cannot pass a type `::String` as a type parameter `T < ::Numeric` │ ::String <: ::Numeric │ ::Object <: ::Numeric │ ::BasicObject <: ::Numeric │ │ Diagnostic ID: Ruby::TypeArgumentMismatchError │ └ Foo.new.foo("") #$ String ~~~~~~ severity : all_error default strict lenient silent error hint error nil nil Ruby::UnexpectedBlockGiven 説明 : ブロックが予期されない場面で渡された場合に発生します。 違反例 : [ 1 ].at( 1 ) { 123 } test.rb:1:10: [error] The method cannot be called with a block │ Diagnostic ID: Ruby::UnexpectedBlockGiven │ └ [1].at(1) { 123 } ~~~~~~~ severity : all_error default strict lenient silent error warning error hint nil Ruby::UnexpectedDynamicMethod 説明 : 動的に定義されたメソッドが存在しない場合に発生します。 違反例 : class Foo # @dynamic foo def bar end end test.rb:1:6: [error] @dynamic annotation contains unknown method name `foo` │ Diagnostic ID: Ruby::UnexpectedDynamicMethod │ └ class Foo ~~~ severity : all_error default strict lenient silent error hint information nil nil Ruby::UnexpectedError 説明 : 予期しない一般的なエラーが発生した場合に発生します。 違反例 : class Foo # @rbs () -> String123 def foo end end test.rb:1:0: [error] UnexpectedError: sig/generated/test.rbs:5:17...5:26: Could not find String123(RBS::NoTypeFoundError) │ ... │ (36 more backtrace) │ │ Diagnostic ID: Ruby::UnexpectedError │ └ class Foo ~~~~~~~~~ severity : all_error default strict lenient silent error hint information hint nil Ruby::UnexpectedJump 説明 : 予期しないジャンプが発生した場合に発生します。 違反例 : break test.rb:1:0: [error] Cannot jump from here │ Diagnostic ID: Ruby::UnexpectedJump │ └ break ~~~~~ severity : all_error default strict lenient silent error hint error nil nil Ruby::UnexpectedJumpValue 説明 : ジャンプの値を渡しても値が無視される場合に発生します。 違反例 : while true next 3 end test.rb:2:2: [error] The value given to next will be ignored │ Diagnostic ID: Ruby::UnexpectedJumpValue │ └ next 3 ~~~~~~ severity : all_error default strict lenient silent error hint error nil nil Ruby::UnexpectedKeywordArgument 説明 : 予期しないキーワード引数が渡された場合に発生します。 違反例 : class Foo # @rbs (x: Integer) -> void def foo ( x :) end end Foo .new.foo( x : 1 , y : 2 ) test.rb:7:18: [error] Unexpected keyword argument │ Diagnostic ID: Ruby::UnexpectedKeywordArgument │ └ Foo.new.foo(x: 1, y: 2) ~ severity : all_error default strict lenient silent error error error information nil Ruby::UnexpectedPositionalArgument 説明 : 予期しない位置引数が渡された場合に発生します。 違反例 : class Foo # @rbs (Integer) -> void def foo (x) end end Foo .new.foo( 1 , 2 ) test.rb:7:15: [error] Unexpected positional argument │ Diagnostic ID: Ruby::UnexpectedPositionalArgument │ └ Foo.new.foo(1, 2) ~ severity : all_error default strict lenient silent error error error information nil Ruby::UnexpectedSuper 説明 : super を呼び出した際に親クラスに同名のメソッドが定義されていないなど、予期しない場面で super が使用された場合に発生します。 違反例 : class Foo def foo super end end test.rb:3:4: [error] No superclass method `foo` defined │ Diagnostic ID: Ruby::UnexpectedSuper │ └ super ~~~~~ severity : all_error default strict lenient silent error information error nil nil Ruby::UnexpectedTypeArgument 説明 : 予期しない型引数が渡された場合に発生します。 違反例 : class Foo # @rbs [T] (T) -> T def foo (x) x end end Foo .new.foo( 1 ) #$ Integer, Integer test.rb:8:27: [error] Unexpected type arg is given to method type `[T] (T) -> T` │ Diagnostic ID: Ruby::UnexpectedTypeArgument │ └ Foo.new.foo(1) #$ Integer, Integer ~~~~~~~ severity : all_error default strict lenient silent error hint error nil nil Ruby::UnexpectedYield 説明 : yield が予期しない場面で使用された場合に発生します。 違反例 : class Foo # @rbs () -> void def foo yield end end test.rb:4:4: [error] No block given for `yield` │ Diagnostic ID: Ruby::UnexpectedYield │ └ yield ~~~~~ severity : all_error default strict lenient silent error warning error information nil Ruby::UnknownConstant 説明 : 未知の定数が参照された場合に発生します。 違反例 : FOO test.rb:1:0: [error] Cannot find the declaration of constant: `FOO` │ Diagnostic ID: Ruby::UnknownConstant │ └ FOO ~~~ severity : all_error default strict lenient silent error warning error hint nil Ruby::UnknownGlobalVariable 説明 : 未知のグローバル変数が参照された場合に発生します。 違反例 : $foo test.rb:1:0: [error] Cannot find the declaration of global variable: `$foo` │ Diagnostic ID: Ruby::UnknownGlobalVariable │ └ $foo ~~~~ severity : all_error default strict lenient silent error warning error hint nil Ruby::UnknownInstanceVariable 説明 : 未知のインスタンス変数が参照された場合に発生します。 違反例 : class Foo def foo @foo = ' foo ' end end test.rb:3:4: [error] Cannot find the declaration of instance variable: `@foo` │ Diagnostic ID: Ruby::UnknownInstanceVariable │ └ @foo = 'foo' ~~~~ severity : all_error default strict lenient silent error information error hint nil Ruby::UnreachableBranch 説明 : if , unless による到達不可能な分岐が存在する場合に発生します。 違反例 : if false 1 end test.rb:1:0: [error] The branch is unreachable │ Diagnostic ID: Ruby::UnreachableBranch │ └ if false ~~ severity : all_error default strict lenient silent error hint information hint nil Ruby::UnreachableValueBranch 説明 : case when による到達不可能な分岐が存在し、分岐の型が bot でなかった場合に発生します。 違反例 : x = 1 case x when Integer " one " when String " two " end test.rb:5:0: [error] The branch may evaluate to a value of `::String` but unreachable │ Diagnostic ID: Ruby::UnreachableValueBranch │ └ when String ~~~~ severity : all_error default strict lenient silent error hint warning hint nil Ruby::UnresolvedOverloading 説明 : オーバーロードが行われているメソッドに対して型が解決できない場合に発生します。 違反例 : 3 + " foo " test.rb:1:0: [error] Cannot find compatible overloading of method `+` of type `::Integer` │ Method types: │ def +: (::Integer) -> ::Integer │ | (::Float) -> ::Float │ | (::Rational) -> ::Rational │ | (::Complex) -> ::Complex │ │ Diagnostic ID: Ruby::UnresolvedOverloading │ └ 3 + "foo" ~~~~~~~~~ severity : all_error default strict lenient silent error error error information nil Ruby::UnsatisfiableConstraint 説明 : RBSと型注釈の辻褄が合わないなど、どうやっても型制約が満たされない場合に発生します。 違反例 : class Foo # @rbs [A, B] (A) { (A) -> void } -> B def foo (x) end end test = Foo .new test.foo( 1 ) do |x| # @type var x: String end test.rb:9:0: [error] Unsatisfiable constraint `::Integer <: A(1) <: ::String` is generated through (A(1)) { (A(1)) -> void } -> B(2) │ ::Integer <: ::String │ ::Numeric <: ::String │ ::Object <: ::String │ ::BasicObject <: ::String │ │ Diagnostic ID: Ruby::UnsatisfiableConstraint │ └ test.foo(1) do |x| ~~~~~~~~~~~~~~~~~~ severity : all_error default strict lenient silent error hint error hint nil Ruby::UnsupportedSyntax 説明 : Steep としてサポートされていない構文が使用された場合に発生します。 違反例 : (_ = []).[]=(*(_ = nil )) test.rb:1:13: [error] Unsupported splat node occurrence │ Diagnostic ID: Ruby::UnsupportedSyntax │ └ (_ = []).[]=(*(_ = nil)) ~~~~~~~~~~ severity : all_error default strict lenient silent error hint information hint nil 狙ったエラーを引き起こすというのは今年の RubyKaigi であった Ruby "enbugging" Quiz に近い感覚でした。難しい。 基本的には Steep リポジトリにあるテストケースを見ながら埋めていったんですが、中にはテストケースがないものもあったので soutaro さんに直接質問をしながら進めていきました。 また、副産物として Steep で使われなくなったが定義として残っているルールを発見し、削除する patch を作れたのも個人的には良かったです。 github.com
エンジニアリング本部 プラットフォームエンジニアリング1G 橋本です。我々のグループでは業務の柱の一つとして、クラウドインフラの構築・運用を行っています。その中でAmazon Aurora MySQL(以下、AuroraもしくはAurora MySQL)のアップグレードがビジネスインパクトが大きい作業となりました。本記事はAurora MySQLアップグレード方法の検討について記述した投稿になります。 この記事のまとめ 前提情報や課題感について Blue/Green Deploymentsによるアップグレードとは もしもの場合はロールバックしたい 検討したロールバック手法 DMS方式 リストア方式 逆レプリ方式(没案) 困った。どうしよう? タイムリーな機能を教えてもらえた? SwitchOver時に静止断面を教えてくれる 逆レプリ(新案)はどうなるのか? いままでの方式案との比較 まとめ この記事のまとめ 比較的 大きなデータ で且つ 更新量の多い Auroraクラスターのアップグレードで且つ ダウンタイムが少ない ロールバック方式を検討していました ダウンタイム最小化の部分で大きな課題感 があったが、 Auroraの機能追加により大きく緩和 できることが分かりました この記事では、ダウンタイム最小化を軸にした場合のロールバックに関する課題感と解決方法を追加された当該機能に触れながら紹介します 前提情報や課題感について Blue/Green Deploymentsによるアップグレードとは Auroraの機能により既存クラスタをベースに無停止で新規クラスターをBlue/Green Deploymentsという機能を用いて作成することができます。なお、Green側クラスターにはEngineVersion(8.0.mysql_aurora.3.02.0, 5.7.mysql_aurora.2.11.2 etc.)や、パラメータグループを既存クラスターと異なるものを適用できるので、アップグレードや新規設定の適用をダウンタイム少なく行うことができます。 切替自体はワンクリックで可能で SwitchOver という命令を与えると、1分程度のダウンタイムは発生しますが特に難しいことはなくGreenクラスターに切り替えることができます。この際にアプリケーションサーバー等が参照しているエンドポイントも自動で切り替わります。 B/Gデプロイメントのイメージ もしもの場合はロールバックしたい この記事のテーマ はGreenクラスターに切り替えたあとにアプリケーションサーバー等で不具合があった場合に、元のクラスター(Blueクラスターそのもの or 同等の構成のもの)に ロールバックできるかどうか、その手法 についてになります。 ロールバックの可能性はそれほど高くない と考えています。当然事前に動作検証はテスト環境で行い、アップグレードに臨みます。しかし、何らかの不具合が 本番環境でのみ発生する可能性はゼロではない 為、万が一の備えであってもロールバックの手法はコンティンジェンシープランとして持っておく必要があります。弊社でもサービスの中核にAurora MySQLを利用しているため、 事業継続性の観点でも重要 なものとなります。 ロールバックから戻せるか? 検討したロールバック手法 当初は次の表の3つの方式を検討しました。なおダウンタイムはできれば数分、長くても30分程度のダウンタイムを許容(◯)として考えています。 この比較は 更新頻度や量が多く、ダウンタイムを極小化したいユースケースでAurora MySQLを使用している前提 としています。たとえば、分析用DBであったり、小規模のDBであったりすると許容できるポイントが変わると思われます。 方式名 ダウンタイム 互換性 容易さ 特徴 AWS Database Migration Service(DMS) ◯ ☓ ◯ データ互換性の担保が難しい GreenのバックアップデータをBlueへリストア ☓ ◯ △ ダウンタイムはデータ量に比例 GreenからBlueへの逆方向レプリケーション (没案) △ ◯ △ ロールバック時のダウンタイムに加えて、 SwitchOver前にダウンタイムが必要 DMS方式 AWS DMS を利用してGreenクラスタの更新情報をロールバック用クラスターに同期する方式です。当初この方式での移行を検討していました。流れは以下の通りになります。 ★ 事前準備 予めロールバック用クラスターを既存クラスターと同じ定義で作成しておく DMSでGreenからロールバック用クラスターに 変更データキャプチャ(CDC)により適宜データ同期が行われる構成 にしておく ★ SwitchOver! ★ 問題発生!ロールバック開始 - ダウンタイム サービス・メンテナンス状態にする(=DBの更新が行われないようにする) DMS定義を削除する ★ ロールバック終了 - サービス再開 アプリケーション・サーバーの接続先をロールバック用クラスターに変更してサービスを再開する この方式はDMSの設定をしてしまえば 複雑な操作・設定を必要とせず、簡単にロールバックができる ところが特徴になります。この点が最大の魅力であり、当初の採用理由だったのですが、テストを行っていると データ互換性の担保が難しい ことに気づきました。 特に、 DMSのユーザーガイド に記載されている制限やデータ型の変換が大きな問題となりました。 例えば、上記ガイドに制約として 列の AUTO_INCREMENT 属性は、ターゲットデータベース列に移行されません。 と記載されています。AUTO_INCREMENTのような属性はMySQL独自の自動増分の機能であり、ターゲットデータベースがOracleやPostgreSQLなど異なる場合にも汎用的に移行可能なように引き継がれない仕様となっているようです。 同じく、例えばJSON型がCLOB型に型変換をして同期するデフォルトマッピングがあり、ソースとターゲットデータベースでデータ型が変わってしまう点も、MySQL to MySQLでの単純なデータコピーに使いたい用途としては考慮事項が多く データ互換性という観点では☓(バツ)を付けざるを得ないと判断 しました。 リストア方式 次に検討したのはMySQLのバックアップ・リストア機能を用いてロールバックをする方式です。互換性の維持を主眼にすると以下の方式は確実な方式となります。 ★ 事前準備 予めロールバック用クラスターを既存クラスターと同じ定義で作成しておく ★ SwitchOver! ★ 問題発生!ロールバック開始 - ダウンタイム サービス・メンテナンス状態にする(=DBの更新が行われないようにする) GreenクラスタのDBバックアップを取得する ロールバック用クラスターに先の バックアップデータでリストア を行う ★ ロールバック終了 - サービス再開 アプリケーション・サーバーの接続先をロールバック用クラスターに変更してサービスを再開する DBバックアップ取得やリストアはmysqldumpなどのバックアップツールを用います。マネージド・サービスを普段利用しているとCLIベースでのバックアップ・リストアには一定の習熟が必要となるため、 ”容易さ”は△ としています。 この方式の最大の問題点は バックアップ・リストアに要する時間 でした。1TB弱(数百GB)オーダーのデータを対象として事前にテストしたところ、mysqldumpでは数十時間かかることが分かり全く現実的ではありませんでした。 mysqlshを用いて並列度を上げることで高速化できますが、それでも4時間程度の時間を要することが分かりました。Aurora MySQLのデータ書き込みがボトルネックとなっておりインスタンスサイズを大きくするなどして検証しましたが、これ以上の高速化は見込めず採用が難しいと判断しました。 なお、データ量が100GB以下程度と比較的小さければ数分〜30分程度のダウンタイムを許容する限りは、この方式が確実ではないかと考えています。 逆レプリ方式(没案) ここまで来て、最後の手段ではありますがGreenクラスターからのデータ同期にMySQLのレプリケーション機能(Primary/Secondary方式)を用いることができれば良いのではないかということに思い当たります。Auroraでレプリケーション設定が可能か分からなかったのですが、 Cyber Agentさまの記事 にズバリ書いていたため参考にさせていただきました。流れは以下のようになります。 ★ 事前準備 Blueクラスターを利用するため、ロールバック用クラスターは”作成しない” ★ 静止断面取得作業 - ダウンタイム サービス・メンテナンス状態にする(=DBの更新が行われないようにする) ★ SwitchOver! GreenクラスターをPrimary、BlueクラスターをSecondaryとした レプリケーションを設定 して同期させる サービス・メンテナンスを解除する ★ サービス再開 ★ 問題発生!ロールバック開始 - ダウンタイム サービス・メンテナンス状態にする(=DBの更新が行われないようにする) Blueクラスターを昇格させる(レプリケーション設定を解除) ロールバック終了 - サービス再開 アプリケーション・サーバーの接続先をBlueクラスターに変更してサービスを再開する この方式は標準的なMySQLレプリケーションを用いるため、データ互換性に対する懸念は少ないことが期待できます。しかしながら、SwitchOverを行う前に サービスダウンを発生させる必要がある ことが問題となりました。サービスダウンが必要な理由は所謂、静止断面を作るためになります。 # ※前提: サービス停止(更新停止)をした状態でSwichOverを行う # Greenクラスターで現在のポジション(静止断面)を取得する [Green] > show master status; +----------------------------+----------+--------------+------------------+-------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | +----------------------------+----------+--------------+------------------+-------------------+ | mysql-bin-changelog.000133 | 941 | | | | +----------------------------+----------+--------------+------------------+-------------------+ # BlueクラスターでGreenクラスターをPrimaryとしたレプリケーション定義を行う(CHANGE MASTER TO相当) CALL mysql.rds_set_external_master( 'GreenクラスターのWriterEndpoint', 3306, 'replicationユーザ名', 'replicationパスワード', 'mysql-bin-changelog.000133', 941, 0); Green, Blueクラスターは、それぞれ異なるbinlogファイル・ポジションを保持しているため、BlueクラスターにとってGreenクラスターのどのポジションから読み出しにいくべきか?、レプリケーション開始時点のポジションが必要になります。 上記のコマンド例のように、 show master status; コマンドにより ポジション取得を行うためには書き込みを一旦停止する 必要があり、これがSwitchOver前にサービス・メンテナンスを行う理由となります。 結果的には、本案は没案となりました。 そもそもロールバックは万が一の備えであるため、その備えのための事前作業でサービス停止が100%発生することは許容できなかった からです。 困った。どうしよう? ここまで長々と各方式の説明をしてきましたが、これ!という方式が見つかりません。大きめのデータを持つAuroraをダウンタイム少なくロールバックする手法を確立すべく検討をしていましたが、行き詰まってしまいました。 タイムリーな機能を教えてもらえた? AWSの方にも相談をしながら検討をしていたのですが、24’ 8/6に公開された このブログポスト を紹介していただきました。先ほど没案となった逆レプリ方式を改善できる一手になる!という手応えを得て検証を開始しました。 SwitchOver時に静止断面を教えてくれる ポイントはSwitchOverでGreenクラスターに切り替わった瞬間のポジションを 静止断面として教えてくれる ことにあります。次の図のようにSwitchOverしたときにクラスターEventとしてバイナリファイルとポジションがメッセージに出力されます。 一見すると地味な機能ですが、先に没案の課題として述べたとおり 通常はサービス停止をしなければ取得できない情報が無停止で取得できる ので、とても強力な機能です。 SwitchOver時のイベントメッセージ あとは次の図のように、もともとあったBlueクラスターをSecondaryとしたレプリケーション設定を行います。なお、ブログ執筆時点では記事中の設定に誤りがあり、BlueクラスターのWriterEndpointを指定する記述になっています。正しくはここに記載したように”GreenのEndpoint”を指定する必要があるのでご注意ください。 詳細の手順は先のAWSブログポストにすべて記載されていますので、ここではこれ以上の詳細は割愛します。気になった方は是非ご参照ください。 逆レプリケーションのイメージ 逆レプリ(新案)はどうなるのか? 逆レプリ(新案)は以下のような流れになります。SwitchOverを行ったあとに逆レプリ設定をして同期を行っておく点がポイントになります。 ★ 事前準備 Blueクラスターを利用するため、ロールバック用クラスターは”作成しない” ★ SwitchOver! クラスターEventに出力された静止断面のポジションを確認する GreenクラスターをPrimary、BlueクラスターをSecondaryとした レプリケーションを設定 して同期させる ★ 問題発生!ロールバック開始 - ダウンタイム サービス・メンテナンス状態にする(=DBの更新が行われないようにする) Blueクラスターを昇格させる(レプリケーション設定を解除) ★ ロールバック終了 - サービス再開 アプリケーション・サーバーの接続先をBlueクラスターに変更してサービスを再開する いままでの方式案との比較 上記を踏まえて、他案との比較を行いました。あくまで 弊社環境においてダウンタイムは30分以内という条件 で選んだ場合、この逆レプリ(新案)方式はベストな選択となりました。 1点、 容易さが△ としているのは、 Aurora MySQLの運用において普段はあまり行わないCLIベースでの操作を行うことであったり、binlogによるレプリケーション同期の機序について理解する必要がある 点に起因しています。 組織的な対応強度を備えるために一定の学習コストがかかりますが、一つの技術習得としてじっくりと時間をかけて取り組んでいこうと考えています。 方式名 ダウンタイム 互換性 容易さ 特徴 AWS Database Migration Service(DMS) ◯ ☓ ◯ データ互換性の担保が難しい GreenのバックアップデータをBlueへリストア ☓ ◯ △ ダウンタイムはデータ量に比例 GreenからBlueへの逆方向レプリケーション(没案) △ ◯ △ ロールバック時のダウンタイムに加えて、 SwitchOver前にダウンタイムが必要 GreenからBlueへの逆方向レプリケーション (新案) ◯ ◯ △ 没案の事前ダウンタイムが克服された! まとめ この投稿では弊社でAurora MySQLアップグレードを行う際に、データ量が比較的多いデータベースで、ダウンタイムを極小化してロールバックを行う方式について検討した軌跡についてシェアをしました。 同じような課題に突き当たった方もいるのではないかなと思います。この記事が課題解決の参考になれば幸いです。
iOSDC 2024 こんにちは、iOSエンジニアの前田( @naoya_maeda ) 、Androidエンジニアの伊藤( @tick_taku77 )です。 2024年8月22-24日に早稲田大学理工学部 西早稲田キャンパスで開催されたiOSDC Japan 2024に、タイミーもゴールドスポンサーとして協賛させていただきました。 イベントは以下のように、3日間連続で行われました。 8月22日(木):day0(前夜祭) 8月23日(金):day1(本編1日目) 8月24日(土):day2(本編2日目) 私達もイベントに参加したので、メンバーそれぞれが気になったセッションや感想をご紹介します。 naoya編 前夜祭で、「動かして学ぶDockKit入門」というタイトルで発表しました。 www.docswell.com 去年行われたWWDC23で発表されたDockKitフレームワークでできることを、体系的に紹介するトークになります。DockKit対応デバイス自体はよくメディアに取り上げられていて認知度が高いですが、DockKitフレームワークはまだ歴史が浅いこともあり、DockKitフレームワークを使用して何ができるかということはあまり知られていません。 本トークでは、DockKitフレームワークでできることを開発者目線でデモを通して紹介しました。 技術記事ではさらに詳しく解説していますので、ご興味をお持ちいただけた方は是非ご覧いただけますと幸いです。 zenn.dev zenn.dev zenn.dev ここからは僕が気になったセッションをご紹介します。 タイトル : GIS入門 - 地理情報をiOSで活用する 登壇者 : 堤 修一 さん www.docswell.com 地図の仕組みを一から理解し、iOSアプリでさまざまな応用ができるようになることを目的としたトークです。iOSアプリエンジニアの方であれば、MapKitのAPIを使用してiPhoneの画面に地図を表示したことは、一度はあるのではないでしょうか。 一方で、地図が表示される仕組みについて深く考える機会はほとんどないと思います。本トークでは、地図の仕組みから始まり、実際にコードを見ながら地図の仕組みの解説を進めていきます。最初はベーシックな地図を表示する方法、最後はマップ上に人間とモンスターを配置した、某ゲームのようなデモを披露してくださいました。 タイトル : iPhone × NFC で実現するスマートキーの開発方法 登壇者 : 岡 優志 さん www.docswell.com NFCのハードウェア特性や規格といった基礎知識的な話に始まり、NFCでスマートキーを作成する方法をデモを通じて紹介するトークです。NFC周りは僕も触っていたことがあるのですが、曖昧な理解だった部分が多いことを実感したトークでした。 このトークを聞いた後、僕もNFCデバイスを使用して何か作ってみたいなと思いました。 基礎を丁寧にわかりやすく説明してくださる、聞き手のことをしっかり考えてお話しされるokaさんらしいトークだなと感じました! iOSDC 2023でご登壇された「作って学ぶBluetoothの基本攻略 〜スマートキーアプリを作ってみよう〜」も非常に面白いので是非ご覧ください! www.youtube.com tick_taku編 タイトル : App Clipの魔法: iOSデザイン開発の新時代 by log5 登壇者 : log5 さん speakerdeck.com App Clip の概要やユースケースについて熱量高く解説されていて、iOSDC 2024のトップバッターを飾るにふさわしい未来にワクワクできる素晴らしいセッションでした! App Clipは名前だけ知っていましたが、Androidで言うInstant Appのようなものでしょうか。 想定している活用方法を聞いてとても感動しました。 自分自身(特に普段使わないのにクーポンなどのために)アプリをインストールすることに結構抵抗があるタイプですし、昔バイトでレジを打ちながらアプリを勧めてインストールのヘルプまで行うのはカロリーも高く、忙しい時間帯ですとレジ待ちの列が長くなりかなり大変でした。 そのステップが短縮されるならユーザーにとっても店舗の方にとってもかなり負担が減ると思われるのでとても効果が高そうだと感じました。 また開発においてもモバイルアプリの共有には非常に課題を感じていて、webと違いURLを共有するだけでは完結せず準備に手間がかかります。App Clipを利用し、スプリントレビューなどにおいてその場でエンジニア以外にロールプレイをしてもらうことでより当事者意識の高いフィードバックが得られるデモンストレーションができそうな予感がします。 ゆくゆくはモバイルアプリはインストールするものではなくなる未来が待っているかもしれませんね。 とはいえ、話されていたユースケースを実現するにはまだまだ課題がありそうなので今後の動向に注目したいと思います。 タイトル : Wallet API, Verifier APIで実現するIDカード on iPhoneの世界 登壇者 : 下森 周平 さん speakerdeck.com ここ最近マイナンバーカードをiPhone(Apple Wallet)に搭載できるようになると話題になっていますね。自分も持ち歩くものがまた減るので非常に楽しみにしています! www.digital.go.jp モバイルeID (モバイル端末に搭載される身分証明書)に関する国際標準規格 (ISO 23220)があり、マイナンバーカード搭載の話もこの一環だそうです。このセッションでは モバイルeID についての理解と、証明書情報を取り扱うAPIの特徴やユースケースを紹介されていました。 我々アプリケーションエンジニアが気になっているアプリからの取得もAPI ( Verify with Wallet API )経由でサポートされているとのことなので、ユーザー登録に本人確認が必要なサービスはセキュリティ的にも利便性的にもぜひ対応した方がいいとのことでした。タイミーでも登録時に本人確認を行っているので導入できたら面白そうだなと思っています。 ただし、現時点ではAppleへの利用申請が必要なことに加えて金融など特定のカテゴリーに分類されるサービスでしか利用が許可されていないそうなので注意が必要です。 これからのデジタル社会に向けてキャッチアップが必要そうな内容が解説されていて非常に勉強になりました。 ちなみにスピーカーの方は日本在住でカナダの仕事をしているそうです。僕はカナダへのあこがれがあるので職の探し方などもとても興味があります。 参考 https://www.soumu.go.jp/main_content/000779585.pdf https://www.jssec.org/column/20231127.html 最後に この三日間を通して技術的な知見を深めたり、久しい友人に会って話をすることができ、すごく有意義な時間を過ごすことができました。この場を用意してくださったiOSDCスタッフの方々、参加者のみなさん本当にありがとうございました! 上記で紹介したセッション以外にも非常に興味深いセッションが多くありました。 記事にある内容や、その他の内容についても、もしタイミーのエンジニアと話したいという方がいらっしゃればぜひお気軽にお話ししましょう! product-recruit.timee.co.jp
こんにちは、タイミーDevRelの河又です。 タイミーはDroidKaigi 2024にゴールドスポンサーとして協賛しています。 当日はブースも出展しておりますので是非、お立ち寄りください。 今回はDroidKaigiを前に一度、タイミーのAndroid開発を数字で振り返ろうという企画です。 Androidエンジニアの中川をインタビューアーとしてAndroid領域のリードエンジニアである村田にタイミーのAndroid開発についてインタビューする形式でお届けします! タイミーのAndroidアプリのクラッシュフリーレートについて 2022年 2024年現在 ※グラフ上の7日間、30日間の数値は当該期間全体の数値ではなく、デイリーの移動平均の数値です 中川: 僕が入社したのが今年の1月なんですが、その時点でクラッシュフリーレートは高いな、という印象がありました。 タイミーでは2年前の段階から99.9付近で安定していますよね。僕は前職でライブ配信アプリをやっていたので一概に比較出来るものではないですが、このレベルの数字は中々見たことがありませんでした。 この2年間で更に改善されていて、クラッシュに対して高いプライオリティを持っていると感じることも多いのですが、改めて取り組みとか方針みたいなところを聞かせてください。 村田: まず、段階的なリリースプロセスを採用しています。新しいバージョンをリリースする際、最初は全体の30%のユーザーにのみ提供します。その後、半日から1営業日ほどの観察期間を設け、Crashlyticsを用いて慎重にモニタリングを行って問題がないことを確認できた場合にのみ、全ユーザーへのリリースを行います。 次に、静的解析ツールを積極的に活用しています。今はCodeClimateを使用していますが、現在はより柔軟なカスタマイズが可能なDetektなどのツールの導入を検討しています。これらのツールを使用することで、人間のレビューでは見逃しやすい細かな問題も検出することができます。 そして定期的にメトリクスを眺める同期会を開催し、2桁以上のクラッシュがあるものは細かくIssue化し解決に取り組んでいます。 また、コードスタイルの統一にも注力しています。ktlintを使用して、コードスタイルの一貫性を自動的に担保しています。チーム全体でのコードの可読性が向上し、バグの早期発見にもつながっています。 中川: こうやって聞いてみると結構色々取り組んでいますよね。 あと、無闇に新しい技術をすぐに取り入れる、っていう文化ではないですよね。ある程度、実績や信頼性を重視していると感じます。 村田: そうですね。そこはバランス感覚を重視してます。 どっちに偏り過ぎても良くないと思っていて、DroidKaigiアプリがやっている様な最先端なスタイルを今のタイミーのプロダクト規模で積極的に取り入れるのは難しいと思っていますが、とはいえ新しいものを取り入れていかないのはメンバーのモチベーション観点や学習サイクルの観点で言っても良くないと思っています。 例えばJetpack Composeとかも比較的、早めに取り組み自体は開始しました。それこそ中川さんにリードして貰いましたが、Kotlin2.0へのバージョンアップとかも早めに対応出来た方なのではないでしょうか? ただ、本当に新しいものに飛びつく、みたいなカルチャーではないのはその通りで代表的なところで言うとCoroutineですよね。 Composeといえば、CoroutineみたいなところがあるのでCompose化に伴って導入を開始しましたが、それまではRxがメインだったので一旦、そこは慎重に立ち止まったりしながらメリット・デメリット考えて技術選定に向き合いましたね。 中川: 前職とかだと、Coroutine最高だからRx剥がそう! みたいな動きが強かったのでそれはそれで面白かったのですが、同じパラダイムがアプリ開発してる中でも結構存在していて、認知負荷とか学習コストは高くなった部分があるので確かにメリット・デメリットの比較は大事だなと感じます。 今のタイミーだとRepository層から返すのがRx、それ以降がFlowという形が出来ているので迷うことが少なくて良いなと思います。 村田: 併存はしているものの、しっかり境界が分かれているので分かりやすいですよね。 慎重に検討して、今の境界が出来上がっているのでそこはしっかりと検討して良かったと思います。 既存のView systemがRxにしっかり紐付いているので、Composeの導入は慎重に進めないといけないのですが、既存のView systemがなくなってJetpack Composeの割合が100に近づいたらRepository層以降も全部Coroutineに置き換えても良いのかな、とは思っています。 人が増えても品質を保つために 中川: そもそもCodeClimateってどういった経緯で導入したんですか? 村田: 有難いことに採用が上手く進んで、Android開発メンバーが3人から6人なるということが見えた時にそれに備えて導入しました。 3人体制で、開発プロセスも非常にシンプルでした。CIの警告チェックや、品質指標を追跡するような仕組みはほとんどなく、主に手動のテストとコードレビューに頼っていました。6人にチームが倍増することが決まり、従来の手法では立ち行かなくなると感じました。 まず、コーディングの方針を明確化しました。それまでは特に明文化された方針がなかったので 「こういう方針で書いていきましょう」という指針を作りました。その上で、コードレビューやテスト以外に何ができるかを考え、仕組みで品質を保証したいという思いから、静的解析に着目しました。 ただ、3人という限られたリソースでは、独自の静的解析ツールを開発するのは難しいと判断し、既存の静的解析サービスであるCodeClimateを導入することにしました。 中川: 視座が高い、というか、しっかり長期的な目線を持ってそういった取り組みをしていること自体がめちゃくちゃ偉いな、と思いました。 村田: ありがとうございます(笑) ただ、やってみないと分からなかったことも多かったですね。 良かった点からいくと技術的負債の見える化には非常に役立ちました。特に、行数や技術的負債に関するレポートを自動生成し、チーム全体で共有できた点は良かったです。また、プルリクエストに対して自動的にコメントを付けてくれる機能も便利でした。「このメソッドが長すぎます」などの指摘を機械的に行ってくれるのですが、機械的なルールに基づく指摘なので受け取る方も素直に受け取りやすい。 一方でアプリ全体で一つのルールセットしか作れないという制約があり、機能ごとに異なるニーズに応じたルール設定が出来ませんでした。なので、ある部分ではルールが緩すぎる一方、別の部分では厳しすぎるといった問題が発生しました。例えば、1メソッドの行数は25行に制限されていました。Android開発としては厳しいので80行に緩和しました。他にもファイル全体の行数も250行まで、といった設定があったりなど……。 これでは厳しいということで現在CodeClimateは主に技術的負債の指標取得にしか使用していない状況です。なので、現在Detektの検討を進めており、もう少し各所のニーズにあった柔軟な設定が出来ることを期待しています。 開発のスピードが上がる中で品質を追求する 中川: LOCは順調に増えている中で負債は減って、クラッシュフリーレートも上がっているのは単純に偉業だなと感じるのですが何か心がけていることはありますか? 村田: そうですね。それこそ、3人体制の頃は今ほど自動化された仕組みがなかったため、逆に常に全員が品質を意識しながらという所に尽きるのですが例えば、コードレビューの際は単にバグを見つけるだけでなく、「自分が次にこのコードを触るときにスムーズに作業できるか」という視点で見たりしていましたね。他人が書いたものでも自分がオーナーシップを持ってたらどうするか、みたいな意識はあったと思います。 今だとコーディングとかComposeのガイドラインもあって、意識レベルではなく仕組みレベルで整ってきたなと感じています。 中川: 自動化、というところを除いても、以前からリリースフローみたいなのは結構洗練されてるなとも感じます。 村田: 3人時代でも一部はしっかり自動化されてはいました。例えば、アプリのバイナリを作ってそれをアップロードするところとか、クリティカルな部分は手動にならない様にみたいなことはその時代からも意識はしてましたね。やっぱり違う間違ったブランチのバイナリあげちゃったみたいなことが発生してしまうと大きい問題なのでそういったものは起こらない様に気をつけてました。 やっぱり3人でやってた頃はリソースが限定されていたので取捨選択して重要なものだけしっかりと取り組む意識は強かったのかなと思いますね。 タイミーは社会インフラとなるプロダクトになっていくことを掲げていますし、エンジニアも各々がしっかりとそれを意識してくれているので品質に対する意識は皆、高く持ってくれているなと感じます。 まとめ インタビュー、如何でしたでしょうか? タイミーのAndroid開発チームは、急速な成長と変化の中で、高い品質基準を維持しながら開発速度を向上させてきました。クラッシュフリーレートの改善、新技術の慎重な導入、そしてチーム拡大に伴う品質管理の進化など、多くの課題に取り組んできました。 今後は、Compose化やCoroutineへの移行などにも取り組みつつ、様々な部分で自動化をより推し進め、効率的で品質の高い開発プロセスを目指していきます! DroidKaigi 2024ではタイミーのAndroidエンジニアも数多く参加しますので是非、現地来場される方はタイミーのエンジニアとお話頂ければ幸いです! 皆さんとお話出来ることを楽しみにしております!
こんにちは! Agile Practice Teamでプロセス改善やアジャイルコーチとしてチームの支援を担当しています、吉野です。 2024年4月にタイミーに入社後、初めてオフラインにて社内のOST(オープンスペーステクノロジー)イベントを体験してきましたので、レポートします。 今回お話しする内容 どんなイベントを行ったのか? タイミーでのOSTはどんな雰囲気で開催されたのか? を、お話ししていきます! どんなイベントを行ったのか? 今回開催されたイベントの概要 OST(オープン・スペース・テクノロジー) 形式でのディスカッション 会議室をレンタルしてのオフサイト開催 14:00~15:30の1.5h という形式でのOSTが開催されました。 参加者について タイミーのプロダクト開発組織に関わっている方の中から参加希望者を募っての開催でした。先陣を切って動かれていたrazさん、 りっきーさんの一声で、20人近くの人が一気に集まりました!(しかも3日ぐらいで!すごい!!) 結構OSTとか慣れているのかな?と思いながら当日参加したところ、なんと今までOSTを経験したことのある参加者は1/3ほどでした。 OSTの様子 私はまだ社内のオフサイトイベントに参加していなかったので、応募時点ではみんなのモチベーションがどれぐらい高いのかわからずの参加でした。 未体験のイベントにオフサイトで参加することには、一定のハードルがあると思っていましたが、一気に参加者が集まり「みんなのイベント参加へのオープンさ」を感じることができました! イベント参加や勉強へ前向きな環境は、その場にいてめちゃくちゃテンションが上がります! タイミーでのOSTはどんな雰囲気で開催されたのか? どんなOSTだったのか? 枠としては、15分区切りの4枠にて開催されました。 お題がめっちゃ出てきた! OSTの原則として、 💡 OSTの原則の一部 いつ始まろうと、始まったときが適切なときである いつ終わろうと、終わったときが終わりのときである があるので、1枠の長さはそこまで気にしなくても良さそうと思いつつ、15分という時間は自分が経験してきた中で一番短い時間だったので、どうなるのかな?忙しくならないかな?と少し心配でした。 ですが、そのような心配は杞憂に終わり、15分の中でみんなポイントを絞って議論したり、時間が足りなかったら自分たちでテーブルや場を用意して議論を継続したりしていました。 全体の時間としても1.5h(マーケットプレイスを含めず)という短時間での開催でした。 そんな中、各々が自主的に話したいことを話して時間を最大限に活用したOSTらしいOSTだったと思います。 (運営されていたお二人も、ひたすら運営に集中、というわけではなく話し合いにも参加して自身でも楽しむスタイルで立ち回られていました!) どんな雰囲気だったのか? タイミーでは、プロダクト開発に関わる多くの方がフルリモートでお仕事をしています。 そのため、今回オフサイトで集まった直後は「ワイワイ!ガヤガヤ!」というわけではなく、少し緊張している空気を感じました。 しかし、オフサイトイベントへ自主的に参加されていることもあり、いざOSTが始まると自己紹介や自身が向き合っているお仕事の紹介など、積極的にコミニュケーションを取りにいっていました。 最終的には、お互いの悩みに共感する声や、笑い声が多く飛び交い、一体感を感じられるイベントになっていたと思います。 「初めまして」とか、「社内イベントへの参加が初めてなんです」という声も多かった中、2hもしないうちに熱量の高い場になっており、人見知りな私も最後はすごく楽しませて頂きました!笑 今後のイベントにも期待をしていきたいプロダクト開発組織 私は社外のオフサイトで開催されるコミュニティイベントへの参加が好きで、よく参加しています。 社内でも、何かイベントが開催できないかな?もしくは開かれたら参加できないかな?と考えていたところ、今回のOSTイベントへ参加しました。 初めての参加でしたが、熱い議論をしたり、同じ組織の人との繋がりができたりと、とても有意義な時間を過ごすことができました。 普段からリモートでお仕事しているからこそ、オフラインで集まれた時はコミュニケーションを重視する時間に対しての熱量は高くなるのかなーと思いました! 今後も、社内のオフサイトイベントが立ち上がれば積極的に参加していきたいですし、自分でも何か開催してみたいと思います! あわせて、主催であるりっきーさん、razさんのご感想です! OSTにかける思い スクラムマスターをやっているりっきーです。 弊社はフルリモートの環境のため、会議の集中力を阻害する要因(Slackの通知だったり、会議とは関係ないことを勧めたり)が多いと感じています。そこで、どのような設計であれば参加者が集中できるかを模索した結果、OSTに辿り着きました。 OSTが優れている点は「自分自身で興味あるテーマを公募する / 自分自身で興味あるテーマにサインアップする」に集約されています。誰かに呼ばれて会議に参加するのではなく、”自分自身”でアクションを起こさなければ会議がうまくいかないので、参加者としても集中して会議に参加できる環境になると考えています。 今回は最もやりやすい環境であることと、全社のイベントで出社する機会があったのでオフラインで開催しましたが、オンライン環境ではより効果が発揮できると思うので、今後は回数を増やしていければと思います。 初参加でもあり、初運営のOST、うまくいってよかった どうもスクラムマスターやったりエンジニアやったりしてるrazです。今はエンジニアをしております。思いつきでやってみたいと言ったところ、2人や参加者の協力のもとOSTを開催できました。僕の記憶してる範囲では社内で実施するのは初めてだったと思います。 OST開催の動機 今回、全社のイベントがありオフサイトで集まる機会がありました。その会までの時間の使い方として提案させてもらいました。普段フルリモートだったり、違うチームだったりであまり会話する機会のない人も参加してくださったのはとても嬉しかったです。 なぜOSTなのか?の理由は3つあります。 実は自分が未経験で興味があった 毎週開催してるアジャイル相談会の様子から、普段話せてないことが色々ありそうだった 雑に大人数集めて開催してもなんとかなりそうだから 1と3はあまり説明することもないので省略しますが、2のアジャイル相談会について補足します。 アジャイル相談会とは、毎週水曜の夕方ごろにりっきーさんが主催してくれてるその名の通りアジャイルとかスクラムに関して気軽に相談していい会です。ただ、実際にはアジャイルとかスクラムの枠にとらわれず、組織論やマネジメントといった様々な内容で会話しています。その中でも、組織全体に関わることは、話す機会が少ないんだろうと感じていました。そこで、熱量のある人たちが集まって会話できるOSTをやれたらいいのではと思って開催しました。 開催してみた感想 一応運営ではあるので、全体の様子をみながらではありますが、極力会話に参加してテーマを盛り上げていきました。経験者が1/3程度いましたが、半数以上は未経験で何もわからない状態での参加でした。しかし、みんなの自主性の高さがあってか、初回セッションから盛り上がっていて安心しました。自分もその様子に安心できたので、2セッション目以降はより会話に集中できたと思います。途中会話に夢中になりすぎてタイムキープを忘れていたほどです笑。 みなさん初めての開催にもかかわらず適応能力が高いので、事前に出したテーマじゃないことに転換していて、会話を楽しんでいると感じました。個人的にはもう少し「どうしよ?」感に包まれるんかな?と心配してたのですが、杞憂でした笑 次回開催なるか? 今回、準備期間も会自体も短い中での開催でしたが、思ったよりも盛り上がってましたし、個人的には成功したんじゃないかと思ってます。いい成功体験になったと思うので、次回開催に向けて思っていること3つあげて、僕の感想を終わりにしようと思います。 フルリモートへの適応 多種多様な役職や職種の人の参加 組織を変革していくアクションを生み出せる会へ フルリモートへの適応 : タイミーのプロダクト開発組織は、フルリモートなのでリモートでも開催してみてもいいかなとは思いました。ただ、実際に運営してみてオフサイト環境だからこそ成り立っているものがありそうとも感じました。四半期に1度くらいのペースで、オンラインとオフラインを交互で開催できても面白そうです。 多種多様な役職や職種の人の参加 : 急な呼びかけなのもあって参加できたメンバーは限られていました。組織に存在している様々な役職や職種の人が充足できてない状態でしたので、次回はより多種多様な人に参加してもらえるような会を開催できたらなと思います。 組織を変革していくアクションを生み出せる会へ : 会話メインで会は終わりました。しかし、もう少し時間を長くとれれば、会話も深まりますし、具体的なアクションを生み出したりして、組織やチームに変化をもたらす会にもしていけると思うので、そういう会を目指せればと思います! 以上レポートでした! ハピネスドアもハッピー感想めっちゃ多かったです!