テレビ配信サービスだけではないTVer

こんにちは。 TVer Advent Calendar 2024の12日目の記事を担当するおかみと申します。 11日目の記事は @slme_not_found さんの ListDetailPaneでのアダプティブな左右分割画面の実装 でした。

私はTVerで配信している番組の管理やサイトの表示を設定しているCMS等と、TVerのオウンドメディアの開発ディレクションを担当しています。

TVerのオウンドメディアって...? 実はTVerは民放公式テレビ配信サービス以外にもサービスを持っているのです。

それが Screens というメディアサイトです。 実は正直な話、社内でもそれほど知名度があるわけでもなさそうなので、知名度を少しでも上げることはできないかと考えていました。 そんな時にこのアドベントカレンダーの話をもらいまして、技術的なお話はないですがこの機会に紹介できればと思います!

Screensとは?

映像メディアの可能性や価値、ビジネスの動向を、様々な切り口の記事、事例紹介やデータで伝えていく、情報サイトです。 引用元:Screensとは - Screens

テレビの価値向上に繋がる記事をBtoB向けに発信していくために、2016年7月に誕生しました。 私は勝手に映像メディアの業界専門誌みたいな感じだと思っています。 中身をざっくりと紹介します。

ニュース、インタビュー、イベント記事

全国のテレビ局さんの取り組みの紹介記事や、メディアや広告に関するカンファレンスのレポート、あとはTVer社員が登壇したイベントの記事などが掲載されています。

たまにサイトを開いてトップにTVer社員が登壇した際の写真があると、つい記事を読んでしまいますね..。 TVerに関する記事も多数あるので、TVerって今どんな感じ?と思ったらぜひ読んでいただきたいです。

視聴率、視聴質のランキング

週間での視聴率と視聴質のランキングを掲載しています。

視聴率は知っていましたが、視聴質はScreensに関わって初めて知りました。 テレビの本当の⾒られ⽅を計測するREVISIO社が提供していて、テレビの前にいる人のうち、テレビ画面を見ていた人の割合を注目度としてランキングを算出しているそうです。 参考:週間テレビ番組視聴質ランキング - Screens

用語集

この用語集はテレビ関連の用語をはじめ、マーケティング分野の用語を掲載しています。 社内で出てきた単語を、載ってるのかなー?って思いながらこのページで確認したりしてました。

Screens以外にも..

実はScreens以外にもオウンドメディアがあったのですが、残念ながら2024年9月30日をもって終了してしまいました。11月末に完全終了済み TVerプラス という、TVerで配信している番組に関する事前事後記事や、俳優さんへのインタビュー記事など掲載していました。

またTVerで書いた記事だけではなく、テレビ局さんが運営しているオウンドメディアからもRSSで記事を提供いただき、記事の公開も行なっていました。

入社から2年3か月担当し、途中からほぼ一人で担当していたので愛着もあり、終了ページの確認作業をしていたときは深夜という時間帯もあってか、終わった後は少ししゅんとしながらPCを閉じました。 TVerプラスで得られたノウハウを、今度はScreensで実践していけたらと思っています。

以上、TVerのオウンドメディアに関する話でした。 あ、もし新たにオウンドメディアを立ち上げることになったらやってみたいな〜なんて(小声)

明日は@fujioka_さんの「Google Cloudのコストレポートで急に利用料が0円になった話」です。

TVer 広告プロダクト開発タスクの SRE になってからの1年間を振り返る

この記事は TVer アドベントカレンダー 2024 10日目の記事です。

こんにちは、TVer 広告事業本部でインフラエンジニア・SRE をしている髙品です。 9日目の記事は @smizuno2018独自実装した FeatureFlag によるシステム移行でした。

10日目の記事では、TVer 広告プロダクト開発タスクの SRE になってからの1年間を振り返り、2024 年の SRE の取り組みを点検しつつ、2025 年の SRE の取り組みを考えてみたいと思います。

まえがき

個人的なことですが、私が TVer 広告プロダクト開発タスクに参加したのは 2023年11月なので、この記事を書いている時点で TVer に入社してから丸1年以上経過しました。昨年 TVerアドベントカレンダーに参加して New Relic の Change Tracking を紹介する記事*1を書くついでに、私が理想とするチームの在り方について語っていたのですが、今年もアドベントカレンダーの季節がやってきて、昨年語った理想にどれだけ近づけたのか振り返ってみたくなりました。

このような思いつきから記事を書くことに決めて1年間の業務を振り返ってみたところ、あるシステムの新規開発段階から New Relic の利用を推進したことで負荷試験が捗ったことを思い出しました。New Relic のおかげでボトルネックを容易に特定することができたという体験を提供できたことで New Relic に関心を持ってくれる開発者が増えました。昨年のアドベントカレンダーに書いていた、チームメンバー全員で New Relic のダッシュボードを見るという理想が近づいてきています。自画自賛は気後れしますが、このエピソードによって私は2024年の自分の仕事に一定の満足感を得ることができました。

