分析チームで技術ブログを更新し続けるために最初にやったこと

Da Vinci Studio 分析チームの濱口です。
Da Vinci Studio の分析チームでは、少し前から定期的にブログを書いて、チームに溜まった知見を社外に公開しています。継続してブログを更新するためにやったことをまとめようと思います。

背景

分析チームの方針として

  • 社外の新規案件を獲得したい
  • 将来的には仲間を増やしたい

の2つがありました。そのためには分析チームがどんな活動をしているのかをあらゆる手段で発信して、もっと社内外にアピールする必要がありました。その手段の1つとして、分析チームがどんな技術を使っていて、どんな工夫をして日々アウトプットを出しているのかを技術ブログで発信すると良いのではと考えていました。

ブログを更新していくためにやったこと

「ブログを更新していくためにやったこと?技術ブログなんて書いて公開するだけでは?」と思う方もいるかもしれません。
弊社の技術ブログ(2021年4月時点)を確認してみると

f:id:da-vinci-studio:20210930155201p:plain
2021年4月当時のブログの最新記事

2020年7月3日が最後の更新となっており、9ヶ月くらい放置されている上に2記事しか投稿されていませんでした。ブログ開設当初は「書きたい人が主体的に書いていこう!」という感じで始まった技術ブログでしたが、ほとんど投稿されず幽霊ブログになってしまっていたのでした。
というわけで、これまでと同じような運用だと続かないと思い、分析チームで技術ブログを投稿し続けるために以下のことをやりました。

  1. 技術ブログの運用事例を集める
  2. 執筆計画を立てる
  3. 率先して自分から書き始める

ブログが更新されていないのは全社的な問題ですが、とりあえずは分析チームとして小さく切り込んでいくことにしました。

1. 技術ブログの運用事例を集める

世の中の多くの企業が技術ブログをやっていて、同じように技術ブログの運用に苦労しているところもあるのではないかと思い、まずは他社の事例をいくつか集めました。

調べてみるとほとんど多くの企業が、何かしらの運用の仕組みや計画をしっかり立てて技術ブログを運用していることが分かりました。その中でも

社内技術ブログのはじめかた - Gunosyデータ分析ブログ

の記事が、チーム内ですぐに実践できそうで、最初のステップとしてはとても参考になりました。

2. 執筆計画を立てる

というわけで、上記の事例をもとに運用計画を立てていきます。具体的には

  1. ブログを書く目的を確認する
  2. 目的に沿って、読者のペルソナを考える
  3. ペルソナごとに記事のネタを洗い出す
  4. 執筆担当を決める
  5. どれくらいの頻度で出していくかを決める

の順番でやっていきました。今回は記事ネタを洗い出して担当を決めるところまでやって、スケジュールに関しては厳密に定めませんでした。

1. ブログを書く目的を確認する

目的は背景でも挙げましたが

  • 社外の新規案件を獲得したい
  • 将来的には仲間を増やしたい

の2つです。上記をもとに読者のペルソナを考えたいと思います。

2. 目的に沿って、読者のペルソナを考える

2つの目的に対してそれぞれペルソナを考えました。

  • 社外の新規案件を獲得したい

    • サービスとかのグロース担当
    • 日本在住
    • 分析の知識あんまりない
    • 日本の企業とか行政で、グロース、マーケティング、分析担当のいずれか
  • 将来的には仲間を増やしたい

    • 中途
    • 23~32歳
    • 日本在住
    • 企業や行政で分析担当、機械学習担当、データインフラ担当
    • データ分析、分析基盤の保守・運用、BIツールの利用経験有

あまり難しく考えすぎず、どんな人から案件を獲得して一緒に仕事をしたいか、どんな人が今のチームにいたら助かるかなどから想像してざっくりで出していました。

3. ペルソナごとに記事のネタを洗い出す

続いて、上記をもとに記事のネタを洗い出していきます。

  • 社外の新規案件を獲得したい

    • 分析基盤整備の話
    • サービスグロースの話
    • データドリブンな施策の話
  • 将来的には仲間を増やしたい

    • BIツールの話
    • 分析SQL運用についての話
    • チームでの取り組みの話

社外の新規案件を獲得したい向けには案件や施策実施についての全体の話を、将来的には仲間を増やしたい方向けには具体的な技術の話やチームでの取り組みの話などをできると良さそうという風に考えて洗い出しました。
このタイミングでとりあえず思いつく限りを出していますが、新しく思いついたら随時リストに加えています。

4. 執筆担当を決める

ネタを洗い出した後は、誰がどの記事を書いていくのかを決めました。
執筆スケジュールに関しては各々に任せた運用になっていますが、その代わりにチームとしてどれくらいの頻度で記事を投稿していくかを決めていて、現状では 1~2 投稿/月をノルマとしてブログを更新しています。分析チームのメンバーは3名なので、それぞれのリソース配分を気にしながら書ける人が書くような流れができています。

3. 率先して自分から書き始める

執筆計画を立てたら、あとはひたすら書いていきます。チームメンバーにもしっかり書いて欲しかったので、旗振り役の自分が率先して書きました。

結果

