Timee Product Team Blog

タイミー開発者ブログ

Kaigi on Rails2023 に参加しました

タイミーの新谷、江田、酒井、正徳です。
Kaigi on Rails 2023 が10月27日、28日の2日間で開催されました。タイミーは去年に引き続きKaigi on Railsのスポンサーをさせていただきました。

note.com

また、タイミーには世界中で開催されている全ての技術カンファレンスに無制限で参加できる「Kaigi Pass」という制度があります。詳しくは以下をご覧ください。

productpr.timee.co.jp

この制度を使ってオフラインでは4名のエンジニアが参加しました。
参加して聞いたセッションのうち印象に残ったいくつかをピックアップしてご紹介します。

生きた Rails アプリケーションへの Delegated Types の導入

docs.google.com

Rails 6.1から導入されたDelegated Typeに関する発表でした。

デプロイ履歴を管理する機能において、AWSのECSやLambdaなどのサービスごとに固有で設定したいデータと、各デプロイ共通のデータ管理をする上で単一テーブル継承(Single Table Inheritance: STI)ではなくDelegated Typeが活用されていました。

タイミーでは一部のテーブル設計でSTIが活用されています。ただ、すべてのサブクラスのすべての属性を持つ単一のテーブルが作られ、nullが避けられない等のデメリットはよくある話だと思いまして、発表の中でも触れられていました。
一方Delegated Typeではスーパークラスの共通データ、サブクラスの固有データを格納するテーブルがそれぞれ作成されます。ポリモーフィック関連をベースにした委譲による実装がなされ、個別データを持つ側のクラスからアソシエーション(今回の発表では has_one)で辿れる仕組みでRails開発者としても直感的に使える仕組みだと感じました。
また各種Gem側での対応も順調に進められており、特にFactoryBotで詰まることなくデータ生成が出来るのは開発・運用の観点でも心理的ハードルが下がる話でした。
そもそもRails 6.1からActiveRecordに導入された機能で、まだ実際に稼働中のプロダクトでの実例もあまり聞いたことが無かったので具体例と共にその特徴を知ることができ、有益な時間になりました。実際に導入する際にはまたこの発表を参考にさせていただこうと思います。 (@edy2xx)

生きた Rails アプリケーションへの delegated types の導入 by mozamimy - Kaigi on Rails 2023

やさしいActiveRecordのDB接続のしくみ

speakerdeck.com

MySQLへの接続確立するまでのActiveRecord内部の動きを、findメソッドをデバッグ実行した時のスタックトレースを参考に、接続に関する主要なクラスの役割を紹介しながら、内部処理を学ぶセッションでした。

ActiveRecordを用いるとdatabase.ymlに必要な情報を載せるだけでDBにアクセスしデータ取得や操作を行うことができるためあまり意識をすることがありませんでした。しかし、このセッションを通して改めてどのようにDBへの接続を確立・管理するかや、クラスごとの責務を理解できた気がしました。
後半の知見でも触れられていた事ですが、この内部処理を理解することで、接続プールはDBの接続情報を保持したインスタンスを持つため(端折っています)、フェイルオーバーなどでDBホストの変更を行っても、変更以前の接続情報を用いてしまう可能性があります。そのような可能性があることを知っておく事は運用において大事ですし、そのような事態になった時や、そうならないための打ち手を常に持っておく事も重要です。
最後に軽く触れられていたバリデーションクエリの他にも、Railsの再起動等を通して再接続を試みたり、一時的にコネクションプールをやめて都度接続にしたり、クエリ実行時にエラーが発生した場合にエラー内容によってリトライをかけるなど色々あるので改めて理解を深めようと思いました。 (@pokohide)

やさしいActiveRecordのDB接続のしくみ by kubo - Kaigi on Rails 2023

Simplicity on Rails - RDB, REST and Ruby

speakerdeck.com

偶発的な複雑性が少ない状態をシンプルと定義した上でテーブル設計・API設計・クラス設計の観点でどんなことを意識すべきかを紹介するセッションでした。

  • テーブル設計:リソースエンティティだけでなくイベントエンティティを見出してhas_many through 関連を作ること。
  • API設計:テーブルとリソースは似ていることもあるが同じではないので、テーブルに対応しない Controller を作ることは問題ないので気にしなくていいこと。
  • クラス設計:Controller の create アクションは INSERT INTO 以上のことをしても良いこと。Controller が複雑になってきたと感じたら FormObject の利用を検討すること。

事業としてはイベントエンティティがコンバージョンポイント(お金に変換できるデータという理解をしました)であるという話は確かになと思いました。タイミーのコンバージョンポイントは働くワーカーさんが求人に申し込むマッチングなのですが、ここに対応するモデルは UserOffering という User モデルと Offering モデルの多対多を管理するリソースエンティティとしての命名になっています。様々なものに紐づいているモデルなので変更を加えるのは大変ですが、長期的には手を加えていきたいと思いました。
イベントエンティティをモデルとして表現するという話は texta.fm でも耳にしていて、今回の発表を聞いて更に理解を深めたいと思ったので texta.fm での話の元になっていたパーフェクト Ruby on Rails を読んでみようと思います。 (@euglena1215)

Simplicity on Rails - RDB, REST and Ruby by MOROHASHI Kyosuke - Kaigi on Rails 2023

Railsの型ファイル自動生成における課題と解決

speakerdeck.com

Railsアプリに型を導入するときに起きるいくつかの課題と、それらの課題に対する解決策を紹介するセッションでした。

  • 課題: Railsアプリに手軽に型を導入したい -> 解決: orthoses-rails
  • 課題: アプリケーションコードの型が無い -> 解決: rb prototype
  • 課題: YARDを活用したい -> 解決: orthoses-yard
  • 課題: YARDのシンタックスをチェックしたい -> 解決: rubocop-yard

弊社でも型(RBS)を活用しているため、課題に共感しながら聞いていました。 Orthoses は使用していませんが、いくつかの解決策は弊社の開発環境でも活用できそうなので、Orthoses への乗り換えも含めて検討・参考にしたいと思いました。

また、発表の後に3Fの休憩部屋(ROOM 0)で今後の型に関する話をすることができて、個人的に型に対するモチベーションが上がる1日になりました。 今後は型(RBS)に関するOSSにも少しずつコントリビュートしていきたいと思います。 (@sinsoku)

Railsの型ファイル自動生成における課題と解決 by Yuki Kurihara - Kaigi on Rails 2023

管理機能アーキテクチャパターンの考察と実践

speakerdeck.com

後追いで作ることになりがちな管理機能を Frontend, Backend のソースコードを置き場を元にいくつかのアーキテクチャパターンに分類しそれぞれのpros/consをまとめたのちに、B/43ではどのアーキテクチャを選択したのかと選択した理由・振り返りを共有するセッションでした。