入社から間もない頃に語った理想に近づけていることが分かって良かったのですが、自己満足な振り返りだけだと記事がここで終わってしまうので、この記事では以下について書いてみることにします。

  • New Relic に関心を持ってくれる開発者が増えた経緯
  • 2024 年の SRE の取り組みを点検し、2025 年の SRE の取り組みを考える

New Relic に関心を持ってくれる開発者を増やせた経緯

先に New Relic を利用してボトルネックを容易に特定することができたおかげで New Relic に関心を持ってくれる開発者が増えたと書きました。しかし、昨年アドベントカレンダーを書いたときは New Relic に関心がある開発者はほとんどいなくて、どうすれば関心を持ってくれる開発者が増えるか考えている状況でした。ちなみに、New Relic に関心を持ってくれる開発者を増やしたい理由は昨年のアドベントカレンダーに書いていて、当時から全く考えを変えていません。以下が理由です。

私は、信頼性と生産性を共に最適化できるチームにおいて、システムの状態はメンバー全員の関心事であるべきだと考えているので、チームの全員にNew Relicの画面を見てほしいと思っています。そこで、チームでNew Relicのダッシュボードを見て、アプリケーションのレイテンシースループット、エラー率といったパフォーマンス指標の傾向を知ることでシステムの状態を把握する定点観測の時間を作ろうとしています。いずれは、SREのサービスレベルの考え方をチームのシステム運用に導入することを見据えての活動です。

オブザーバビリティツールに限らず、チームに何かツールを紹介して使い始めようというときは、そのツールが便利であるとか、役に立つのであるとか説明するだけではなかなか使ってはもらえないもので、利便性を実感してもらうことがツールの導入において重要ですが、ツールの価値を理解するきっかけになる出来事はそう都合よく降ってきません。実際、昨年アドベントカレンダーで紹介した Change Tracking は、開発者に New Relic の利便性を実感してもらうには至りませんでした。

広告プロダクト開発タスクにおいて、New Relic に関心を持ってくれる開発者が増えたきっかけは負荷試験でした。この記事の主題ではないので負荷試験の詳細は省きますが、記事を分かりやすくするために、負荷試験において New Relic を利用してボトルネックを容易に特定できたエピソードを簡単に紹介します。

ログ書き込みがボトルネックになっていた

広告プロダクト開発タスクでは、2023年10月から2024年11月にかけて新しいシステムを開発してきました。どのようなシステムであるかはこの記事で書きたいことに関係ないので省略しますが、TVer の広告事業にとって非常に重要なシステムです。このシステムにはレイテンシースループットに関する明確な性能要件が存在していたので、負荷試験を実施して要件を満たせているか検証する必要がありました。

負荷試験は約半年間も続き、レイテンシーの目標を達成できないが、ボトルネックがどこにあるのか分からない状況に度々直面しました。アプリケーションは GKE で動いており、GKE の Pod, Node の CPU 使用率や、外部データベース(Memorystore, Bigtable)との通信時間に問題がないにも関わらず、アプリケーションのレイテンシーが遅くなり、ひどいときはロードバランサータイムアウトして 504 エラーが発生したりすることもありました。

そのような状況でボトルネックの調査に利用したのが、New Relic APMトランザクショントレースです。負荷試験の対象となっているアプリケーションは Go 言語で書いているので、New Relic APMGo agent をアプリケーションにインストールして、処理時間を計測するためのコード*2をアプリケーションに書き加えていきました。

トランザクショントレースによって、アプリケーションのどの処理で時間がかかっているか可視化されたことで、レイテンシーボトルネックを容易に特定できるようになりました。いくつかのボトルネックがありましたが、特に印象に残っているのがログ書き込みがボトルネックになっているケースです。

以下は New Relic APM で取得できた、あるトランザクショントレースです。トランザクションとセグメントの名前は隠しています。極端に遅いトレースをピックアップしているのですが、ログ書き込み全体で 100 秒もかかってしまっています。この記事はログ書き込みが遅くなる原因と対処方法を書くことが目的ではないので、どのようにして改善したのかは書きませんが、APMトランザクショントレースをとってみるまでは、まさかログ書き込みがボトルネックになっているとは想定していなかったので非常に驚きました。

New Relic APMトランザクショントレースの画面

ログ書き込みが遅くなる原因は常に同じではなくて、ログ書き込みがボトルネックになる度にロギングライブラリを直したり、GKE の構成を変更したり、と色々なことをやりましたが、そのような手を打てたのは New Relic APM でアプリケーションのボトルネックを可視化できたからです。このような体験があったおかげで、開発者が New Relic の利便性を実感でき、結果として New Relic に関心を持ってくれるようになったと考えています。

2024 年の SRE の取り組みを点検し、2025 年の SRE の取り組みを考える