半年ほど運用してみた結果、分析チームとしては、1~2 投稿/月 のノルマを大方クリアできており、継続的に社外へ発信できています。また分析チーム以外の他のチームでもブログを書いて発信する動きが少しずつ出てきています。
具体的に採用や案件獲得という結果には繋がっていませんが、継続的にブログの更新を続けていくことで中長期的に結果として表れるのではないかと考えています。

次にやること

正直なところ、現状の投稿頻度をデータ分析チームだけで維持するのは結構大変です。また先にも述べましたが、ブログが更新されていないこと自体は全社的な問題です。なので次にやることとして

  • 分析チーム以外の投稿も活発にする
  • 活発な状態を全社的に継続する

の 2 点が必要だと思っています。 現状の技術ブログは分析チームが率先して投稿しているので、多くの投稿がデータ分析に関連する内容になっています。しかし Da Vinci Studio にはデータ分析以外にもサーバーサイドやアプリ、インフラ、デザインなどの部署が存在し、サービス開発を行っています。データ分析に関連する投稿だけでなく、他の部署の投稿も活発にできれば、どんなことをやっている会社なのかをちゃんと知ってもらえるようになります。また投稿が活発な状態も継続することができ、技術ブログをよく書いている会社と認知されて、会社全体の技術ブランディングも良い方向に進み、採用や案件獲得に繋がるのではないかと思っています。
他の部署の活動も発信されるように、ブログ等の社外へのアウトプットの啓蒙であったり、会社全体の投稿を活発化させる仕組みの構築が必要だということが見えてきました。

まとめ

分析チームでのブログ運用についてまとめました。
分析チームの方針だけでなく会社全体の技術ブランディングも含め、今後は全社的に技術ブログでの発信を活発化させていく必要があると感じました。他部署のメンバーへのヒアリングなどを行い、施策の実施や仕組みを構築していきたいと思います。

さいごに

Da Vinci Studio では一緒に働ける仲間を絶賛大募集中です!募集職種と詳細に関しては、以下のリンクからそれぞれ確認できます。

コードをゴリゴリ書いて開発したい方、マネジメントでチームの成果を最大化したい方、多種多様なサービスのインフラ構築に携わりたい方、それ以外の方でも興味のある方はご連絡ください。

GitHub リポジトリに貯めていく分析 SQL 運用

リポジトリにためていく分析SQL運用

Da Vinci Studio 分析チームの濱口です。
分析チームでは各々が書いた SQL をドキュメントにまとめて GitHub リポジトリに貯めて共有しています。この試みを始めてから1年ほどたったので、運用してみて感じたことや気づいたことについて紹介しようと思います。

背景

SQL を GitHub リポジトリに貯め始めたのは、以下のような問題意識からでした。

レビューの枠組みがなかった

ちゃんとした枠組みがなく、タスク管理ツール上で雑に行われていたり、そもそもレビューが行われていなかったりという状態でした。このような状態が続いてしまうと、SQL のミスやデータの誤りに繋がってしまい、データの信頼性が損なわれてしまいます。

書いた SQL が集約されていなかった

様々なところでレビューされていたりされていなかったりで、それぞれの書いた SQL がどこにあるのかがわかりづらい状態でした。他の人が書いた SQL を参考にしたり再利用したりするのにも一苦労で、とても非効率です。

GitHub リポジトリに貯めるまでの流れ

貯めるまでの流れですが、特に特別なことはなく

  1. プルリクエストを作成する
  2. レビューをもらう

という手順になります。

1. プルリクエストを作成する

マークダウン形式のファイルに作成した SQL の概要、実際に書いた SQL などを記載したプルリクエストを作成します。ディスクリプションには、データ抽出の目的と SQL を実行して抽出したデータを記載し、データの数値についても確認できるようにしています。

2. レビューをもらう

チームメンバーからレビューをもらい、SQL やデータに誤りがないかチェックします。
ディスクリプションにレビューのチェック項目を設けており、その項目に沿ってレビューを行うことで漏れが出ないように対策しています。最低でも1人から Approve もらって問題なさそうであればマージします。

レビューのチェック項目

運用してみて

  • クエリのミスやデータのエラーに気づけるようになった
  • 他の人が書いた SQL を再利用しやすくなった

といったように、当初抱えていた問題は解消できました。
しかし、運用する上でまたいくつか別の課題が出てきました。

参考にしたいファイルが見つからない

最初のうちは良かったのですが、ファイルがどんどん増えていくうちに参考にしたいファイルを見つけるのが困難になってきました。
解決策として、全てのファイルを同じ 1 つのフォルダに貯めていたのを、外部と内部の案件別で分割した上で、さらにデータマートごとにクエリを分けるようにしました。

レビューが滞る

データの抽出内容によってはかなり長い SQL になることもあり、とにかくレビューが大変でした。 レビューが大変だと後回しにされがちで、レビューが滞るという問題が出てきました。
この問題に関しては

  • SQL のコーディングスタイルを定める
  • 再利用できる箇所は切り出して共通化する

などを行っており、現在も改善を続けています。 上記2つの改善についてはいくつか別のブログにもまとめています。

blog.da-vinci-studio.com

