TECH PLAY

タイミー

タイミー の技術ブログ

264

こんにちは、タイミーでデータアナリストをしている 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
アバター
こんにちは。バックエンドエンジニアの須貝( @sugaishun )です。 今回はタイミーが本番運用しているRailsアプリケーションに対してRuby3.3.0へのアップデートを行った(YJITは引き続き有効なまま)のでその結果をご紹介したいと思います。 昨年弊社の id:euglena1215 が書いたエントリーのRuby3.3.0版です。 tech.timee.co.jp 前提 タイミーのWebアプリケーションとしての特性は基本的には昨年と変わりありません。ですので、昨年の内容をそのまま引用させてもらいます。 タイミーを支えるバックエンドの Web API は多くのケースで Ruby の実行よりも DB がボトルネックの一般的な Rails アプリケーションです。JSON への serialize は  active_model_serializers  を利用しています。 今回の集計では API リクエストへのパフォーマンス影響のみを集計し、Sidekiq, Rake タスクといった非同期で実行される処理は集計の対象外としています。 今回はRuby3.2.2+YJITからRuby3.3.0+YJITへアップデートを行い、パフォーマンスの変化を確認しました。 結果 以下のグラフはAPIリクエスト全体のレスポンスタイムの50-percentileです。 グレーの点線がアップデート前の週で、青い線がアップデート後の週になります。集計した期間ではアップデート前後の平均でレスポンスタイムが 10%高速化 していました。 今回も前回にならってレスポンスが遅く、時間あたりのリクエスト数が多いエンドポイントに注目し、タイミーのWeb APIのうち3番目に合計の処理時間が長いエンドポイントへのパフォーマンス影響を確認しました。 以下のグラフは3番目に合計の処理時間が長いエンドポイントのレスポンスタイムの50-percentileです。こちらも同様にグレーの点線がアップデート前の週で、青い線がアップデート後の週になります。集計した期間ではアップデート前後の平均でレスポンスタイムが 約12%高速化 していました。 またECSの起動タスク数にも良い変化がありました。 タイミーではCPU使用率が一定の割合になるようにタスク配置する設定をしているのですが、リリース後は起動タスク数が減りました。リリース前後1週間の比較で下記のように変化しています。 平均で33.1 tasks → 30.36 tasks 最大値で58.6 tasks → 53.0 tasks このあたりはYJITの効果でリクエストに対するCPU負荷が下がった影響ではないかと推測しています。メモリ上に配置した機械語を実行するJITならでは、という感じがします。コスト的にどれだけインパクトがあるか具体的な数値は出せていませんが、パフォーマンス以外のメリットもありそうです。 と、ここまでは良かった点です。 以降では自分たちが遭遇した事象について述べたいと思います。 一部のAPIでメモリ使用率が増加 タイミーのRailsアプリケーションはモノリスですが、ECS上ではネイティブアプリ向けのAPIとクライアント様の管理画面向けのAPIはそれぞれ別のサービスとして稼働しています。前者のネイティブアプリ向けAPIでは特に問題なかったのですが、後者の管理画面向けAPIでは メモリ使用量の最大値が約3倍超(20%弱→65%程度)になりました。 以下のグラフの赤いラインがRuby3.3.0にアップデートしたタイミングになります。 さすがに3倍超は困ったなと思い、 YJITのREADME を読んだところ下記のようにありました。 Decreasing --yjit-exec-mem-size The  --yjit-exec-mem-size  option specifies the JIT code size, but YJIT also uses memory for its metadata, which often consumes more memory than JIT code. Generally, YJIT adds memory overhead by roughly 3-4x of  --yjit-exec-mem-size  in production as of Ruby 3.3. You should multiply that by the number of worker processes to estimate the worst case memory overhead. We use  --yjit-exec-mem-size=64  for Shopify's Rails monolith, which is Ruby 3.3's default, but smaller values like 32 MiB or 48 MiB might make sense for your application. While doing so, you may want to monitor  RubyVM::YJIT.runtime_stats[:ratio_in_yjit]  as explained above. メタデータ( yjit_alloc_size )の増え方が想定以上なのではと思いつつ、ddtrace *1 では yjit_alloc_size は送信していないようなので、 code_region_size を確認。実際、YJITの code_region_size (JITコードのサイズ)とメモリ使用率はほぼ同じ動きをしていました。以下のグラフの左が code_region_size で、右がメモリ使用率です。 というわけで --yjit-exec-mem-size をデフォルトの64MiBから32MiBに減らしたところ、メモリ使用量はアップデート前より少し増えた程度の水準まで抑えることができました。なお、この変更によるパフォーマンスへの影響は見られませんでした。 以下の右のグラフがメモリ使用率で、赤いラインが --yjit-exec-mem-size の変更をリリースしたタイミングになります。実際にメモリ使用率が下がっているのが見て取れます。 今回のメモリ使用量の大幅な増加は完全に予想外で、ステージング環境でもメモリ使用量の変化はウォッチしていましたが、特に大幅な増加は見られず本番環境で初めて発覚する事態になりました。すでにRuby3.2系でYJITを有効にしているプロダクトでも、Ruby3.3.0+YJITにアップデートされる際には --yjit-exec-mem-size の値には注意したほうが良さそうです。 まとめ Ruby3.2ですでにYJITを有効にしているプロダクトでもRuby3.3.0にアップデートしてパフォーマンスの改善が見られました(YJITの影響なのかそれ以外の最適化による恩恵なのかまでは検証しておりません)。 すでにYJITを有効にしていた場合でもRuby3.3.0へのアップデートでメモリの使用量が大幅に増加する可能性があるので注意しましょう。 調査の際には下記の(主にk0kubunさんの)ドキュメントに非常に助けられました。この場を借りてお礼を申し上げます。 github.com k0kubun.hatenablog.com gihyo.jp 本エントリーがこれからRuby3.3.0+YJITへアップデートされる方のお役に立てば幸いです。 *1 : タイミーはDatadogを使っています。ddtraceはDatadogにメトリクスを送信するgemです
アバター
イベント概要 2024年2月21日に「GENBA #2 〜Front-End Opsの現場〜」と題してタイミー、Sansan、ココナラ、X Mileの4社でFront-End Opsに関する合同勉強会を開催しました。 今回はそちらの勉強会からタイミーフロントエンドエンジニアの西浦太基さんの発表をイベントレポートでお伝えします。 こんにちは、西浦 太基です。家族で北海道札幌市で暮らす33歳です。 Javaでキャリアをスタートし、今はフロントエンドエンジニアとして3年目になります。趣味はカレー作りとゲームです。 本日は「いつか来たる大改修のために備えておくべきこと」について、実際に大きな改修を経験して学んだこと、泥臭い現場の話や反省点を、エピソードベースでシェアします。 1. どんな大改修だったか 店舗向けの管理画面をシングルページアプリケーション(SPA)へ移行する改修でした。もともとサーバーサイドレンダリング(SSR)で構築されていたシステムを、ReactとNext.jsを使用したSPAに段階的にリプレイスしていきました。改修した機能は大きく下記3つです。 求人機能(作成/編集) 求人ひな形機能(作成/編集) メッセージ機能(一覧表示/送信) 2. 泥臭かったこと 既存機能の仕様調査に苦労 既存のSSR画面の詳細なドキュメントが残っておらず、非常に苦労しました。これは、タイミー入社前のSler時代もよく経験したことで、 “あるある” なのですが、状況を打開するためには非常に泥臭い調査が必要です。もちろんドキュメントが全く残っていないわけではなく、Figma や Notion に部分的に残されてはいたのですが、完全にはカバーされていません。おまけにソースコードの情報が必ずしも正確でないこともあり、かなりの時間と労力をかけ、調査を行いました。 E2E自動化の仕組みがなく、431件のE2Eテスト項目に自動テストを実施した ユニットテストはコンポーネント単位で存在していましたが、E2E自動化の仕組みが存在しませんでした。VRT(Visual Regression Testing)は、Storybook を用いて行われていたのですが、今回のプロジェクトの規模や顧客への影響を考えると、軽い検証を行うだけではリスクが大きいと判断し、結果的に 431件の E2Eテストを手動で泥臭く実施する道を選びました。 レビュー体制がなく、レビューコストが大きくなった 当時のフロントエンドチームはレビュー体制の整備が不十分で、メンバーも限られており、レビューコストが高くなってしまいました。チームは僕を含めてわずか2人と、非常勤の業務委託1人。レビューに十分な時間を確保するのが難しく、プロジェクトの進行に影響が出ていました。そのため、レビューを効率的に進めるために、一人のエンジニアの時間をまとめて確保し、一気にレビューを完了させる方法を取りました。本来は、フルリモートでの非同期レビューを行える体制が理想的だと思います。 3. 備えておくべきと感じたこと 機能の仕様は背景込みで残しておく 詳細なソースコードコメントや定義書があれば、将来の改善に役立ちます。特に複雑な機能や多くの入力項目を持つ場合、実装の背景を理解することで、後々の改善が容易になり、属人化を避けることができます。最近は、Figma や Notion に仕様と背景を含めて記録するよう心がけています。 自動化テストの仕組みを導入する これまでUTやVRTが充実していれば、そこまでE2Eは重要ではないと考えていました。しかし、今回のリプレイスを経て、自動化テストの重要性を再認識しています。E2Eによって、UTで検知できない問題をリリース前に特定できることがあり、改めてE2Eの重要性と自動化の利点を実感しました。 レビュー時の観点を整備 レビューの文化を根付かせておく 同時にレビュー時の観点や視点に関してチーム内で明文化 ドキュメント化することで普段からレビューコストを下げておく レビュー文化をしっかり根付かせつつ、レビューのポイントをチーム内でしっかりドキュメント化しておくことが大事です。ドキュメント化してないと、都度のすり合わせで時間を取られ、レビューコストが増えます。その結果、リリースサイクルも遅れたりするわけです。現在はフロントエンドチームの拡大もあり、レビュールールを明文化する作業を進めています。 まとめ 私がSler時代に直面した、「ソースコードを見なければ仕様が理解できない」といった “あるある” な内容も含め、日頃から大きなリプレイスに向けた心構えや備えが必要性を共有できたら嬉しいです。泥臭い作業を発見したら、悲観的になるのではなく「これは考え直すタイミングだな」というマインドを持つことも大切かと思います。 n個と言いつつそこまで数は挙げられなかった 日頃から大規模リプレイスに備えた心構えをしておくことは大事 (何が起こるかもわからないので)かもしれない運転 あるあるな内容になったと感じている Timee以外のプロジェクト(SES就業時代)でも直面した事が多かった 泥臭いこと゠備えておくべきこと、に繋げるマインドが大事に感じた その他の方の発表も気になる方はこちら! www.youtube.com 少しでも興味を持っていただいた方は是非こちらからカジュアルにお話しましょう! product-recruit.timee.co.jp
アバター
はじめに こんにちは、株式会社タイミーの貝出と申します。データサイエンティストとして働いており、直近はカスタマーサポートの業務改善に向けたPoCやシステム開発など行っております。 さて、今回は2024年3月11日(月)~3月15日(金)に開催された「言語処理学会第30回年次大会(NLP2024)」にオンラインで参加してきましたので、参加レポートを執筆させていただきます。 言語処理学会年次大会について www.anlp.jp 言語処理学会年次大会は 言語処理学会 が主催する学術会議であり、国内の言語処理の研究成果発表の場として、また国際的な研究交流の場としての国内最大規模のイベントとなっています。 今回の年次大会は第30回を迎え、発表件数が599件、参加者数(当日参加者は除く)が2045人と大会の規模も過去最大となっており、年々大会が盛り上がっていることが伺えます。 ※ 下のグラフは大会のオープニングで共有されたものです。 言語処理学会第30回年次大会に参加できなかった方でも、 こちら から発表論文が閲覧できます。 興味深かった研究 初日のチュートリアルから最終日のワークショップまで興味深い発表がたくさんありましたが、個人的に気になった発表をいくつかピックアップします。 [C3-4] InstructDoc: 自然言語指示に基づく視覚的文書理解 概要 こちらの研究では、自然言語指示に基づいて文書を視覚的に理解するための基盤データセット「InstructDoc」が提案されています。InstructDocは、12種類の視覚的文書理解(VDU)タスクから構成され、多様な自然言語指示を提供する最大規模のデータセットとなっています。 研究チームでは、大規模言語モデル(LLM)の推論能力を活用し、文書のレイアウトや視覚要素を同時に理解することが可能な新しいモデル「Instruction-based Document reading and understanding model(InstructDr)」を提案し、実験を通じてその性能を検証しています。InstructDrは、自然言語指示を基に未知のVDUタスクに適応し、従来のマルチモーダルLLMの性能を超えることが確認されました。また、指示チューニング済みのモデルの重みを初期値としてFine-Tuningすることで、複数のVDUタスクで世界最高性能を達成しました。 感想 こちらの研究では視覚的文書理解の汎化性能の向上に貢献されています。自然言語指示を用いて文書画像からタスクを汎用的に実行できる技術は、社内オペレーションの様々なタスクを容易にする可能性を秘めており、今後の研究にも期待です。 NTT人間情報研究所の方による以下の過去発表資料と今回の研究はリンクする内容だと感じており、合わせて読むことで全体像がイメージしやすかったです。 Collaborative AI: 視覚・言語・行動の融合 - Speaker Deck [A6-1] Swallowコーパス: 日本語大規模ウェブコーパス 概要 オープンな日本語言語大規模モデルの学習には、CC-100、mC4、OSCARなどのコーパスの日本語部分が用いられてきました。しかし、これらはあくまで海外で開発されたものであり、日本語テキストの品質を重視して作られたわけではありません。 そこで、研究チームは商用利用可能な日本語コーパスとしては最大のウェブコーパスを構築しました。Common Crawl のアーカイブ(2020 年から 2023 年にかけて収集された 21スナップショット分、約 634 億ページ)から、日本語のテキストを独自に抽出・精錬し、最終的には約3,121 億文字(約 1.73 億ページ)からなる日本語ウェブコーパス( Swallowコーパス )を構築しています。 Swallowコーパスは、「(1) Common Crawl の WARC ファイルから日本語テキストを抽出する。(2) 品質フィルタリングおよび重複除去で日本語テキストを厳選する。(3) テキスト内の正規化を行う。」の手順により構築されました。 Swallowコーパスを用いて Llama 2 13B の継続事前学習を行ったところ、既存のコーパスを用いた場合と比べて同等かそれを上回る性能の LLM を構築できたと報告されています。 感想 業務上LLMの日本語大規模コーパスを作ることはありませんが、自然言語処理のデータセットを作成するうえでのTipsとして大変勉強になりました。例えば、日本語判定をするためにサポートベクターマシンを学習させ fastText より高速化させた話や、MinHash による文書の重複判定など。 また、 [A8-5] では Swallow コーパスを利用した継続学習について詳しい内容が発表されており、そちらも面白かったです。 [ A7-6 ] AmbiNLG: 自然言語生成のための指示テキストの曖昧性解消 概要 大規模言語モデル(LLM)の登場により自然言語の指示を用いた指示によって様々な言語処理タスクが実行可能になりました。しかし、これらの指示の曖昧性によりユーザの意図と異なるテキストが生成されることが問題となっています。 こちらの研究は、自然言語生成(NLG)タスクでの指示テキストの曖昧性を解消するためのベンチマークデータセットとして「AmbiNLG」が提案されました。AmbiNLG でのデータセットの作成に LLM を用いてアノテーションを行い、幅広い29のNLGタスク2000事例からなるデータセットを構築されています。また、実験により曖昧性補完の手法については、実験により複数の曖昧性カテゴリを明示的かつ組み合わせで与えることが重要であると示唆されました。 感想 LLMを使いこなすためにはプロンプトを適切に調整することが重要だと言われていますが、指示テキストの曖昧性を能動的に指摘 or 修正できるような仕組みがあれば、よりユーザーフレンドリーなLLMを構築することが可能かと思われます。個人的にも欲しい機能です! 今後の展望では、曖昧性認識・追加指示の生成・推論をend-to-end で行う対話システムの構築について言及されていたので、実際にユーザの意図をどのようにシステム側で汲み取っていくかが気になります。 おわりに NLP2024では、他にも多数の魅力的な研究が発表され、5日間という期間が非常に充実したものとなりました。特に、大規模言語モデル(LLM)に関連する研究が目立ちましたが、その範囲はデータの構築から事実性や安全性の検証に至るまで広がっており、多様な角度からの研究成果を見ることができたのが印象的でした。 現在、タイミーでは、データサイエンスやエンジニアリングの分野で、共に成長し、革新を推し進めてくれる新たなチームメンバーを積極的に探しています! また、気軽な雰囲気での カジュアル面談 も随時行っておりますので、ぜひお気軽にエントリーしてください。↓ hrmos.co hrmos.co
アバター
こんにちは、iOSエンジニアの前田( @naoya_maeda ) 、早川( @hykwtmyk )、三好、岐部( @beryu )、Androidエンジニアのみかみ( @mono33 )です。 2024年3月22-24日に渋谷で開催されたtry! Swift Tokyoに、タイミーもGOLDスポンサーとして協賛させて頂きました。 私達もイベントに参加したので、メンバーそれぞれが気になったセッションを紹介します。 特に気になったセッション(前田編) 僕が特に気になったセッションは、リルオッサさんによる「SF Symbolsの芸術的世界:限りない可能性を解き放つ」です。 リルオッサさんは、iOSDC Japan 2022でも SF Symbolsアートの可能性 についてお話されており、今回のセッションでは、アニメーションを交えたダイナミックなSF Symbolsアートを紹介されていました。 SF Symbolsとは SF Symbolsは、Appleが提供するアイコンおよびシンボルのセットです。 WWDC 2019で初めて開発者に公開され、iOS 、macOS、watchOSで使用可能になりました。 SF Symbolsは、定期的にアップデートが行われ、新しいアイコンやシンボルの追加、パフォーマンスの改善が行われています。 SF Symbols 5とは SF Symbols 5では、5000を超えるアイコンおよびシンボルが提供されています。さらに、わずか数行のコードでアニメーションエフェクトを実現することができるようになりました。 「SF Symbolsの芸術的世界:限りない可能性を解き放つ」では、さまざまなアイコンやシンボル、そしてSF Symbols 5で追加されたアニメーション機能をフルに活用し、ダイナミックなSF Symbolsアート作品が紹介されています。 www.youtube.com 普段何気なく使用しているSF Symbols達が組み合わされていき、一つのSF Symbolsアート作品になる様は圧巻でした! また、SF Symbols 5には、様々なアニメーションエフェクトが用意されていると知ることができ、タイミーアプリ内でもSF Symbolsを活用していきたいと感じました。 今回ご紹介しきれなかった作品はGitHubで公開されているので、ぜひ一度ご覧ください! github.com ご登壇資料 www.docswell.com 特に気になったセッション(早川編) 今回、自分が気になったセッションは「 コード署名を楽しく乗り切る方法 」です。 このセッションではCertificate、Provisioning Profile、Application Identifierなど、アプリをリリースする上で必要なコード署名の要素を分かりやすくパズルに見立てて解説していました。 また、パズルに見立てることによってどこにエラーが起きているのかが分かりやすくなり解決するための要素の推測も容易になったかなと思います。 下の図で言うとProvisioning ProfileはCertificateとは問題なく結びついていますが、Application Identifierと正しく結びついていないためエラーが起きています。 解決するには「Provisioning Profileに結びついているApplication Identifierと一致するようにアプリ側のApplication Identifieを設定しなおす」か、「Provisioning Profileをアプリ側に設定されているApplication Identifierに合わせて作成しなおす」かになります。 各要素をパズルのピースに見立てることで、相互の結びつきが視覚的に解像度高く理解できました。 登壇者の Josh Holtz さんの発表内容も相まってコード署名を楽しく感じることができました笑 特に気になったセッション(みかみ編) 特に印象に残ったセッションは「マクロをテストする」です。今回のtry! Swiftでも注目を集めていた TCA を開発する、 Point-Free のStephenさんとBrandonさんによる発表です。 github.com 発表ざっくりまとめ マクロはSwift 5.9から導入されたコンパイル時にソースコードの一部を生成する機能です。主にボイラープレートを減らすことを目的に利用されます。iOSアプリ開発の世界ではSwiftUIのプレビューを簡潔に行う「#Preview」や新しいデータ永続化のフレームワークであるSwiftDataのモデル定義を簡略化する「@Modelマクロ」などの標準のマクロが数多く用意されています。 マクロは有益ではあるもののマクロによって生成されるコードが多ければ多いほどエラーが発生する可能性は高まります。特にマクロを実装する場合は生成されるコードも対象に、Swiftの文法のあらゆるエッジケースを考慮して多くのテストを行うことが望ましいです。 しかし、マクロのテストも多くの課題があります。例えばマクロで生成されたコードのエラーや警告がXcode上からわかりにくく、 Apple標準のマクロのテストヘルパーの文字比較の判定がシビアでフォーマットなどの本質的ではない部分でテストが落ちてしまうという問題があります。また、マクロの実装が変わった場合はテストを修正する必要がありメンテナンスも大変です。 そこでPoint-Freeが swift-macro-testing というテストライブラリを公開しています。swift-macro-testingは検証したいマクロのソースコードを assertMacro() に書き込むだけでどのように展開されるかを自動でテストコードに反映します。 感想 途中でライブデモがあったり、黒魔術的に生成されるテストコードを披露されたりとなんとなく会場も賑わっていた気がします。Androidにおいて自動生成を行うコードはたまに書くのですが、自動生成のコードをテストするというのは個人的に盲点で面白いなと思いました。 特に気になったセッション(三好編) 今回気になったセッションは、 平和に大規模なコードベースを移行する方法 です。 数あるセッションの中では、割と実用的ですぐに活用できるような内容でした。 セッション資料はこちら drive.google.com 破壊的変更を避ける方法 デフォルトパラメータを利用 メソッドを拡張するときに、パラメータを増やすとそれを利用する側すべての修正が必要になる デフォルトパラメータを利用することで、利用する側の変更なく機能拡張することができる protocol extensionを利用 プロトコルのメソッドを増やすと、それに準拠したclassやstructではそのメソッドを必ず実装する必要がある protocol extensionを利用し、デフォルトの実装を定義することで、利用する側は変更なく機能拡張することができる @availableの利用 命名変更も破壊的変更になるため Diagnosticのwarningを利用 enumの利用 caseが増えると、利用側でdefault実装がない場合変更が必要になる non-frozen enumだと、@unknown defaultの定義が必要なので、破壊的変更は避けられる しかし、non-frozen enumは開発者に提供されておらず利用できない enumのcaseそれぞれをstatic letで利用できるようにする Disfavoured Overload @_disfavoredOverloadを使う デフォルトパラメータを減らすなど 機能削減での破壊的変更を避ける目的で使えそう Finding Problem swift package diagnose-api-breaking-changes [ブランチ名] 上記コマンドで、指定したブランチの間で破壊的変更があるか教えてくれる いつ破壊的変更を行うのか 技術的負債とユーザーのイライラを天秤にかけた際に大きな偏りが生まれたとき @_disfavoredOverload等存在自体知らなかったので、とても勉強になりました。 来年の開催すると思うので、ぜひ皆さんも参加してみてください。 特に気になったセッション(岐部編) try! Swiftは4回目(2016,2018,2019,2024)の参加でした(2017年の記憶が曖昧なので、5回目かもしれない…)。 どのセッションも有意義でしたが、私は特に「Accessibility APIを使ってアプリケーションを拡張する」のセッションが非常に興味深く、サンプルアプリまで作りました。 1分にまとめたショート動画を用意したのでご覧ください。 時間の関係で動画中では詳細に触れていませんが、Accessibility APIを通して取得できる AXUIElement インスタンスには大きな可能性を感じました。このクラスのインスタンスには起動中の全アプリについての以下のようなウィンドウ情報が詰まっています。 アプリ名 ウィンドウのXY座標 ウィンドウサイズ 他のアプリのUIに自分のアプリが直接的に作用できるこの考え方は、Sandboxのcapabilityを取り除けるmacOSならではでとても面白いと感じました。 実際にユーティリティアプリ作る際は、以下のような手順で行いました。 Xcodeプロジェクト新規作成、macOSアプリを選択 まずはスライドにある設定を実施 capabilityからSandbox設定削除 ContentViewを開いて雑に実装 他ウィンドウの情報を埋める構造体と配列 他ウィンドウの情報を集めるメソッド 指定したウィンドウをアクティブにするメソッド ウィンドウの背景を透明にするView ホットキーを監視するメソッド これらを組み合わせて完成 以下のGitHubリポジトリで公開してあるので、ぜひ触ってみてください。 (あくまで検証用で作ったので粗は多いと思います…。PRもお待ちしております) github.com 最後に try!Swiftは世界中からiOSエンジニアが集まるイベントなので久しい友人に会えるのはもちろん、タイミーのエンジニアはフルリモートで働いているので普段WEBカメラ越しにしか話していない同僚とも対面で会話できて非常に楽しい期間でした。 この場を用意してくださった運営チームの皆さん、および会場でお話ししてくださった皆さんに心から感謝します。 上記で紹介したセッション以外にも非常に興味深いセッションが多くありました。 記事にある内容や、その他の内容についても、もしタイミーのエンジニアと話したいという方がいらっしゃればぜひお気軽にお話ししましょう! product-recruit.timee.co.jp
アバター