2024 年は、New Relic に関心を持ってくれる開発者を増やそうとし、個人的には満足のいく結果を得ることができました。ところで、2024 年に私がやってきた SRE の取り組みは、個人や組織が SRE を開始し、成熟度を高めていく正しいやり方だったのでしょうか。

「私はサイトリライアビリティエンジニアリングを仕事にしている」と意識をして、実際 SRE という肩書のエンジニアになってから 3 年が経つので、慣れやこれまでの経験の反復で仕事をしていないか?と考えることがあります。そこで、仕事のやり方を間違えていないか、今年の 10 月に刊行された『SREをはじめよう 個人と組織による信頼性獲得への第一歩』*3を頼りにして点検しつつ、2025 年の取り組みを考えてみます。

『SREをはじめよう』の 14 章で Dickerson の信頼性の階層構造が紹介されています。階層構造は階層の一番下から始めて、下の階層が「強固」とみなされるようになった時点で初めて上の層に進むというものです。

Dickerson の信頼性の階層構造

監視/オブザーバビリティが第1階層である理由を本では次のように述べています。

第一階層は、もっとも簡潔に言えば「状況は良くなっているのか、それとも悪くなっているのか」ということです。監視は、システムの信頼性に関する客観的なデータや、あなたの取り組みが信頼性に及ぼしている短期的・長期的な影響に関する最良の情報源です(あるいは、そうあるべきです)。あなたが行った変更(新しいソフトウェアのバージョン、構成の変更、環境の修正など)が、システムの信頼性にプラス、マイナス、あるいは中立の影響を与えているかどうかを判断するために必要です。次に向かうべき方向を理解するのに役立ちます。*4

監視/オブザーバビリティ基盤に蓄積されるデータが間違っていると、システムの信頼性について正しい判断ができない、という言説はその通りであると思います。

さて、点検結果について書きます。2024 年の広告プロダクト開発タスクにおける SRE の取り組みは、信頼性の第1階層を強固にするために役に立っていると思われるので、私の仕事は間違っていなかったと言ってよいと思います。なぜなら、負荷試験を通じて開発者が New Relic に関心を持ってくれるようになったことは、監視/オブザーバビリティ基盤を強固にするためにプラスに働くと思うからです。システムの信頼性に関する判断を下すための最良の情報源であるところの New Relic が、開発者にとっての関心事でなかったら、信頼性の階層構造を積み上げていく以前の問題です。

最後に、2025 年の SRE の取り組みについて書きます。2025 年の SRE の取り組みは、引き続き信頼性の第1階層の監視/オブザーバビリティを強固にしていくことだと考えます。New Relic に関心を持ってくれる開発者が増えたことで、私だけではなく、チームで New Relic を最良の情報源にしていく準備ができました。

監視/オブザーバビリティを強固にしていくためには、データの収集、可視化、分析のサイクルを回して改善を続ける必要があると考えます。そのために、2025 年はパフォーマンス定点観測会を始めます。これは、昨年のアドベントカレンダーに実現したいことの1つに挙げていたもので、週1回システムダッシュボードを確認してメトリクスのトレンドを把握し、1週間のイベント(デプロイ, アクセスパターンの変化など)とメトリクスの相関関係の有無を確認する定例です。定期的にダッシュボードを確認することで、不足しているメトリクス, ログ, トレースを見つけたり、メトリクスの統計を見直したりなど、監視/オブザーバビリティを強固にするために必要なアクションを発見することができます。

また、定期的なダッシュボード確認によってシステムメトリクスのトレンドを把握することは、システムの信頼性に関する正しい判断を下すことに繋がり、さらにはインシデント対応の質を向上させることにも繋がると考えています(第2階層の取り組み)。インシデントが発生したときには各種メトリクスを必ず見ることになりますが、それらのメトリクスが普段どのように推移しているのか知っているのと知っていないのとでは、対応に大きな差が出ます。普段からメトリクスを良く見ていると、ちょっとした違和感から問題解決につながる気づきが得られるものなのです。

あとがき

2024 年は新しいシステムの開発・構築に注力していて、AWS / Google Cloud のアーキテクチャ設計とインフラ構築、バックエンド開発、負荷試験をやっていたので、システム運用はほとんどやっていませんでしたが、2025 年からは新しいシステムを安定運用することが最重要業務になります。記事作成を通して 2024 年を振り返ってみたら意外とシステム運用に向けた準備ができていたことが分かり、2025 年の SRE の取り組みを考えることもできたので、今年もアドベントカレンダーに参加してよかったなと思います。

明日は、@slime_not_foundListDetailPaneでのアダプティブな左右分割画面の実装 です。

独自実装したFeature Flagによるシステム移行

TVerでバックエンドエンジニアをしている水野といいます。

この記事はTVer アドベントカレンダー 2024の9日目の記事です。