blog.da-vinci-studio.com

まとめ

  • プルリクエストによるレビューを実施することで、データの信頼性向上につながった
  • GitHub のリポジトリに集約することで他の人が書いた SQL を再利用しやすくなった

運用していて出てきた新たな課題についても、さらに改善していきたいと思います。

We are hiring!!

Da Vinci Studio では一緒に働ける仲間を絶賛大大大募集中です!

データ分析に興味がある方、それ以外の開発に興味がある方、あるいは一緒に働いてみたいという方は recruit@da-vinci-studio.net までご連絡ください。

Core Web Vitals 数値の改善

Da Vinci Studio 第 2 サーバー部、社会人 2 年目の吉延です。 今回は最近開発してリリースしたばかりの 『ごっこランドTimes』で行った Core Web Vitals(CWV) 数値、その中でも First Contentful Paint(FCP) と Largest Contentful Paint(LCP) 数値の改善に焦点を当てた施策について紹介します。『ごっこランドTimes』は子どもの興味・関心を伸ばし、親子で一緒に楽しめる情報が満載のメディアです。Web フレームワークとして Ruby on Rails を用いて開発しています。

背景

SEO 対策はサイトへの集客アップのため重要なのは既知の事実かと思いますが、2021 年 6 月から CWV も Google の検索ランキングの要因となります。 ( 詳しくは Google 検索セントラルブログ を参照してください。) CWV はユーザー体験をより良いものにするために Google が提唱しているコンセプトで、表示速度、反応速度、表示の安定性の 3 つ要素に焦点が当てられています。そのため、表示ズレやサイトスピード改善はより重要となります。これらのスコアや細かな指標は PageSpeed Insights で測定することができます。

改善前の状態

対象のサイトは表示ズレに関わる Cumulative Layout Shift(CLS) のスコアは良かったので、表示速度に関連する FCP と LCP に焦点を当てて改善を行いました。 改善前のスコアを計測してみたところ、 FCP の良好とされるスコアは 1.8s 未満、 LCP は 2.5s 未満とされているのでかなり悪いことがわかります。現状は広告も入っていない状態にも関わらずこの状況なので、将来的に広告が入った場合さらにスコアが下がることが予想されます。

  • TOP ページ
    FCP : 6.7s
    LCP : 9.2s
  • 記事ページ
    FCP : 7.1s
    LCP : 9.5s

f:id:da-vinci-studio:20210702122623p:plain:w640
改善前のTOPページのスコア
f:id:da-vinci-studio:20210702120809p:plain:w640
改善前の記事ページのスコア

改善の方針

ではどんな施策をすればよいのだろう?となるのですが、それについては PageSpeed Insights の「改善できる項目」が参考になります。どの項目に対する施策を打つのかについては各サイトの性質によって異なると思うので、そちらを考慮した上で項目を選択してください。対象のサイトでは

  • レンダリングを妨げるリソースの除外
  • 使用していない JavaScript, CSS の削除

に対応した施策を打ちました。

施策一覧

検討した施策は以下となります。

  • Web フォントを除外する
  • 表示処理に関係のない JavaScript の読み込み順番の変更
  • 使用していない JavaScript の削除
  • 表示サイズが小さい画像の Retina 対応をやめる
  • オフスクリーン画像の遅延読み込み
  • ページごとに CSS を分割する

各施策の詳細

Web フォント ( Google フォント) の使用をやめる

こちらが一番効果があると思います。 Web フォントを使用しているのであれば一番に検討すべき施策です。 Web フォントを外すだけで対象サイトの記事ページでは以下のようにスコアが改善されます。

  • FCP : 7.1s → 1.7s
  • LCP : 9.5s → 2.7s

f:id:da-vinci-studio:20210702122842p:plain
Web フォントありのスコア
f:id:da-vinci-studio:20210702122905p:plain
Web フォントなしのスコア

しかし、 Web フォントを外すとサイトの印象がガラリと変わってしまうので、そのあたりを十分に考慮した上で Web フォントを使用するかどうか検討すべきです。一部の固定の文字のみに Web フォントを使用したい場合は URL に &text= を付けて当てたい文字を記入すればサブセット化されるので、少量のフォントの呼び込みで済みます。

<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@500&display=swap&text=フォントをあてる文字" rel="stylesheet">

表示処理に関係のない JavaScript の読み込み順番の変更

JavaScript を外部ファイルとして HTML で読み込む場合、記述順に呼び込まれます。なので表示処理に関係のないファイルはなるべく最後に呼び出したほうが良いです。そうすることでファーストビューの描画が早くなり、 FCP や LCP のスコア改善につながります。対象のサイトで表示に関係ないものをなるべく body 要素に近い場所で呼び出すようにしました。

使用していない JavaScript の削除

余計な JavaScript を読み込むと表示に時間がかかる場合があります。なので使用していないものは気づいたら削除しておくのが良いと思います。不要なファイルの見つけ方として Google Chrome の検証モードを用いるのが良いと思います。 検証モード内の Network 項目を見ると、呼び出されいる js ファイルのサイズや処理時間の一覧を見ることができるので、不要な js ファイルや処理に時間がかかっているファイルを見つけることができます。また、ネットワークスピードを Fast 3G にしておくと、 PageSpeed Insights のネットワーク環境に近づけることができます。対象サイトでは active storage や action cable を使っていませんでしたが、 rails new 時に skip し忘れていたので残っていました。

