TECH PLAY

タイミー

タイミー の技術ブログ

288

こんにちは、タイミーのデータエンジニアリング部 データサイエンス(以下DS)グループ所属のYukitomoです。 今回はPythonのLinterとしてメジャーなflake8のプラグインの作り方を紹介したいと思います。 コードの記述形式やフォーマットを一定に保つため、black/isort/flake8などのformat/lintツールを使うことはpythonに限らずよく行われていますが、より細部のクラス名や変数名を細かく規制したい(例:このモジュールのクラスはこういう名前付けルールを設定したい等)、けれどコードレビューでそんな細かい部分を目視で指摘するのは効率的でない、といったケースはありませんか?そんな時、flake8のプラグインを用意して自動検出できるようにしておくと便利です。 ネット上には公式サイトを含めいくつかプラグイン作成の記事があるのですが、我々の想定ケースと微妙に異なる部分がありそのままでは利用できなかったため、 最新のflake8(2024/7現在, 7.1.0)を用い 比較的新しいパッケージマネージャーであるpoetry(1.8.3を想定)を利用して 2種類のプラグインのそれぞれの作り方 を改めてここにまとめます。 準備するもの Python: Versionは特に問いませんが、3.11.9で動作確認しています。 Poetry: 1.8 以上 (後述しますが1.8より導入されたpackage-mode = falseを指定しているため)。この記述を変えることで1.8以前のバージョンでも動くとは思いますが、この記事では1.8を前提としています。 上記が利用可能な環境をvenvやコンテナを利用して作成しておいてください。flake8本体はpyproject.tomlの依存モジュールとして導入されるため事前に準備する必要はありません(3.8以降で動作するはずですが、本記事では7.1を利用します)。 全体の構成 サンプルで利用するファイル群は以下の通りです。構文木を利用するタイプと1行ずつ読み込んでいくタイプと2種類あるため、それぞれをtype_a、type_bとしてサンプルを用意し、それら2つのサンプルを束ねる上位のプロジェクトを一つ用意しています。本来なら各プラグイン毎にユニットテスト等も実装すべきですが、本記事ではプラグインの書き方自体の紹介が目的のため割愛しています。なお、type A, type Bの呼称はflake8プラグインにおいて一般的な呼び名ではなく、本記事の中で2つのタイプを識別するために利用しているだけなので注意してください。 # poetry.lock 等本記事の本質と関係のないものは省略しています (.venv) % tree . # この位置を$REPOSITORY_ROOTとします。 . ├── pyproject.toml └── plugins    ├── type_a    │   ├── pyproject.toml    │   └── type_a.py    └── type_b    ├── pyproject.toml    └── type_b.py ${REPOSITORY_ROOT}/pyproject.tomlは以下の通り。 # cat ${REPOSITORY_ROOT}/pyproject.toml [tool.poetry] name = "flake8 plugin samples" version = "0.0.1" description = "A sample project to demonstrate flake8 plugins" authors = [ "timee-datascientists" ] package-mode = false # この記述を外せばきっとpoetry 1.8より前でも動くはず [tool.poetry.dependencies] python = ">=3.11.9" [[tool.poetry.source]] name = "PyPI" priority = "primary" [tool.poetry.group.dev.dependencies] # flake8を利用するので一緒によく利用されるblack/isortも導入 flake8 = "~7.1.0" isort = "~5.13.2" black = "~24.4.0" # プラグインはローカルからeditable modeで登録 type_a = { path= "./plugins/type_a" , develop = true} type_b = { path= "./plugins/type_b" , develop = true} [build-system] requires = [ "poetry>=1.8" ] build-backend = "poetry.masonry.api" Type A: AST Treeを利用する場合 Python codeの1ファイルをparseして抽象構文木(AST)として渡すタイプのプラグインです。ネットでflake8のプラグインを検索した時、こちらのタイプの実装例が出てくることが多く、また、構文木の処理が実装できるなら、こちらの方が使いやすいです。 構文木で渡されたpython ファイルを巡回し、その過程で違反を発見するとエラーを報告しますが、本記事のサンプルでは構文木の巡回結果は無視し、巡回後必ずエラーを報告しています。詳細はast.NodeVisitorを参照いただきたいのですが、各ノードを巡回する際に呼ばれるvisit()だけでなく、visit_FunctionDef() などファイル内で関数定義された場合、など個別の関数が用意されているので、これらを適切に上書きすることで、目的の処理を実現していくことになります。 なお、プラグインのコンストラクタには抽象構文木(ast)の他、lines, total_lines等公式ドキュメントの ここ に記述されているものを追加することができます。 以下にサンプルの実装(type_a/type_a.py)とプロジェクトの定義ファイル(type_a/pyproject.toml)を示します。 # type_a/type_a.py import ast from typing import Generator, List, Tuple # プラグインの本体 class TypeAPluginSample : def __init__ ( self, tree: ast.AST #, lines, total_lines: int = 0 ) -> None : self.tree = tree def run (self) -> Generator[Tuple[ int , int , str , None ], None , None ]: visitor = MyVisitor() visitor.visit(self.tree) # サンプルでは常にエラーを報告するが本来ならvisitorに結果を溜め込んで # 結果に応じてエラーをレポート if True : yield 0 , 0 , "DSG001 sample error message" , None # プラグインから利用する構文木の巡回機 class MyVisitor (ast.NodeVisitor): # visit() やvisit_FunctionDef()を目的に応じて上書き pass # 他のサンプルでは必須っぽく書いてあるが、pyproject.tomlのentry-points # の指定と被ってるなぁと思ってコメントアウトしても動いたので今はいらない気がする。 # def get_parser(): # return TypeAPluginSample (.venv) % cat plugins/type_a/pyproject.toml # 親プロジェクトから直接ロードするため [project]の記述もしていますが # プラグイン単体で独立したプロジェクトとするなら不要。 [project] name = "type_a" version = "0.1.0" description = "Sample type a plugin" authors = [{name = "timee-datascientists", email = "your.email@example.com"}] [tool.poetry] name = "type_a" version = "0.1.0" description = "Sample type-a plugin" authors = ["timee-datascientists"] [build-system] requires = ["setuptools", "wheel", "poetry>=1.8.3"] build-backend = "setuptools.build_meta" [tool.poetry.dependencies] python = ">=3.11.9" flake8 = ">=7.1.0" # ここでプラグインのクラス名を登録 [project.entry-points."flake8.extension"] DSG = "type_a:TypeAPluginSample" Type B: 1行ずつ処理する場合 対象となるpython ファイルを1行ずつ処理していくタイプのプラグインです。 公式ドキュメント にある通り、歴史的な経緯で2種類あるようですが、こちらの1行ずつ処理するタイプを使ったサンプルを見かけたことがありません。特に非推奨とされているわけでもないですし、実装したいルール自体がシンプルであればこちらの方法で実装するのもありだと私は思います。physical_lineもしくはlogical_lineを第一引数に設定し、physical_lineの場合はファイルに書かれている1行ずつ、logical_lineの場合はpython の論理行の単位で指定した関数が呼ばれます。physical_line, logical_lineの両方を同時に指定することはできず、他の変数を追加する場合もphysical_line/logical_lineは第一引数とする必要があります。 以下にサンプルの実装(type_b/type_b.py)とプロジェクトの定義ファイル(type_b/pyproject.toml)を示します。 # type_b/type_b.py from typing import Optional # プラグイン本体 def plugin_physical_lines( physical_line: Optional[str] = None, line_number: Optional[int] = None, filename: Optional[str] = None, ): if line_number == 2: yield line_number, "DSG002 sample error message" (.venv) % cat plugins/type_b/pyproject.toml # type_aのものとほぼ同じ。project.nameおよびtool.poetry.nameをtype_bに書き換えた後、 # 差分は以下。プラグイン本体の関数を指定してやれば良い。 : [project.entry-points. "flake8.extension" ] DSG = "type_b:plugin_physical_lines" 実行結果 以下のようなサンプルファイルを用意し、flake8を実行した結果を示します。 # sample.py def main (): print ( 'Hello, World!' ) if __name__ == '__main__' : main() 実行結果 % flake8 sample.py sample.py: 0 : 1 : DSG001 sample error message sample.py: 2 : 3 : DSG002 sample error message 注意点 Type A, Type B両方とも 公式ドキュメント に書いてある変数は全てコンストラクタに追加できるのですが、それぞれのタイプにおいて意味のあるものは限られるため、必要なもののみを追加すれば良いです。 まとめ flake8 のプラグインの定義方法を2通りご紹介しました。 タイミーのデータサイエンスグループでは通常のformat/lintだけでカバーできない(けれど少しの工夫により機械作業で抽出できる)運用ルールを本記事のようなflake8プラグインを用いてCIで事前に検出することで、コードレビューはできるだけ本質的な部分に集中できるよう取り組んでいます。 We’re Hiring! タイミーのデータエンジニアリング部・データアナリティクス部では、ともに働くメンバーを募集しています!! 現在募集中のポジションは こちら です! 「話を聞きたい」と思われた方は、是非一度 カジュアル面談 でお話ししましょう! References flake8 Pluginの書き方公式ドキュメント: https://flake8.pycqa.org/en/latest/plugin-development/index.html Pluginの入力として利用できる変数一覧: https://flake8.pycqa.org/en/latest/plugin-development/plugin-parameters.html flake8 Version 2.5.4を用いた参考記事(本記事でType Aと呼んでいるのと同じタイプのもの) https://blog.amedama.jp/entry/2016/04/12/063359#google_vignette さらに別な本記事でType Aと呼んでいるタイプのプラグインの作成記事: https://qiita.com/misohagi/items/756954d7f4315cea0230
タイミー QA Enabling Teamのyajiriです。 去る6月28日〜29日の2日間、ファインディ様主催の「開発生産性カンファレンス2024」に参加してきました。 (タイミーには世界中で開催されるすべての技術系カンファレンスに無制限で参加できる「Kaigi Pass」という制度があり、今回もこれを利用して新潟からはるばる参加してきました。) productpr.timee.co.jp タイミーでは弊社VPoE(VP of ええやん Engineering)の赤澤の登壇でもご紹介した通り、チームトポロジーを組織に適用し、プロダクト組織の強化と改善にチャレンジしています。 speakerdeck.com この登壇でも紹介されておりますが、私自身もイネイブリングチームの一員として、プロダクト組織全体のQA(品質保証)ケイパビリティの向上や、障害予防プロセスの改善に取り組んでいます。 開発生産性の観点から考える自動テスト まずQAの視点で最も印象に残ったのは、皆さんもご存知のt_wadaさんによる「開発生産性の観点から考える自動テスト(2024/06版)」です。 speakerdeck.com なぜ自動テストを書くのか? この問いに対してt_wadaさんは 「コストを削減するためではなく、素早く躊躇なく変化し続ける力を得るため」 そして 「信頼性の高い実行結果に短い時間で到達する状態を保つことで、開発者に根拠ある自信を与え、ソフトウェアの成長を持続可能にすること」 と表現されていました。 (ここまで一言一句に無駄のない文章は久々に見た気がします) タイミーでもアジャイル開発の中で高速なテストとフィードバックのサイクルを意識し、自動テストを含むテストアーキテクチャの強化に取り組んでいます。しかし、活動がスケールすると共にテストの信頼不能性(Flakiness)や実行時間の肥大化、費用対効果などの問題が発生します。 これらの問題に対する合理的な対応策を検討する上で、各々のテストの責務(タイプ)や粒度(レベル)を分類し、費用対効果と合目的性の高いものから重点的に対応していく必要があります。 そのためのツールとして「アジャイルテストの四象限」や「テストピラミッド」「テスティングトロフィー」などを活用し、テストレベルを整理し、テストのポートフォリオを最適化するアプローチを取っていましたが、具体的なアーキテクチャに落とし込んだ際に「これってどのテストレベルなんだっけ?」といった想定と実態の乖離がしばしば発生していました。 サイズで分類しテストダブルでテスト容易性を向上する それを解決する手段として、テストレベルではなくテスト「サイズ」で整理する方法が提唱されました。 テストサイズの概念は古くは「 テストから見えてくる グーグルのソフトウェア開発 」、最近では「 Googleのソフトウェアエンジニアリング 」で紹介されていました。今回紹介されたのは、テストピラミッドにおいても具体的なテストタイプではなく「サイズ」で分類し、テストダブル(実際のコンポーネントの代わりに使用される模擬オブジェクト)を積極的に利用することでテスタビリティを向上させ、テストサイズを下げ、速度と決定性の高いテストが多く実装される状態を作るというアプローチです。 このアプローチは、タイミーのDevOpsカルチャーにも親和性が高く、ぜひ自動テスト戦略に取り入れたいと感じました。 おわりに 他にも魅力的で参考になる登壇が盛りだくさんで、丸々2日間の日程があっという間に過ぎる素晴らしいイベントでした。 主催のファインディ様やスポンサー、登壇者の皆さまに感謝するとともに、来年の開催も心より楽しみにしています。
こんにちは、タイミーのデータエンジニアリング部データサイエンス(以下DS)グループ所属の菊地です。 今回は、 H3 を使用したBigQueryでの空間クラスタリングについて検証した内容を紹介したいと思います! BigQueryでの空間クラスタリングとは BigQueryにはクラスタリングという機能があり、うまく活用すると、クエリのパフォーマンスを向上させ、クエリ費用を削減できます。 クラスタリングは空間データにも適用でき、BigQuery がデフォルトで使用するS2インデックス システムを使用して、空間クラスタリングを行うことができます。 また、H3やGeohashなどの他の空間インデックスに対しても空間クラスタリングを行うことができ、今回はタイミーでも良く使用している H3 を使用して、空間クラスタリングを行う方法を検証してみました。 BigQueryでのクラスタリング及び空間クラスタリングについては、下記の記事が参考になるかと思います。 cloud.google.com cloud.google.com H3を使用した BigQueryでの空間クラスタリングの検証 上記の参考記事でも挙げましたが、基本的にこちら記事の内容に沿いつつ、一部具体の実装が記載されていない箇所を補完しながら検証を行いました。 cloud.google.com 1. 検証用のテーブル作成 検証用のテーブルとして、経度と緯度のランダムポイントを、H3セルID(解像度13)に変換したテーブルを作成します。 DECLARE H3_INDEX_RESOLUTION INT64 DEFAULT 13 ; -- 連番を格納しておくためだけのテーブル -- CTEだと後続のテーブル作成が遅かったので実テーブルにしてます CREATE OR REPLACE TABLE `tmp.tmprows` as SELECT x FROM UNNEST(GENERATE_ARRAY( 1 , 10000 )) AS x; -- 経度と緯度のランダムポイントを、H3セルID(解像度13)に変換したテーブル DROP TABLE IF EXISTS `tmp.h3_points`; CREATE OR REPLACE TABLE `tmp.h3_points` CLUSTER BY h3_index AS WITH points AS ( SELECT `carto-os`.carto.H3_FROMLONGLAT(RAND() * 360 - 180 , RAND() * 180 - 90 , H3_INDEX_RESOLUTION) AS h3_index -- 後の検証のために追加 , RAND() AS amount FROM `tmp.tmprows` AS _a CROSS JOIN `tmp.tmprows` AS _b ) select h3_index , amount FROM points テーブルのストレージ情報と内容は以下のようになります。 2. クラスタリングによる絞り込みが効かないクエリ例 次に、 参考記事 で紹介されているように、親セルID(今回は解像度7)をWHERE句で指定してクエリを実行してみましたが、このクエリはテーブルをフルスキャンしてしまいます。 DECLARE PARENT_CELL_ID STRING DEFAULT ' 870000000ffffff ' ; -- H3解像度7のセルID SELECT ROUND ( SUM (amount), 6 ) AS sum_amount FROM `tmp.h3_points` WHERE `carto-os`.carto.H3_TOPARENT(h3_index, 7 ) = PARENT_CELL_ID ジョブ情報と結果 H3インデックスでクラスタリングを行っているにもかかわらず、テーブルをフルスキャンしてしまう理由としては、 H3_ToParentにはビット演算が関係し、複雑すぎて BigQuery のクエリアナライザが、クエリの結果がクラスタ境界にどのように関連しているかを把握できないために発生します。 と 参考記事 では言及されています。 3. クラスタリングによる絞り込みが効くクエリ例 次に、クラスタリングによる絞り込みが適用されるクエリを検証してみます。 「2. クラスタリングによる絞り込みが効かないクエリ例」との違いとしては、低解像度の親セルに含まれる、高解像度セルの開始IDと終了IDを取得し、WHERE句で指定していることです。 DECLARE H3_PARENT_ID STRING DEFAULT ' 870000000ffffff ' ; -- H3解像度7のセルID DECLARE H3_INDEX_RESOLUTION INT64 DEFAULT 13 ; DECLARE RANGE_START STRING; DECLARE RANGE_END STRING; -- 低解像度の親セルに含まれる、高解像度セルの開始IDと終了IDを取得しセットする SET (RANGE_START, RANGE_END) = ( SELECT AS STRUCT `carto-os`.carto.H3_TOCHILDREN(H3_PARENT_ID, H3_INDEX_RESOLUTION)[ 0 ], ARRAY_REVERSE(`carto-os`.carto.H3_TOCHILDREN(H3_PARENT_ID, H3_INDEX_RESOLUTION))[ 0 ] ); SELECT ROUND ( SUM (amount), 6 ) AS sum_amount FROM `tmp.h3_points` WHERE h3_index BETWEEN RANGE_START AND RANGE_END ジョブ情報と結果は以下のようになっており、スキャン量が削減され、クエリのパフォーマンスも向上しています。クエリ結果も「2. クラスタリングによる絞り込みが効かないクエリ例」の結果と合致しています。 ジョブ情報と結果 まとめ H3を使用した BigQueryでの空間クラスタリングについて検証してきました。 タイミーでは位置情報を活用した分析を行うシーンが多く、うまく活用することで機械学習時の特徴量生成や、BIツールからのクエリ最適化に繋げることができる可能性があるので、今後のデータ分析に活かしていきたいと思います。 We’re Hiring! タイミーのデータエンジニアリング部・データアナリティクス部では、ともに働くメンバーを募集しています!! 現在募集中のポジションは こちら です! 「話を聞きたい」と思われた方は、是非一度 カジュアル面談 でお話ししましょう!
2024年6月22日(土)に Kotlin Fest 2024 が開催されました。Kotlin Festは「Kotlinを愛でる」というビジョンを掲げた技術カンファレンスです。タイミーのAndroidエンジニアはエンジニアの成長を支援する制度の一つである Kaigi Pass を利用して参加しました。 本投稿では、Kotlin Fest 2024に参加したメンバー(中川、haru、みかみ、 しゃむ 、 むらた 、 tick-tack )が気になったセッションや感想のレポートします! メンバーによるレポート 中川編 効果的なComposable関数のAPI設計 私が気になったセッションは、haru067さんによる「効果的なComposable関数のAPI設計」です。このセッションでは、Composable関数を書くときに引数をどのように定義すべきかという、現場で直面する具体的な疑問に対して、様々なケーススタディを通じて考察が行われました。 セッションでは以下のプラクティスに触れられました: State hoisting Slot API DSLでのslot APIの活用 デフォルト引数 Property drilling 特に印象的だったのは、これらのプラクティスが常に最適な解決策とは限らないという点が強調されていたことです。むやみに使うのではなく、適切な場面で使うことが重要であるという、現場での経験に裏打ちされた具体的なアドバイスが参考になりました。 haru編 Kotlinで愉しむクリエイティブコーディング まず最初にご紹介するのは、 畠山 創太 さんによる Kotlinで愉しむクリエイティブコーディング です。 私はクラブイベントにたまに行くので、VJさんという存在を元々知っていたのですが、そんなVJさんの中でもジェネ系と呼ばれる画面をリアルタイムに生成するライブコーディング的なアプローチのVJさんとプライベートで繋がりがあり、それに利用されているフレームワークなどを知っていました。 そんな中、このセッションではKotlinでリアルタイムにグラフィックスを処理できて、ジェネ系VJにも使えそうな OPENRNDR が紹介されていました。 OPENRNDRはProcessingやTouch Designerなどのジェネ系VJで使われるフレームワークとよく似たフレームワークで、KotlinベースのDSLでグラフィックス処理を記述することができます。 このセッションでは、OPENRNDRで書かれたいくつかのデモ(Boidsなど)が紹介され、OPENRNDRでできることの自由度や簡単に記述できることを紹介していました。 OpenGLベースのグラフィックスバックエンドをもち、RealSense, Kinect, TensorFlow, DMXなど多種多様な連携先が存在しており、これらを使えばセッションで紹介されていた以上のこともできそうだなと感じました。 Okioに愛を込めて 次にご紹介するのは、RyuNen344さんによる Okioに愛を込めて です。 OkioはBlock社が開発しているKotlin向けのI/O ライブラリで、OkHttpやMoshiのベースにも使われているライブラリです。 まず、Kotlinの標準ライブラリが充実しているのに、なぜOkioを採用するのかという話から始まりました。 いくつかの理由を紹介されていましたが、地味に落とし穴だなと思ったのは、Kotlinが元々JVMをターゲットとした言語としてスタートしているが故にJava標準ライブラリを呼び出しているところが多々あったり、それをKMPから使えなかったりするというところでした。 そんな中、OkioはJava標準ライブラリなどへの依存がなく、それでいて使い勝手の良いI/Oライブラリになっているということで、これから直接的・間接的問わず利用する頻度は増えていきそうでした。 これからKotlin向けのライブラリを作る上では、JVM以外のターゲットで使われることも前提として考えないといけないと思いました。 そして、綺麗なダジャレでセッションは終了。お見事でした。 みかみ編 例外設計について考えて Kotlin(Spring Boot&Arrow)で実践する 「例外設計について考えて Kotlin(Spring Boot&Arrow)で実践する」というセッションを紹介します。例外設計の重要性とプロダクト開発に与える影響について深く掘り下げ、KotlinとArrowライブラリを活用した柔軟な例外設計の実践方法が詳しく説明されていた発表でした。 特に印象的だったのは「例外設計とモデリング」についてです。このセッションでは、例外を「技術的例外とビジネス例外」および「予期する例外と予期しない例外」の組み合わせで大きく4つに分類できるという説明がありました。そしてそれぞれの例外に対して、ドメイン駆動設計(DDD)の考え方を基に、具体的にどのようにコードに反映させるかが紹介されました。例外をドメインに結びつけて考えることにより、プロダクト開発に良い影響を与える例外設計を行うことができると感じました。 例外自体は普段の実装でも意識しますが、その複雑さのため設計に関しては深く意識できていないことが多いと感じています。本セッション内容を通してプロダクト開発をより良くしていくための例外設計の考えた方と実践に挑戦していきたいと感じました。 しゃむ編 しゃむ( @arus4869 )です。FF16を最近ようやくクリアできたので、FFVIIリバースやり始めました。最高ですね。 KotlinのLinterまなびなおし2024 私が気になったセッションは「 KotlinのLinterまなびなおし2024 」です。このセッションでは、各種Lintツールの紹介だけでなく、Lintツールを効果的に活用するための実践的なアドバイスも多数紹介されました。 中でも特に気になったのはkonsistです。konsistは、標準セットルールがなく、各プロジェクトの特性に合わせたルール設定が可能である点が魅力的でした。また、テスト環境やユニットテストでの動作が主な特徴で、アノテーションを活用することで特定の用途に応じたルール設定ができる点も興味深かったです。 またセッションの中で、Lintルールを段階的に導入することでチームの負担を軽減しつつ、徐々にコード品質を向上させるアプローチも印象的でした。 このセッションを通じて、KotlinのLintの効果的な使い方について多くの知見を得ることができ、学び直しの良い機会になりました。ありがとうございました。 むらた編 むらた( @orerus )です。最近夫婦でカイロソフトさんのアプリにハマっています。 withContextってスレッド切り替え以外にも使えるって知ってた? さて、早速ですが私が気付きを得たセッションとしてT45Kさんによる「withContextってスレッド切り替え以外にも使えるって知ってた?」 を紹介させていただきます。 スライド も公開されています。 Kotlin coroutinesを使っていると頻繁に登場する withContext ですが、セッションタイトルでズバリ指摘されている通り、私もスレッドの切り替え用関数であるかのように意識してしまっていたことに気づきました。 使い方が間違っているわけではありませんが、セッションで紹介されている通り、withContextの挙動は正確にはスレッド切り替えではなく「CoroutineContextを切り替える」(厳密には既存のCoroutineContextと引数で渡されたCoroutineContextをマージする)ことにあります。そのうえで、渡されたブロックをcoroutineContextで指定されているcoroutineDispatcherにて実行するという形になります。(詳細については是非T45Kさんのスライド資料を参照ください) そのため、 withContext(Dispatchers.IO) のように切り替え先のスレッド (厳密には CoroutineDispatcher ) を指定するだけでなく、 withContext(Job() + Dispatchers.Default + CoroutineName("BackgroundCoroutine")) のように、複数のCoroutineContextを合成する形で引数を指定することができるんですね。( CoroutineContextの要素についてはこちらを参照ください ) なお、 withContext 以外のコルーチンビルダー( launch や async など)についても、引数で指定されたCoroutineContextと既存のCoroutineContextをマージして用いる挙動は同じです。 今回のセッションを通じて、Kotlin coroutinesへの理解がさらに深まりました。とても良いセッションをありがとうございました! tick-tack編 まだ JUnit を使ってるの? kotest を使って快適にテストを書こう Kotest についての HowTo を熱く語っておられるセッションで Kotest への愛を感じました。最近よく名前を聞くライブラリな気がします。 タイミーでも hamcrest を採用していますが Java 向けのテストライブラリは Kotlin の予約語が使われていてエスケープしないととても見づらいことがあります。やっぱり Kotlin first に書けるのは非常に気持ちがいいですね。Kotest は Runner が JUnit で安定した環境で動かせるのもグッド。 個人的にセッション内で刺さったポイントとしては Eventually と Property Based Testing です。 Eventually 内部で非同期処理を実行するメソッドのテストを書くときに実行しても assertion のタイミングが変更前で失敗するといったケースはよくあります。そういう時に eventually を使うと一定時間評価しつづけ期待する結果に変わったら成功と見なしてループを抜けてくれます。めちゃめちゃかしこい。逆に一定時間変更がないことを評価する continually もあるそうです。 Property Based Testing 都度実行する度に自前で用意しなくても、ランダムに自動生成された property を利用して複数回テストするといったことができます。境界値テストを用意する場合に役立ちそうです。 さっそく assertion だけですが触ってみました。 記述方法だけでも inifix で name shouldBe "tick-taku" みたいに書けて最高にワクワクします。楽しくテストが書けそうですね。 触ってみていいなと思ったのが、例えばインスタンスが別だけど中の property が同じな事だけ確認したい場合はこんな感じに書けました。1つずつ取り出して equals とかしなくてもスッキリしていいですね。 data class User( val id: Long , val name: String , val age: Int ) checkAll( iterations = 3 , Arb.long(), Arb.string( 1 .. 10 , Codepoint.katakana()), Arb.int( 1 .. 100 ) ) { id, name, age -> val user = User(id = id, name = name, age = age) repository.save(user) repository.getUser() shouldBeEqualToComparingFields user } 一応あまり有用な例ではないですが上で紹介した property testing の checkAll や property のランダム生成もせっかくなので書いてみました。 個人的には Google の Truth が好きでしたが推し変しそうです。Android プロジェクトに導入するのもよさそうでした。 まとめ Kotlin Fest 2024はKotlinという言語の可能性を改めて再認識するとともに熱意と活気に満ちたイベントでした。また、普段リモートワークで働くタイミーのエンジニアにとってもチームメンバーと対面で交流する貴重な機会でした。今回得られた知見を活かして今後のプロダクト開発にもさらに力を入れていきたいと思います、次回のKotlin Festも楽しみにしています!
こんにちは、タイミーでデータアナリストをしている yuzuka です。 主にプロダクトの分析に携わっています。 ビジネス職からデータアナリストに転向して約1年経った私が、1年前の自分に教えてあげたい、BigQueryや LookerStudioに関する落とし穴を、いくつか挙げてみようと思います。 はじめに 弊社では、分析環境として BigQueryを採用しています。LookerStudioを使って、 BigQueryのデータを参照してダッシュボードを作ることもよくあります。 BigQueryの SQLを使った分析を進めていく中で、想定と異なるデータが出てきてしまい、原因を特定するのに苦労し、無駄な時間を費やしてしまった経験が何度もあります(実際には、そんな過程もきっと無駄ではないと信じたい)。 こちらのブログを読んでいただいたみなさまには、同じ苦労を味わっていただきたくないので、私が今までにハマってきた落とし穴をいくつか紹介します。 1. BigQueryで使える一部の記法は、LookerStudioでサポートされておらず、接続エラーになる BigQueryでは正常に動いていたクエリが、LookerStudioを使った途端に謎のエラーになることがあります。 これは、一部の記法が LookerStudioでサポートされていないことに起因しているようです。 私が遭遇した範囲では、以下の2つの記法でエラーになることが確認できています。 DECLARE , CREATE DECLARE , CREATE を使うと、事前に変数や関数の内容を宣言できます。 DECLARE , CREATE を含むクエリを書くと、BigQueryでは正常に動きますが、LookerStudioではエラーになります。 これを回避するには、大人しく LookerStudioのパラメータ機能 を使うなどするのが良さそうです。 QUALIFY句 QUALIFY句は WHERE句と異なり、Window関数の結果で絞り込めるという特徴があります。 基本的に、QUALIFY句を使ったクエリは、BigQueryでは正常に動きますが、LookerStudioではエラーになります。 これは QUALIFY句と WHERE句を併用することで回避できるようです(なにゆえ・・・) (参考記事: BigQuery "QUALIFY" Function is not supported by data studio? ) なので QUALIFY句を使うときは、なるべく習慣的に WHERE句をつけるようにしています。 SELECT column1 ,ROW_NUMBER()OVER(PARTITION BY xx ORDER BY yy) AS rank FROM table WHERE true -- エラー回避のためだけに追加 QUALIFY rank = 1 2. LAST_VALUEは使い方を間違えると、最後の値を返さないことがある LAST_VALUEを使っても、なぜか最後の値が返ってこないことがあります。 これは、LAST_VALUEの処理範囲がデフォルトで「RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW(最初から現在までの行)」になっているためです( 公式ドキュメント )。 つまり、以下のようなクエリを書いた場合、 SELECT LAST_VALUE(aa)OVER(PARTITION BY bb ORDER BY ymd) AS rank FROM table ① まずはymdが古い順に並び替える ② 最初から現在の行までで、ymdが最新の場所を探す → 現在の行になる ③ 現在の行のaaが返ってきてしまう ということになっているようです。 これを回避するには、処理範囲を「ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING(最初から最後の行まで)」と指定するか、以下のようにFIRST_VALUEとDESCを使う形にするのが良さそうです。 SELECT FIRST_VALUE(aa)OVER(PARTITION BY bb ORDER BY ymd DESC ) AS rank FROM table 3. 日付の表示フォーマットでYYYY を使うと、正しい西暦が返ってこないことがある LookerStudioなどの日付の表示フォーマットで、西暦の表示形式に「YYYY」を指定すると、正しい西暦が返ってこないことがあります。 これは、YYYYが単純な西暦ではなく、「その暦週の基準年」を返しているからでした。 簡単に言うと、「新年度の1月1日と同じ週に属する日については、新年度に属することにする」という考え方になっているそうです。 単純な西暦を出したい場合は、大文字の「YYYY」ではなく小文字の「yyyy」を使わなければならないようです。暦週の基準年を出したいケースはそうないと思うので、とりあえず「西暦は小文字」と覚えてしまうのが良さそうです。 もはや SQLの話ではないですが、当時こちらの答えに辿り着くまでにちょっぴり苦労しており、どうしても紹介したかったので最後にご紹介しました。 おわりに ここまで、私が経験してきた BigQuery・LookerStudio のニッチな落とし穴についてまとめてみました。 今回の記事が、少しでもみなさまの業務のお役に立てれば幸いです。 (「それはニッチな落とし穴でもなんでもないよ」「他にもこんなのがあるよ」など、ご意見ご感想ありましたら、当ブログや X などでコメントいただけますと幸いです) 分析の正確性を担保するためには、このような落とし穴を知っておくことも大事ですが、実際には、これらを理解したところで、毎回1つもミスをせず、一発で正しいクエリを書きあげることは難しいのではないかと思います。 常に自分の書いたクエリを疑いつつ、実際のデータを見て検証したり、別の指標と比較して違和感がないか確かめたり、必要に応じて他の人にクエリのレビューをお願いしたり、といった工夫の方が、個人的には大事なのかなと思っています。 We’re Hiring! タイミーでは、一緒に働くメンバーを募集しています。 https://hrmos.co/pages/timee/jobs カジュアル面談も実施していますので、少しでも興味を持っていただけましたら気軽にお申し込みください! 個人的にもアナリストやデータ関連職の方と繋がりたいと思っているので、よければ X のフォローもよろしくお願いします。
株式会社タイミーのkatsumiです! dbtのバージョン1.8以上を利用することで、unit testsが利用可能になります。今までもSingular テスト(単一テスト)やGeneric テスト(汎用テスト)は可能でしたが、テストデータを利用した単体テストも行うことができます。 導入準備 dbt-coreの場合 dbt v1.8 以上を利用してください。 dbt-cloudの場合 2024/06/12時点では dbt「Keep on latest version」を選択することで利用できます。 弊社ではunit-test用の環境のみlatest versionを利用しています。 Unit Testの基本 # run data and unit tests dbt test # run only data tests dbt test -- select test_type : data # run only unit tests dbt test -- select test_type : unit # run tests for one_specific_model dbt test -- select "one_specific_model" # run data tests limited to one_specific_model dbt test -- select "one_specific_model,test_type:data" # run unit tests limited to one_specific_model dbt test -- select "one_specific_model,test_type:unit" unit-testに関係する新しいコマンドが追加されました。このコマンドは、以前のデータテストで使用していたselect機能と同様に、特定のテストケースを選択して実行することができます。 ymlによるテストレコードの書き方 - name: test_name description : "テストの説明" model : my_model given : - input: ref ( 'users' ) rows : - { id : 1 , user_email : example@example . com } expect : rows : - { id : 1 , domain : example . com } name: test_name これはテストの名前です。この名前はテストケースを識別するために使用します。 description: “テストの説明” これはテストケースの説明です。この説明には、テストが何を意図しているのか、テストの目的や背景について記載します。 model: my_model これはテスト対象となるモデルの名前です。ここでは「my_model」がテスト対象のモデルとして指定されています。 given データの内容です。ここでは「id: 1」で「user_email」「example@example.com」のユーザーを指定しています。このデータがテストの入力として使用されます。 expect これは期待される結果を指定します。テストが成功するためには、モデルが「id: 1」のユーザーに対して「domain」が「example.com」として返される必要があります。期待される結果と実際の結果が一致するかどうかを検証します。 ファイルによるテストレコードの書き方 unit_tests : - name: test_my_model model : my_model given : - input: ref ( 'users' ) format : csv fixture : users プロジェクトのtests/fixturesディレクトリにあるCSVファイル名を指定することで利用できます。test-pathsオプションを使用することで、ディレクトリ構成を柔軟に指定することもできます。 未定義のカラムの挙動 未入力のカラムに関しては、safe_cast(null as INT64)のように型が定義されたnullのデータで補完されます。リレーションが必要なものや、ロジックに影響を与えるカラムの記入が必要になります。 実施における知見 大規模なクエリは”ephemeral”で細かいテスト行う。 with句が複数ありテストケースが複雑で見通しが悪くなるケースがあります。弊社ではSQLのテスト単位のロジックを”ephemeral”で分けて個別のmodelにてテストを書く実装を試しています。 通常のモデルと同じ書き方でテストを実施することが可能です。 WITH 処理1_cte AS ( SELECT * FROM {{ ref( ' 処理1のephemeral ' ) }} ) , 処理2_cte AS ( SELECT * FROM {{ ref( ' 処理2のephemeral ' ) }} ) , 処理3_cte AS ( SELECT * FROM {{ ref( ' 処理3のephemeral ' ) }} ) 時系列系の時間の停止をマクロで行う。 テストしたいケースにはcurrent_datetimeなど現在の時刻を利用するものがあります。その場合、テストを書く際に時間を固定する必要があります。 dbtのユニットテストでは、YAMLファイル上でdbtのマクロを置き換える機能があります。この機能を利用して、時間を固定する実装を行っています。 - name: test_case model: my_model overrides: macros: current_datetime_jst: "date('2024-01-01')" {{ config ( materialized = 'ephemeral' ) }} SELECT --  ここにロジックを書く FROM {{ ref ( 'users' ) }} AS users WHERE DATETIME_TRUNC ( created_at , MONTH ) = DATE_TRUNC ({ { current_datetime_jst () } } , MONTH ) Testに関するSQLの確認ができる。 実際の仕組みとしてはテスト用のSQLが生成され、フィクスチャ(テストデータ)も含めたSQLが実行されます。debugコマンドやコンパイルされたSQLを確認することで、テストの挙動をチェックできます。 テストケースの問題が起きた時にSQLにて要因分析を行いました。 まとめ 重要指標の計算や複雑な時系列処理、プロダクトのロジックを再現する箇所では、テストケースを用意していこうと考えています。またテストケースを先に定義したのちにクエリを書くことも簡単にできるようになったように感じます。信頼性の高いモデルにするために、重要な機能になっていきそうです。 以上、unit-testsを試した時に得られた知見のまとめでした。この情報が役立てば幸いです! We’re Hired タイミーでは、一緒に働くメンバーを募集しています!! product-recruit.timee.co.jp 参考資料 Unit tests | dbt Developer Hub https://docs.getdbt.com/docs/build/unit-tests Unit Testing https://github.com/dbt-labs/dbt-core/discussions/8275
はじめに dbt snapshotとは(ざっくり) 今回の例 全体の流れ snapshot内部処理の詳細 delete処理:宛先テーブルに存在するレコードがソーステーブルでdeleteされていた場合 update処理:宛先テーブルと比較してソーステーブルのレコードがupdateされていた場合 insert処理:宛先テーブルに無いレコードがソーステーブル側に新規で作成されていた場合 check戦略の場合 check戦略の詳細 まとめ We’re Hired はじめに こんにちは☀️okodooooonです 最近、社内のdbt snapshotモデルでパフォーマンスの問題が発生し、その解決に苦労しました。dbt snapshotの内部処理が公式ドキュメントなどで提示されておらず、詳細なクエリを理解していなかったためです。 そこで、今回、dbt snapshotの内部クエリについて解説してみることにしました。ただし、今回の解説内容は、ドキュメントで説明されている通りの挙動がどのようにSQLで表現されているのか確認したもので、新しい発見やTipsみたいなものは特にないです! 内部処理をしっかり理解することで、dbtによって抽象化された処理をより効果的に活用できることもあるかな〜と思っておりますので、どなたかの参考になれば幸いです! (今回解説するクエリは、dbt-bigqueryで生成されるクエリです) dbt snapshotとは(ざっくり) SCD Type2 Dimensionという思想に従って、過去時点の状態の遷移を蓄積できるような仕組みです。 ソースシステム側ではステータス変更が行われると、そのナチュラルキーのレコードが上書き処理されますが、その上書き処理前後のレコードをそれぞれ有効期限付きで保存します 公式Doc: https://docs.getdbt.com/docs/build/snapshots 今回の例 以下のようなモデルを仮定して、snapshotのクエリを見ていきたいと思います。 モデルファイル上の定義はこんな感じです。 {% snapshot snapshotted_sample_table %} {{ config( target_schema= ' sample_dataset ' , strategy= ' timestamp ' , unique_key= ' id ' , updated_at= ' updated_at ' , invalidate_hard_deletes= True , ) }} select * from {{ source( ' sample_dataset ' , ' sample_data ' ) }} {% endsnapshot %} ソーステーブル側で一意であるカラムをunique_key, レコード更新日時を記録するカラムをupdated_atに指定しています。 左のテーブルがsnapshot化されることで、右のように有効期限(dbt_valid_from, dbt_valid_to)とsnapshot後のレコードに対するユニークキー(dbt_scd_id)が付与されます 全体の流れ dbt snapshotはBigQueryにおいて2つのクエリを実行しています。 ソーステーブルと宛先テーブルからデータを抽出して、snapshot先にmergeするためのtmpテーブルを、update,delete,insertそれぞれの処理ごとに分割して作成する処理 tmpテーブルでラベリングされた処理ごとにMERGEクエリを実行する処理 それぞれ実行されるクエリの詳細は以下のようになります。 tmpテーブル作成クエリ全文  (クリックで展開) ```sql create or replace table `sample_project`.`sample_dataset`.`sample_table__dbt_tmp` OPTIONS( description="""""", expiration_timestamp=TIMESTAMP_ADD(CURRENT_TIMESTAMP(), INTERVAL 12 hour) ) as ( with snapshot_query as ( SELECT * FROM `sample_project`.`sample_dataset`.`sample_table` ), snapshotted_data as ( select *, id as dbt_unique_key from `sample_project`.`sample_dataset`.`snapshotted_sample_table` where dbt_valid_to is null ), insertions_source_data as ( select *, id as dbt_unique_key, updated_at as dbt_updated_at, updated_at as dbt_valid_from, nullif(updated_at, updated_at) as dbt_valid_to, to_hex(md5(concat(coalesce(cast(id as string), ''), '|',coalesce(cast(updated_at as string), '')))) as dbt_scd_id from snapshot_query ), updates_source_data as ( select *, id as dbt_unique_key, updated_at as dbt_updated_at, updated_at as dbt_valid_from, updated_at as dbt_valid_to from snapshot_query ), deletes_source_data as ( select *, id as dbt_unique_key from snapshot_query ), insertions as ( select 'insert' as dbt_change_type, source_data.* from insertions_source_data as source_data left outer join snapshotted_data on snapshotted_data.dbt_unique_key = source_data.dbt_unique_key where snapshotted_data.dbt_unique_key is null or ( snapshotted_data.dbt_unique_key is not null and ( (snapshotted_data.dbt_valid_from < source_data.updated_at) ) ) ), updates as ( select 'update' as dbt_change_type, source_data.*, snapshotted_data.dbt_scd_id from updates_source_data as source_data join snapshotted_data on snapshotted_data.dbt_unique_key = source_data.dbt_unique_key where ( (snapshotted_data.dbt_valid_from < source_data.updated_at) ) ), deletes as ( select 'delete' as dbt_change_type, source_data.*, current_timestamp() as dbt_valid_from, current_timestamp() as dbt_updated_at, current_timestamp() as dbt_valid_to, snapshotted_data.dbt_scd_id from snapshotted_data left join deletes_source_data as source_data on snapshotted_data.dbt_unique_key = source_data.dbt_unique_key where source_data.dbt_unique_key is null ) select * from insertions union all select * from updates union all select * from deletes ); ``` merge実行クエリ全文  (クリックで展開) ```sql merge into `sample-project`.`sample_dataset`.`sample_table` as DBT_INTERNAL_DEST using `sample-project`.`sample_dataset`.`sample_table__dbt_tmp` as DBT_INTERNAL_SOURCE on DBT_INTERNAL_SOURCE.dbt_scd_id = DBT_INTERNAL_DEST.dbt_scd_id when matched and DBT_INTERNAL_DEST.dbt_valid_to is null and DBT_INTERNAL_SOURCE.dbt_change_type in ('update', 'delete') then update set dbt_valid_to = DBT_INTERNAL_SOURCE.dbt_valid_to when not matched and DBT_INTERNAL_SOURCE.dbt_change_type = 'insert' then insert (`id`, `foo`, `bar`, `created_at`, `updated_at`, `dbt_updated_at`, `dbt_valid_from`, `dbt_valid_to`, `dbt_scd_id`) values (`id`, `foo`, `bar`, `created_at`, `updated_at`, `dbt_updated_at`, `dbt_valid_from`, `dbt_valid_to`, `dbt_scd_id`) ``` 上記クエリ内の各CTEで行われる処理をざっくりまとめると以下のような処理のフローになります。 処理の詳細を詳しく見ていきたいのですが、クエリ自体がちょっと長いので、insert, update, deleteそれぞれの処理に分割して詳細を見ていこうと思います! snapshot内部処理の詳細 delete処理:宛先テーブルに存在するレコードがソーステーブルでdeleteされていた場合 tmpテーブル生成クエリのうち、ソース側でdeleteされたレコードをmerge用レコードに変換する処理の抜粋 (クリックで展開) -- 宛先履歴テーブルから履歴が確定していないレコードを抽出 snapshotted_data as ( select *, -- unique_keyに指定したカラムをdbt_unique_keyとする id as dbt_unique_key from {{ 宛先テーブル }} where dbt_valid_to is null ), deletes_source_data as ( select *, -- unique_keyに指定したカラムをdbt_unique_keyとする id as dbt_unique_key from {{ ソーステーブル }} ) deletes as ( select ' delete ' as dbt_change_type, source_data.*,                  current_timestamp ()  as dbt_valid_from,                  current_timestamp ()  as dbt_updated_at,                  current_timestamp ()  as dbt_valid_to, snapshotted_data.dbt_scd_id from snapshotted_data left join deletes_source_data as source_data on snapshotted_data.dbt_unique_key = source_data.dbt_unique_key where source_data.dbt_unique_key is null ) tmpテーブル生成の処理の内訳は以下のようになります。 【処理の概要】 - 履歴が確定していない(valid_toに値が入っていない)レコード群を宛先テーブルから抽出 - 履歴が確定していないレコードのうち、ソーステーブルに存在しない(削除された)レコードに絞り込み - dbt_valid_from, dbt_valid_toをクエリの実行時刻に設定 - dbt_change_typeを’delete’に設定 ソーステーブル側で削除されたmerge用レコードをmergeするクエリ (クリックで展開) merge into {{宛先テーブル}} using {{マージ用tmpテーブル}} on {{宛先テーブル}}.dbt_scd_id = {{マージ用tmpテーブル}}.dbt_scd_id when matched and {{宛先テーブル}}.dbt_valid_to is null and {{マージ用tmpテーブル}}.dbt_change_type in ( ' delete ' ) then update set dbt_valid_to = DBT_INTERNAL_SOURCE.dbt_valid_to 【処理の概要】 - dbt_scd_idをキーにして宛先テーブルとマージ用tmpテーブルを結合 - 宛先テーブルの履歴が未確定で、tmpテーブルのdbt_change_typeが’delete’の場合 - 宛先テーブルのdbt_valid_toをtmpテーブルのdbt_valid_to(クエリ実行時刻)に上書き 以下図に表したような処理の流れによって、ソーステーブル側で削除されたレコードdbt_valid_toにsnapshot時の時刻が入るようになります。 update処理:宛先テーブルと比較してソーステーブルのレコードがupdateされていた場合 tmpテーブル生成クエリのうち、ソース側でupdateされたレコードをmerge用レコードに変換する処理の抜粋 (クリックで展開) -- 宛先履歴テーブルから履歴が確定していないレコードを抽出 snapshotted_data as ( select *, -- unique_keyに指定したカラムをdbt_unique_keyとする id as dbt_unique_key from {{ 宛先テーブル }} where dbt_valid_to is null ), updates_source_data as ( select *, -- unique_keyに指定したカラムをdbt_unique_keyとする id as dbt_unique_key, updated_at as dbt_updated_at, updated_at as dbt_valid_from, updated_at as dbt_valid_to from {{ ソーステーブル }} ), updates as ( select ' update ' as dbt_change_type, source_data.*, snapshotted_data.dbt_scd_id from updates_source_data as source_data join snapshotted_data on snapshotted_data.dbt_unique_key = source_data.dbt_unique_key where ( (snapshotted_data.dbt_valid_from < source_data.updated_at) ) ) 【処理の概要】 - 履歴が確定していないレコード群を宛先テーブルから抽出 - ソーステーブルから抽出したレコードのdbt_valid_from, dbt_valid_toを現在時刻に設定 - 履歴が確定していないレコードのうち、宛先のdbt_valid_fromより後にupdated_atがソーステーブルに存在するレコードに絞る - dbt_change_typeを’update’に設定 ソーステーブル側でupdateされたmerge用レコードをmergeするクエリ (クリックで展開) merge into {{宛先テーブル}} using {{マージ用tmpテーブル}} on {{宛先テーブル}}.dbt_scd_id = {{マージ用tmpテーブル}}.dbt_scd_id when matched and {{宛先テーブル}}.dbt_valid_to is null and {{マージ用tmpテーブル}}.dbt_change_type in ( ' update ' ) then update set dbt_valid_to = DBT_INTERNAL_SOURCE.dbt_valid_to 【処理の概要】 - dbt_scd_idをキーにして宛先テーブルとマージ用tmpテーブルを結合 - 宛先テーブルの履歴が未確定で、tmpテーブルのdbt_change_typeが’update’の場合 - 宛先テーブルのdbt_valid_toをtmpテーブルのdbt_valid_to(現在時刻)に上書き 以下図に表したような処理の流れによって、宛先テーブルの履歴が未確定のデータのうち、ソースで更新が走ったレコードのdbt_valid_toにスナップショット時の日時が入ります。 insert処理:宛先テーブルに無いレコードがソーステーブル側に新規で作成されていた場合 tmpテーブル生成クエリのうち、insert対象のレコードをmerge用レコードに変換する処理の抜粋 (クリックで展開) -- 宛先履歴テーブルから履歴が確定していないレコードを抽出 snapshotted_data as ( select *, -- unique_keyに指定したカラムをdbt_unique_keyとする id as dbt_unique_key from {{ 宛先テーブル }} where dbt_valid_to is null ), insertions_source_data as ( select *, id as dbt_unique_key, updated_at as dbt_updated_at, updated_at as dbt_valid_from, nullif (updated_at, updated_at) as dbt_valid_to, to_hex(md5( concat ( coalesce ( cast (id as string), '' ), ' | ' , coalesce ( cast (updated_at as string), '' )))) as dbt_scd_id from {{ ソーステーブル }} ), insertions as ( select ' insert ' as dbt_change_type, source_data.* from insertions_source_data as source_data left outer join snapshotted_data on snapshotted_data.dbt_unique_key = source_data.dbt_unique_key where snapshotted_data.dbt_unique_key is null or ( snapshotted_data.dbt_unique_key is not null and ( (snapshotted_data.dbt_valid_from < source_data.updated_at) ) ) ), 【処理の概要】 - ソーステーブルのunique_keyにしていたカラムとupdated_atに指定していたカラムを組み合わせてsurrogate_keyを生成 - ソーステーブルに対して履歴未確定の宛先テーブルをLEFT JOINして以下の条件に絞る - 宛先テーブルに指定したunique_keyが存在しないが、ソーステーブルには存在するレコード - 宛先テーブルに指定したunique_keyのレコードが存在して、ソーステーブル側のupdated_atが宛先テーブルのvalid_fromよりも後のレコード - dbt_change_typeを’insert’に設定 ソーステーブル側でinsertされたmerge用レコードをmergeするクエリ (クリックで展開) merge into {{宛先テーブル}} using {{マージ用tmpテーブル}} on {{宛先テーブル}}.dbt_scd_id = {{マージ用tmpテーブル}}.dbt_scd_id when not matched and {{マージ用tmpテーブル}}.dbt_change_type = ' insert ' then insert (`id`, `foo`, `bar`, `created_at`, `updated_at`, `dbt_updated_at`, `dbt_valid_from`, `dbt_valid_to`, `dbt_scd_id`) values (`id`, `foo`, `bar`, `created_at`, `updated_at`, `dbt_updated_at`, `dbt_valid_from`, `dbt_valid_to`, `dbt_scd_id`) 【処理の概要】 - dbt_scd_idをキーにして宛先テーブルとマージ用tmpテーブルを結合 - dbt_scd_idがマッチしなくて、dbt_change_type=’update’の場合にinsert処理を実行 - 宛先テーブルのdbt_valid_toをtmpテーブルのdbt_valid_to(現在時刻)に上書き 以下図に表したような処理の流れによって、指定したユニークキーが宛先に存在しないか、履歴が未確定のレコードのうちソース側で前回実行からupdateが走ったものがinsertされます。 check戦略の場合 上で紹介したのは snapshot_strategy=timestamp の場合のスナップショットの挙動であり、ソーステーブル側で updated_at に指定したカラムが更新された場合に、すべてのプロパティの情報を履歴的に保持するものです。 dbtにはもう一つのスナップショット戦略として、 check 戦略があります。 {% snapshot snapshotted_sample_table %} {{ config( target_schema= ' sample_dataset ' , strategy= ' check ' , unique_key= ' id ' , invalidate_hard_deletes= True , check_cols=[ ' foo ' , ' bar ' , ' created_at ' , ' updated_at ' , ], ) }} select * from {{ source( ' sample_dataset ' , ' sample_data ' ) }} {% endsnapshot %} このモデルでは全カラムを選択していますが、特定のカラムの変更のみを履歴的にトラッキングする仕組みです。 strategy=check においても、 strategy=timestamp の時と同様に、snapshot処理はtmpテーブルを作成するクエリとmerge処理を実行するクエリに分割されます。 strategy=checkの場合のtmpテーブル作成クエリ (クリックで展開) strategy=checkの場合のmerge実行クエリ (クリックで展開) merge実行クエリはstrategy=timestampの時と変わらず、tmpテーブルの生成方法が異なっているので、詳しく見ていこうと思います check戦略の詳細 insert 用データや update 用データを出力するCTEでは、以下のようなWHERE条件が使用されます。 (( snapshotted_data.`foo` != source_data.`foo` or ( ((snapshotted_data.`foo` is null ) and not (source_data.`foo` is null )) or (( not snapshotted_data.`foo` is null ) and (source_data.`foo` is null )) ) or snapshotted_data.`bar` != source_data.`bar` or ( ((snapshotted_data.`bar` is null ) and not (source_data.`bar` is null )) or (( not snapshotted_data.`bar` is null ) and (source_data.`bar` is null )) ) or snapshotted_data.`created_at` != source_data.`created_at` or ( ((snapshotted_data.`created_at` is null ) and not (source_data.`created_at` is null )) or (( not snapshotted_data.`created_at` is null ) and (source_data.`created_at` is null )) ) or snapshotted_data.`updated_at` != source_data.`updated_at` or ( ((snapshotted_data.`updated_at` is null ) and not (source_data.`updated_at` is null )) or (( not snapshotted_data.`updated_at` is null ) and (source_data.`updated_at` is null )) ) )) この条件により、insert と update の対象となるレコードの抽出条件は次のようになります。 insert用データの抽出条件 ( 宛先にユニークキーが存在しない ) OR ( (宛先にユニークキーが存在する) AND (ユニークキー以外のcheck_colsに指定したカラムが、宛先とソースで何かしら変化が発生している) ) update用データの抽出条件 (宛先にユニークキーが存在する) AND (ユニークキー以外のcheck_colsに指定したカラムが、宛先とソースで何かしら変化が発生している) checkで指定されたカラムの変更をどのように追跡しているかを確認できました。 まとめ 今回はdbt snapshotの内部処理をdelete, update, insertの処理に分解して説明してみました。 公式ドキュメントで説明されている通りの処理が生成されるSQLによって行われていることが確認できました。 dbt snapshotを使用している際に、期待した挙動が得られない場合や何かしらエラーが発生したときに、この情報が役立てば幸いです! We’re Hired タイミーでは、一緒に働くメンバーを募集しています!! product-recruit.timee.co.jp
タイミーの yorimitsu です。 世界中で開催されているDevOpsDaysカンファレンスは、ソフトウェア開発、ITインフラ運用を中心としたカンファレンスで、2024/4/16、17の2日間にわたって開催されました。 www.devopsdaystokyo.org 今回の参加はタイミーのプロダクトおよびエンジニア向けに用意している、技術系カンファレンスに無制限で参加できる「Kaigi Pass」という制度を利用しています。この制度は世界中で開催されるカンファレンスを対象にしています。 productpr.timee.co.jp 価値貢献を意識したチームの作り方 タイミーでも顧客に価値を届けることを大切に日々の開発運用を行っていますが、それを担うチームをより良くする取り組みのノウハウが、「 Value-Driven DevOps Team〜価値貢献を大切にするチームがたどり着いたDevOpsベストプラクティス〜 」のセッションで紹介されていました。 仮説検証を早く回すための開発環境の工夫や、チームのカルチャーの作り方はとても参考になる話でした。特にチームにメンバーが増えた際に「チームの状態を理解して各メンバーのやりたいことを共有して、チームの型に落とし込んでいくか」という部分は参考にしたい考えが多くありました。 開発チームに限らず、チームを組成すると何を目的としているのか、何の価値を提供するのかなど チームに属しているメンバーの認識をある程度合わせる必要があり、その際にインセプションデッキを利用したり、ワーキングアグリーメントを作成するのは、改めて有効だなと感じました。 How先行ではなくWhyを意識しなくてはいけない 「 君もテスト自動化の同志を増やすパターンで大勝利! 」のセッションでは、SETチームを立ち上げる際に経験した問題について発表されていました。SETチームなので当然ながらテスト自動化を 推進すべく、初手は自動E2Eテストに力を入れて取り組まれていたそうですが、その取組みはSETがやりたいことであり、開発チームがやりたいことではなかったので、テスト自動化の推進が止まってしまったとのこと。 そして推進方法を見直す中で、自動E2Eテストを推進するというHowが選考して、何のために実施するのか、ニーズが有るのかという部分が欠けていたことに気が付き、改めて開発チームの困り事を把握してから、取り組んだらテスト自動化の取り組みが進んだとのことでした。 タイミーでも各スクラムチームの困り事を把握して、品質管理に関する支援に取り組まなくてはいけないと学んだセッションでした。 おわりに 今回はオンラインでの参加になりましたが、来年は現地で参加して積極的な情報交換を行ってみたいと思いました。
こんにちは!タイミーのデータアナリストの @akki です。 タイミーのデータアナリティクス部では、様々な形式の勉強会が盛んに行われています。 アナリスト自身のスキルアップはもちろん、チーム全体での知識の共有や実務への応用を目指し、常に3〜4つの勉強会が開催され、有志が参加する形式をとっています。 今回は『 効果検証入門〜正しい比較のための因果推論/計量経済学の基礎 』(安井 翔太)を題材にした輪読と実践の勉強会についてお話しします。 この本は 以前も勉強会で取り上げ ましたが、今回は新しいメンバーとともに再度実施しました。 輪読パート 輪読パートでは、本書を10に分けて担当を設定しました。毎週、各自が自分の担当分を要約し、Notionに記載して発表します。その後、参加者からの質疑応答や、本書の内容に基づいたディスカッションを行いました。 今回の勉強会での分担イメージ 感想: すでに知っていた手法についても、改めて理論的な背景や実践する上での注意事項を学ぶことができ、理解が深まりました。 同じチームで働いているメンバーとの勉強会なので、議論の中で「実務で使うためには」という観点の話が出やすいのもよかったです。 実践パート 輪読を終えた後、本書で触れられた効果検証の手法をタイミーのデータに実際に適用してみたので、その要点をお伝えします。 やったこと: タイミーの営業領域の施策で、施策を実施した効果について検証しました。 分析手法: DiD(Difference in Differences)を選択しました。 勉強会の題材ということもあり、事前に検証設計に入ることができなかったため、RCT等は実施できませんでした。 ただ介入群・非介入群ともに介入前後のデータが取得できたため、DiDを採用しました。 DiDによる効果検証のイメージ 結果: 施策による改善効果が具体的に把握できました。 またいろんな分析軸とクロスしてみることで追加の知見も得られ、今後の意思決定につながりました。 今回の分析の課題: 特にDiDについては、「何をもって平行トレンドとするか」が悩ましいポイントだなと改めて感じました。 スタートアップのような変化が激しい状況では、常に平行トレンドを満たしている対照群が設定しにくく、期間の切り方次第で平行トレンドを満たす対照群が変わることもあります。 過去の目的変数の変化だけを見ても、本来満たすべき「時間を通じた目的変数の変化が同一である」という仮定が十分に満たせていないケースも多いです。 定量面だけでなく、実験群や対照群の特性や、営業活動のオペレーションといった定性情報も把握した上で、対照群を決定することが重要であると感じました。 対照群の選定イメージ。目的変数だけ見ると、時期次第で平行トレンドの認識が変わりうる。 まとめ 今回の勉強会を通じて、手法を正しく理解することでより精緻な検証ができ、それが意思決定につながることを改めて実感できました。 これからも座学と実践のサイクルを回すことで、より会社に貢献できるデータ分析をしていきたいと思います! We’re Hiring 私たちは、ともに働くメンバーを募集しています!! カジュアル面談 も行っていますので、少しでも興味がありましたら、気軽にご連絡ください!
タイミーのyajiri、yorimitsu、seigiです。 アジャイルとテストのコミュニティの祭典に関する国内最大級のカンファレンス「Scrum Fest Niigata(スクフェス新潟)2024」が2024/05/10、11の2日間にわたって開催されました。 www.scrumfestniigata.org タイミーからもQAコーチ、マネージャー、スクラムマスターの3名が参加。世界中で開催されるすべての技術系カンファレンスに無制限で参加できる「Kaigi Pass」という制度を利用しました。 productpr.timee.co.jp 本レポートでは、印象に残ったセッションの内容を中心に、2日間の会の様子をお伝えします。 タイミーのQAコンセプトに役立つノウハウを得られた話 Yorimitsuです。私が参考になったセッションについてお話させてください。 タイミーではQAの仕組みを現在構築している最中で、コンセプトとしてQAの活動を2つに分類してプロダクト組織に導入しようと考えています。コンセプトの1つ目は、QA Enablingとしており 各スクラムチームが行っている品質管理活動の支援、例えばテスト観点の作成支援や、テスト設計の手法支援、自動テストの支援などを考えています。そして2つ目のコンセプトはQA Platformとして、品質分析、自動テストのインフラ整備、スクラムチームを横断するリグレッションテストの運営(自動テスト)などを計画しています。 このコンセプトを実現するにあたり、今回の発表セッションにあった「 スクラムチームが一体になるために行ったQAプロセス変革の道のり 」は大変参考になる内容でした。 各スクラムチームへのQAの入り方や、その後に起こる課題、そして課題の整理方法など、実例が 多く紹介されていました。 そして改めて、QAマニフェストの必要性にも注目しました。QAが何を担うのか、QAを担当する人及び、QAに何かを期待する人にとって、同じ方向性を向いて会話するために非常に有効な取り組みだと受け止め、タイミーでの策定を検討してみよう思いました。 ベイビーステップで不確実性を乗り越えるQAエンジニアの挑戦に感銘を受けた話 駆け出しQAコーチのYajiriです。 私はこの春にCSM研修を受講し、晴れてスクラムマスターに認定されたこともあり、初めてスクラムフェスに参加しました。 現在の役割がQAコーチということもあり、スクラムチームにおける品質保証に関するプログラムを中心に視聴しました。どれも有意義なものでしたが、特に印象に残ったセッションは「 受け入れテスト駆動開発によって不確実性を段階的に解消するアプローチ 」です。 今回紹介された事例は、ビジネスや経済のインテリジェンスを軸にサービスを展開するWebシステムの開発チームで、XP(エクストリーム・プログラミング)開発プロセスの中でATDD(受け入れテスト駆動開発)をどのように実践しているかが紹介されました。 Web開発で厳格にXPを取り入れるには様々な困難がある中、「ベイビーステップ(よちよち歩き)」を徹底することで、不確実性をコントロール可能な粒度に落とし込み、結果的に不確実性を低減させながら大きな成果を成し遂げるというものでした。 このベイビーステップは、Gaugeで実装される受け入れテストのコードでも徹底されており、一つのステップに対して多くの期待結果を盛り込むことを「ベイビーステップ違反」として統制するカルチャーが根付いているとのことでした。 これらの取り組みは、開発手法が変わっても不確実性をコントロールする手法として非常に参考になり、私たちの自動テストでもこの考え方を取り入れていきたいと感じました。 スクラムフェスはやっぱり楽しい!そしてオフラインで参加したい!! スクラムマスターの正義です! アジャイル/スクラムに関するコミュニティ活動が好きで、いくつかのカンファレンスに参加したり、自身でもスクラムフェス神奈川を運営したりしています。 今回、スクラムフェス新潟へオンラインで参加しました。 (家庭の都合でどうしても現地参加できず…泣) スクフェス新潟では現地参加した時の体験が素晴らしいらしく、是非とも次回は現地で参加したいと思います!ネットワーキングパーティでは、新潟ならではのご飯やお酒を堪能できるんだとか・・・! 今回、私はスクラムフェス新潟というアジャイルコミュニティイベントで「素敵だなー!」と思った点についてレポートさせていただきます! スクフェス新潟の趣旨について 主催者のじゅんぺーさんによる、イベントの趣旨が説明されていました まだまだ、アジャイルのコミュニティとテストのコミュニティはそれぞれが別のジャンルとして開催されていますが、スクラムフェス新潟ではその2つを合わせて開催する流れを作ることが目指されていました! テストとアジャイルに対して熱い想いがあるからこそのビジョンだと思います! 行動規範を徹底したイベント イベントを最高の形で終えるためには、参加者の行動がとても大切になります。 特に、ギャザリングを大切にするアジャイル/スクラムのコミュニティイベントでは、特に意識したい事項となります。 どのようなことを自分たちは大切にしているのか、具体的な楽しみ方、困った時にどうすれば良いのかについて、しっかり時間をとって説明されていました。 (運営の方によるハラスメントの寸劇までありました!) 今後、イベントを開催して業界を盛り上げていく人たち、コミュニティ、企業は是非とも参考にしたい点だと思います。 コミュニティイベントの楽しみ方を最初に紹介! Keynoteの前に、菩薩さんによる最初のセッションがありました! セッション名は「いかにしてオンラインで知り合いを増やすか」 現地に参加されている方だけではなく、オンラインに参加されている方もギャザリングで楽しめるように、どういうことを考えておくと良いかを経験を交えて紹介されていました。 視聴しつつチャットを楽しむコツとして、いくつかピックアップすると… わからないことをはわからない、と正直に言おう! テクニックとしては…「つまり〇〇….ってことコト!?」と言えば、なんとかなる チャットの流れが早くてついていけなくても、気にせず喋ろう! 勇気を出して発言しても、誰も反応しない…でもそんなこと気にしなくて良い!とにかく接触回数を増やしていこう! とのことでした! 今まで幾つかのスクラムフェスに参加してきた中で、個人的に気になっていたことでしたがあらためて気にしなくていいんだ!という安心感をえられました。 また、相手の発言に対して、リアクションをつけたり、反応をしてみるだけで一緒にワイワイできていいよね!という話は下記スライドでクスッときましたw 今までは「さしすせそ」を使ってきましたが、今後は「はひふへほ」も活用しようと思います! 最後に スクラムフェス新潟は、セッションで得られる情報だけに価値を置くわけではなく、オンライン/オフラインでのギャザリングや、パーティで素敵な料理を食べられるなどセッション以外でのコンテンツでも参加者が得られる体験がとても素敵なイベントだと感じました! 次回は…絶対に新潟の現地会場に行きたいです…! 一番印象的だった言葉は…「新潟のお酒は水」でした! 以上ですmm おわりに 次回以降はなんらかの形でコミュニティを盛り上げることに貢献できたらと思います。
はじめに はじめまして、タイミーでモバイルアプリエンジニアをやっている tick-taku です。 5/15 - 5/17 の三日間にわたって沖縄で RubyKaigi 2024 が開催されました。全国から Rubyist が集結するイベントで弊社からもたくさんのメンバーが参加しており、自分も初めて参加してきました。 今回はそんな RubyKaigi に参加して感じたことや気になったポイントを紹介します。 rubykaigi.org タイミーメンバーの参加レポートはこちら。 みなさん各セッションを解像度深く解説されていてとても勉強になりました。 tech.timee.co.jp tech.timee.co.jp tech.timee.co.jp なぜ参加しようと思ったか 冒頭で自己紹介した通り、僕はモバイルアプリエンジニアで普段は Android や iOS アプリ開発がメインです。 そんな自分がなぜ RubyKaigi に参加しようと思ったか。それはこれから Ruby (rails) の開発ができるようになりたいと考えているからです。 そこで、まずは言語やコミュニティの雰囲気を掴むため Ruby の中で大規模なカンファレンスに参加してみようと思ったのがきっかけでした。 タイミーでは開発組織においてチームトポロジーをベースとしたストリームアラインドチームを運営しています。 その中で僕が所属しているチームはクライアント様に向けた機能開発を目的としており、クライアント様が使う管理画面の改善などが多いです。そのため、稀にワーカー様向けであるモバイルアプリの開発タスクが希薄になることがあります。 逆にバックエンドタスクがまだまだ手が足りていないので、モバイルにクローズせずにケイパビリティを発揮していきたいと考えました。 また僕は専門領域特化型ではないと昔から感じているため、全体を満遍なくできるようになることでゴール達成のためにどこか人が足りていない部分を補う動き方をしていきたいと考えています。 チームの方向性ともマッチしているためバックエンドで採用している Ruby を勉強していこうと思いました。 後は沖縄で開催と言うのもかなり魅力でした。沖縄ですよ、沖縄。こんなに聞くだけで胸躍るキーワードなかなかありません。キラキラドキドキですね。 RubyKaigi は毎回日本各地を転々と開催しており、毎年同じチームの Rubyist がワクワクしていたのを羨ましく感じていました。 参加してみて 「 楽しかった 」 これに尽きると思います。 コミュニティの交流であったり「こういう事を考えて言語をよりよくアップデートしている」といった事が聞けたり、普段の関わりから遠い話がたくさん自分事として聞けたことがとても楽しかったです。 知識的な話 RubyKaigi に関しては参加理由にも少し言及していますが Ruby に慣れる事を目的として参加しました。 と言うのも、Ruby を使って開発している人たちによる「どういう課題をどう解決したか」と言った話が聞けると Ruby をより身近に感じられ、モチベーションに繋がるかなと思ったからです。 モバイル系のカンファレンスで言うと DroidKaigi や iOSDC みたいなものを想定していました。 ですが、実際に RubyKaigi に参加してみるともっと低レイヤーの、Ruby の中はこう動いているだったりコンパイラの話などばかりでした。正直何言ってるか分からないことだらけでしたが、セッションの端々から Namespace や RBS など気になる単語が聞こえてきて知的探求心が刺激されました。 アスキーアートもあまり馴染みがなく新鮮で面白かったです。ゲームを動かしてみたりとスピーカーの Ruby が愛が伝わってきました。 以下に気になったワードを列挙します。 Namespace 今回のセッションの中で一番興味を持ったテーマがこの Namespace について です。 Java で言う package (や Kotlin の alias import )を Ruby でやりたいのかなと思いました。 自分は今まで Ruby (と言わずスクリプト言語全般) の変数がどう参照されているかが分かり辛く、またクラスや変数のコンフリクトが起きやすいのではと思っていました。Google が Ruby のライブラリなんか作ったらそれはもう大変なことに... モジュラモノリスなアプリケーションにおいてチームの規模が増えるにつれ、こういった話の課題感は飛躍的に上がっていきそうな気がするので重要度は大きいのではないでしょうか。 Refinements Namespace のセッション内で Refinements というワードが聞こえたので、Refinements について調べました。 こちらはメソッドに対してある特定のスコープ内の挙動を書き換えるものだとわかりました。Namespace が package に対して Refinements はどちらかと言うと extensions なのかな?少し違うかも... Refinements が Namespace に成り代わる(統合される)わけではないと言及されていて 、上記の比較が正しければ確かにそもそもの目的・用途が別物ですね。実際に触って理解していきたいと思います。 RBS 自分は Java からスタートしたので馴染みがありますが、動的型付けの Ruby でもタイプセーフのメリットを傍受したい!的な話でしょうか。コンパイルでエラーを吐き出されたりエディタ上で確認できた方がいいのは開発スピードや品質にも関わってくるのでそれはそうだと思います。 Java の記述が冗長になりがちなデメリットを Kotlin が型推論でカバーしていることを考えると自然な流れに見えます。 ただし定義が .rbs (別ファイル) に定義されることが、必要に応じて定義できるフレキシブルさを持っている反面運用時のネガティブコストにならないかは心配になりました。 TypeProf そして型推論をやろうとしているのが TypeProf でしょうか。( Good first issues of TypeProf ) .rbs ファイルを自動生成するから管理を気にしなくてよくなるのかもしれない? こちらも触って確かめてみようと思います。 Parser 今回の RubyKaigi で最も聞いた単語だと思います。普段プログラムの Parser を意識することはあまりありませんでしたが、実際にどういうアルゴリズムで動いているとかこの言語だとこうだけど Ruby やこのツールはこうなんですよみたいなのが聞けて面白かったです。 調べているとかなり歴史や思想があって興味深いのですが詳細を書くととても長くなってしまいそうなので割愛します。 The grand strategy of Ruby Parser を発表されていた kaneko-san の こちらの記事 がとても勉強になりました。 コミュニティの話 社内外問わずたくさんの人にはじめましてが出来たことも良い刺激でした。 モバイル界隈に生息しているため社外の Rubyist はもちろんのこと、タイミーではフルリモートを採用しており、自分はまだ入社して半年も経っていないためチームでも現地で初めて顔を合わせる人がたくさんいました。 そういった人たちとパーティやランチで普段何しているかだったり業務では聞けない話をたくさんできて楽しかったです。 RubyKaigi で驚いたと共にいいなと思ったことが、 各スポンサーや有志がアフターイベントを企画しそのイベントをオフィシャルが公表していること です。 参加する人が口を揃えて RubyKaigi はお祭りだと言っている意味がわかりました。Official Party はもちろんですが、最終日にも懇親会があることも驚きましたし、各社が企画する DrinkUp やカラオケ大会、果てにはクラブを貸しきる DJ イベントもあり、なんでもありだな...と。 タイミーも初日から二日続けて DrinkUp を開催するという狂気っぷりを発揮しています。 timeedev.connpass.com せっかく初参加なのでと時間に都合がつく限り参加してみました。 とは言え、初参加だし専門領域も違うので単身乗り込んで行って大丈夫か...?ちゃんとコミュニケーションできるか...?知らん人に囲まれて歌えるか...?と不安ばかりでした。 ですが実際に飛び込んでみるとそんな不安は杞憂に終わりました。話す人みなさんが歓迎ムードで相手へのリスペクトを感じ、Ruby コミュニティのウェルカムマインドはなんて素晴らしいんだと感動しました。 こちらは2日目の rubykaraoke で午前3時まで完走した猛者たちの様子。面構えが違う… 一人不安に思いながら参加しましたが、楽しみ過ぎて完全に声が出なくなりました😇 #rubykaraoke 完走組🤣おつかれさまでした! #rubykaigi #rubykaigi2024 #rubyfriends pic.twitter.com/9tx4AJyJTW — ヤノ | ROUTE06 (@ynndino88) 2024年5月16日 このお祭りみたいな雰囲気と、それをオフィシャルが大々的に謳っていることは社外の人と交流するハードルが一気に下がってとてもよい取り組みだと思います。旅先であることも盛り上がりの燃料となっている気がしますね。 だからこそ Ruby コミュニティはここまで規模が大きくなっているんだと実感できました。 ここまでの規模でこれだけ盛り上がりの大きいカンファレンスは自分が知る限り国内ではあまり見かけないので非常に良い機会提供の場になっていると思いました。(Android のカンファレンスでもこういうのないかなぁ) そりゃ毎年みんな行きたがるし帰ってきてからもわいわいしてるわけだ... さいごに まずは関わってくださったみなさまに感謝を。 Ruby を開発してくださっているコミッターの人、RubyKaigi を運営してくださったスタッフの人、雑に話に行って歓迎してくださった人、チームのメンバー、専門領域が違うにもかかわらず参加させてくれた上司・会社などなど、本当にありがとうございます。 タイミーでは KaigiPass と呼ばれる制度があって、レポートを書いたり登壇するなど何かしらのアウトプットでコミュニティに貢献することを前提に、国内外問わずカンファレンス参加の費用を負担してくれる制度があります。 冒頭で紹介した通り僕はモバイルアプリエンジニアで Ruby とはほど遠く、社歴もまだ半年も経っていないのに、それでも参加を認めてくれています。タイミーはなんて素晴らしい組織なんだ。 今後のための教訓として RubyKaigi や Ruby についてある程度事前に調べていくべきだったと反省しています 。 上述した通り RubyKaigi の趣旨もそうですが、知らない単語を調べながらセッションを聞いていると途中でついていけなくなったりしました。まぁついていけてもわかってなかったですが... とは言え Ruby に関しては何から手をつけていいかわからなかったので、ワードからこういうことがあるんだなと調べるためのとっかかりが得られたのはとても大きな一歩だと感じています。 またネックストラップの色によって写真の掲載に承諾するかを意思表示できるようになっています。黄/赤 は 🙆‍♀️、白/青 は 🙅‍♂️ です。 ところがアイキャッチの写真をよく見てください。1人だけ青いですね。 そう、僕です。完全に理解していませんでした。自分のイメージカラーだから青にしよ♪くらいの気持ちでいました。 撮り終わった後に教えていただいて慌てて付け替えたんですが、ちゃんと会のレギュレーションをチェックしておけばと後悔しています...お手数おかけしました... 何事も事前準備が大事ですね。 以上、モバイルアプリエンジニアが RubyKaigi に初参加してみた参加レポートでした。 振り返ってみると圧倒的によかったこと・得られたものが多く、今回参加してみて本当に満足しています。 来年は愛媛県松山市ということでぜひ次回も参加したいですね! せっかくだから自転車持っていって帰りはしまなみ海道渡ってから帰ろうかな...
こんにちは、タイミーの @masarakki です。 先日、5月15日から3日間開催された「RubyKaigi2024」に参加しました。 本記事で取り上げるのは、そのRubyKaigi2024の最後のセッションであるmatzのキーノートで、「これが入ったらRuby 4.0」とまで言われた @tagomoris 氏のNamespace機能。 セッション終了後、目の前に本人が座っていたので「責任重大だねwww」と煽りに行こうとしたところ、感極まって帽子を目深に被りなおしている瞬間だったのでそっとしておきました。 というわけで、 セッションの内容 は他にいくらでも記事があると思うので、実際に手を動かしてみようと思います。 参考: https://gist.github.com/tagomoris/4392f1091f658294bd4d473d8ff631cb 作業ブランチが Namespace on read にあるのでビルドしてみましょう。 $ git clone https://github.com/tagomoris/ruby $ cd ruby $ git checkout namespace-on-read $ ./autogen.sh $ mkdir build $ cd build $ ../configure --prefix=$HOME/ns-ruby $ make $ make install $ ~/ns-ruby/bin/ruby -v ruby 3.4.0dev (2024-03-28T13:58:33Z namespace-on-read f0649a2577) [x86_64-linux] どうやらうまくビルドできたようです (rubyのビルド人生で初めてやった)。 かんたんな検証コードを動かしてみましょう。 # foo.rb ------ require ' ./bar ' class Foo def self . var= (val) @@var = val end def self . var @@var end end # ------------- # bar.rb ------ class Bar end # ------------- # nstest.rb --- def dump (obj) puts "#{ obj } : #{ obj.object_id }" end require ' ./foo ' ns = Namespace .new ns.require ' ./foo ' dump Foo dump Bar dump ns:: Foo dump ns:: Bar Foo .var = ' abc ' ns:: Foo .var = ' xyz ' puts "#{ Foo .var } , #{ ns:: Foo .var }" # ------------- 実行してみましょう。 ~/ns-ruby/bin/ruby nstest.rb Foo: 100 Bar: 120 #<Namespace:0x00007ff908cf1cd8>::Foo: 140 #<Namespace:0x00007ff908cf1cd8>::Bar: 160 abc, xyz Foo と ns::Foo が全く独立していることがわかります。 普段遣いのrubyでは動かないことを確認しましょう。 $ ruby -v ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [x86_64-linux] $ ruby nstest.rb nstest.rb:8:in `<main>': uninitialized constant Namespace (NameError) ns = Namespace.new ^^^^^^^^^ 確かセッションでは例として Oj.default_options が 行儀の悪い gem によって書き換えられてしまう事例が挙げられていたので試してみましょう。 # foo.rb ------ require ' oj ' Oj .default_options = { symbol_keys : true } class Foo def self . oj Oj .load( ' {"key":"symbol_or_string"} ' end end # ------------- # nstest.rb --- require ' ./foo ' ns = Namespace .new ns.require ' ./foo ' Oj .default_options = { symbol_keys : false } p Foo .oj p ns:: Foo .oj 実行してみましょう。 $ ~/ns-ruby/bin/gem i oj $ ~/ns-ruby/bin/ruby nstest.rb $HOME/ns-ruby/lib/ruby/3.4.0+0/date.rb:51: [BUG] Segmentation fault at 0x0000000000000168 ruby 3.4.0dev (2024-03-28T13:58:33Z namespace-on-read f0649a2577) [x86_64-linux] -- Control frame information ----------------------------------------------- c:0013 p:0039 s:0052 e:000051 CLASS $HOME/ns-ruby/lib/ruby/3.4.0+0/date.rb:51 _人人人人人人人_ > 突然のSEGV <  ̄Y^Y^Y^Y^Y^Y^Y^ ̄ ちなみに date.rb:51 は def coerce(other) です。 なぜ・・・ そういえばセッションで @tagomoris も祈りながら実行していたな・・・というのを思い出し、何度か実行してみたところ、 確率10% くらいで成功しました。 $ ~/ns-ruby/bin/ruby nstest.rb {"key"=>"symbol_or_string"} {:key=>"symbol_or_string"} 他のライブラリでも試してみましょう。 json 標準ライブラリで oj と同じようにC拡張を持つライブラリです。 特に何も問題なく読み込めました。 csv '<top (required)>': uninitialized constant Array (NameError) ネームスペースの中でArrayが見つからないみたいです。 もっと単純なコードで試してみましょう。 # foo.rb ----- puts "#{ Array } : #{ Array .object_id }" module Mod def foo p :foo end end Array .include( Mod ) # ------------ # nstest.rb -- puts "#{ Array } : #{ Array .object_id }" ns = Namespace .new ns.require ' ./foo ' [].foo # ------------ $ ~/ns-ruby/bin/ruby nstest.rb Array: 80 Array: 80 :foo なにかおかしいですね。 Array は見つかるものの、予想外にトップレベルの Array まで汚されてしまっています。 さらに foo.rb に require 'csv' を追加すると $ ~/ns-ruby/bin/ruby nstest.rb $HOME/ns-ruby/lib/ruby/gems/3.4.0+0/specifications/csv-3.2.8.gemspec:4: [BUG] vm_cref_dup: unreachable _人人人人人人人_ > 突然のSEGV <  ̄Y^Y^Y^Y^Y^Y^Y^ ̄ トップレベルで require ‘csv’ した後に ns.require './foo' すると uninitialized constant Array (NameError) のエラーに戻ります json と csv $ ~/ns-ruby/bin/ruby nstest.rb $HOME/ns-ruby/lib/ruby/3.4.0+0/forwardable.rb:230: [BUG] Segmentation fault at 0x00000000000001da -- Control frame information ----------------------------------------------- c:0017 p:---- s:0088 e:000087 CFUNC :proc c:0016 p:0004 s:0084 E:001920 TOP $HOME/ns-ruby/lib/ruby/3.4.0+0/forwardable.rb:230 [FINISH] _人人人人人人人_ > 突然のSEGV <  ̄Y^Y^Y^Y^Y^Y^Y^ ̄ 問題のない json と問題のある csv を両方requireするとなぜか別の問題が発生しました。 面白いですね。 (自分のrubyのビルド方法が間違ってる疑惑・・・?) こういう開発途中の機能を触ってみるのは初めてなので、Rubyがこんなに簡単にぶっ壊れるんだ・・・って新鮮な気持ちです。 いろいろなパターンでSEGVが出たので次回はコードを読んでみたいと思います。 最後に コロナ禍で人生が一変し、結婚 + 出産 * 2 が続き、2019年以来 実に5年ぶりのRubyKaigi参加になりました。 RubyKaigi、やっぱりドチャクソ楽しいですね・・・特にrubykaraoke・・・ッ!! 1歳と0歳を連れて参加できたのも、ひとえに託児所やイベントに子供を受け入れてくださった各社様のおかげです。この場を借りてお礼申し上げます。 なお、RubyKaigiには「Kaigi Pass」という制度を利用し参加しました。 Kaigi Passとは、世界中で開催されている全ての技術カンファレンスに無制限で参加できるタイミーの制度です。 制度について気になる方はぜひ以下の記事もご覧ください。 productpr.timee.co.jp
5月15日から17日の3日間、RubyKaigi 2024が沖縄県那覇市で開催されました。 rubykaigi.org タイミーには世界中で開催されるすべての技術系カンファレンスに無制限で参加できる「 Kaigi Pass 」という制度があります。今年もこの制度を活用してタイミーから総勢12名のエンジニアが参加しました。 前回 に引き続き参加レポートの2回目として、各エンジニアが印象に残ったセッションの感想をお届けします。 YJIT Makes Rails 1.7x Faster rubykaigi.org ShopifyでYJITの開発をしているk0kubunさんによるRuby 3.2から3.3(と3.4)にかけてのYJITの進化についての発表でした。YJITはRubyの実行速度を向上させるJITコンパイラで、Ruby 3.3ではいくつもの改善が施されています。 その中でも特にパフォーマンス改善にインパクトがあったものとして強調していたのが、メソッド呼び出しのフォールバック、例外ハンドラのコンパイル、スタックに置いていた値のレジスタ割り当て、メソッドのインライン化の4つでした。 こうした地道な改善の積み重ねにより、Ruby 3.3のYJITはRubyプログラムのパフォーマンスを10-20%も向上させることができるようになったそうです。今後のRubyの高速化に期待が持てるセッションでした。 3月末に Rubyを3.3.0にバージョンアップした 際にYJITのことを少し調べていたので自分ごととして聞けたセッションでした(タイミーではYJITは Ruby3.2のときに有効化 しています)。 リリースノート のYJITの"Major performance improvements over Ruby 3.2"というセクションにも目を通してはいたのですが、そのときは「なんかいろいろ最適化して速くなったんだろうな」くらいの気持ちで読み流していました。 このセッションでリリースノートの1行1行の背後で具体的にどういう改善を行ったのかがわかりやすく解説されたおかげで、自分の中でYJITに対する理解が少し深まった気がします。 同じくYJITに関する@maximecbさんの「Breaking the Ruby Performance Barrier」も聞いていて、YJITを最大限活用するようなコードの書き方やgemの選定といった観点がパフォーマンス文脈では重要になるかも?などと思ったりしました。 が、本来はあまりそういうことを意識しなくてもふつうにRubyのコードを書いてYJITが有効だったら速い、が理想な気もしますし、YJIT開発チームの精力的な活動を見ていると自分の想像を超えるようなブレイクスルーが起きる気もしていて今後が楽しみです。 ( 須貝 ) speakerdeck.com RuboCop: LSP and Prism rubykaigi.org セッションの大きなテーマは以下の二つでした。 Rubocop and LSP Rubocop and Prism 「Rubocop and LSP」ではLSPがどう実装されているかやVSCodeやvimなどのEditorでどう利用されているか事例などの話がありました。加えて、LSPを利用しないと毎回CLIなどでコマンドを叩かないといけないが、LSPを利用するとその流れが省略されるのでフィードバックが早いメリットがあることや、VSCodeでは既にYJITを利用している話もありました。 メリットなどの話もありましたが、LSPの対応で新しく発生したイシューもありました。CLI上では作業を完了してからコマンドを実行するので問題ないが、Editorだとどのタイミングで作業が終わったのか判断が難しいというところです。現在Parser Gemではエラー耐性設計(Error Tolerance)をサポートしてないのでこの問題が解決できないが、それをサポートしているのがPrismで、「Rubocop and Prism」で関連の話がありました。 「Rubocop and Prism」ではどのタイミングで作業が終わったのか判断が難しい問題を解決するための説明やどういうメリットがあるかなどの話がありました。Prismではエラー耐性設計(Error Tolerance)をサポートしているため、作業が終わるタイミングの判断の問題が解決できるかつ、Parsingスピードが速くなるらしいです。 最後には現在はParser Gemのインターフェースを利用してPrismを利用しているが、将来的にはそれを無くして直接Prismを利用したい話、Prismを利用すると得られるメリットの話でセッションが終わりました。 rubocopがなんとなくパーサーで動いているんだろくらいは考えていたがどういう原理や技術で動いているか分かってなかったので、話を聞くだけでも非常に勉強になりました。現在vimを利用していて、素早くrubocopの結果がEditor上で表示されるのは非常に助かっていますが、それがLSPのおかげだということがわかりました。まだPrismではエラー耐性設計を対応してないので、すぐprismを利用するのは難しいかと思いますが、対応出来次第もっと速くなったrubocopを利用するのが楽しみです! ( @JinsuKim26 ) speakerdeck.com From LALR to IELR: A Lrama's Next Step rubykaigi.org 概要 RubyのParserであるLramaは次にIELR化に取り組むよという話。 課題 現行のLramaのParserはLALR Parserであるが、Lexerの状態管理に関して課題がある。 事例として p 1.. || 2 が正しくパースできない問題は、 シンボルを "||" と解釈するか "|" + "|" と解釈するかの問題に起因しており、これが文脈に寄って異なるというLexerとParserの複合問題である。 これを現状のLALR Parserで対応しようとすると当該Parseロジックに個別カスタム処理を書く必要があるが、構文を管理するparse.yは既に現時点で16kLを超えており個別カスタム対応には限界がある。 解決策 この問題はLexerとParserを直列に動作させていることに起因しており、LexerとParserが相互に連携しながら解析することで解決可能である。 これを実現するアルゴリズムがPSLRアルゴリズムである。 実装 PSLRアルゴリズムは Canonical LR parser で実装可能であるが、 Canonical LR parser は動作が重たいという問題がある。 LALR Parser は Canonical LR parser を軽量化したものであるが、軽量化の代償としてPSLRアルゴリズムを実現するのに必要な表現能力を失っている。 そこで両者のいいとこ取りをしたIELR Parser というものがある。 着想としては高速な LALR Parser を基本としつつ、表現能力を超えた場合に Canonical LR parser を動かすと言ったシンプルなものもの。 感想 Parserが順当に進化しているなという所感。 言語工学の基本としては字句解析と構文解析は直列に動かすものですが、実際の課題としてそれでは解けない問題が存在するというのは興味深かったです。 LR Parserの改善の文脈でありLR Parserの知識があればベターですが、実際には字句解析処理の文脈依存性の問題です。非常に豊富な文法をもつRubyらしい課題であり、他言語ではなかなかココまで言語工学の研究に踏み込むことは無いのではないか思われます。 問題の複雑さに対して、実際の解法はシンプルに既存の技法を組み合わせるものであり、これは言語工学における先行研究の成果物の豊富さを物語っていると感じました。 (@Kazuki Tsunemi - tsunemin) speakerdeck.com Cross-platform mruby on Sega Dreamcast and Nintendo Wii rubykaigi.org 概要 mrubyをドリームキャストとWiiで動かしてやったぜという内容。 なおスライド自体もWiiを動かして表示していた。 ドリームキャストは2020年 、 Wiiは2023年 にmrubyで正式サポートされている。 なぜこの2つを選んだかと言うとドリキャスとWiiをスペック対決させるため。両者はハードウェアアーキテクチャからSDKに至るまでが全く異なるのだが、ともにFreeSDKがあるので採用された。 実機デバッグは非常に辛いものがあるので、エミュレータが非常に助かるなどの苦労話があった。実機で動作デモも行われた。 感想 登壇者のSEGA愛がすごい。 SEGAロゴデザインのRUBYシャツ(多分自家製)を着て現れた時点ですべてを察しました。Wiiは如何にドリームキャストがすごいのかと伝えるために引き合いに出された感じもあります。 mrubyのv3.3.0 のリリースノートを見るとNintendo Wii、Dreamcastがきっちり記載されています。(AndroidやMS-DOSと同じ並びにあることで異様さが際立つ) ゲーム機は汎用PCとは違って独特のハードウェア構成をしており、特にVRAM周りの深い見識がないと動作させるのは難しいためハードウェアの話がメインになりました。 ドリームキャストはビジネスとしてはヒットこそしませんでしたが、技術としては凄いものがあったらしくハードウェアオタクの心を未だに掴み続けているようです。尺の都合でハードウェアの深い話にまでは踏み込まず、使われている技術の違いの話がメインでした。 エミュレータ開発を含めてゲーム機ハック界隈は独特の文化が醸成されていますが、ローレベルプログラミングの深淵を除きたい人はいい窓口かもしれません。 Rubyでやる理由は謎に思いつつ、Rubyでなければマージされないような気もします。 それだけRubyが「懐の深い言語」ということでしょうか。 備考 Ruby Conf in Taiwanで同様の発表が行われていた模様 (@Kazuki Tsunemi - tsunemin) www.youtube.com Finding Memory Leaks in the Ruby Ecosystem rubykaigi.org メモリデバッグやメモリリークの検出に使われる Valgrind をRubyプログラムに適用して、メモリリークを検出するアイデアを実現した発表でした。 ValgrindをそのままRubyプログラムで用いるとRuby自体がシャットダウン時に全てのリソースを解放しないため、Valgrindのレポートで数千のエラーが誤検知され調査が困難な状態でした。そこで、 ValgrindのXML出力オプションを有効にし全てのエラーを標準出力し、その結果を1つずつ解析してRuby自体のメモリリークではなく、Rubyプログラムのメモリリークを検知できないかというアイデアから生まれたのが [ruby_memcheck](https://github.com/Shopify/ruby_memcheck) です。 ruby_memcheckでは、Valgridの出力したエラー1つずつのリークされた 各メモリ割当てのスタックトレースを走査し、Ruby起因のエラーかネイティブGem起因なのかを確認 しています。実際にこのツールによってnokogiriやprotobufといったGemでメモリリークを検知し、修正されたそうです。アイデア実現のアプローチも面白かったのですが、個人的にはその後が興味深かったです。 彼らはそこで終わらず、そもそも Rubyインタプリタが終了する際に確保されたメモリ全てを解放してくれればメモリリークの検知は今より容易になるということで問題提起 を行い、Ruby3.3から RUBY_FREE_AT_EXIT という機能が導入されました。このオプションを有効にすると、Rubyインタプリタ終了時に確保されたメモリを解放するため、より効率的にメモリリークの検出ができると言うものです。これによって最新の ruby_memcheck の メモリリーク検出のコード もスッキリしているのも良かったです。 Day3のMatzのKeynoteで触れられていた様々な側面のパフォーマンスの話のように、YJITとRubyアプリへの直接的なパフォーマンス改善だけでなく、今回のようなメモリリークの検出を手助けする開発者体験向上文脈のRubyの発展の一例を知れて面白かったです。 今回の発表者の1人である Peter Zhuさん が、最近RubyのGCのチューニングに使える示唆を与えてRailsアプリを高速化をサポートする Autotuner というGemに関する記事を公開していたので早速使ってみたいなと思っています。 ( ぽこひで ) https://blog.peterzhu.ca/assets/rubykaigi_2024_slides.pdf blog.peterzhu.ca 最後に パート1 に引き続き、興味深いセッションが並びました。 あらためて、スピーカーの皆様に感謝をお伝えさせていただきます。貴重なお話をありがとうございました! RubyKaigi 2024への参加で得たさまざまな知見を「タイミー」の開発にも活かしていきたいと思います。 Written by:須貝, @JinsuKim26 , @Kazuki Tsunemi - tsunemin, ぽこひで
5月15日から17日の3日間、RubyKaigi 2024が沖縄県那覇市で開催されました。 rubykaigi.org タイミーには世界中で開催されるすべての技術系カンファレンスに無制限で参加できる「 Kaigi Pass 」という制度があります。今年もこの制度を活用してタイミーから総勢12名のエンジニアが参加しました。 今回から2回に分けて、各エンジニアが印象に残ったセッションの感想を参加レポートとしてお届けします。 Good first issues of TypeProf rubykaigi.org このセッションでは、動的型付け言語が苦手とするエディタ上でのエラー表示、コードジャンプ、コード補完などの機能を公式で提供しようとしている TypeProf の紹介と、TypeProf に貢献するための方法や tips の紹介がメインでした。 手元で TypeProf を動かして遊んでみる方法、バグを見つけたら修正しなくても known-issues として Pull Request を作るだけでも歓迎していること、TypeProf の実装における設計のコンセプトの紹介など TypeProf への貢献のハードルが下がるような発表でした。 自分としては、まず patch を歓迎していると分かったのが1つの収穫でした。TypeProf は mame さんが数年前から取り組んでいることは認知していましたが、まだ試験的な状態で他の開発者からの patch はあまり歓迎されないんじゃないかと感じていました。(そもそも patch を送るという発想すらなかった) 貢献へのハードルの下がった自分はいくつかの patch を投げてみました。迅速にレビューしてもらってありがたかったです。 github.com github.com github.com patch を投げてみた感想として、難しいのは間違いないものの新しい構文をサポートすることはクイズを解くような楽しさや、少しずつ機能が増え成長していく育成ゲーム的な要素を感じました。楽しい。ウキウキしながら Ruby "enbugging" quiz を解いた人は多分楽しめるんじゃないでしょうか。 また、個人的な TypeProf の見どころの1つはシナリオテストを動かすための構文をサポートしている ScenarioCompiler クラスです。正直処理を追いかけるのは大変ですが、Ruby の表現力の高さを再認識させてくれます。 github.com 個人的には TypeProf をはじめとする Ruby の型システム周りの技術要素には Ruby での開発体験を大きく高める可能性を秘めていると感じています。これからの動向を追いつつ、自分でもできそうな貢献を見つけていきたいと思います。 ( @euglena1215 ) speakerdeck.com Unlocking Potential of Property Based Testing with Ractor rubykaigi.org 概要 Ractorを使う機会というのがあまりないというのが、Rubyを利用する側の正直なところだと思うが「Propaty based testingがRactorの良きuse caseになるのでは?」と思い、その仮説をもとに取り組んだ。 普段MinitestやRSpecで書くテストは、あるプログラムの実行結果が指定した期待値になっているかどうかというテストを行っているだろう。これをExample based testingという。このテストでは、プログラマの思いつく範囲のテストしか行えないという弱点がある。 Propaty based testingとは、入力値に取りうる値をランダムに多くのパターンを機械的に与えながら実行し、与えられたすべての入力値に対してプログラムの実行がパスするかどうかをテストするものである。こちらはプログラマが想定しなかったような大量で多様なテストケースが実行されるのが強みである。ただし、大量のテストケースを実行するため、テストの実行時間は当然長くなってしまう。これをRactorを使うことで軽減できるのではないか?というのが今回の仮説である。Propaty based testingは大量にテストケースが実行されるものの、それぞれに関連性は全くなく、順序など気にせず並列で動かしても何の問題もないことからRactorのuse caseにぴったりだと言える。 Propaty based testingを簡単に行えるようにするために、 pbt gemを作成した。これはMinitestやRSpecといったテストフレームワークの代替となるようなものではなく、それらと組み合わせて使用されることを想定したライブラリである。また、pbtはテストが失敗した場合、その時点からシュリンキングという手法を使ってテストの失敗を再現する最小の入力値を求めに行く。 実際にPropaty based testingをRactorを使って実行したところ、CPU-boundな処理を実行するテストではRactorを使うことでシーケンシャルなものより5倍ほど速いという結果になった。しかしそれ以外のケースでは圧倒的にシーケンシャルなものの方が速いという結果であった。 Ractorを使うことで、Propaty based testingの実行時間が短くなるのか?という仮説に対する結果は「部分的にそう」と言える。また、今回の実験でRactorに対応したエコシステムがまだまだ不足しているなどの課題感も見えた。 感想 Propaty based testing自体には別件で raap gemを知った際に興味を持っていたため、デモ含めとても勉強になりました。確かに実行時間がネックになるだろうなとは思っていたものの、 Ractorではほとんど解決しないという結果に驚きました。勉強不足であるためにCPU-boundな処理というものが具体的にすぐにイメージできないのが悔しいところではあるのですが、少なくとも利用できるケースがあることがわかっただけでも学びになりました。 pbt gemについて言及させてもらうと、これは所謂テストにロジックを書くものになりそうだなというのがパッと見の感想です。しかし試したわけではないので、実際に使ってみるとまた違った感想になるかもしれません。仮に本当にテストにロジックを書くものになってしまう場合、好みは分かれてしまいそうだなと感じました。 周辺ライブラリがRactor互換ではないことは、Ractorを使いたい人たちからするととても大きな問題だと思います。自分もRactorに興味のある一人ではありますが、未だRactorを使うために真剣に動き出せてはいないので、いい刺激をもらいました。周辺ライブラリがRactor互換になることはとても良いことだと思う一方で、そのための実装は複雑になってしまうかもしれません。それでもRactor対応を歓迎してくれるライブラリは、その旨をどこかに明記しておくと、有志からのcontributeを得やすいのではないかと感じています。 (@rhiroe) speakerdeck.com It's about time to pack Ruby and Ruby scripts in one binary rubykaigi.org 概要 Rubyで作ったゲームを配布したいが、配布先の環境でRubyのコードを動かすためには、Ruby本体やGemのインストールが必要であったり、それらのバージョンを揃えたりする必要があるため面倒である。これを解決し、配布先で事前準備なしで気軽にプログラムを実行できるようにするため、RubyをOne binaryに変換して配布したいと思うようになった。ここでのOne binaryとは単一ファイルのみで実行可能なプログラムを指している。 プログラムの実行環境をパッケージングして配布するという意味ではDockerやWasmも似たような手段として考えられるが、ちょっとしたプログラムを実行したいだけでDockerを使うのはヘビーだし、WasmだとRubyの機能が一部制限されてしまう。また、RubyのコードをOne binary化するライブラリはすでに存在するものの、以下のような課題を抱えている。 対応しているRubyバージョンが古い Ruby本体にパッチを当てているためメンテが大変 Windows限定 一時ファイルに書き出す処理があり遅い そのため、これらを解消した Kompo gemを開発した。Kompo gemはモンキーパッチのみ、一時ファイルへの書き込みなし、gemのインストールのサポートをしているとのこと。 感想 Rubyで書かれたプログラムをOne binary化し配布するという発想は、Rubyが好きだからこそ生まれるものだと感じており、とてもRubyKaigiらしい話でした。なので、ここではあえてRuby以外の言語を使用するという話はしないことにします。 Rubyで書かれたプログラムをOne binary化したい動機をゲーム以外で考えてみると、CLIツールが真っ先に思い浮かびます。またGUIツールもRubyで作成可能なので、これを配布したい場合もOne binary化したい動機につながりそうです。自分の身の回りにはプログラムのことなんて全くわからないという人の割合の方が多く、そういった人たちに作業の効率化のツールとして配布したい場合に、One binaryであるという点はとても魅力的に感じました。 セッション中に、手元のRubyファイルに変更が加わると実行結果が変わるという、面白いデモを見せてもらいました。これは、Kernel#require等にモンキーパッチを当てることで実現しているそうです。One binary化するツールとしてKompo gemの紹介がされていましたが、モンキーパッチを当てていたりOne binary化するにあたってのキモの実装は Kompo-vfs の方にありそうです。 Kernel#require等にモンキーパッチを当てたという話はこの辺りのことだと思われます。 github.com RubyKaigi中の別の発表でKernel#requireはよく上書きされている話を聞いており、「ここでもKernel#requireが上書きされる事例が…!」と思いながら聞いていたのを覚えています。Kernel#requireの上書きって、具体的にどういう用途で使われるんだろう?というのが気になっていたので、勉強のための良い教材となってくれそうです。 普段の業務で「RubyをOne binary化したい!」と感じたことは残念ながらないのですが、趣味で作ったプログラムを「プログラムを1ミリも知らない友人に使って欲しい」と思ったことはあります。そういったものを作る場合にRubyが選択肢の1つとして挙がることはRubyistとして大変喜ばしいことです。Kompo gem、同じRubyが好きな者として、機会があればぜひ利用させていただこうと思います。 (@rhiroe) Let's use LLMs from Ruby 〜 Refine RBS types using LLM 〜 rubykaigi.org Leaner Technologies に所属する黒曜さん(kokuyouwind) の発表です。 Rubyの型定義であるRBSを大規模言語モデル(Large Language Models: LLM)を用いて推測し、アウトプットの質やスピードをLLMのプラットフォームやモデル間で比較検証した内容でした。 RubyにおけるLLMを用いた型推測については、昨年のRubyKaigi 2023において MatzのKeynote でも触れられており、個人的に関心があるテーマでした。今回は現実的な活用性がどれほどあるか気になって視聴しました。 結論から述べると、まだ業務レベルでLLMの推測一本では期待した型定義が生成可能ではありませんでした。LLMのプラットフォームやモデル間でも成績に大幅な差がある状態となっております(詳しくは発表スライドをご覧ください) 一方でOpenAIのGPT-4 Omniモデルは比較した他モデルに比べても圧倒的な精度とスピードを誇っていました。 GPT-4 Omniは偶然にもRubyKaigi 2024のDay0(開催前日)に発表されていたのですが、流石に日程的に反映は難しいだろうと思っていたところ、黒曜さんが急ピッチで実行結果を取ったらしくDay1の発表に間に合っていて心の中で称賛の拍手を送りました笑。 発表中にはRubyコードから型推測を行うgemとして本人が作成された rbs_goose やRubyでLLMを活用するためのgemである Langchain.rb などが紹介されました。いずれも私自身の知見が乏しかったので実例を通してライブラリを知ることが出来たのは有益でした。RubyのLangchainはPythonやJavaScript版のそれと比較してGitHub上のスター数などが見劣りしてはいますが、今後Rubyへの型システムの浸透が進むと関連して活用も増えていくのではないかと考えています。 (江田) slides.com 最後に RubyKaigiでは毎年多くの学びがありますが、今年もたくさんの知見を得ることができました。今から来年のRubyKaigiが楽しみです。 さて、RubyKaigi参加メンバーによる残りのセッションまとめも発信予定。次回パート2の更新もぜひお楽しみに! Written by: @euglena1215 , @rhiroe, 江田
タイミーの林です。 TSKaigi 2024 が5/11に開催されました。タイミーはゴールドスポンサーとして参加させていただきました。 また、タイミーには世界中で開催されている全ての技術カンファレンスに無制限で参加できる「Kaigi Pass」という制度があります。詳しくは以下をご覧ください。 productpr.timee.co.jp 私は今回この制度を使ってTSKaigiに参加しました。印象に残ったセッションをいくつかご紹介します。 TypeScript ASTを利用したコードジェネレーターの実装入門 セッションの詳細ページ セッションの資料 このセッションでは、AST(抽象構文木)について丁寧に解説され、ASTを活用したコードジェネレータの実装方法やそのメリット・難しさについて説明がありました。 この後の様々なセッションでキーワードになるASTについての詳細な説明を早い段階で聞けたことは非常に有益でした。後のセッションでの理解度が非常に上がったと思います。タイミーでもESLintの独自ルールを作る際にASTに触れている箇所もあるので、そこの実装を思い出しながら説明を聞いていました。 TypeScriptの型システムを使ってコードジェネレーターを作成するところについては、実際にやってみないと分からない具体的な課題や実践的な知識も紹介され、コードジェネレータの作成にとても興味が湧きました。 ハードウェアを動かす TypeScript の世界 セッションの詳細ページ セッションの資料 このセッションでは、ハードウェア開発の面白さに触れつつ、TypeScriptでハードを動かす際に考慮すべきことについて説明がありました。 普段Web開発ばかりしているので、日頃聞くことのないハードウェアの話が非常に興味深かったです。特に「考えることが多すぎる」と繰り返し言及されていましたが、やりたいことや予算に合わせたデバイスの選定、デバイスに応じた言語の選択や実行環境の選定、そして一度デバイスを選定して調達すると後から修正がしづらいといったハードウェア特有の難しさについての話が印象に残りました。ハードウェア開発の難しさと面白さの一端を垣間見ることができたかと思います。 この難しさを乗り越えることで、より多くのことが可能になると感じ、自分たちの仕事にもハードウェアを活用できる場面があるのではないかと想像しました。また、普段は無意識にソフトウェアの手段に制限していることを再認識し、TypeScriptでハードウェアのコードが書けることに驚きました。 実務でハードウェアを使うこと、実装することはまだ少しハードルが高く感じられますが、Webやソフトウェアに捉われない視点を持つという点では非常に刺激を受けたセッションでした。 Exploring type informed lint rules in Rust based linters セッションの詳細ページ セッションの資料 このセッションは、最近流行しているRust製のlinterが直面している問題と、Biomeのコミッターとして考えられている解決策についての発表でした。 具体的には、Oxcやdeno_lintなど他のRust製ツールとBiomeを比較しつつ、これらのRust製ツールがTypeScriptの型情報を用いたLintを行えないという問題に焦点が当てられていました。この問題について聞いたことはありましたが、その背景については知らなかったため、非常に勉強になりました。Linterの内部での仕組みについての話から、形情報を元にLintルールを作ることがなぜ難しいのか、それを解決する方法について理解できたように感じています。今のところタイミーの開発ではESLintが主流ですが、Biome・Oxc・deno_lintの比較は今後乗り換えを検討する時には参考にしたいと思いました。 結論としては、—isolatedDeclarationsを活用しつつ、Type Inferenceの実装を進めることになるとのことでした。簡単な道のりではないように感じられましたが、これからの進展に期待しながら注視したいと思います。 Prettier の未来を考える セッションの詳細ページ セッションの資料 このセッションでは、Prettierが今に至るまでの歴史的な経緯や、これからのPrettierの方向性について話がありました。 具体的には、ESLintがTypeScriptをサポートしていないため、追加のツールを入れる必要があることや、CIで全体に対してPrettierを適用するとフォーマットに時間がかかるといった問題が挙げられました。私も、過去にhuskyを使ってpushするたびにPrettierでformatをかける設定にしていたことがあったので、pushするたびにかかるformatが非常に重くストレスを感じた記憶が蘇りました。ESLint+Prettierの設定の複雑さの話についても、過去の苦い経験を思い出しながら話を聞いていました。 また、誰もPrettierを早くしようと思ったことがないという話には驚きました。BiomeやDenoのような高速に動作する競合ツールの台頭がPrettierの方針に影響を与えているところを見ると、やはり競合の出現が成長を促すこともあると実感しました。 タイミーでは今メインで開発を進めているプロジェクトでPrettier + ESLintを活用しています。Biomeへの全面的な移行という可能性もありますが、ESLintのルールを独自で作っているところもあるので、Prettierの今後の行く先を見守りたいなと思いました。 まとめ 今回はTSKaigiのセッションの内容をいくつか抜粋して紹介させていただきました。どのセッションも非常に興味深く、学びになるものでした。最後の懇親会では多くのエンジニアの方と交流させていただき、とても楽しかったです! また来年もTSKaigiが開催されるとのことでしたので、来年もお会いしましょう!
こんにちは、タイミーのデータアナリティクス部でデータアナリストをしている山本です。普段は主にマーケティング部の向き合いとして、分析業務に従事しています。 先日、社内の資格支援制度も活用しながら統計検定準1級(CBT)を受験し合格しました。不合格を経て合格に至ったため、実際の学習の流れやデータアナリストに統計検定準1級がおすすめできるポイントなどをご紹介したいと思います! 試験について 準1級のレベル感 「実社会の課題に対する適切な手法の活用力」というレベルで具体的には2級までの基礎知識をもとに、実社会の様々な問題に対して適切な統計学の諸手法を応用できる能力を問うものとされています。( 統計検定公式HP ) 試験範囲 試験結果レポートでは「確率と確率分布」「統計的推測」「多変量解析法」「種々の応用」と分けられていますが、非常に広範囲をカバーしています。(公式の範囲は こちら ) 受験形式 CBT形式により、都合の良い試験日時や会場で受験が可能です。 受験の動機 統計知識を体系的に整理し幅広く基礎的な理解を身につけたかったため。 実務で活用できる分析アプローチの幅を広げたかったため。 弊社での 資格支援制度 が活用できたため。 合否に関わらず、受験費用を支援してくれるため積極的に挑戦できました。 学習開始時の状況 新卒から4年間データアナリストとして働いており、本試験の範囲内に理論の理解や実践経験がある箇所が含まれている。 2年前に統計検定2級を取得済み。 数学力は文系大学卒レベルだが、経済学部卒で数式に比較的抵抗はない。 受験結果 1回目は「3.多変量解析法」「4.種々の応用」の後半範囲の理解が如実に甘く、不合格に終わりました。 約10ヶ月後に再受験をして見事合格🌸優秀成績賞をいただきました。 4つのセクション全てで合格基準の60%を超えることができていたのが嬉しかったです! 教材について メイン教材 日本統計学会公式認定 統計検定準1級対応 統計学実践ワークブック 日本統計学会公式認定 統計検定 準1級 公式問題集 サブ教材 Youtube動画やWeb上の公開記事 社内勉強会 統計学入門や時系列分析、ベイズ統計に関わる輪読会などがあり、間接的に理解の習熟やモチベーション維持の助けになりました。(勉強会の詳細は こちら ) 学習方法について 不合格時、合格時の学習方法と習熟のレベル感を振り返っているのでこれから受験される方の参考になれば嬉しいです。 学習期間 合格時、不合格時の双方で受験前約2ヶ月間で短期集中で学習しました。自分としては、これ以上の長期の学習期間を取ることがモチベーション維持観点で厳しかったです。 学習方法と習熟レベルの振り返り 1度目の受験(不合格)まで 上記の統計学実践ワークブックが試験範囲を網羅している参考書なので、各章を読み込んで理解→章末の演習問題を解くという流れで学習しました。分からないところは詳しい解説を探しつつ、全32章に及ぶため疑問点が解消されない場合はマークだけつけて飛ばして先の章に取り組み、まずは範囲を一周することを優先しました。 2周目として1周目でマークをつけている理解が浅い箇所の再学習し、公式問題集の過去問を3年分ほど解き受験に挑みました。 1度目の受験結果をうけて 点数的には52点で、不合格でした。あと8点で合格点という点差以上に自分の理解度合いが足りていない感覚を受けました。特に統計学実践ワークブックの演習問題が解けるだけのレベルでは試験問題には対応できず、より本質的な理解や数式での理解ができているレベルが求められていると感じました。 2度目の受験(合格)まで 約半年開けて再度学習するモチベーションが湧いてきたので、再挑戦することを決めました。教材としては引き続き統計学実践ワークブックを用いて1度目の受験で点数が取れていなかった領域を中心に学習し直しました。各章を自分の言葉で説明できる理解度を目標に取り組み、特に回帰分析、多変量解析、時系列解析、分散分析などの範囲は数式や導出を丁寧に追いかけて暗記に頼らない理解を心がけました。 学習時間について 平日は終業後に1~1.5時間、休日は土日合計で4~5時間ぐらいを目安に時間を確保していました。1ヶ月で50時間弱ぐらいになるボリューム感です。 予定があったり、業務が忙しいタイミングがあったりするのできっちり時間を確保するというより、週間単位で全然時間を確保できなかったとはならないように帳尻を合わせていました。 また机に向かうモチベーションが湧かない時でもテキストを流し読むとか関連動画を見るとかしながら、学習から離れたからモチベーションが下がってきたという状態にならないように気をつけていました。 データアナリストに統計検定準1級がおすすめできるポイント 幅広い範囲を体系的に理解できるため、分析業務をする上での引き出しの幅が広がり業務に活かせるところ。教科書通りの分析手法を業務でそのまま使えることは稀ですが、多くの選択肢の中からより良い手法の選択をできるようになったと感じます。 統計モデリングや因果推論、機械学習などさらに発展的な内容を身につける際の基礎になるためキャッチアップがスムーズになるように感じるところ。 回帰分析などPythonのパッケージを使ってアウトプットを出している分析手法の導出方法や考え方の理解を深められ、活用できるタイミングや注意点に気づけるようになるところ。 最後に 今回は統計検定準1級の受験体験記として、学習の流れやデータアナリストにおすすめできるポイントなどを紹介させていただきました。 弊社のデータアナリストは分析手法の選定が基本的にメンバーに権限が委譲されており、弊社のデータアナリストには、分析方法を自分で選ぶ自由が与えられています。そのため、さまざまな分析の引き出しを持っていることが、ビジネスに貢献するために非常に重要です。資格取得を通じて得た学びをどんどん実務において活かしていけるように努めたいと思います! We’re Hiring! 私たちは、ともに働くメンバーを募集しています!! データアナリストのポジション カジュアル面談 も行っています。 統計知識を活用した業務できるので、少しでも興味がありましたら、気軽にご連絡ください。
イベント概要 2024年2月21日に「GENBA #2 〜Front-End Opsの現場〜」と題してタイミー、Sansan、ココナラ、X Mileの4社でFront-End Opsに関する合同勉強会を開催しました。 今回はそちらの勉強会からタイミーフロントエンドエンジニアのyama_sitterさんの発表をイベントレポートでお伝えします。 2023年9月にタイミーにジョインしたやましたです。よろしくお願いいたします。前職ではスクラムマスターやEMを担っており、タイミーで久々にエンジニア復帰しています。 1. どんな状況で何が起きたか 今回はフロントエンド特有の問題に対し、あらゆる施策を実施してきた結果、「結局、コミュニケーション大事!」という話をします。まずはフロントエンド特有の課題やタイミーでの状況を整理します。 浅く広くフロントエンドに向き合っている フロントエンドは1機能・1画面に幅広いドメインが集約されることが多く、その特性上「浅く広く」になりがちな印象があります。さらに新たな技術やフレームワークの導入が頻繁にあり、この点でも大変です。もちろんバックエンドも新しい技術やパターンが導入されることはありますが、フロントエンドほどの頻度や速度ではないことが多いと思います。 タイミー独自の環境 タイミーでは、多様な機能を開発するfeatureチームがあり、フロントエンドエンジニアもこれらのチームに参加しています。一応「chapter」と呼ばれる職能別の横断組織はあるのですが、あくまで主戦場はfeatureチームのため、横断課題の優先順位がすごく上がりづらい状態です。各チームが自分のタスクに専念するあまり、組織全体の課題に目を向けることが難しいことがあります。さらに、フロントエンドの担当範囲は一つのサービスやリポジトリに限られがちで、その結果、誰がどの部分を担当しているのかが不明瞭になることがあります。また直近は、フロントエンドエンジニアの数が増え、リモートワークが常態化することで、コミュニケーションの複雑さはさらに増しています。 この状況下で何が起きたか チームメンバー個々では、全ての領域を網羅することが困難です。特定の外部サービスの運用などは、有志の自発的な取り組みに依存しています。知識の共有が限定的なグループ内でしか行われず、組織全体への伝搬が不十分になるため、一部の課題に対して担当者が固定され、柔軟な対応が困難になっています。横断的な課題に対する担当者の割り当てが難しく、解決に至らないことが多いです。多くの重大な横断的課題が放置されたままです。「一旦起票します」と起票はするものの、それ以上の進展が見られないケースも多くありました。 広さに立ち向かうための運用や体制が別の課題を生む タイミーでフロントエンドの広範な業務に対応するために構築した体制や運用が、意図せず別の課題を引き起こしていると考えました。 積み重なる横断課題 業務の属人化 地層化 伝搬されない知識 解決策が新たな問題を生んでしまう、一種のジレンマと言えるでしょう。 2. 何をしてどうなったか 横断課題の整理とリファインメントの実施 私が最初に着手した施策です。チームメンバーの課題認識を一致させるために、まず横断的な課題の洗い出しと詳細化を行いまし た。これは具体的な問題解決だけでなく、今後の方向性や現状認識の共有にも役立ちました。 「改善デー」の導入 フェデックスデーや20%ルールのようなイメージで、これまで2回「改善デー」を開催しています。この日は、メンバーが一堂に会し、解決したい横断課題を自由に選んで取り組みます。活発なプルリクエストのやり取りがあり、「一旦、起票します」という言葉で終わらせることなく、実際に成果を出すようになりました。 業務知識の共有 知識の属人化を防ぐため、不定期に知識共有会を開催しています。もちろん即座に「知識」となるわけではありませんが、「知らない」ことを減らすことに成功しています。※私が主導する前から不定期で実施されていました。 アーキテクチャの定例の開催 私が最も注力している取り組みです。フロントエンドのアーキテクチャが地層化することへの懸念から、どのようなアーキテクチャが望ましいかを話し合う場を設けています。私が1人で考えていても解決できない課題に対し、多様な意見が出されることで、改善への一歩となっています。 テックトークの導入 技術的な話題にフォーカスした「テックトーク」を導入しました。これは任意のメンバーが集まり、フロントエンド開発に関連する技術的な話題について議論する場です。 3. 重要なことは何だったか これまで紹介してきたことをまとめると、要するに「コミュニケーションが大切だよね」ということです。日常的な共有や議論、意見の交換を通じて、チーム全体が同じ方向を向いて進めるようになりました。 選ばれたのはコミュニケーションでした フロントエンドの広さゆえのメンバー間の思考や課題感の違いを理解するために、コミュニケーションが中心的な役割を果たしていることが分かりました。フロントエンドの領域が拡大し続ける中、コミュニケーションによって多くの問題に対処できていると感じています。チームやドメインが拡大する中でも、そのスピードに遅れを取っていないと思います。 ただし、リモートワークには固有の課題があります。チャットだけではコミュニケーションの限界があると感じています。私はリモートワークが好きなので継続するためにも、リモートでも効果的なコミュニケーションを図る方法を模索しています。 注:コミュニケーション = 雑談ではない コミュニケーションについて多く語りましたが、目的は単なる雑談を促すことではありません。雑談も重要ですが、重要なのは課題を特定し、それに対するコミュニケーションの方法を模索することです。 人に依存しない仕組み化が重要 コミュニケーションはもちろん、人に依存しない仕組みを構築することも非常に重要です。コミュニケーションは問題解決のトリガーに過ぎません。根本的な解決には仕組みが必要です。チームが拡大し、単純なコミュニケーションだけでは対応できなくなった現在、仕組み化を進めることの重要性を強く感じています。 4. これから考えていきたいこと あらゆる施策を実施するなかで、改善の兆しは見えつつありますが、課題もあります。 課題を整理し「旗」を立て直す 多岐にわたる取り組みが散発的になってしまい焦点がぼやけがちです。線ではなく点の施策をたくさん実施してきましたが、これではいくら各コミュニケーションが良くても、離散してしまいます。今後は、課題を明確にし目標を再設定する必要があると考えています。 例えば、リファインメントのセッションが「しーん」と静かになる瞬間があることや、限られたメンバーのみが参加する状況は、チームの一体感を損ないます。また、課題の認識に齟齬が生じたり、優先順位が不明確になることも問題です。 これらの問題に対処するためには、共通の目標を示す「旗」を立て直し、チーム全体が同じ方向を向いて進めるようにすることが重要だと考えています。 まとめ フロントエンドは浅く「広く」なりやすい この「広さ」に立ち向かうためには「コミュニケーション」が重要 課題を捉え、そのために必要なコミュニケーションの形を模索する 但しコミュニケーションそのものは解決「策」ではないので注意 その他の方の発表も気になる方はこちら! www.youtube.com 少しでも興味を持っていただいた方は是非こちらからカジュアルにお話しましょう! product-recruit.timee.co.jp
はじめに こんにちは!タイミーでデータアナリストをしているhatsuです。 私は普段、タイミーの営業戦術などについての分析に携わるほか、社内でのデータ利活用を推進する取り組みを行っています。 今回は、社内のデータ利活用推進に取り組む中で、これまで定期的に開催していたBIツールの社内講習会の運営方法を見直した話をご紹介したいと思います。 従来のLooker講習会における問題点 タイミーでは、社内のデータ利活用推進のため、LookerというBIツールを導入しています。 このLookerというツールをより多くのメンバーに活用してもらうため、これまでにも社内でLookerの使い方をレクチャーする講習会、通称「 Looker講習会 」を定期的に実施してきました。 従来のLooker講習会はオンラインのウェビナー形式で、40~90人ほどの人数を対象に実施していました。 しかし、講習会実施時にとったアンケートや、社内メンバーとの会話の中で、従来のLooker講習会には以下のような問題点があることがわかりました。 受講者側の問題 大人数(40~90人程度)の前だと質問しにくい 講習会の内容についていけない 説明の途中に質問をしにくい 講師・運営側の問題 受講メンバーからのリアクションや質問が受け取りにくく、説明が伝わっているかがわからない Looker講習会はLookerの基本的な使い方をマスターするための講習会であり、受講者もLookerをほとんど使ったことがないメンバーが大半を占めます。 そのため、講習会の内容が基礎的なものであってもつまずく受講者は多く、それゆえ「質問しにくい」という現状は看過しがたいものでした。 少人数制のLooker講習会の概要 これらのLooker講習会の問題点を解消するために、参加人数を10人以内に絞った少人数制のLooker講習会を開催してみることにしました。 参加者の人数を少なくすることで質問する際のハードルが下がり、講習会の内容についていけない受講者のサポートをしやすくすることが狙いです。 また、参加人数以外にも、開催方法をオンラインからオフライン(対面)に変えました。 講習会をオフラインに変えることにより、受講者のリアクションを汲み取りやすくなるほか、講習会に付いていけていない受講者を講師側のサポートメンバーが個別にサポートできるようになると考えたためです。 さらには、従来の講習会用の資料に沿って進めるのではなく、実際にLookerの操作画面を見せながら操作方法を説明するように講習会の内容も少し変更しました。 これにより、受講者は自分のLooker操作画面と講師のLooker操作画面とを見比べながら操作を覚えることができます。 少人数制Looker講習会をやってみた結果 新しい方式でLooker講習会を実施したところ、講習会の途中にも質問が飛び交い、従来のオンラインでの講習会よりも講師と受講者のコミュニケーションをとりながら講習会を進めることができました。 また、オフラインでの開催だったため、隣の座席の受講者同士で教え合う様子なども見られ、置いてけぼりの受講者が従来より生まれにくくなった手応えを感じました。 実際、講習会後にとったアンケートでも「ちゃんと初心者向けでついていけた」「初歩的な質問でもすぐに回答頂けたので、とても理解できた」などの感想が多く見られました。 Looker講習会の今後 今回実施した少人数制のLooker講習会にも、まだまだ課題は残っています。 例えば、現在の方式では一度に少ない人数しか参加できないので、社内全体へLookerの使い方を広めるには講習会の回数を重ねる必要がありますし、講習会をオフライン開催のみにしてしまうと、地方支社に所属するメンバーにLookerの使い方を学ぶ機会はなかなか提供できません。 これらを解決していくため、今回の少人数制のLooker講習会の取り組みを踏まえ、今後も継続してLooker講習会の運営方法の改善を続けていこうと考えています。 We’re hiring! タイミーでは、一緒に働くメンバーを募集しています。 https://hrmos.co/pages/timee/jobs カジュアル面談 も実施していますので、少しでも興味を持っていただけましたら気軽にご応募ください!
こんにちは、タイミーでデータサイエンティストとして働いている小栗です。 先日、群馬大学にご招待いただき、大学生向けにキャリアに関する講演を行いました。 講演や学生との交流を行うにあたり、データサイエンティストの仕事やキャリアについて考える時間が自然と発生しました。 この記事では、 学生からいただいた以下の質問をテーマに据えて、私やタイミーの事例を紹介しつつ考えてみます 。 大企業とベンチャー企業のデータサイエンティストはどう違う? 未経験からデータサイエンティストを目指すには? 大学生向けに講演を行いました 今回、 「群馬大学 グローバルフロンティアリーダー(GFL)育成プログラム」 の同窓会にご招待いただき、大学生向けに講演を行いました。 私自身もGFL育成プログラムの修了生であることから、今回は講演のご依頼をいただき、発表を行いました。その後、学生との座談会や交流会に参加させていただきました。 当日の様子↓ 1月21日(日)に第4回GFL同窓会が開催されました。 既卒生の講演、座談会、自由交流など、現役生・既卒生の枠を超えた交流は有意義な時間となりました。 様々な活動に挑戦している先輩方は本当に素敵です! また、卒業後長らく会えていなかった既卒生同士の感動の再会もありました。 pic.twitter.com/NjRgoMF22p — 群馬大学GFL (@GundaiG) 2024年1月29日 学生からの印象的な質問 講演では、私のキャリア、データサイエンティストの仕事内容、タイミーに関する紹介、学生へのアドバイスなどを中心にお話ししました。 質疑応答や座談会のなかで、エネルギッシュな学生のみなさんから興味深い質問をたくさんいただきました。 その中でも、データサイエンティストとして働く立場から重要と感じた2つの質問について取り上げて考察してみます。 質問1:大企業とベンチャーの仕事や働き方はどう違いますか? 質問2:未経験からデータサイエンティストになるにはどうしたら良いですか? 大企業とベンチャー企業のデータサイエンティストの違い 大企業とベンチャー企業の違いは、就職を控えた学生にとっては非常に重要でありながらも、イメージが湧きづらいテーマだと思います。 私はこれまで社員として中小規模の事業会社、AIベンチャー、Web系大企業、タイミーと渡り歩きつつ、フリーランスとしても数社で働いてきました。 その経験をもとにして、大企業とベンチャー企業のデータサイエンティストの違いについて考えてみます。 ただ、大企業とベンチャー企業という括りはあまりにも広すぎるため、 「Web系の自社開発企業」に限定して、両者のデータサイエンティストの仕事内容や働き方の傾向について述べます 。 あくまでも 独断と偏見に基づく内容 なのと 傾向の話 なので、その点はご了承ください…! 担当業務の幅と成長の方向性に違いが出やすい 両者の違いは「担当業務の幅(≒専門性の深さ)」に大きく現れると個人的には考えています。 大手の自社開発企業では、自社内の事業、技術、データ、業務に関する知識の深さを求められることが多いと感じています。 これは当然の話で、大規模の会社・サービスをワークさせるためには、専門性を明確化して細かくチームを分化する組織デザインを行う場合がほとんどであり、その結果として、個人に求められる担当業務の幅は狭くなり、求められる専門性は深くなります。 あくまで極端な一例ですが「AさんはシステムXの機能Yを支えるロジックZの開発を担当しており、そこで使用するデータや自然言語処理のWという手法の数理と実装に詳しい」みたいな状況が生まれます。 そのような環境では、 各メンバーが自身の担当業務のドメイン知識、データ、使用技術に対する深い知識を持つ必要があり、自然と特定のドメインや技術に尖る方向に成長する傾向がある ように思います。 また、専門性とチームが細分化されているため、同じ会社内のデータサイエンティストでも部署やチームによって業務内容、使用技術、働き方が大きく異なることがあります。 先ほどは機械学習(ML)プロダクトの開発に携わるデータサイエンティストを例に挙げましたが、他の部署・チームではビジネス施策の効果検証をやっていたり、新規技術のR&Dをやっていたりすることもあり得ます。 一方、自社開発ベンチャー企業では業務の幅が比較的広いことが特徴です。 会社とサービスが発展途上であることや、抱えるメンバー数に限りがあることから、 データサイエンティスト以外のロールと連携しつつ、データ分析したりプロダクトや機能の提案・導入をしたりする仕事の割合が高くなり、一人が持つ業務の幅が広くなりやすい です。 もちろん高い専門性を持つに越したことはないのですが、自身の専門性に捉われすぎずに、会社とサービスの成長のために必要な仕事をスピーディに行う必要も出てきます。 以上のことから、大企業のデータサイエンティストと比較すると、 ベンチャー企業のデータサイエンティストはジェネラリスト寄りの成長をしていく傾向がある と思います。 両者を区別しやすい軸は他にも、保有データのリッチさの違い、データ/ML基盤の充実度の違い、裁量の大きさの違いなど挙げればキリがないのですが、全てを語っているとスペースが足りないのでこの辺にしておきます。 タイミーのデータサイエンティストの仕事内容や働き方は? ではタイミーのデータサイエンティストはというと、 ベンチャーの仕事内容と働き方に非常に近いものがありつつ、大手企業への遷移の初期段階 という印象を持っています。 タイミーは データ基盤 、 ML基盤 がかなり整備されてきており、 本番稼働・運用フェーズにあるMLプロダクト も複数存在します。 データサイエンティストがデータ分析、MLモデリング、MLパイプライン開発などの開発業務に集中できる環境が整ってきたと感じています。 一方で、データ/MLプロダクトの提案・PoC・導入フェーズなどにおいて他部署・チームと連携することも多く、ベンチャーらしく広く裁量を持って仕事に取り組むことができます。 未経験からデータサイエンティストを目指す際に意識すること 2つ目の質問は、情報系の専攻ではないがデータサイエンティストに興味がある学生の方からいただきました。 実は私も大学院までは化学を専攻しており、20代中盤になってデータサイエンスやプログラミングに初めて触れて、そこからデータサイエンティストになりました。 その経験をもとに、キャリア初期(大学卒業 ~ 20代あたり)において未経験からデータサイエンティストを目指す場合に意識したいことを考えてみます。 データサイエンティストを目指す意義と発揮できる価値を熟考する 当然の話として、未経験からデータサイエンティストを目指す場合、機械学習や統計学、それに類するバックグラウンドを持つ人と労働市場では競合することになりますし、就職後も彼らと共に仕事をすることになります。 分野自体がホットなために、機械学習やデータサイエンスを学生時代から専門とする人が増えており、競争は激化しやすいと思われます。加えて、生成AIの興隆もあり、数年先の市場環境すら見通せません。 激しい環境の中、 これまで築いた専門性を半ば捨て去ってデータサイエンティストを目指す意義と、自身がデータサイエンティストとして発揮できる/したい価値を、やはり前もって熟考すべきだと思います (私は正直そこまで深く考えられていなかった)。 また、現在の専門性と掛け合わせたキャリアを歩むことが出来ると人的/金融資本という意味では有利なので、そういった道も模索すると良いと思います。 自分自身で熟考した結果、「別に問題ない」「人生にとってネガティブな影響も受け容れて進みたい」ということであれば、 それは素晴らしい選択になる と私は信じています。 想像以上にソフトスキルが重要 私がデータサイエンティストになる前となった後で大きな認知ギャップを感じたのは、ソフトスキルの重要さでした。 機械学習・統計学の理解、ソフトウェア開発に関する技術力をはじめとした ハードスキルが重要なのは大前提として、その価値を引き出せるかどうかはソフトスキルに依存します 。 例えば、分析結果をビジネス上の意思決定に活かすには、相手(データの専門家とは限らない)の立場に立って分析の意義や示唆を明確に示し、丁寧に説明する必要があります。 ソフトウェア開発の場合でも、チームで仕事をするケースがほとんどであり、コミュニケーション力に長けていれば物事がスムーズに進みます。 …自分で書いていて耳が痛い内容になってきました。 ソフトスキルが低いと話にならない職業ではないものの、 高いハードスキルに立脚しつつもソフトスキルが高い人、ソフトスキルを高める姿勢がある人が活躍しやすい職業 だと思っています。 タイミーのデータサイエンティストが有するスキル・経験は? 他に重要なトピックとして、機械学習・統計学・プログラミングをはじめとしたハードスキルの習得がありますが、このトピックはすでに界隈内で議論し尽くされた感があるので、ここでは深く言及しません。 その代わりとして、タイミーに所属するデータサイエンティストが有するハード系のスキル・経験について簡単にご紹介します。 データ分析、MLモデリング 効果検証、因果推論 ML周りのソフトウェアエンジニアリング MLパイプライン構築、MLシステム設計・開発、クラウドにおける開発、Gitを用いた開発、など 使用言語はPythonとSQL(全員不自由なく書ける) もちろん上記すべてのスキルを全員が高いレベルで保有するわけではないですが、 それぞれのwill/canを生かして相乗効果を生み出しながら働いています 。 おわりに ここまで、大学生への講演を通して感じた、データサイエンティストの仕事内容や未経験就職について、私やタイミーの事例を交えながら紹介しました。 エネルギッシュな学生のみなさんとの交流・議論を通して、自分やデータサイエンティストのキャリア、そしてタイミーについて客観的にみつめ直すことができました。 大学での講演機会をいただいたのは初めてでしたが、とても楽しく有意義な時間になりました。 貴重な機会を与えてくださった群馬大学GFLの学生・スタッフのみなさまに、深く感謝申し上げます。 今回の記事の内容が、読者のみなさまのお役に立てば幸いです。 We’re Hiring! タイミーでは、データサイエンティストやエンジニアをはじめ、一緒に働くメンバーを募集しています! hrmos.co カジュアル面談も随時行っておりますので、ぜひお気軽にお申し込みください! product-recruit.timee.co.jp
はじめに こんにちは、タイミーでAndroidエンジニアをしているsyam(@arus4869)です 昨年、「 チームで育てるAndroidアプリ設計 」という本について、計10回にわたって輪読会を実施しました。本書は「アーキテクチャとチーム」に焦点を当てた一冊になっており、タイミーのAndroid組織の技術顧問としてさまざまなサポートをしてくださっている釘宮さん(@kgmyshin)が著者として名を連ねている本になります。 この記事では、技術顧問の釘宮さんとAndroidメンバーでの輪読会で得た学びをシェアできたらと思っています。 輪読会の説明 週に1回テーマを設けてAndroid会という勉強会を実施しています。 勉強会の中では、miroを利用した輪読会を実施しています。 輪読会は参加者の「感想」や「勉強になったこと」を共有し、「わからなかったこと」、「話してみたいこと」について議論しながら深掘りをし、学びを得ています。 学びと実践への応用 セクションごとに学びがありましたが、特に実践へ応用された部分について抜粋して紹介しようと思います。 アーキテクチャを図にしてREADMEに見える化しました。 第2章の中にある多層アーキテクチャの例が非常に参考になったので、今のタイミーのアーキテクチャはどうなっているかも気になり、miroを利用した輪読会ならではの「その場で図にする」というワークを行いました。 下記は輪読会中に実際に図を描いた様子です。 Miroのワークの様子 タイミーでは、4層アーキテクチャから3層アーキテクチャに変遷した歴史があり、今もレガシーコードとして残っているので、新旧のアーキテクチャをREADMEに記載することにしました。この時に明確にできたおかげで、新規参画者への説明のハードルが下がり、輪読会の議論がとても良いきっかけになりました。 下記は輪読会で図にしたものを改めて整理して、READMEに記載したものです。 アーキテクチャの構成図 モジュールごとにREADMEを用意することにしました。 本書の第3章ではアーキテクチャの理解と適用を促進し、チームの生産性向上にどう貢献するか紹介されていました。特に理解の促進のためには、アーキテクチャの概要図やパッケージ構成、各層、クラスの説明をドキュメント化することが推奨されており、チームでもREADMEに記載することになりました。 また輪読会の中では、モジュールにも説明が必要ではないか?と議論が発展しモジュールの説明も各モジュールごとに用意することになりました。 モジュールについての議論 タイミーのAndroidアプリでは、Featureごとにモジュールを分割しているので、モジュール数が多岐に渡っており、モジュールの解釈に認識齟齬が発生するリスクがありました。モジュールに対してもREADMEを記載するアプローチを生んだのは、良い議論に発展したおかげだと思います。 また、モジュールの書くべきことについては、本書に書いていない内容だったので、筆者の釘宮さんに改めて確認することができたのは、筆者が参加する輪読会ならではでとても有り難かったです。 終わりに タイミーでは定期的に輪読会を開催しております。 輪読会では、本を読んでただ学ぶだけでなく様々な議論がおこなわれ新しい洞察を発見する場となっています。 本輪読会で取り扱った「 チームで育てるAndroidアプリ設計 」についても新しい洞察が得られ実際に実務への応用に発展しました。是非一度よんでみてください! 少しでもタイミーに興味を持っていただける方は、ぜひお話ししましょう! product-recruit.timee.co.jp