8日目の昨日は @pikopiko_hammer さんによる 「Webディレクター目線でダークモード対応の思い出を振り返る」 でした。

今日は、睡眠時や仕事中以外はTVerで動画を見ているTVer大好きな私が、最近アサインされたプロジェクトについてお話しします。

はじめに

現在、私はTVerの一部システムの移行作業を行っています。短期間で移行できる規模ではなく、半年規模でいくつかのリリース日に分けた移行計画で対応しています。

移行計画の課題

いくつかのリリース日に分かれているため、次のリリース日までmainブランチへのマージができず、プルリクエストの残存期間が長くなってしまいます。その結果、コンフリクトが発生しやすくなるのと、大量の変更を一気にリリースしなければならないという課題がありました。これを避けるために、段階的なリリースができる仕組みが必要でした。

Feature Flagの導入

高機能なFeature Flagは必要なく、段階的なリリースができれば良かったので、簡易的なFeature Flagを独自実装しました。今回TVerにとって初めての試みだったので、その話を紹介したいと思います。

独自実装したFeature Flag

今回実装したFeature Flagは、機能単位でフラグを設定し、柔軟に管理できるようにすることを目的としています。

サンプルコード

まず、Feature Flagの機能を定義するためのenumを作成します。ここでは、ユーザー管理サービス(UserManagementService)のユーザー作成機能とユーザー更新機能を例にサンプルコードを書いてます。

type UserManagementServiceFeature int

// UserManagementService切り替えで動作に影響がある機能リスト
const (
    // UserManagementServiceのユーザー作成機能
    UserManagementServiceFeatureCreateUser UserManagementServiceFeature = iota
    // UserManagementServiceのユーザー更新機能
    UserManagementServiceFeatureUpdateUser
)

次に、Feature Flagの構造体を定義し、各機能がどの環境で有効かをリストで管理します。

type featureFlag struct {
    // Feature Flag対象機能のenum
    feature UserManagementServiceFeature

    // Feature Flag対象機能を有効にしたい環境のリスト
    enabledIn []string
}

// Feature Flagのリスト
var features = []featureFlag{
    {
        feature:   UserManagementServiceFeatureCreateUser,
        enabledIn: []string{"local", "development"},
    },
    {
        feature:   UserManagementServiceFeatureUpdateUser,
        enabledIn: []string{"local", "development", "production"},
    },
}

最後に、指定された機能が現在の環境で有効かどうかを判定する関数を実装します。

// Feature Flagのenumを受け取って、その機能が有効かどうかを返す
func IsEnabled(feature UserManagementServiceFeature, currentEnv string) bool {
    for _, f := range features {
        if f.feature == feature {
            for _, env := range f.enabledIn {
                if env == currentEnv {
                    return true
                }
            }
        }
    }
    return false
}

使用例

// アプリケーションコード
if IsEnabled(UserManagementServiceFeatureCreateUser, "development") {
    // ここにやりたい処理を書く
}

このコードでは、UserManagementServiceFeatureというenumを使って、ユーザー管理サービスの各機能を定義しています。featureFlag構造体は、各機能がどの環境で有効かをリストで管理します。IsEnabled関数は、指定された機能が現在の環境で有効かどうかを判定します。

できること

  • 機能 x 環境単位の細かい制御が可能

できないこと

  • デプロイなしでのFeatureFlagの切り替え

このようにして、機能ごとに細かい制御が可能となり、段階的なリリースや環境ごとの設定が容易になります。

最後に

TVerではこのように、運用保守のための開発だけでなく、ユーザーファーストでより良い視聴体験が得られるようにシステム移行を始め、さまざまなプロジェクトが進行しています。 現在進行中のプロジェクトの詳細を知りたい方は、ぜひカジュアル面談でお待ちしております。

recruit.tver.co.jp

明日は、 @datahaikuninja さんによる 「TVer広告プロダクト開発タスクのSREになってからの1年を振り返る」 です。明日もお楽しみに!

BigQueryのExternal Tableのスキーマ変更に対応する方法の一つ

TVerでデータシステムの開発・運用をしている黒瀬です。

TVer Advent Calendar 2024の4日目の記事です。

3日目の昨日は @ko-ya346 さんによる 「Terraform + GitHub でデータマート基盤を作った話」 でした。

今日は、BigQueryでExternal Tableのスキーマ変更に対応する方法の一つについてご紹介いたします。

サマリ

BigQueryのExternal Tableをスキーマごとにバージョン分けし、それを包含するviewを作成することで、データのスキーマ変更にも対応しやすくなります。

背景と課題

弊社のデータシステムでは、データをGCSといった安価なデータストアに置き、それをBigQueryのExternal Tableから参照する構成を採用することがあります。

その際、データストアに置いたデータのスキーマが変わってしまうことで、External Tableでスキーマエラーが発生する問題がよく発生します。

方法検討