f:id:da-vinci-studio:20210702123127p:plain

表示サイズが小さい画像の Retina 対応をやめる

サイトで使用している画像は Retina ディスプレイ対応させるため、元の画像サイズを表示されるサイズの 2 倍の大きさにしていることが多いと思います。もちろんこの対応すると読み込むデータ量が増えるため、表示スピードが遅くなる可能性があります。なので比較的 Retina 対応の影響が少ない、表示サイズの小さい画像の元サイズを表示サイズと同じにすると速度改善する場合があります。

オフスクリーン画像の遅延読み込み

オフスクリーン画像を最初のロード時に読み込む必要がないので、それらの画像を遅延読み込みするのが良いです。 JavaScript で遅延読み込みを行う方法もありますが、 HTML5 の標準仕様の loading 属性を使って画像に loading=lazy を付与するだけで HTML だけで遅延読み込みを行うことができます。

<img width="116" height="116" alt="" loading="lazy" src="image.png">

ページごとに CSS を分割する

  • app/javascript/packs/application.js
require("stylesheets/application")
  • app/views/layouts/application.html.haml
= stylesheet_pack_tag "application"

上記のようにとすべての CSS ファイルと一度に読み込んでいませんか?このようにすると当然、無駄な CSS も多数読み込むようになります。そこで各ページごとで読み込む CSS を分割する方法があります。各ページに対応したエントリーポイントとなる js ファイルを用意してそのファイルの中で必要な CSS のみを呼び込むようにします。

  • app/javascript/packs/article.js
import "../stylesheets/article.css"

そして各ページに対応する view ファイルから対象の js ファイルを呼び出します。

  • app/views/articles/show.html.haml
= stylesheet_pack_tag 'article', media: 'all'
= javascript_pack_tag 'article'

このようにすることで無駄な CSS 読み込みを避けることができます。規模が大きなサイトほど効果がある施策だと思いますが、大きくなってからだと対応が大変になるので、規模が小さいうちから対策しておくことをオススメします。

結果

対象のサイトでは開発環境でスコア改善が見られた以下の施策を実施しました。

  • 表示処理に関係のない JavaScript の読み込み順番の変更
  • 使用していない JavaScript の削除
  • オフスクリーン画像の遅延読み込み
  • ページごとに CSS を分割する

対象のサイトでは Web フォントを除外してしまうとサイトの印象が大きく変わってしまうため、 Web フォントは除外しませんでした。以下が対策後の数値です。良好なスコアにすることはできませんでしたが、 LCP は対策前と比べると TOP ページでは約 13% 、記事ページでは約 22% ほどスコア改善することができました。

TOP ページ

  • FCP : 6.7s → 6.5s
  • LCP : 9.2s → 8.0s

f:id:da-vinci-studio:20210702122623p:plain:w640
改善前のTOPページのスコア
f:id:da-vinci-studio:20210702122512p:plain
改善前のTOPページのスコア

記事ページ

  • FCP : 7.1s → 7.1s
  • LCP : 9.5s → 7.4s

f:id:da-vinci-studio:20210702120809p:plain:w640
改善前の記事ページのスコア
f:id:da-vinci-studio:20210702120418p:plain
改善前の記事ページのスコア

まとめ

ごっこランドTimes』 で行った CWV 数値、中でも FCP と LCP 数値の改善に焦点を当てた施策について紹介しました。サイトによって効果がある施策、ない施策は異なると思うので、まずは試してみると良いと思います。

コーディング規約を定めて SQL の可読性を上げる

Da Vinci Studio データ分析チームの濱口です。
現在データ分析チームで運用されている SQL のコーディング規約について紹介します。

なぜコーディング規約を定めるのか

SQL のコーディング規約を定める主な理由として、 可読性を上げるというのがあると思います。 スタイルを統一せず、書き手によって異なるスタイルで書いていると以下のような弊害が発生します。

レビューしづらい

他の人が書いた別の作業の SQL というだけでも読みづらいですが、コーディングスタイルが揃っていないとさらに読みづらくなってしまいます。 そうするとレビューにも時間がかかってしまい、他の作業に割ける時間が短くなってしまいます。

再利用しづらい

Da Vinci Studio の分析チームでは、過去に書いたクエリを GitHub のリポジトリに貯めており、他の人も再利用できるようにしています。 しかし SQL が読みづらいとバグも発生しやすくなり、かえって作業効率を悪くしてしまいます。

どんな SQL に適用すべきか

他の人が読む可能性がある SQL は全て、定めたコーディング規約に則って書くことにしました。具体的には

  • 他チームからの依頼の SQL
  • アプリケーション内で利用される SQL

などを指します。個々人のアドホックな分析に関しては、必ずしもスタイルガイドに沿わなくてもいいというルールにしました。

コーディング規約

