ナレッジワークにおけるデータ基盤の構成 - 2025/09
はじめに
ナレッジワークでエンジニアをしている @_sisisin です
2025/04 にデータ基盤チームへ移ってきて初めてのデータ基盤屋さんをやっています
主に基盤の整備やデータ活用の支援をしたりしています
この記事ではナレッジワークでのデータ基盤の現状を社内のメンバーへ共有する目的で書きました
内容的に公開できそうなので、Zenn に公開する形にしました
ということで、この 2025/09 時点のデータ基盤の構成とその思想・経緯を残しておきます
前提の説明 - ナレッジワークのサービスについて
ナレッジワークのサービス基盤について
ナレッジワークでは主に Google Cloud 上にサービス基盤を構築しています
余談ですが、Poetics社とのM&Aからシステム統合を進めているJamRollというサービスがAWS上にあります
こちらにもデータ活用のための基盤があるのですが、記事執筆時点で引き継いだばかりで何も手がつけられていないので ナレッジワークのサービス基盤に関連する部分に絞って説明します
データ基盤に関連する部分としては、以下のようなリソースを利用しています
- CloudSQL (PostgreSQL)
- BigQuery
また、開発環境から本番環境まで dev,qa,stg,prd の 4 つの環境を利用して開発・運用を行っています
インフラのコード管理には Terraform を利用しています
データ基盤が関わるナレッジワークの機能について
ナレッジワークは利用状況などの分析レポート出力機能を提供しています
この分析レポート出力機能ではデータ基盤から作成したデータを利用して出力を行っています
データ基盤の設計にはこれも加味する必要があります
データ基盤の全体像
サービス基盤が Google Cloud なので、データ基盤も同様に Google Cloud & BigQuery を中心に構築しています
データの抽出・変換・集計には dbt を利用しており、Cloud Run Job で実行しています
大まかに以下のような構成を取っています
論理レイヤーとして、 Data Sources
, [1]Data Lake
, Data Warehouse
, Data Marts
, Data Consumers
の 5 つに分けて表現しています
レイヤー | 説明 |
---|---|
Data Sources | 元データが存在する場所。CloudSQL や GoogleAnalytics, スプレッドシートなど。 |
Data Lake | Data Source から取得したデータを格納する場所。なるべく元の構造を保持する。ここから先は全て BigQuery でデータを扱う。 |
Data Warehouse | 分析用に整形されたデータを格納する場所。dbt の Staging, Intermediate model が該当。 |
Data Marts | 特定のビジネスユニットや目的に特化したデータを格納する場所。dbtのMart modelが該当。 |
Data Consumers | データを利用するユーザーやシステム。BI ツールや分析ツールなど。先述の通り、ナレッジワークのサービスも含まれる。 |
また、物理インフラとして knowledgework-{env}
, kwdp-elt
, kwdp-mart
の 3 つの Google Cloud Project が登場します
プロジェクト名 | 説明 |
---|---|
knowledgework-{env} | サービス基盤のプロジェクト。サービスの Data Lake もこちらに配置する |
kwdp-elt | データ基盤のコアプロジェクト。主に社内システムを Data Source とするデータの Data Lake 及び Data Warehouse を担う |
kwdp-mart | データ基盤のデータマート用プロジェクト。データ利用者向けの Data Marts を担う。BI ツールのホスティング等もここ。 |
全体の流れとして、以下のようになっています
- サービス基盤を含む各種データソースを何らかの手段で BigQuery へ取り込めるようにする(
Data Sources
~Data Lake
レイヤー) - BigQuery に取り込んだデータを dbt で変換・集計して分析に適した形にしてデータマートへ出力する(
Data Lake
~Data Warehouse
~Data Marts
レイヤー) - 出力したデータを各種 BI ツールや分析ツールで参照・活用する(
Data Marts
~Data Consumers
レイヤー)
以下、データの流れに沿って、各レイヤーについて詳しく説明していきます
Data Sources ~ Data Lake
- サービス基盤を含む各種データソースを何らかの手段で BigQuery へ取り込めるようにする(
Data Sources
~Data Lake
レイヤー)
ということで、元データを BigQuery へ取り込む部分です
Data Source はサービス由来のものと、社内システム由来のもので大きく 2 系統に分かれます
サービス由来のデータ
サービス基盤が各環境分あるため、環境ごとに扱えるようにする必要があります
そのため、サービス用の knowledgework-{env} プロジェクトに Data Lake レイヤーの BigQuery Dataset を配置する、という構成を取っています
データソースとして主に、CloudSQL, GoogleAnalytics, CloudLogging を扱っています
CloudSQL
Google Cloud の Datastream を利用して、CloudSQL(PostgreSQL)のデータを BigQuery へストリーミングで取り込んでいます
ナレッジワークでは複数のバックエンドサービスが動いており、PostgreSQL の database,schema も新規サービスが立ち上がるタイミングで作られることが多いです
そのため、これらのリソースは Terraform module 化しており、PostgreSQL リソースを追加するタイミングで 一緒に Datastream,BigQuery などの関連リソースが作成されるようにしています
GoogleAnalytics,CloudLogging
- GoogleAnalytics: BigQuery Export 機能を利用して BigQuery へエクスポートしています
- CloudLogging: Log Sink 機能を利用して LogBucket へ出し、BigQuery の Linked Dataset 機能を利用して BigQuery 上で参照できるようにしています
社内システム由来のデータ
これは物によって様々なので、ほぼ個別に対応しています
現状マネージドサービスは使ってません(そもそも検討も出来てないですが・・・)
一例として GitHub のデータを紹介しますと、以下のような作りにしています
- GitHub の Webhook でイベントを Cloud Run で受けて Request Body をそのまま PubSub へ流す
- BigQuery Subscription で PubSub のメッセージを BigQuery へ取り込む
dora-team/fourkeys のリポジトリと近い構成になっていますね
Request Body をそのまま PubSub へ流し、BigQuery 上で JSON 型の列に入れています。こうしておくことでスキーマの変更への追従をしないでも全てのデータが常に取れるようにしています
どのデータソースに対しても同じアプローチをしており、基本的に JSON 列に未加工でデータを入れるようにしています
最近はこういうのが生成 AI で簡単に作れてしまうので、今のところは割とカジュアルにデータ取り込みを作り込んでいく方向でやっています
Data Lake ~ Data Warehouse ~ Data Mart
- BigQuery に取り込んだデータを dbt で変換・集計して分析に適した形にしてデータマートへ出力する(
Data Lake
~Data Warehouse
~Data Marts
レイヤー)
ここからは dbt の世界になります
dbt の詳細な設定やモデリングの話よりは、どちらかというとインフラリソースの説明とデータの流れを中心に解説していきます
主なdbtを動かすリソース構成
dbtはCloud Scheduler と Cloud Workflows でワークフローを定義し、その中で Cloud Run Job を起動して実行しています
さて、サービスのプロジェクトが 4 環境あるのに対して、データ基盤用のプロジェクトは 1 つにしています
ではどのようにして各環境のデータの transform をしているかというと、 kwdp-elt
プロジェクト内で 4 環境に対応した Cloud Run Job をそれぞれ動かす構成としています
4環境向けのCloud Run Jobの図
Docker Imageは共通で、Dockerfileで以下のような dbt
コマンドをエントリポイントとしたものを利用します
# 前略
ENTRYPOINT ["/app/.venv/bin/dbt"]
これらのCloud Run Jobはmainブランチへのマージドリガーで一律デプロイされるようにしており、変更を素早く反映できるようにしています
サービスの機能に利用しているアウトプットもあるのにマージトリガーでいいのかという論点はありますが、サービス側の作りで一定リスク回避ができるような仕組みになっているのでOKとしています
こぼれ話 - 元々はサービス基盤の方にdbt Cloud Run Jobを作っていた
元々は サービス基盤のある knowledgework-{env}
の方でdbtのJobを構築していました
ナレッジワークはモノレポなので、デプロイの仕組みなどがある程度ある・利用が限定されていたなどの理由からそうなっていたようです
しかし、実際に運用していると以下のような課題が出てきていました
- dbtのデプロイのライフサイクル・仕組みの問題: サービス向けに構築されたデプロイパイプラインを流用していたことに起因して若干歪な設定になっていたり、prd環境まですぐにデプロイして良いのにデプロイトリガーがサービス準拠なので手間が多い
- Terraformのデプロイライフサイクルがデータ基盤の開発サイクルと噛み合わない: 例えば本番用のdbt Jobへ追加の権限を付与する、といった場合にもサービス向けのTerraformのデプロイを待つ必要がある
- 社内システムのデータなどの扱い: GitHubのデータなどをサービス基盤上で扱うのは直感に反するし、セキュリティ要件が全く異なる中での権限制御がやりにくい
- そうやって
kwdp-elt
プロジェクトが最初に生まれ、そちらにもdbtを作成した。が、そうすると今度はdbtのメンテナンスがサービス基盤とデータ基盤で2重管理になる
- そうやって
これらの課題を解消するために、 kwdp-elt
プロジェクトへ1本化した、という経緯があります
dbtを利用したデータの流れ
さて、dbt projectで各環境向けにprofiles.ymlでtargetを定義しておいて、 dbt-kw-{env}
JobをWorkflowから起動する時に DBT_TARGET
環境変数を与えて挙動を環境ごとに変えています
具体的には以下のようなリソースが環境別に切り替わります
- sources.ymlに定義されるdbt Source:
knowledgework-{env}.*
(prdのみkwdp-elt.*
も含める) - staging,intermediate modelの出力先dataset:
kwdp-elt.dbt_kw_{env}.*
(prd のみkwdp-elt.dbt
) - mart modelの出力先dataset:
- for サービス:
knowledgework-{env}.dbt_marts_*
- for 社内利用:
kwdp-mart.dbt_*
- for サービス:
dbtの中間データ(staging, intermediate model)も kwdp-elt
プロジェクト内に dbt_kw_{env}
(prdのみ単に dbt
)datasetを定義しています
中間データについては外部から触れられないようにしており、基本的にdbtのモデル開発者にのみ閲覧権限を付与しています
dbtの最終出力データ(mart model)は、用途に応じて knowledgework-{env}.dbt_marts_*
dataset か kwdp-mart.dbt_*
dataset に出力しています
実際のmodelの例を挙げて、どのようにデータが流れているかを説明します
例1: ナレッジワークの分析レポート出力機能向けの出力
以下の図は分析レポート出力用に定義された usage_analysis_logged_in_data
のmart modelのリネージグラフです
このモデルを target: dev
で実行した場合
- まず Source として
knowledgework-dev
プロジェクト上にサービスのPostgreSQLからレプリケーションしているknowledgework-public
datasetのactivities
,group_users
,groups
,users
テーブルを参照 - staging レイヤーとして
kwdp-elt.dbt_kw_dev.stg_users
といったstaging modelを作成 - intermediate レイヤーとして
kwdp-elt.dbt_kw_dev.int_usage_analysis_logged_in_data
intermediate modelを作成 - 最終出力として
knowledgework-dev.dbt_marts_usage_analysis.usage_analysis_logged_in_data
dataset へ出力
というような参照・出力になります
例2: 開発生産性の可視化用のGitHub集計データ出力
以下の図は社内で生産性の可視化に利用しているGitHubのデータを集計した engneering_merged_pull_requests
のmart modelのリネージグラフです
このモデルは、データソースがGitHub Webhook経由で取り込んだテーブルとなっていて環境が1つしかないため、 target: prd
でのみ実行されるように制御しています
さて、実際に動かすと
- Sourceとして
kwdp-elt.github.events_raw
を参照 - baseレイヤーとして、一度
kwdp-elt.dbt.base_github__events
modelを作成 - stagingレイヤーとして、イベント種別ごとに
kwdp-elt.dbt.stg_github__pull_request_events
などのstaging modelを作成 - intermediateレイヤーとして、
kwdp-elt.dbt.int_pull_requests
intermediate modelを作成 - 最終出力として
kwdp-mart.dbt_engneering.engrneering_merged_pull_requests
dataset へ出力
といった流れになります
ここでは Sourceが kwdp-elt
プロジェクトにあるため、サービス用のプロジェクトは参照されていません
Data Lake から Data Martまでの流れのまとめ
事前にData LakeレイヤーとしてBigQueryへ集約したデータを元に、dbtを利用して集計結果をData Martレイヤーとして出力するまでの流れを見てきました
ざっくりまとめると、 kwdp-elt
プロジェクトで4環境分のdbtのCloud Run Jobがそれぞれいい感じに動いて環境に応じた出力先へ出力している、という感じです
Data Marts
~ Data Consumers
- 出力したデータを各種 BI ツールや分析ツールで参照・活用する(
Data Marts
~Data Consumers
レイヤー)
dbtでデータを出すまで追ってきました
データ基盤の終着点、データ利用側の話をします
サービス向けの出力・社内利用向けの出力でそれぞれ大きく異なるので分けて説明します
サービス向けの出力
dbtを利用したデータの流れ にて、mart modelの出力先は knowledgework-{env}.dbt_marts_*
だと述べました
実際には2025/09現在は分析レポート出力機能向けに dbt_marts_usage_analysis
dataset へのみ出力しています
これはもし追加で参照したいという話になった場合には別のdatasetを作成する、という思想での命名となっています
この辺は「ユースケースに応じたData Martを作成する」という原則に則っている形です
こうすることで、機能を提供するサーバーごとに閲覧権限を与えるように出来るし、参照するべきテーブルの役割も明確になります
こぼれ話 - レポート機能のサービスレベル
お客様向けの機能としてレポート出力できるようにしているため、もちろん当日集計が失敗したらインシデントとなります
ではどのぐらいの時間までに終わっていればいいのか?という話ですが、これもPdMと話し合って決めて運用しています
論点は様々あったのですが、最終的にはdailyで実行しているdbtジョブが正午までに集計が終わっていない場合はインシデント扱いとする、という話に着地しました
さらにこぼれ話をすると、Google Analyticsのデータが日本時間で午前9時をまたがないと前日分のデータが入らない(!)(UTC 0:00起点なんやろなあ)ため、dbtジョブは午前9時台で実行しています
いざ失敗したときに復旧までの時間が短くて緊張感がありますね
社内利用向けの出力
社内利用向けの出力は kwdp-mart.dbt_*
dataset へ出力しています
mart datasetはなるべく細かく分けるようにしています
これは、利用者・用途・オーナーシップを明確にするためです
誰がどこから参照しているかわからない状態になると運用面で問題が起きることが多いため、なるべく細かく分けるようにしています
datasetは以下のような tf で定義しており、データ利用したいオーナーシップを持つチームごとに必要なだけ権限を払い出す、という構成が自然と出来るようにしています
locals {
# dataset定義
mart_datasets_config = {
platform_and_sre = {
dataset_id_suffix = "platform_and_sre"
friendly_name = "mart for Platform and SRE analytics"
description = "Platform and SRE領域向け分析用データマート"
viewers = concat(
module.const.users.data_developer_members, # 役割ごとに定義したmember. 基本的にメーリスを利用している
module.const.users.data_user_members
)
}
}
}
resource "google_bigquery_dataset" "mart_datasets" {
for_each = local.mart_datasets_config
project = var.project_id
dataset_id = "dbt_${each.value.dataset_id_suffix}"
friendly_name = each.value.friendly_name
description = each.value.description
location = var.region
}
閲覧は主にlightdash,redashから行っています
redashが昔から利用されていたのですが、いくつか課題があるために新規ではlightdashの利用をするようにしています
こぼれ話 - redashの課題
歴史的経緯で間違ったクエリがあったり、ロジックが散逸していたり、といったよくある話もありますが、それよりも苦しいのが以下の点です
redashはログインユーザーの権限でBigQueryへクエリが出来ない
(まあlightdashでこれが出来るようになったのも最近なのですが)
ここまでの説明では特に言及してきませんでしたが、BigQueryのポリシータグを利用したデータのアクセス制御も細かく運用しています
お客様の個人情報は当然自由に見れてはいけませんよね、というやつです
さてredashの話ですが、redashでBigQueryへクエリを実行する場合、redashの設定でサービスアカウントを設定し、その権限でクエリが実行されます
つまり、限定的に権限を払い出すといったことが非常にやりにくくなっています
例えばお客様の問い合わせ対応で特定の開発者にのみ個人情報閲覧の権限を払い出したい、みたいなことがやりにくいですね
redash上の個人情報閲覧用グループ、といったものを作ってそこに所属させて・・・みたいなことはやってはいるのですが・・・
lightdashでは、Google Loginをしていれば、そのユーザーの権限でBigQueryへクエリ実行が出来るため、権限制御が非常にやりやすくなっています
dbtとの親和性の話などもあるのですが、こういった事情があってlightdashを新規では利用する、という方針で進んでいます
まとめ
ナレッジワークの現在のデータ基盤について、データの論理・物理レイヤーを示し、データの流れを追っていく形で説明をしました
ホントはdbtの話とか権限制御とか書こうと思ってたのですが、データの流れを追うだけでかなりの分量になってしまったので割愛しました
そのへんは追々書いていきたいな〜という気持ちです(気持ちだけ)
ここまで読んでいただき、ありがとうございました
KNOWLEDGE WORK Blog Sprint、明日9/15の執筆者はバックエンドエンジニアのnasumさんになります
-
ここの
Data Lake
,Data Warehouse
,Data Marts
はゆずたそさん( https://x.com/yuzutas0 )の提唱した定義を参照しています。グローバルの用語の定義とは異なるため注意。 ↩︎
Discussion