そこで、スキーマ変更に対応しつつテーブルに対してクエリを実行できるようにする方法を検討しました。

アプローチ1

まず考えられるのは、データストアにあるデータそのものに手を入れる方法です。

スキーマに何らかの変更が入った際に、過去の全データをさかのぼって修正を入れていきますが、この方法では以下のような問題があります。

  • オリジナルのデータを残しておけない、もしくはバックアップをとる必要がありストレージの余分なコストがかかる
  • 一つ一つのファイルを開いてスキーマや値を修正する作業はデータが増えるとさかのぼる量も増えるためスケールしない

アプローチ2

一方で、データストアにあるデータそのものに手を入れない方法を検討しました。

異なるスキーマごとにデータストアのパスを分離し、それぞれに個別のExternal Tableを作成します。そして、それらの差分を吸収してまとめてクエリを実行できるようにするViewを作成します。

より具体的には、下記のようなものになります。

  • データストアの準備: スキーマにバージョンをつけて、そのバージョンごとにデータストアのパスを分けてファイルを配置する
  • テーブルの準備: そのバージョンごとにExternal Tableを作成する
  • viewの準備: そのExternal Tableごとのスキーマの差分を吸収するViewを作る
  • スキーマの変更への対応: 新しいデータストアのパスにファイルを配置し、そのパスを参照するExternal Tableを作成し、 Viewを修正する

この方法は、アプローチ1に対して下記のようなメリットがあります。

  • オリジナルのデータをデータストアに残しておけるため、オリジナルのデータを参照したいユースケースが後で出てきてもすぐに対応できる
  • スキーマが変わった際にもその新しいスキーマ用のExternal Tableの用意とViewの変更だけでよいため、スケールする

そのため、弊社ではアプローチ2を採用することにしました。

実践例

ここからは、上記アプローチ2のごく簡単な実践例を示します。

前提

この例で使うバージョンとそのスキーマを下記の通りとします。

  • version 1.0.0のデータ
    • id STRING
    • updated_at TIMESTAMP
  • version 1.1.0のデータ
    • id STRING
    • updated_at TIMESTAMP
    • label STRING

データストアの準備

GCSのバケットを用意し、パスをバージョンごとに区切り、それぞれのパス配下に対応するバージョンのデータを置きます。ここでは、下記のようにファイルをアップロードしました。

  • gs://sample/1.0.0/file1.0.0.csv としてアップロード
id,updated_at
1,2024-10-24 00:00:00
  • gs://sample/1.1.0/file1.1.0.csv としてアップロード
id,updated_at,label
2,2024-10-25 00:00:00,foo

テーブルの準備

次に、上記GCSのパスを参照するExternal Tableをそれぞれ作成します。

cloud.google.com

  • gs://sample/1.0.0/*.csvを参照するExternal Table sample.version_1_0_0 を作成します。
CREATE EXTERNAL TABLE sample.version_1_0_0 (
  id STRING,
  updated_at TIMESTAMP
) OPTIONS (
    format = "CSV",
    uris = ["gs://sample/1.0.0/*.csv"],
    skip_leading_rows = 1
)
  • gs://sample/1.1.0/*.csv を参照する External Table sample.version_1_1_0 を作成します。
CREATE EXTERNAL TABLE sample.version_1_1_0 (
  id STRING,
  updated_at TIMESTAMP,
  label STRING
) OPTIONS (
    format = "CSV",
    uris = ["gs://sample/1.1.0/*.csv"],
    skip_leading_rows = 1
)

viewの準備

最後に、上記External Tableを集約するviewを次の通り作成します。

cloud.google.com

CREATE VIEW sample.all AS (
  SELECT
    *,
    NULL AS label
  FROM
    sample.version_1_0_0
  UNION ALL
  SELECT
    *
  FROM
    sample.version_1_1_0
)

スキーマの変更への対応

ここでは、カラム名がlabelからvalueに変更になり、以下のような新しいスキーマのデータが入ってくることになったとします。これをversion 1.2.0とします。

  • version 1.2.0のデータ
    • id STRING
    • updated_at TIMESTAMP
    • value STRING

gs://sample/1.2.0/file1.2.0.csv として下記ファイルをアップロードします。

id,updated_at,value
3,2024-10-26 00:00:00,bar

次に、gs://sample/1.2.0/*.csv を参照するExternal Table sample.version_1_2_0 を作成します。

CREATE EXTERNAL TABLE sample.version_1_2_0 (
  id STRING,
  updated_at TIMESTAMP,
  value STRING
) OPTIONS (
    format = "CSV",
    uris = ["gs://sample/1.2.0/*.csv"],
    skip_leading_rows = 1
)

最後に、viewを変更します。

CREATE OR REPLACE VIEW sample.all AS (
  SELECT
    *,
    NULL AS value
  FROM
    sample.version_1_0_0
  UNION ALL
  SELECT
    * EXCEPT(label),
    label AS value
  FROM
    sample.version_1_1_0
  UNION ALL
  SELECT
    *
  FROM
    sample.version_1_2_0
)