以下で定めているコーディング規約をいくつか紹介します。
なお、今回は分析チームで主に利用されている BigQuery の利用を前提としたコーディング規約になっています。

予約語は小文字で書く

# NG
SELECT
  name,
  comment,
FROM SAMPLE.table
WHERE
  name = 'name1'

# OK
select
  name,
  comment,
from SAMPLE.table
where
  name = 'name1'

SQL の予約語は大文字で書く文化が広く浸透しており、大文字で書く人はかなり多いかと思います。 しかし上記を踏まえた上でも、小文字で書く方が良いと思った理由として

  • 予約語だけ大文字で書くのめんどくさい
  • 予約語だけ大文字で書いても可読性は変わらない

の2点でした。可読性が変わらないのであれば、予約語だけ大文字で書くメリットはないという結論に至りました。

select 句のカンマの位置は行末

# NG
select
  name
  , comment
  , date
from SAMPLE.table

# OK
select
  name,
  comment,
  date,
from SAMPLE.table

このルールは可読性というよりは、試行錯誤の効率の観点で決められました。

  • 行頭にカンマを書くと、一番上のカラムをコメントアウトの際にエラーになってしまう
  • BigQuery は select 句の最後の行にカンマがついていてもエラーにならないので、一番最後の行をコメントする際もカンマの削除が不要

の2点が主な理由です。より少ない労力で各行をコメントできる書き方を採用しました。

where 句の and と or は先頭に置く

# NG
select
  name,
  comment,
from SAMPLE.table
where
  date between date('2021-04-27') and date('2021-05-05') and
  name = 'name1' or
  comment = 'comment1'

# OK
select
  name,
  comment,
from SAMPLE.table
where
  date between date('2021-04-27') and date('2021-05-05')
  and name = 'name1'
  and comment = 'comment1'

条件が複雑で長くなっている際に and や or が行末についていると見づらいというのがありました。 先頭に書いておけば、条件のかたまりの切れ目が分かりやすく良いのではという議論がありました。

サブクエリではなく、 with 句を使う

# NG
select
  name,
  sum_price
from (
  select
    name,
    sum(price) sum_price,
  from SAMPLE.table
  where
    date between date('2021-04-27') and date('2021-05-05')
  group by name
)
where
  sum_price >= 10000

# OK
with
  t_base as (
    select
      name,
      sum(price) sum_price,
    from SAMPLE.table
    where
      date between date('2021-04-27') and date('2021-05-05')
    group by name
  )
select
  name,
  sum_price
from t_base
where sum_price >= 10000

主な理由としては

  • 一つのクエリが大きくなりすぎない
  • ネストが深くなりすぎない
  • 同じクエリを何度も再利用できる

の3点が挙がりました。可読性だけでなく、再利用性の観点からも with 句を使うようにしています。

ビフォーアフター

以下、全ての規約を適用したときのビフォーアフターの一例です。

# 適用前
SELECT
  date,
  target_name,
  SUM(price) sum_price,
  AVG(price) avg_price,
FROM (
  SELECT
    date,
    CASE
      WHEN regecp_contains(name, r'regexp1') THEN 'name1'
      WHEN regecp_contains(name, r'regexp2') THEN 'name1'
    ELSE null
  END target_name,
    price,
  FROM DataBase.table1
  WHERE date BETWEEN DATE('2021-01-01') AND DATE('2021-01-31'))
WHERE target_name IS NOT NULL
GROUP BY date, target_name
GROUP BY date, target_name

# 適用後
with
  t_base as (
    select
      date,
      case
        when regecp_contains(name, r'regexp1') then 'name1'
        when regecp_contains(name, r'regexp2') then 'name1'
        else null
      end target_name,
      price,
    from DataBase.table1
    where date between date('2021-01-01') and date('2021-01-31')
  )
select
  date,
  target_name,
  sum(price) sum_price,
  avg(price) avg_price,
from t_base
where target_name is not null
group by date, target_name
order by date, target_name

紹介した規約以外にも、インデントに関する規約などが適用されており、読みやすくなりました。 規約通りにかけているかのチェックは、現状は目視で確認しています。最低限レビューや後で読み返すときに困らない程度の可読性を保てたら良いので、そこまで厳密にはチェックしていません。

まとめ

分析チームで運用している SQL のコーディング規約についてまとめました。カラム名や with 句における命名規約なども定めていますが、それはまた別の記事で紹介しようと思います。
このコーディング規約はあくまでも一例で、状況によって規約の内容や運用方法は変わると思います。その時のチームの課題や状況に応じてアップデートしていきたいと思います。

We are hiring!!

Da Vinci Studio では一緒に働ける仲間を絶賛大大大募集中です!

データ分析に興味がある方、それ以外の開発に興味がある方、あるいは一緒に働いてみたいという方は recruit@da-vinci-studio.net までご連絡ください。

BigQuery で定数を使う方法

f:id:da-vinci-studio:20210528105644p:plain

Da Vinci Studio 分析チームの末安です。
今回は BigQuery で定数を使う方法をいくつか紹介します。

日付だけ変えて同じクエリを実行したいケースなどよくあると思いますが、変更箇所が漏れていたりして無駄に時間を消費してしまったりします。 こういう場合、定数にしておくと便利です。