タイミーは ActiveAdmin を用いてモノリシックコードベースに管理機能を密結合させています。ActiveAdmin を使う上で色々と大変な面はあるのですが、なんだかんだ ActiveAdmin は便利ということで使い続けています。とはいえ、過去・現在に最適なアーキテクチャが未来も最適である保証はないので、今後の管理機能の在り方を考える上で非常に参考になりました。
また、管理機能のバックエンドとエンドユーザー向け機能のバックエンドを同じにするのも同意です。管理機能の中には本来は機能として提供すべきなものが提供されていないことから運用に負がたまって社内管理機能に機能追加の力学が働き、結果として管理機能が肥大化していくというのはあるあるだと思っています。バックエンドを分けると管理機能に閉じた変更の方が容易になり、管理機能の肥大化を加速させる要因になりうると感じました。 (@euglena1215)

管理機能アーキテクチャパターンの考察と実践 by ohbarye - Kaigi on Rails 2023


オフライン参加楽しかったですね。2日間で多くの企業のエンジニアとも交流が出来ました。
今年は抽選に外れたのでブース出展できなかったのですが、来年はブース出展したいと思います。来年はブースでまた会いましょう!

タイミーデータ基盤のモデリング設計について

はじめに

こんにちはokodoonです
タイミーのデータ基盤に対してデータモデリングを始めてしばらく経ったので、現状の全体構成を紹介したいと思います

全体構成

弊社のBigQueryは以下の4層にレイヤリングされています
それぞれの役割は以下のような切り分けになっています

レイヤー名 役割
データレイク層 複数ソースシステムのデータを未加工の状態でBigQueryにロードする宛先
dbt snapshotによるソースの履歴化
ステージング層 複数ソースシステムのデータを共通した処理でクレンジングする層
DWH層 ソースシステムのデータ形式を分析に適した形に変換する層
ディメンショナルモデリング/ログテーブルをイベント単位に分割/その他便利テーブル作成
データマート層 特定用途に対して1:1で作成されたテーブル群を格納する層
ダッシュボード用テーブル/Looker用テーブル/GoogleSheets用テーブル/外部サービス連携用テーブル

図で表すとこんな感じです

DWH層, データマート層の切り分けについて

「DWHとデータマートとは?どう切り分けるか?」と100人に聞いたら100通りの答えが返ってきそうなこちらの定義
弊社内ではデータ基盤チーム内で議論した結果、以下のような切り分けをしています

- DWH: ステージングデータを分析ニーズに合わせて加工したもの
- データマート: 特定ニーズに対して1:1で作成されたテーブル

なので

  • ディメンショナルモデリングなどの分析用データへ変換したテーブル
  • クエリを発行する際に、結果の出力が重くなってしまうテーブルを軽量化したもの

などがDWH層に格納されているテーブル群で

  • 特定ダッシュボード用に作られたテーブル
  • Looker探索環境用に作られたwide_table
  • 外部サービス連携用に成形されたテーブル

などが現在データマート層に格納されているテーブル群になります

※「ダッシュボードに1:1で対応するマートを作る方針なのかあ」と思われるかもですが、こちらは現在推進中のdbt exposureを用いたアウトプット管理を見据えているためです(またこの辺りは別の機会にアウトプットします!)

各層の説明を少しだけ深掘り

データレイク層

この層でdbt snapshotを活用した履歴化を実行しています

下流のステージング層/DWH層などで履歴化をしていない理由としては、何かしらの加工がされたあとのデータを履歴化する場合、加工処理に何かしらのバグや想定漏れがあった場合のロールバックが大変なためです

分析に使用する場合は以降の層でクレンジング/モデリングされたデータのみでいいため、基本的に分析ユーザーはデータレイク層へのアクセス権限を保持していません

ステージング層

以下のような処理を加えて、生データをクレンジングしています

  • データ型の変更と統一
  • パーティショニング/クラスタリング
  • データマスキング
  • 品質担保のテスト
    • uniqueテスト
    • Nullテスト
    • 外部キーテスト

ユーザーが3NF形式のデータに対してクエリを書く場合は、品質が保証されたステージング層のテーブルに対してクエリを実行してもらうような運用にしています

DWH層

DWH層内は以下のような区分が作られています