例えば下記のようなクエリを実行することで、これらのデータにアクセスができます。

SELECT
  *
FROM
  sample.all

効果

このアプローチにより、下記のような効果がありました。

  • オリジナルのデータに手を入れなくてよいため、スキーマ変更の際のオペレーションが簡単になった
  • スキーマ変更が想定されるユースケースにおいても、安価なデータストアを活かしてBigQueryでクエリを実行できるようになった

まとめ

データソースのスキーマが変わることはよくあることですが、それに対応しつつExternal Tableをうまく利用する方法を検討しました。

明日の TVer Advent Calendar 2024@tomonish888 さんによる 「TVer動画配信を支えるCDN」 です。お楽しみに!

採用について

ユーザ数が急上昇中のTVerでは、サービスを支えるデータも日々増え続けています。データエンジニアとして、データをビジネス価値につなげるためのシステムづくりに一緒に取り組んでみませんか?

herp.careers

Terraform + GitHub でデータマート基盤を作った話

こんにちは。TVer でデータ分析をしている高橋です。
こちらは TVer Advent Calendar 2024 の3日目の記事です。
2日目の記事は @takanamito さんの connect-goでHTTP GETリクエストを受け取る でした。
この記事では分析環境を効率化するために弊社で活用している、Terraform と GitHub を使ったデータマート基盤をご紹介します。

開発のきっかけ

これまで分析業務は、データレイクに集約された生ログを都度前処理し、個別の集計作業を行っていました。クエリ作成のたびに手作業でロジックを組み立てるか過去のクエリからロジックをコピペするような状況で、あまり作業効率が良いとは言えません。
時々前処理が刷新されることがあり、全員で過去のクエリを全て修正するというマンパワーに頼った運用をしていました。
またスケジュールクエリの管理もコンソール上での手作業が中心で、更新作業はコンフルやスクショに証跡を残しながら行っており何とも手間がかかる状態でした。

......

そのような状況の中、昨年からダッシュボード構築などのニーズが高まり、コスト観点からデータマート開発が求められました。そこでコスト観点とともに前述の作業効率化と品質向上を目指し、汎用的なデータマート基盤を構築することにしました。
サンプルのリポジトリはこちらです。

github.com

基盤の構成と運用

以下はディレクトリ構成です。

├─.github
│  └─workflows
├─scheduled_query
│  └─dataset.sample_mart
├─scripts
├─table_valued_function
├─terraform
│  ├─environments
│  │  ├─devintegrate
│  │  ├─production
│  │  └─staging
│  └─modules
├─user_defined_function
└─view

スケジュールクエリとテーブルを作成する流れを例に、具体的な手順を以下に示します。

  1. ディレクトリ作成
    scheduled_query配下にリソースごとのディレクトリを作成(例: dataset.sample_mart)。
  2. 必要ファイルの作成
    • query.sql: 定期実行するクエリ
    • settings.json: スケジュールクエリやテーブルの設定
    • schema.json: テーブルスキーマ(テーブルを作成する場合のみ)
  3. Terraform コマンド実行
cd terraform
terraform init -reconfigure -backend-config=./environments/devintegrate/dev.tfbackend
terraform apply -var-file ./environments/devintegrate/terraform.tfvars

工夫したこと

Terraform の知識不要で簡単リソース作成

query.sqlsettings.jsonschema.jsonの3つのファイルを用意するだけでリソース作成が可能です。Terraform の Data Sourceを活用し、ディレクトリ配下の設定情報を動的に読み込む仕組みを導入してこれを実現しています。
以下は、settings.json を収集するスクリプトの例です。