まず前提として BigQuery では、定数を定義する機能はありません。( 2021年05月時点 )
ここで紹介する方法は、定数っぽくつかえる BigQuery の機能になります。

今回紹介する方法は以下の 3 つです。

  • with 句を利用する
  • UDF を利用する
  • BigQuery Scripting を利用する

ではひとつずつ説明していきます。

with 句を利用する

with 句で固定値をサブクエリとして定義することで定数っぽくする方法です。

with
  const as (
    select date('2021-04-01') st, date('2021-04-30') en
  ),

  date_array as (
      select 
        date(sequence) item,
      from unnest(generate_date_array('2021-01-01', '2021-05-01')) sequence
  )

select
  date_array.*
from date_array, const
where
  item between const.st and const.en

このように定義した const というサブクエリを CROSS JOIN することで利用します。
都度 from に書く必要があって少し面倒です。

UDF を利用する

UDF とは、ユーザー定義関数のことです。固定値を返す関数を定義することで、定数のように扱うことができます。

cloud.google.com

create temp function const()
as (
  struct(date('2021-04-01') as st, date('2021-04-30') as en)
);

with 
  date_array as (
      select 
        date(sequence) item,
      from unnest(generate_date_array('2021-01-01', '2021-05-01')) sequence
  )

select
  date_array.*
from date_array
where
  item between const().st and const().en

上記の場合では、構造体を返すようにすることで複数の定数を一つの関数で定義できるようにしています。
with 句を利用する方法と違って CROSS JOIN する必要がありません。

デメリットとしては、 JOIN する際の条件のなかで利用できないという点があります。
例えば以下のようなケースでは Subquery in join predicate should depend on one of join sides. のようなエラーがでます。

create temp function const()
as (
  struct(date('2021-04-01') as st, date('2021-04-30') as en)
);

with 
  date_array as (
      select 
        date(sequence) item,
      from unnest(['2021-01-01', '2021-02-01', '2021-04-01','2021-05-01']) sequence
  ),
  date_array2 as (
      select 
        date(sequence) item,
      from unnest(['2021-01-01', '2021-02-01', '2021-04-01','2021-05-01']) sequence
  )

select
  date_array.*
from date_array
  join date_array2 on date_array.item = date_array2.item and date_array2.item = const().st
where date_array.item between const().st and const().en

BigQuery Scripting を利用する

BigQuery Scripting では、変数やif 文やループ処理などのステートメントを特に何の設定もすることなく利用できます。この変数の宣言と代入を利用することで定数のように扱います。

cloud.google.com

declare st, en date;
set st = date('2021-04-01');
set en = date('2021-04-30');

with 
  date_array as (
      select 
        date(sequence) item,
      from unnest(generate_date_array('2021-01-01', '2021-05-01')) sequence
  )

select
  date_array.*
from date_array
where item between st and en

with 句や UDF を利用する方法と違い CROSS JOIN する必要もなく、JOIN の条件の中でも利用できます。 また他の方法よりも可読性も良く、現時点では一番良い方法だと思います。

ただし、BigQuery Scripting を使うと事前にスキャン量を計算できないので注意が必要です。

まとめ

今回は BigQuery で定数を使う方法を紹介しました。 クエリを再利用する際に有用なので、ぜひ試してみてください。

BigQuery Scripting はこれ以外でも工夫次第で色々応用できそうなので活用していきたいと思います。

We are hiring!!

Da Vinci Studio では一緒に働ける仲間を絶賛大大大募集中です! データ分析に興味がある方、それ以外の開発に興味がある方、あるいは一緒に働いてみたいという方は recruit@da-vinci-studio.net までご連絡ください。

BigQuery 用 ETL ライブラリ inbulk を公開したので紹介する

f:id:da-vinci-studio:20210528104904p:plain

Da Vinci Studio 分析チームの末安です。
今回は 2021 年 1 月に公開した ETL ライブラリ inbulk について紹介します。

github.com

inbulk でできること

  • ジョブを簡潔に定義できる
  • 差分実行できる
  • それらの処理を BigQuery 内で完結できる

ざっくり言うと、ほとんど yaml で定義できる BigQuery の API の wrapper のようなものになっています。

背景

分析チームでは BigQuery を利用することが多く、BigQuery 内のテーブルを加工して別のテーブルを作るといった作業がよく発生します。

以前まで、こういった処理は BigQuery の機能である Scheduled Query を利用して行っていましたが、利用していて幾つかの問題が見えてきました。

1. 依存関係を定義できない

一つの処理が終わった後に別のテーブルを作りたいということはよくあると思います。

Scheduled Query ではそういった依存関係は定義できないため、実行時間をずらすことでどうにか実現していましたが、最初の処理が失敗しても次の処理が実行されてしまうという問題がありました。

2. 差分実行のコストが大きい

バッチ処理においても冪等性を考慮することは重要だと思います。

これも BigQuery 上のみで実現できなくはないですが、宛先テーブルを全スキャンして新規追加分マージしてからそのテーブルを置換する必要がありました。

この方法の場合、宛先テーブルのデータ量が多いとかなりのコストがかかってしまうという問題がありました。