DWH層内区分 役割 dbtのフォルダパス
events ログテーブルを分析要件にあるイベントごとに分割整理して軽くしたテーブル群 models/dwh/events
component ディメンショナルモデリングデザインパターンに当てはまらないが便利な部品のようなテーブル群 models/dwh/component
pre_dimension 同一属性単位にドメイン情報を結合
ドメイン要件に従ったフラグ生成などの複雑な事前処理
models/dwh/dimension_modeling/pre_dimension
dimension SCD Type2カラムの生成(https://en.wikipedia.org/wiki/Slowly_changing_dimension#Type_2:_add_new_row)
サロゲートキーの生成
models/dwh/dimension_modeling/dimension
fact ビジネス指標の作成
関連Dimensionのサロゲートキーの反映
models/dwh/dimension_modeling/fact
conformed_fact 複数プロセスを跨いだよく使われるfactの組み合わせをconformed dimensionで結合
複数プロセスを跨いだビジネス指標の作成
models/dwh/dimension_modeling/conformed_fact

弊社Dimensional Modelingについて

弊社Dimensional Modelingについてざっくり解説します

  • SCD Type 6の採用
    • dimensionテーブルは「履歴化されたテーブル(Type2)」と「最新情報を持つテーブル(Type1)」に分けることなく、SCD Type1~3の全てを一つのテーブルで管理する方針としています
      • こうすることでFactに登録するサロゲートキーが少なくなり、分析時のクエリと思考がスリムになると考えています
  • 採用しているFactテーブルのパターン
    • Transaction Fact + Factless Factを接合したFactテーブル
      • Transaction FactテーブルとFactless Factテーブルを別々に作るのではなく、ビジネスプロセス単位でまとめてくっつけています
        • 例えば「買い物プロセス」を仮定した場合に、Transaction Factにあたる 合計売上 とFactless Factにあたる 決済回数 が同一の買い物Factテーブルにあるイメージです
    • Snapshot Factテーブル
      • Transaction FactやFactless Factではうまく表現できないような指標(etc. ユーザー数全体の推移, 銀行残高の推移, )を日,月単位の粒度でスナップショットして保持します
      • 例えば日毎のユーザーdimensionのサロゲートキーを保持するようなSnapshot Factテーブルを作成することで、日単位のユーザー数の推移をDimensionテーブルで絞り込み可能な状態で追えるようになります
  • FatなFactテーブル
    • Factテーブルは分析ニーズのあるビジネスプロセス単位で作られると思いますが、そのビジネスプロセスで発生する指標を全て一つのFactテーブルで完結して出力できるような方針をとっています
      • 買い物の決済プロセスのFactテーブルを仮定すると、 決済Factの決済金額* 決済Dimensionの消費税率 = 消費税金額 みたいにFactとDimensionを組み合わせて新しい指標を出力するのではなく、決済Factテーブルに 決済金額, 消費税金額を二つとも持っておくような形です
    • こうすることで「Factテーブルに定義されている指標をDimensionテーブルで絞り込むだけでいい」「Factテーブルにない指標があればFactテーブルに足せばいい」とクエリ作成時の思考が軽くなると考えています
    • Factテーブル上に社内の指標を全て整理して保持している状態です

データマート層

データマート層内区分 役割 dbtのフォルダパス
wide_table ディメンショナルモデリング済みテーブルをJOIN済みの状態にしたテーブル群
Lookerの探索環境に1:1で対応している
models/marts/wide_table
dashboard LookerStudioダッシュボード, Spreadsheetのコネクテッドシートに1:1で対応しているテーブル群 models/marts/dashboard
その他 他サービスに連携していたり、特定プロジェクトのために一時的に作られるテーブル群 models/marts/{{その他}}

Lookerとの接続について

LookerのUIを見てみると、dimensionとmeasureが選択できるような、ディメンショナルモデリングを意識したものになっています

そこで弊社ではこの画像のようにLookerのデザインに乗っかる形で、Lookerの接続先をディメンショナルモデリング済みのwide_tableとしており、スター内のFactテーブルの指標をmeasureとして、Factに接続するDimensionテーブル内の要素をDimensionテーブルごとに分割して表示しています

こうすることで

商品の生産国が中国の決済金額を決済方法ごとに出力したい

というユーザーの思考があった時に

商品の生産国だから => 商品Dimensionの生産国を選択
決済方法だから => 決済Dimensionの決済方法を選択
出力したい指標は決済金額だから => Factの決済金額を選択

とLooker上のユーザーの行動に繋がると思っており、
ユーザーの思考,実際に走るクエリ ,Lookerの探索環境のUI ができるだけ一致するようなデザインになっています

こうすることでLookerへのフィードバックがモデリングをより便利にすることに繋がり
モデリングへのフィードバックがLookerをより便利にすることに繋がります

ダッシュボード用 / スプレッドシート用のデータマートについて

基本的にマートはView形式で作成します (補足:Viewテーブルとは)
Viewとして作成することで無駄なビルドやコスト増加を避ける方針です

マート化する判断基準は「正確性と安全性を保守すべき対象かどうか」であり、チーム内で定めた基準に従ってマート化を実施してアウトプットをコード管理します
ダッシュボード用 / スプレッドシート用のデータマートをdbtで作成するのでdbt上でリネージュ管理が行えるようになり、破壊的な変更が上流で発生した場合に保守対象のダッシュボードへの影響を抑えることができます
作成したマートに対応するdbt exposureを宣言することでデータオーナー管理やアウトプット先管理も行えるようにする予定です

将来構想

数ヶ月以内に実現したい将来

  • dbt exposureによるオーナーを含めたアウトプットの管理
  • 主要ビジネスプロセスの全モデル化

1年以内とかにはやりたい将来

  • サンドボックス層の作成
  • ポリシータグを用いた、メンバーのアクセス可能情報レベルに応じたクエリ範囲の制限

まとめ・今後の課題

弊社は組織拡大のスピードがとても早く、データ活用者数もとても多いので
クエリ作成者間で出力される指標値がブレる。ダッシュボードやLookerの値とアドホッククエリの値がブレる。そもそも定義がブレている
などの問題を防ぎつつ
簡便にあらゆる指標値がクエリできる
ように、ディメンショナルモデリングを主軸としたデータモデリングを推進してきました

現在は作成したモデル数はかなりのものになり、それに従ってLooker環境もリッチになってきたのですが、アナリストが作るアドホッククエリやダッシュボード作成クエリにまだまだ採用してもらえていない課題感があります

モデリング済みテーブルを使ってクエリを書くのはDWH開発に参加してくれているアナリストメンバーに集中していて、他アナリストメンバーにモデリングなどの技術的内容とDWHの開発内容などをうまく同期できていない状態です

モデリング済みテーブルを使用することで

  • 指標が全社定義と常に一致する
  • Dimensionの履歴を考慮した指標出力が可能になる
  • クエリ文量が1/5とかになる
  • クエリパフォーマンスが向上するケースもある

などメリットも大きいと思うので、モデリング手法の理解を助けるアウトプットやワークショップなど様々なHowを通して、アドホックなクエリに採用してもらってもっとフィードバックのサイクルが高速化するようにしていきたいです!

We’re Hiring

タイミーのデータ統括部では、ともに働くメンバーを募集しています!!

がそれぞれ公開されておりますので、ドシドシご応募お待ちしております

その他ポジションも絶賛募集中なのでこちらからご覧ください

【イベントレポート】Server Actionsを踏まえた フルスタックTypeScriptの一考察

イベント概要

2023年8月2日に「What’s “Next” JS Meetup」と題してバベル社のuhyoさんをお招きしてNext.jsに関する勉強会を開催しました。 その中でタイミーフロントエンドエンジニアのいーふとさん(@redshoga)の講演をイベントレポートにまとめてお届けします。

今回のスピーカー

Server Actionsの概要

※ 2023年8月2日のイベント開催時点での情報であり、将来的に変わる、変わっている可能性があります。

Server Actionsはざっくり説明するとサーバーで動作するコードをあたかもクライアント上に記述できる機能です。コード下部のLikeボタンがクリックされると、propsに渡されているincreateが呼び出され、その中でPrismaのclientが動作します。

クライアントで動作

レンダリング結果

サーバーで動作

送信データ

一つのファイルだけでなく別ファイルで書くことも可能で左側がクライアントで動作、右側がサーバーで動作します。それぞれに”use client” “use server”というディレクティブを書く必要があります。 レンダリング結果を見ると普通のフォームとして動作することがわかります。右側の”createPost” というのが普通のTypeScriptをインポートしているにも関わらず通常のformとして動いてくれる不思議な機能です。 送信データとしては普通のフォームデータです。内部ではACTION_IDがよしなに付与され、良い感じに動作します。

既存のフルスタックTSのアプローチ

ここまでがServer Actionsを踏まえた未来のTypeScriptをイメージした内容だったのですが、現状のフルスタックTypeScriptにはどんなアプローチがあるのかを見ていきます。

よくあるのがスキーマの活用です。概念としてはOpen APIやGraphQLとも一緒だと思います。スキーマAPIの定義ファイルからTypeScriptの型生成を行う。よく見る代表例としてはopenapi-typescriptなどですね。タイミーではopenapi2aspidaというライブラリを使っています。aspidaというライブラリがあってそれに変換してくれるものですね。当然、定義ファイルが必要になりますが、自動生成される仕組みにしてしまえば楽なのかなと思います。

他の例としてはtRPCが今、流行っているのかなと思っています。TypeScriptに特化したRPCでインターフェースを提供するライブラリという解釈です。Next.js、Express、FastifyにインターフェースとしてつけてあげるとあたかもRPCのような感じで書くことが可能になります。フロント側の対応もしっかりしてReactのhookを勝手に提供してくれる優秀なライブラリだったりします。 シリアライズもsuperjsonが用いられており、MapやSetといったオブジェクトをそのまま送ることが出来ます。

クライアントサイド

レンダリング結果

サーバーサイド

送信データ

こちらが実際に書いた例です。 クライアントサイド側にapi.example.hello.useMutationというのがありますが、これがサーバーサイド側のコードを書くと勝手に生成されているような感覚で書くことができる開発者体験のよいライブラリになっています。 シリアライザとしてはsuperjsonが用いられており、内部ではよしなにwrapされているので使用感としてRPCっぽいといった感じのライブラリになります。

ここまでServer Actionsについてと今までのフルスタックTypeScriptをどうやって書いていたのか、という話をしてきましたが、比較するとServer Actionsは圧倒的に手軽です。”use server”と書くだけで動くので手軽です。 シリアライズの方式も微妙に違っていて、レスポンスで返ってくるやつを色々実験しているのですが、JSXとかも実は送れたりします。 Next.js特化で共通のインターフェースみたいな概念ではないので今のところはServer Actionsを書いて、それをNativeAppから呼ぶみたいなことは想定されていないと思います。revalidatePathの様な所謂、Next.jsのキャッシュサービスと併用して使ってねみたいなサンプルコードとかも散見されるので、インターフェースを提供するというよりはやはりNext.jsに特化したRPCといった印象です。

Server Actionsの活用例

Server Actionsを知っている人は似たイメージを持っていると思いますが、Vercel盛り盛りのフルスタック構成ですね。 サーバーのデータベースを触る際はServer ActionsとかAPI Routesとかで書いて、クライアントコードは普通にReactで書くと。Vercelは最近様々なサーバーレスストレージサービスを出しているのでそれらを活用して永続化していきます。 あとはKey-Valueストアとかもあるのでそれらでキャッシングをしてあげるといった感じになります。

もう一つはBFF as Server Actionsのようなイメージですね。BFF as Server Actionsという言葉自体は私が作った造語なのですが、先程の構成ではネイティブアプリケーションからAPIを叩けないですよね。 それをAPIだけVercelから外に出して、VercelのServer Actionsから叩いてあげる、といったことも可能ではないかなと思っています。あとは単純にServer ActionsにすることでJavaScriptが動かない環境でもフォームとタグが動くのでそういったプログレッシブエンハンスメントを主とした活用もあるのではないかなと思います。

まとめ

今回はSever Actionsこんな感じですよ、ということをお伝えしました。とはいえまだアルファ版なのでServer ActionsちゃんとGAされてほしいなと期待を持っています。便利なので使っていきたいという気持ちですね。最近のNext.jsはフルスタック寄りに様々な機能を提供してくれているんですけど、Railsのような規約(レール)がある訳ではないのでその議論は活発になっていくのでないかなというのを個人的に思っています。

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

uhyoさんの発表や弊社のもう一人の西浦さんの発表が気になる方は以下の動画を是非ご覧ください。

www.youtube.com

少しでも興味を持っていただいた方は是非こちらからカジュアルにお話しましょう!

devenable.timee.co.jp

スクラムフェス仙台2023に登壇してきました!

内容

こんにちは!スクラムマスターのShinoP (@marupopu)です!

この記事は、スクラムフェス仙台2023で登壇した事によるふりかえり記事になります。

実際の発表内容はこちら

speakerdeck.com

※後で動画も公開されると思いますので、ぜひ見てください!

なぜ登壇したか?

初めに、なぜ登壇する事となったか?ですが、時は今年の1月に行われたRSGTでコンフォートゾーンから抜け出す体験をしたのがきっかけでした。

具体的なコンフォートゾーンから抜け出す体験というのは、RSGT初参加で、初OSTのホストを務めた事です。

今までは外部の勉強会に参加しても、話を聞くだけで自分の中で解釈をして納得をして、わかった気になって終わっていました。

また、自分は参加する人で、登壇している人たちはすごい人というような印象を持っていました。

そんな中、RSGTならではの空気感や、OSTのホストをやる人も多く存在し、遠方から参加していることもあり、参加するからには効果を最大限にしたいという思いがあり、勢いのままOSTのホストに申し込んだ結果、多くの方が集まって頂き5チーム程度に分裂して話し合う規模になりました。

その時に、自分が思っているような疑問でも共感や気になってくれる人は案外多いのかもしれない…と認識できました。

その後、タイミー主催の勉強会での登壇経験をして、共同で発表してくださったCoincheckの方々や、参加者の方とScrumについて語り合う体験が楽しいと感じました。

www.youtube.com

自社主催の勉強会での登壇が楽しいと感じたので、さらに課外活動などにも興味が湧きました。

なぜ、スクラムフェス仙台か?

さて、なぜスクラムフェス仙台を選択したのか?というと、私自身が岩手県盛岡在住ということもあり、仙台も割と近いので現地参加しやすかったのと、4年前に東京から岩手に移住してくるほど東北が好きで、東北という括りで盛り上げたかったのが理由になります!

あと、去年も参加したかったのですが、現地チケット取れずに断念した記憶があるのでリベンジも込めて。

雰囲気はこんな感じ

初プロポーザルと文豪制度

もちろん、初プロポーザルでした!

Scrum Fest Sendai 2023 - SMって兼任?専任?結局どうなの? 〜持続可能なチームになる為の鍵〜 | ConfEngine - Conference Platform

どのように書いたら良いかわからなかったので、他の方の書き方などを参考に、自分の伝えたいことを書いてみました。

また、採択頂けたタイミングで資料作成などを行うにあたり、弊社のTDE10の中の人つの”文豪制度”を利用しました。

productpr.timee.co.jp

壁打ちをしてもらいながら資料を作成し、本当に自分が伝えたいことを言語化するのを手伝って頂き、本当に助かりました。 また、壁打ちを行なっていく過程で、最初に出したプロポーザルのアウトラインがずれていたりもしたのに気が付いたので、何を伝えたいのか?から正しく伝えられるような言語化、資料作成までお世話になりました。

登壇者での参加は何がよかったか?

RSGTなどもそうでしたが、登壇者でない時の参加と登壇者としての参加の違いを感じたので書いていこうと思います。

まず、気持ちの問題かもしれないのですが、”圧倒的喋りかけやすさ”があるような気がしました。 何人かに、タイミーのしのぴーさんですよね?って話しかけられて嬉しかったです😆

めちゃめちゃ目立つ登壇者用TシャツもGOOD!

また、認定スクラムマスター研修やクローズドなイベントでの交流のあった方々にも声をかけて頂いたりして、登壇者 = 参加することが確定しているなどのイメージなので、話しかけて頂いたのかな?と推測しています。

あとは、同じ登壇者同士で話せたことも良かったと思います。

初めての登壇者同士で話したりして、緊張しますね…とかその時の心境をシェアできたのはとても良かったです。

その他よかった点

こちらは登壇者でなくともスクラムフェス仙台2023の体験がよかった点を書いていきます。

まず、なんと言っても”クラフトビール”が美味しかった🍻🍻

(ビールを飲みながらのOST最高でした!!!)

Day1で少し飲みすぎて、Day2に酒焼けで声が少し枯れてしまったのは反省😂

ノベルティのシャークレくんグラス

また、その場でしか体験できないワークショップ体験や飲み会、昼ご飯時に話されるディープな悩みの相談会なども現地参加ならではの楽しみ方だなぁと思いました。

あとは、会場が複数の階に分かれていたこともあって、ギャザリング感がめっちゃ味わえました。

1階では、メイントラックと品川トラックとオンライントラックがモニターで映されていたのですが、その空間で話している人もいればモニターを見て観戦している人もいたり自由な感じでとても雰囲気が良かったです。

この場作りという観点で、スクラムフェス仙台2023の運営メンバーが考え抜いたレイアウトだと思うと、すごいと感じておりました。

さらに、スタッフの方々も楽しんでいる&参加者も全員協力的だったと思うので、やはり場を作り上げていくのは全員でやるのだなぁと再認識できました。

反省点

めちゃめちゃ楽しかったのですが、ふりかえっていて、いくつか反省点が見えてきました。

それは、登壇を意識するあまり、他のセッションが頭に入ってこなかった…という点です。

Day1でお酒をたくさん飲んだのも緊張を紛らわすためかもしれません…(酒焼けの言い訳)

そして、お気付きでしょうか…現地でのオリジナル写真がほとんどない事に…

精神的に一杯一杯で写真を撮るのも忘れていました…

また、Day1ではしゃぎ過ぎて、登壇が終わった後の無気力感と疲労が半端なかったです。

(まさにスクラムフェス仙台2022のシャークレくんのツイートみたいなイメージ🤣)

それだけ、この登壇にかけてきたという事にしておきましょう😅

今後について

今回、スクラムフェス仙台に登壇者として参加して、色々な気付きが得られました。

その中で、カイゼンしたい点としては、登壇経験を積み重ねてさらに楽しみたい!ということです。

今回は初参加、初プロポーザル、外部イベントでの初登壇という事でめちゃめちゃ緊張したのですが、楽しみの方が上回る体験となりました。

しかし、もっと楽しむためには登壇に慣れる事により心に余裕を持ってさらに楽しめるのでは?と感じました!

(あと、全力で楽しむために体力もつけたい!)

他には、今回のスクラムフェス2023の運営側の方々が光る場面がいくつも目撃したので、自分でもこのような場作りができるようになりたいと強く思いました。

元々、なぜスクラムフェス仙台か?にも書いた通り、東北を盛り上げたいという気持ちがあります。現在、北東北(青森・岩手・秋田)にスクラムアジャイルの勉強会はほとんど開催されていないですし、コミュニティも私の観測範囲では見当たらないように見えます。 (コンパスで見ると「アジャイルスクラム」のワードで勉強会が一つもない)

ですので、個人的なNextActionとして岩手でアジャイル/スクラムコミュニティを生み出す活動なども今後やっていきたいと考えています!!

最後に、こんな素晴らしい場作りをして頂いた運営の方々を初め、関わってくださった全員に改めて感謝をお伝えして、ふりかえりの記事とさせて頂きます!!

ありがとうございました!そして、おつサメでした!!

【イベントレポート】プロダクトマネジメント組織のデザイン

イベント概要

2023年8月24日にGaudiy社との共催で「不確実性を乗りこなす強いプロダクトマネジメント組織のデザイン」と題してプロダクトマネジメント組織に関する勉強会を開催しました。

不確実性の高いマーケットに対して、高い成果を出せるチームにしていきたいPdMやPOの方、組織設計からプロダクトのアウトカムを高めたいと考えているEMの方などに特にお勧めの勉強会でしたのでイベントの中から高石さんによる「『わからない』に立ち向かい続け成果を生み出す、実験志向なチームの育み方」の講演レポートをお送りします。

今回のスピーカー

「『わからない』に立ち向かい続け成果を生み出す、実験志向なチームの育み方」

不確実性の高いゴールに付き物の「わからない」

プロダクト開発ではゴールを設定します。タイミーではスクラムを採用しているチームが多いのでプロダクトゴールと表現されます。 RetentionRateの10%向上といった定量的なゴールもあれば、ユーザーストーリーの形式で特定属性のユーザーが○○を実現できるようになるといったゴールもあると思います。近年はこの様に何かしらのゴールに向かってイテレーティブな開発をすることが一般的になってきました。

ここで付き物なのが「わからない」ということです。ゴールが定まってもそこにどうやれば辿り着けるのかは「わからない」に満ちています。PdMやPOの方で「わからない」のフィードバックを貰ったことがない人はあまりいないのではないでしょうか?

でもこの「わからない」ことってネガティブなことなのでしょうか? 人間にとって「わからない」ものは本能的に身構えてしまったり、気持ち悪さを感じることが多いと思います。一方でこの「わからない」気持ちは「わかりたい」という行動への原動力でもあります。「わからない」を放置しておくとモチベーションの低下に繋がってしまいますが、適切に解消が出来ればプロダクトの成果に繋がります。

「わからない」を「わかる」にする力がプロダクトの成果を生む

普段、仕事で強く意識することは少ないですが日常業務の中でも「わからない」ことが沢山あります。

こういった様々な疑問がある中で皆さんは無意識に解消する行動を取っていると思います。インタビューをしてみたり、A/Bテストを実施したり、言語化こそされていなくてもわかる様にするアクションを取っているのではないでしょうか。

その様な行動で「わからない」を一つずつ「わかる」に変えていくことで不確実性の高いゴールに対しても道筋が見えてきます。ゴールの達成方法がわかるようになってくるのでプロダクトの成功にまた一歩近づくことが出来ます。

逆に「わからない」を解決出来ないとBIGBANGリリースに繋がったり、出したは良いけどその後の計測が上手く出来ずに成功したかも失敗したかも判断出来ないといったことが発生します。それだけならまだしも「わからない」を解決出来ず、不確実性を許容出来ないチームになってしまうと簡単なゴールしか設定できなくなってしまうリスクもあります。 基本的にどのプロダクトも新規性が高くまだ世の中に無いものを生み出しているわけですから、文化として「わからない」を許容し、それを解消しつづける組織力をいかに創れるのかが企業の競争力に直結すると思います。

タイミーのケースから見る、チームの実験を支える環境づくり

ユーザビリティテストやA/Bテストなどについてはよくまとまった書籍がたくさんありますし、皆さんも勉強されていることと思います。しかし、座学として知識を習得しても、それらを実際にチームとして行動に移すまでには辿り着けないことの方が多いのではないでしょうか? そこでタイミーでは「チームが実験を行ったり情報収集する、心理・物理のハードルを極限まで取り除く」ということを行っています。

チーム専属のアナリスト

チーム専属になる前までは分析チームに依頼をして、回答を待つ形式でした。この場合、分析チームが忙しい場合だと答えが分かるまで1〜2週間掛かってきます。このようなことになると既にスプリントの1週間を超えてしまいます。依頼側としてもそんなに時間がかかるなら別のやり方を模索しよう、となり依頼することそのものを諦めてしまいがちです。 しかし、スクラムチームに1人専任でプロダクトアナリストが所属する様になったことで物事の大小を問わず、迅速な解決ができる様になり、定量面の仮説検証速度が向上しました。

ユーザーインタビューのハードルを極限まで落とす「Interview as a Service

タイミーではユーザーと話したいと思ったらとにかくすぐに話せるようになっています。 Googleカレンダーにパブリックな仕組みがあってユーザーインタビューの予定を作ると自動で人がアサインされ、議事録が用意されて、インタビューアーのリクルーティングまで完結する仕組みがプロダクトマーケティング部によって運用されています。

このプロセスでは最短で明日、中央値でも2、3日後には望んだセグメントの人とお話が出来ます。 これによりスプリントの中に収まってくるので事前にリサーチして準備をしよう、ではなくスプリントの中で解くといったケースまで出てきたのも面白かったポイントです。 ここまで環境を整えていくと勝手に実験が増えていきます。実験をしよう、しようと思っているうちは中々実験が進みませんが、阻害する要因を全て取り除いていくと皆も実験したい気持ちはそもそもあるので自然と実験が増えます。

実験に特化したフレームワークの採用

『LeanUX』の中で紹介されているLeanUXCanvasを採用しています。

1〜8までのボックスがあるのですが、特に7が面白く「一番はじめに学習しないといけないこと」を明示しています。今回の言葉でいうと「わからないものの中でもっとも早くわかる様にしないといけないこと」ですね。「わからない」ことの存在を事前にフレームワークで宣言をしてその解消の重要性も説いていることがユニークに感じています。

取り組みのとある共通点

どのケースも全く違う取り組みに見えて、共通点があります。

① ゴール達成の過程に生まれる「わからない」を「わかる」にする力をつけること
② チームを取り巻く、組織や環境面からアプローチしていること

「わからない」を爆速で楽にわかるようにするといった観点でチームや組織を組成すれば高い不確実性も許容して実験を重ねて解決できる強いチームが生まれます。 また「わからない」を解決するだけであれば手段はさまざまありますが、組織や環境面からのアプローチはレバレッジが効きます。例えばインタビューの話であれば開発チームに限らず、営業やマーケティングチームが使ったりしていて、全社に波及しています。

まとめ

まとめると今日は不確実性という言葉は広いので「わからない」と置き換えましたが、この「わからない」ことをネガティブに受け取って欲しくないと思っています。「わからない」ことも当然ですが、すぐ「わかる」ことよりも「わからない」に対して挑戦して考えていく方が生産的でプロダクトの組織としてもより成果をあげれるのかなと思います。

実験や仮説検証でも個人だけの頑張りではなくチームとして取り組めることが多くあります。プロダクトのチームは元より実験をしたいので妨げる要因を取り除いていくことで自然と実験する文化が育まれ、不確実性に強いチームになっていきます。タイトルに繋がっていきますが皆で「わからない」に立ち向かい続け成果を生み出す、実験志向なチームを育てていきましょう。

充実のパネルディスカッションは本編動画から!

www.youtube.com

実験文化のある組織をいいなと思った方は是非お話しましょう

devenable.timee.co.jp

データアナリストの勉強会をご紹介します!

こんにちは! タイミーのデータアナリストの@takahideです。
今回は、メンバーが行っている「勉強会」を紹介させていただきます。

ご紹介する内容は以下になります。
・勉強会ではどんなことをやっているの?
・勉強会を通じた業務の広がりとは?

勉強会について

まずは、勉強会に関して簡単に紹介させてください。

タイミーでは、有志のメンバーが勉強会を立ち上げる文化が根付いています。
その中でも、データアナリストの勉強会は大きく2種類あります。

一つは、「会計」「プロジェクトマネジメント」などテーマをもとにした「一話完結型」のもの。
もう一つは、課題本を決めて輪読する「読書会型」で、今回はこちらをご紹介します。

勉強会の例

「読書会型」の勉強会の一番の目的は「分析力の向上」です。
勉強会を通じて、分析手法に関する共通言語が浸透することで、分析スキルの底上げだけでなく、
分析プロセスや結果に関するコミュニケーションが円滑になりました。

また、他部署の方と勉強会を行うことで、複数部署間のコミュニケーションもスムーズになりました。

プロダクトチームと行なった勉強会の感想

今回は「効果検証」に関する勉強会を一つの事例として紹介できたらと思います。

勉強会の具体例

背景

効果検証の勉強会は、データアナリストの検証手法の習得を目的に始まりました。

タイミーの組織が大きくなるにつれて、検証が必要な施策の件数も増加傾向にあります。
また、マーケティング施策のように、ABテストでの検証が難しいものも増えており、手法の共通認識を作るニーズが高まっていました。

勉強会で用いた本

勉強会では、『効果検証入門〜正しい比較のための因果推論/計量経済学の基礎』(安井 翔太)を課題図書として用いました。
傾向スコア、差の差分法(DID)など効果検証の手法が網羅的に説明されているのと、コードが豊富なのが良かったです。

勉強会の進め方

勉強会は、週に一度、担当者がgoogle collabを用いて発表を行い、他メンバーから質疑応答を行いながら進めています。

コードを確認しながら進めることで理解が深まりやすくなっています。

勉強会を通して

大きく3つの活動に繋がりました。

一つ目は、当初の目的だった「効果検証手法に関する共通言語の促進」です。

特に、検証手法を用いる際の注意点を共通認識として持つことができました。
例えば、回帰不連続デザイン(RDD)を利用する際には、「分析対象が介入に関する状態を操作できない」ことが求められますが、こうした条件が満たされない場合は、逆に、集積分析(bunching analysis)のような操作を前提にデザインされた手法を試すなどです。

二つ目は、勉強会をきっかけに、データアナリスト内でpythonを推進する活動に繋がったことです。

勉強会はpythonで実行しましたが、勉強会の内容を踏まえてタイミーの実データを用いた検証を行う際に、pythonへの慣れの有無がボトルネックになっていることが分かりました。
そこで、現在は、pythonを通常の分析業務で自然に扱えるための学習パッケージの構築を進めています。

三つ目は、全社的な講習会に繋がったことです。

こちらに関して次に詳細をお伝えできたらと思います。

効果検証の講習会

冒頭でもお伝えしましたが、現在タイミーは組織拡大に伴い、効果検証のニーズが増加しています。
そこで、今回の勉強会の内容と、過去の分析ナレッジを踏まえて、全社的な効果検証の講習会を実施しました。

効果検証の目的から始まり、交絡因子など、効果検証で注意するべき内容をお伝えしました。


また、効果検証を進める際に、データアナリストと事業部がどのように連携すると良いかをお話しました。

まとめ

データアナリストの勉強会のご紹介いかがだったでしょうか。
改めてにはなりますが、データアナリストが勉強会を行うメリットに「分析手法の習得」と「共通言語の促進」があると思います。

特に後者は、勉強会のナレッジを他部署の方に展開するなど、コミュニケーションの円滑化に繋がっていると考えます。

タイミーは事業、組織ともに拡大傾向にありまして、データアナリストが活躍する機会が多く存在しています。
これからもデータアナリスト発信で意思決定に役立つ情報を展開することで事業の成長に貢献したいと考えています。

We’re Hiring!

タイミーのデータ統括部では、ともに働くメンバーを募集しています!!

カジュアル面談も行っていますので、少しでも興味がありましたら、気軽にご連絡ください。
また、データ統括部のマガジンでは有益な情報を共有していますので、ぜひ、フォローお願いします!

dbtCloudから作成したPullRequestにコンパイル済みSQLをコメントする仕組みを作成した話

こんにちは☀️
タイミーでアナリストとアナリティクスエンジニアしてますokodoonです

今回の記事はdbt CloudでPull Requestを作るときに、レビュー負荷が高くなってしまっていた問題を解消できるように、コンパイル済みのSQLをPR上にコメントするような仕組みを作成したことについての紹介です。

もし同じような課題感を抱えている方がいらっしゃれば、参考にしていただければ幸いです

課題感

弊社のデータ基盤ではDWH層DataMart層は「分析用に加工されたデータを扱う層」として定義しています。
各種ドメインに依存した集計や変換のロジックが含まれるため、この層のモデリングに関しては基盤開発側のレビューのみでなくアナリスト観点でのレビューも必須となります。

ですが、分析ドメイン観点でのレビューが必要な場合に、純粋なSQLではないdbtモデルがアナリストレビューの障壁になることが多いです。
またアナリスト以外であっても「このmacroがどのようにコンパイルされるか」を把握するのは少し面倒だったりします

今回選択した解決策

そこでdbtモデルをコンパイルしたSQLファイルをPullRequest上にコメントするような仕組みを考えました。

この実装によって先ほど挙げた課題感が以下図のように解決されることを期待して開発しました

背景/前提

1. 開発環境について

弊社ではdbtを活用したデータ基盤の開発を行っており、

dbtモデル開発をdbt Cloud上の統合環境にて実施するような流れになっています。

2. CIジョブについて

CI用のdbt CloudのJobはRUN ON PULLREQUEST で実行されて、以下のようなコマンドが実行されています。

dbt build --select state:modified+

mainブランチとの差分があるdbtモデルがBigQuery上にビルドされている状態です(本来CIで走るdbtコマンドをレイヤーごとに分割していますが、ここでは簡略化しています)

3. CI環境のスキーマ名とcustom_schemas.yml戦略

dbt Cloudで設定できるCI Jobではtarget schema名の命名dbt_cloud_pr_<job_id>_<pr_id>となっており、連携レポジトリのPR番号に対して動的に作成されます。

(DBT_JOB_SCHEMA_OVERRIDEでこの命名規則の上書きもできますが、PR単位でCIテーブルが作成されて欲しいので、デフォルトの命名規則にしたがっています。)

参考:https://docs.getdbt.com/docs/deploy/continuous-integration#how-ci-works

また、弊社では開発環境とCI環境のスキーマ名が {{ターゲットスキーマ名}}_{{カスタムスキーマ名}}になるように custom_schemas.sql で定義してあります。

そのため、CI環境のテーブルは

dbt_cloud_pr_<job_id>_<pr_id>_{{カスタムスキーマ名}}

命名規則で作成されたデータセット名以下に作成されている状態です。

実装概要

作成したYAMLはこちら(クリックで展開)

name: DBT Compile and Comment on PR
    
on:
  pull_request:
    types: [opened, synchronize, reopened]
    
jobs:
  dbt_compile:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
      id-token: write
    
    env:
      DBT_ENVIRONMENT: dev
    steps:
      - name: Check out repository code
        uses: actions/checkout@v3
        with:
          fetch-depth: 0
          ref: ${{ github.event.pull_request.head.ref }}
    
      - name: Fetch base ref
        run: git fetch origin ${{ github.event.pull_request.base.ref }}
    
      - name: Set Up Auth
        uses: "google-github-actions/auth@v1"
        with:
          token_format: "access_token"
          workload_identity_provider: "hogehogehoge"
          service_account: "hogehogehoge@hogehoge.iam.gserviceaccount.com"
    
      - name: Set up Cloud SDK
        uses: google-github-actions/setup-gcloud@v1
    
      - name: Set up Python
        uses: actions/setup-python@v3
        with:
          python-version: 3.11
    
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install dbt-core dbt-bigquery
    
      - name: Generate profiles.yml
        run: |
          chmod +x ci/compile_sql_comment/generate_profile.sh
          ci/compile_sql_comment/generate_profile.sh ${{ github.event.pull_request.number }}
    
      - name: Compile DBT
        id: compile
        run: |
          dbt deps
          dbt compile --profiles-dir . --target dev --profile timee-dwh
          compiled_sqls=""
          files=$(git diff --name-only origin/${{ github.event.pull_request.base.ref }} ${{ github.event.pull_request.head.ref }} | grep '\.sql$' || true)
          if [ -n "$files" ]; then
            for file in $files; do
              compiled_file_path=$(find target/compiled -name $(basename $file))
              echo "Compiled file path: $compiled_file_path"
              if [ -n "$compiled_file_path" ]; then
                compiled_sql=$(cat "$compiled_file_path")
                compiled_sqls="${compiled_sqls}<details><summary>${file}</summary>\n\n\`\`\`\n${compiled_sql}\n\`\`\`\n\n</details>"
              fi
            done
          fi
          printf "%b" "$compiled_sqls" > compiled_sqls.txt
    
      - name: Comment on PR
        uses: actions/github-script@v6
        with:
          script: |
            const fs = require('fs');
            const output = fs.readFileSync('compiled_sqls.txt', 'utf8');
            const issue_number = context.payload.pull_request.number;
            const owner = context.repo.owner;
            const repo = context.repo.repo;
    
            async function processComments() {
              const comments = await github.rest.issues.listComments({
                owner: owner,
                repo: repo,
                issue_number: issue_number,
              });
    
              const dbtComment = comments.data.find(comment => comment.body.startsWith('DBT Compile Result'));
              const body = `DBT Compile Result:\n${output}`;
    
              if (dbtComment) {
                await github.rest.issues.updateComment({
                  owner: owner,
                  repo: repo,
                  comment_id: dbtComment.id,
                  body: body
                });
              } else {
                await github.rest.issues.createComment({
                  owner: owner,
                  repo: repo,
                  issue_number: issue_number,
                  body: body
                });
              }
            }

            processComments();

actionsの流れを説明するとこうなります。

  • PRブランチにチェックアウトして、PRブランチとmainの差分を確認するためにfetch
  • workload_identity_providerを用いたBigQueryへの認証
  • 必要パッケージのインストールと使用するパッケージの宣言
  • PRの情報をもとにprofiles.ymlの動的生成
  • コンパイル処理の実施
    • dbt compileの実行
    • mainとの差分があるファイルだけを抽出
    • 差分ファイルのcompile結果を文字列化
  • PR上にcompile結果がなければdbt compile結果を新規コメント。既にcompile結果がコメントされていたらコメントをupdate

各ステップの説明

説明が必要そうなステップの説明をしていきます

PRの情報をもとにprofiles.ymlの動的生成

上で述べた通り、CI環境のテーブルはdbt_cloud_pr_<job_id>_<pr_id>_{{カスタムスキーマ名}}命名規則で作成されたデータセットに配置されています。

dbt compileの出力結果のデータセット名がdbt_cloud_pr_<job_id>_<pr_id>_{{カスタムスキーマ名}}になるように、デフォルトスキーマ名を動的に宣言するためのprofiles.ymlを作成するシャルスクリプトを作成しております

#!/bin/bash
set -e

cat << EOF > profiles.yml
timee-ci-dwh:
  target: ci
  outputs:
    dev:
      type: bigquery
      method: oauth
      project: ci_env_project
      schema: dbt_cloud_pr_39703_$1
      execution_project: ci_env_project
      threads: 1
EOF

参考: https://docs.getdbt.com/docs/core/connect-data-platform/bigquery-setup#oauth-via-gcloud

これによりdbt compileの出力結果が、このターゲットスキーマ名を参照したスキーマになります。

コンパイル処理の実施

run: |
          dbt deps
          dbt compile --profiles-dir . --target ci --profile timee-ci-dwh
          compiled_sqls=""
          files=$(git diff --name-only origin/${{ github.event.pull_request.base.ref }} ${{ github.event.pull_request.head.ref }} | grep '\.sql$' || true)
          if [ -n "$files" ]; then
            for file in $files; do
              compiled_file_path=$(find target/compiled -name $(basename $file))
              echo "Compiled file path: $compiled_file_path"
              if [ -n "$compiled_file_path" ]; then
                compiled_sql=$(cat "$compiled_file_path")
                compiled_sqls="${compiled_sqls}<details><summary>${file}</summary>\n\n\`\`\`\n${compiled_sql}\n\`\`\`\n\n</details>"
              fi
            done
          fi
          printf "%b" "$compiled_sqls" > compiled_sqls.txt
  • dbt compileをPRブランチで実行
  • git diffで差分があった.sqlファイル名をdbt compileの実行結果であるcompile済みsqlファイルが格納されるtarget/compiled/ 以下でfindを実行してfileパスを取得
  • fileパスのcat結果を折りたたみタグ内に格納して文字列に追加

って流れの処理にしています。

このような処理にすることでSQLコンパイル結果のPRコメントを必要分だけコメントできる形としました。また、折りたたみタグに格納することでPullRequestの可視性を損なわないようにしました。

PR上にコメント

- name: Comment on PR
        uses: actions/github-script@v6
        with:
          script: |
            const fs = require('fs');
            const output = fs.readFileSync('compiled_sqls.txt', 'utf8');
            const issue_number = context.payload.pull_request.number;
            const owner = context.repo.owner;
            const repo = context.repo.repo;

            async function processComments() {
              const comments = await github.rest.issues.listComments({
                owner: owner,
                repo: repo,
                issue_number: issue_number,
              });

              const dbtComment = comments.data.find(comment => comment.body.startsWith('DBT Compile Result'));
              const body = `DBT Compile Result:\n${output}`;

              if (dbtComment) {
                await github.rest.issues.updateComment({
                  owner: owner,
                  repo: repo,
                  comment_id: dbtComment.id,
                  body: body
                });
              } else {
                await github.rest.issues.createComment({
                  owner: owner,
                  repo: repo,
                  issue_number: issue_number,
                  body: body
                });
              }
            }

PR上のコメントの一覧を取得して、既に DBT Compile Result:で始まるコメントがPR上に存在するのであれば、そのコメントのアップデート、存在しないのであれば新しくコメントをする。

という処理をしています。

条件分岐が発生する処理を簡便に記載したかったのでinlineでjavascriptを記載できるgithub-scriptを使用して記述しています

これによってactionが走るたびにコメントが新規でされるのではなく、一つのコメントが上書きされ続けるような処理となり、PullRequestの可視性を損なわないようにしました。

どんなふうに動くかみてみる

こんなモデルをテストで作ってみました

{{ config(
    schema = 'sample_schema'
)}}

SELECT
    'hogehoge' AS name
    , 30 AS amount
UNION ALL
SELECT
    'fugafuga' AS name
    , 50 AS amount
{{ config(
    schema = 'sample_schema'
)}}

SELECT
    SUM(amount) AS sum_amount
FROM {{ ref('sample_model1') }} AS sample_model1

以下のようにPullRequest上に変更内容が折り畳まれた状態でコメントされて、ref関数のコンパイル結果がCI環境のデータセットになっていることを確認できます

次にsample_model1.sqlを以下のように修正して再度pushしてみます

{{ config(
    schema = 'sample_schema'
)}}

SELECT
    'hogehoge' AS name
    , 30 AS amount
UNION ALL
SELECT
    'fugafuga' AS name
    , 50 AS amount
UNION ALL
SELECT
    'blabla' AS name
    , 100 AS amount

以下のように既存のcommentがeditされて、修正後の内容でコンパイルした結果で上書きされていることが確認できます

結果

弊社のデータ基盤はfour keys計測による開発ヘルススコアの計測を行っており、今回の仕組みのリリース前後で開発リードタイムを計測してみましたが、目立った影響は出ていませんでした😢

レビューを円滑にできる環境を整っていないことが課題ではなく、他業務との兼ね合いでDWH開発のレビューに充てることができる時間がそもそも少なそうだったり、レビューに必要なドメインのインプットが足りていないことがボトルネックになっていそうだなという発見にも繋がったので、そこはプラスに捉えています

メンバーの声を聞いていると便利なことは間違いないらしいので、レビューコストの低減による持続的なトイル削減に将来的には繋がっていけばいいなと思ってます🙏

使えそうだな。試してみようかなと思っていただけたら幸いです!

We’re Hiring!

タイミーのデータ統括部では、ともに働くメンバーを募集しています!!

このたび、アナリティクスエンジニアのポジションが公開されたので、是非ともご応募お願いします
その他ポジションも絶賛募集中なのでこちらからご覧ください