BASE_PATH=$1
declare -A queries
for settings_file in $(find "$BASE_PATH" -name 'settings.json'); do
    key=$(basename "$(dirname "$settings_file")")
    content=$(jq 'if has("dataset_id") then 
                    .dataset_id |= if type == "object" then . 
                    else {prd: ., stg: ., dev: .}
                    end 
                  else . end | @json' "$settings_file")
    queries["$key"]=$content
done
result="{"
for key in "${!queries[@]}"; do
    result+="\"$key\": ${queries[$key]},"
done
result="${result%,}}"
echo $result

Terraform でスクリプトを実行し、リソース情報をローカル変数として扱えるようにしています。

data "external" "scheduled_query_config" {
  program = ["bash", "${path.module}/../scripts/list_queries.sh", "${path.module}/../scheduled_query"]
}

locals {
  scheduled_queries = { for k, v in data.external.scheduled_query_config.result : k => jsondecode(v) }
}

環境ごとの設定管理を簡略化

environments ディレクトリに各環境の設定をまとめ、共通するリソースは再利用可能にしています。これにより、開発・検証・本番といった複数環境を扱う場合でも効率よくメンテナンスを行うことが可能になっています。

例: devintegrate環境の設定ファイル

# dev.tfbackend
bucket = "sample-datamart-bucket-dev"
prefix = "sample-datamart"

# terraform.tfvars
environment       = "dev"
project           = "project-dev"

service_account_email = "datamart-creator@project-dev.iam.gserviceaccount.com"

GitHub Actions を活用した CI/CD の自動化

GitHub Actions を利用して以下のような作業を自動化しています。

  1. terraform plan の結果を出力
    プルリクエストを作成すると、GitHub Actions が terraform plan の実行と対応する Actions のリンクをプルリクエストに投稿します。これにより差分確認が容易になりレビューがスムーズになります。
  2. mainブランチへのマージ後、自動で環境ごとのブランチへプルリクエスト作成
    開発ブランチからmainブランチに変更がマージされると、GitHub Actions が自動で各環境に対応するブランチ(例: devintegrate, staging, production)へのプルリクエストを作成します。
  3. 各環境へのプルリクエストマージ時に terraform apply を自動実行
    環境ごとのブランチへのプルリクエストがマージされると、GitHub Actions が terraform apply を実行して該当環境に変更を反映します。これにより簡単にデプロイ作業を行うことができます。

また、既存リソースを Terraform 管理したい場合もあります。これは import ブロックを使って取り込みを自動化しました。
各環境の terraform.tfvarsにインポートするリソース名とidを記述し、main.tfimport ブロックで呼び出します。
ちなみに import ブロック内での for_each は Terraform 1.7 以降で利用可能です

# terraform/environments/production/terraform.tfvars
~~~

import_resources = {
    "table": [
        {
            "resource_name": "dataset.sample_mart",
            "id": "projects/{project}/datasets/{dataset_id}/tables/{table_id}"
        }
    ],
    "view": [
        {}
    ],
    "scheduled_query": [
        {}
    ],
    "tvf": [
        {}
    ]
}

# terraform/main.tf
import {
    for_each = { for v in var.import_resources.table : v.resource_name => v if length(var.import_resources.table) > 0 }
    id       = each.value.id
    to       = module.bigquery_resources.google_bigquery_table.tables[each.value.resource_name]
}

導入の効果

以下の表は、導入前後の作業比較です。導入前に比べて多くの作業が効率化されました。やったー!

作業内容 導入前 導入後
スケジュールクエリやViewの更新 更新前後のクエリをコンフルに貼り付けてレビュー。レビュー通過後クエリをコンソールにコピペ 開発ブランチで更新してプルリク作成。レビュー通過後マージ
汎用的な処理の管理 以前のクエリからコピペ or Redash のスニペットを利用 UDF や TVF を利用
汎用処理を更新 関連クエリを全て手作業で修正 プルリク→マージ

また、スケジュールクエリの管理が容易になったことで分析用の中間テーブルが数多く生成され、こちらも作業効率化やコスト削減にも貢献しています。

課題

このように様々な作業の効率化が進んでいる一方で、この基盤には以下の課題が残っています。

依存関係が管理できない

例えば新規にテーブルとそれを参照するViewを同時に作成する場合、先に View の生成が実行されて参照先が見つからずエラーになることがあります。
本来であれば Terraformの depends_on 引数を使って依存関係を指定すれば解決できますが、この引数は静的参照しか受け付けておらず利用できません。
このため現状は GitHub Actions でエラー発生時に再実行することで対応していますが、これはあくまで暫定的な方法です。
また、中間テーブルをもとに別の中間テーブルを作成するようなケースでは依存関係が複雑化し、管理が困難になる点も課題です。

デプロイ前の確認が不十分

現在の CI/CD パイプラインでは、terraform plan の出力と terraform applyの実行確認しか行っておらず、以下の観点での確認は出来ていません。

  1. SQLクエリの構文的な正しさ 構文が間違った状態のままデプロイし、実行エラーで初めて気付くといったことがしばしばありました。
  2. SQLクエリの出力内容の正しさ SQLの実行環境がないため、前処理が正しく行えているかや、確からしい集計値が得られているかなどの確認ができません。

これらの課題に対応するために、現在も継続して検討を行っています。
例えば以下のような方針を検討しています。

  • クエリをコピペ実行可能な状態で管理
    • query.sql にはコピペ実行可能な形式で記述し、デプロイ時に変数を上書きすることでクエリ実行のハードルを下げる
  • Redash との連携
    • プルリクエストに query.sql が含まれている場合、Redash 上にそのクエリを自動生成する
  • dataformに乗り換える
    • 上記に加え依存関係の管理も可能であり、現在仕様調査を進めています

おわりに

データマートの管理方法は、世の中に多種多様な手法が存在します。本記事では、その中の一例として弊社が採用した基盤の構築と運用方法をご紹介しました。
この記事がみなさんの運用負荷を軽減し、より効率的なデータマート管理のヒントとなれば幸いです。

明日の記事は @_kurose さんの「BigQueryのExternal Tableのスキーマ変更に対応する方法の一つ」です!お楽しみに!

qiita.com

TVerにおける技術統括事務局の取り組み

この記事はTVer アドベントカレンダー 2024 1日目の記事です。

どうも、TVerでEngineering Managerをしてる 高橋 @ukitaka といいます。

アドベントカレンダー初日のこの記事ではTVerのプロダクトや組織がどんな状況に置かれていて、どんな課題に向き合い、それらをどう解決していこうとしているのかついて俯瞰的に書いてみようと思います。 結果としてこの1年でどうエンジニア組織が変化したのか?については 最終日に技術統括の脇阪さんに熱く語ってもらう予定なのでお楽しみに!

“技術統括事務局” について

この1年でTVerは内製開発のための体制が整い、社内でいくつかの開発チームが動いているような状況に変わってきました。 それぞれのチームの開発が一定安定してくると次に関心ごとになってくるのがエンジニア組織の横断的な課題解決や全体最適で、それを担う仮想的/横断的なチームとして技術統括事務局 が立ち上がりました。 (といっても自分と技術統括の2人チームです)

“技術統括事務局” となんだかすごそうな名前がついていますが、実際は開発 “以外” のあらゆる機能を一旦ここに載せているような状態で、部署の間に落ちたボールを拾ったり、全社へのツール導入をやることもあれば、技術広報的な役割も持っていたりします。

開発組織の現状把握と計測

横断的な組織改善をしていくにあたって、まず手始めに実施したのがDX Criteriaです。 振り返り文化が十分でなく開発プロセスの改善がうまく進んでいないなど、これを実施することで見えていなかった課題や優先して解決すべき課題を把握するのに非常に役立ちました。

技術統括事務局オフサイト@福岡 必死に数百問あるDX Criteriaに回答している様子

DX Criteriaで特に課題感が大きかったところ。かなり真っ赤で恥ずかしいですが、一応厳し目(1つでもできていない部署があったらできていない判定するなど) の採点をしてます。

これらを踏まえてなにをどういう順番で解決していくかを技術統括事務局のロードマップに落とし込み改善を進めています。

上記のDX Criteriaも組織の状態を計測し定量的な改善を進めていくために活用することもできますが、より開発生産性にフォーカスした取り組みとしてFindy Team+ を導入し Four Keysなど基本的な指標を取得する準備をしています。 (といってもこれを書いている時点ではようやく導入&セットアップが終わったくらいですが・・🥺)

直接的に技術統括事務局が課題解決を行うこともありますが、基本的には 振り返り文化の醸成 開発生産性計測・可視化のためのツール導入 のように各チームが自律的に改善活動が行えるような環境づくりをするということを基本方針として活動を行ってきました。

このような感じで現状の把握、そして計測ができるようになったのがこの半年のおおよその動きでした。

技術発信の場づくり、そしてアドベントカレンダーへ・・

また、より採用に近い文脈での取り組みとして技術発信の文化作りや技術広報 (Tech PR)のような役割も担当してきました。

これも先ほどの考え方と同様、技術統括事務局(技術広報)としては組織として技術発信が推進されるような仕組みづくりを行うということを基本的な方針としています。

技術発信をするようになる要因はいくつかあるとは思いますが、メルカリさんが社内で行ったアンケートでは以下のような結果がでているとのことで、TVerでもまず「発信する場を作る」ということを1つのポイントとして注力してきました。

参考: メルペイ Tech PR が実際にまわしている PDCA サイクル | メルカリエンジニアリング engineering.mercari.com

 

アンケート結果によると、発信をした一番の理由は「発信機会や場があったから」ということでした。

具体的な取り組みとして、TVer Tech Talk (通称T3) と呼ばれる社内勉強会を月1で開催するようになりましたし、このアドベントカレンダーも「発信する場を作る」の取り組みの一環として行っています。

結果として発信文化が根付いたと言えるのか?はこのアドベントカレンダーを完遂させることによってみなさんが示してくれるはずです!!!!!

まとめ

ということで初日はTVerにおける技術統括事務局の半年の取り組みをまとめてみました。 感覚的にはやっと改善のスタートラインにたてたというところです。

明日は id:takanamito による 「connect-goでHTTP GETなリクエストを受け取る 」です!お楽しみに!

TVerにバックエンドエンジニアとして中途入社した最初の3ヶ月

はじめまして。id:takanamitoです。

バックエンドエンジニアとしてTVerに入社して3ヶ月が経ちました。 TVerに入ってみて感じたこと、開発組織が何に取り組んでいるのか書いてみようと思います。

  • TVerのオンボーディング
  • ドキュメントをたくさん書く文化を広める
  • たくさん質問・相談する
  • TVerが取り組んでいる開発とは
  • この先やりたいこと
続きを読む