3. レビューしづらい

当時は BigQuery の UI 上で Scheduled Query を設定していたため、レビューがうまくできない状態でした。

この問題に関しては Terraform などで管理すれば解決できそうではありますが、こういった管理方法の場合、GitHub 上の定義と実態に差異が発生してしまうことが多いことと、他にも解決できない課題があったため別の手段を探すことにしました。

調査

1 の依存関係を定義できない問題は、ワークフローエンジンを利用することで解決できるのでそちらに移行することにしました。

差分実行とジョブ定義の実現方法についても調査しましたが、それらしきものは見つかりませんでした。

Embulk を利用すれば実現はできそうでしたが、Embulk 自体はデータ転送ツールであり、今回のケースでは BigQuery 内で完結できる処理のため、一度 BigQuery からデータをダウンロードして再び BigQuery に入れるという内部の流れが個人的に許せませんでした。

inbulk

そんなわけで、ETL ライブラリ inbulk を OSS として開発しました。

特徴

定義

このように Embulk っぽい記述でジョブを定義できます。

Embulk と違うのは、データを実行環境に一切ダウンロードしない点です。そのためジョブ実行するマシンのメモリ消費を抑えることができます。

init:
  credential-file: /.gcp/credential.json
  service: bigquery
in:
  query: |
    select
      id
    from Dataset.users
out:
  project: project
  database: Destination
  table: users
  mode: append

変数

inbulk では、事前に別のクエリを実行してその結果をクエリに組み込むことができます。 具体的には、下記のように in のセクションの vars で組み込みたいデータを指定して query のなかで ${変数名} とすることでクエリに組み込みます。

init:
  credential-file: /.gcp/credential.json
  service: bigquery
in:
  query: |
    select
      id
    from Dataset.users
    where created > '${last_modified}'
  vars:
    - name: last_modified
      database: Destination
      table: users
      mode: meta
      field: last_modified_time
out:
  project: project
  database: Destination
  table: users
  mode: append

上記の例では、last_modified という名前で宛先テーブルの最終更新日を BigQuery のメタデータテーブルから取得して利用しています。これにより差分実行が低コストで実現できます。

また、他のテーブルを参照することも可能で、vars に下記のように指定すると、宛先テーブルの最後の id を取得するようなこともできます。

  vars:
    - name: last_id
      database: Destination
      table: users
      mode: max
      field: id

マージ

マージ機能も用意しています。マージモードでは、指定したフィールドが重複する場合は、指定したオーダーで最初に来るもので置き換えます。 下記の例では、id が重複した場合、最終更新日が新しいレコードで置き換えるような設定になっています。

ただし、マージモードでは内部的には宛先テーブルを全スキャンしてリプレイスしており、宛先テーブルのデータ量が多い場合はスキャン量が多くなってしまうので注意が必要です。

init:
  credential-file: /.gcp/credential.json
  service: bigquery
in:
  query: |
    select
      id
    from Dataset.users
out:
  project: project
  database: Destination
  table: users
  mode: merge
  merge:
    order:
      - column: modified
        desc: True
    keys:
      - id

パーティション

下記の例のように、宛先テーブルのパーティションも定義することができます。

init:
  credential-file: /.gcp/credential.json
  service: bigquery
in:
  query: |
    select
      id
    from Dataset.users
out:
  project: project
  database: Destination
  table: users
  mode: append
  partition:
    mode: time
    field: created
    type: DAY

利用例

日毎に新規ユーザー数を集計して保存しておきたい

宛先テーブルの最新の created 以降に作成された未集計の users を集計する

init:
  credential-file: /.gcp/credential.json
  service: bigquery
in:
  query: |
    select
      date(created) created,
      count(distinct id) user_count,
    from Sample.users
    where
      date(created) > date('${last_created}')
      and date(created) <= date_sub(current_date, interval 1 day)
  vars:
    - name: last_created
      database: Destination
      table: new_users
      mode: max
      field: created
out:
  project: project
  database: Destination
  table: new_users
  mode: append
  partition:
    mode: time
    field: created
    type: MONTH

特定のイベントのみ整形して保存しておきたい

宛先テーブルの最終更新日から昨日までのデータを append するように定義する

init:
  credential-file: /.gcp/credential.json
  service: bigquery
in:
  query: |
    select
      timestamp_micros(event_timestamp) event_time,
      user_id,
      user_pseudo_id,
      (select value.string_value from unnest(event_params) event_param where key = 'screen_class') screen_class,
      device.category device_category,
      device.operating_system os,
      device.operating_system_version os_version,
    from Sample.events
    where
      event_name = 'screen_view'
      and date(timestamp_micros(event_timestamp)) between date('${last_modified}') and date_sub(current_date, interval 1 day)
  vars:
    - name: last_modified
      database: Destination
      table: screen_views
      mode: meta
      field: last_modified_time
out:
  project: project
  database: Destination
  table: screen_views
  mode: append
  partition:
    mode: time
    field: event_time
    type: DAY

まとめ

今回は、ETL ライブラリの inbulk について紹介しました。 inbulk を導入したことによって、課題であった差分実行とレビューのしづらさを解消することができました。

今は BigQuery 専用となっていますが、追々 Redshift などにも対応していきたいと考えています。

なにか要望などあれば気軽に Issue をあげてもらえると嬉しいです。

We are hiring!!

Da Vinci Studio では一緒に働ける仲間を絶賛大大大募集中です! データ分析に興味がある方、それ以外の開発に興味がある方、あるいは一緒に働いてみたいという方は recruit@da-vinci-studio.net までご連絡ください。

Redash の metadata だけでできるダッシュボードのアクセス解析

f:id:da-vinci-studio:20210527152952p:plain Da Vinci Studio インフラ基盤部データ分析チームの濱口です。
Da Vinci Studio のデータ分析チームでは、グループ内に溜まっている膨大なデータを日々分析・集計しています。そうして得られた学びについては、公開可能なものに限り Redash のダッシュボードとしてグループ内で確認できるようにしています。 ただ公開して終わりではなく、ダッシュボードがどれくらい見られているのかも計測して、グループ内のデータへの関心の把握やダッシュボードの改善などに繋げたいですよね。
というわけで今回は Redash 内のクエリやダッシュボードの閲覧状況を可視化した話についてまとめておこうと思います。

Redash の metadata

閲覧状況を可視化するにあたって Redash の内部データベースに貯まっている metadata を利用しました。 Redash の metadata には、登録ユーザーやユーザー権限、追加されたクエリやダッシュボードなど色々なデータが貯まっています。

Redash の metadata のテーブル一覧
Redash の metadata のテーブル一覧

events というテーブルがあり、その中にユーザーの閲覧履歴や操作履歴が記録されるようになっています。

events テーブルの詳細
events テーブルの詳細

これを利用して閲覧状況を可視化してみましょう。

Redash で Redash の metadata にクエリを投げられるようにする

metadata に今回必要そうなデータが入っていることが確認できたので、次に Redash の metadata をデータソースに接続します。 今回は docker のコンテナに立てているので、それぞれ設定した値から host, port, user, password などを入力していきます。

Redash のデータソース接続画面
Redash のデータソース接続画面

設定を完了して、以下のようにテーブル一覧が確認できたら接続完了です。

Redash のデータソース接続確認
Redash のデータソース接続確認

events テーブル

ここまできたらあとはいつものごとくクエリを書いて events テーブルのデータを集計します。

events テーブルのデータ例
events テーブルのデータ例

先ほどテーブル内容を紹介しましたが、その中の action に何の操作が行われたか、object_type に action の対象物が記録されています。ダッシュボードの閲覧であれば action = 'view' and object_type = 'dashboard' の条件で絞り込むことでデータを確認できます。ここに users テーブルや dashboards テーブルを join すれば、誰が何のダッシュボード見ているのかを集計できます。

with
    t_base_events as (
        select
            u.name user_name,
            e.action,
            d.name dashboard_name,
            e.created_at
        from events e
            join users u on e.user_id = u.id 
            join dashboards d on cast(e.object_id as integer) = d.id
        where
            action = 'view'
            and object_type = 'dashboard'
    )

select
    dashboard_name,
    count(*) view_dashboard_count
from t_base_events
group by dashboard_name
order by view_dashboard_count desc

action には他にも create, update, load_favorites, execute_query など、object_type は dashboard, query, page などが入っていて、色々な対象物の様々な操作履歴を把握できます。閲覧状況以外にも、誰がどれくらいクエリを書いているかやどれくらいダッシュボードを作成しているかもモニタリングできそうです。

ダッシュボード完成

ダッシュボードとクエリの日別閲覧状況
ダッシュボードとクエリの日別閲覧状況

大きく伸びている日は、新しくダッシュボードを追加して周知した日です。それ以外の期間では、あまり継続して見られていないことが分かります。何の情報をどういう見せ方にすれば興味を持って継続的に見てもらえるのか、まだまだ改善の余地があります。

人物、会社別の閲覧状況
人物、会社別の閲覧状況

また会社別に見てみると、直近でダッシュボードを見ている会社と見ていない会社で大きく差があることが分かりました。追加したダッシュボードに対して強く興味を示している会社は良いですが、あまり興味を示していない会社に対してはどういった情報だとより興味が惹かれるのかをもう少し深ぼりして考える必要があります。

まとめ

  • Redash の metadata には様々な使えるデータが入っている
  • events テーブルにありとあらゆるものの操作履歴が入っている
  • Redash に接続して集計して可視化すると、閲覧状況や利用状況などをモニタリングできる

分析結果を Redash のダッシュボードで公開するのはもちろんですが、公開内容が見られているかどうか、しっかり計測までできるようになりました。今後のダッシュボードの公開内容やまとめ方の改善に繋げていきたいと思います。
最後までご覧いただきありがとうございました。

We are hiring!!

Da Vinci Studio では一緒に働ける仲間を絶賛大大大募集中です! データ分析に興味がある方、それ以外の開発に興味がある方、あるいは一緒に働いてみたいという方は recruit@da-vinci-studio.net までご連絡ください。もしくは 濱口 までご連絡いただいても大丈夫です。