── レガシー基幹システムをGoogle Cloudで「今すぐ動く」分析基盤に蘇らせる、ちょっとずるい考え方
この記事はこんな人に向けて書いています
商社やメーカーなどで「DXを推進せよ!」とミッションを任されたエンジニアや、レガシーなオンプレミス基幹システム(SQL Serverなどなど)のデータ活用に頭を抱えている技術選定者、DXコンサルの方へ。
既存の基幹システムに1ミリも手を加えず、安全かつ爆速でリアルタイムBIやAI予測基盤を作るための私たちの「設計アイデア」をまとめました。
Q:基幹システムのリプレースって、なぜいつも炎上するんだろう。
A:きっとそれは動いているものを壊しているからです。
何十年もかけて積み上げた業務ロジック、カスタマイズ、誰が把握しているかもわからない例外処理——
そのジェンガのピースを抜いたり置いたりして壊す。
やっぱそうなるわなと。
で、今回は「基幹システム触るのやめればいいんじゃねえ」っていう設計アプローチです。
- 24時間稼働している日々の基幹業務を1秒も止めない
- 現行「資産(負債)」を刷新する
- なにより事故らない
レガシーなオンプレ基幹システム(SQL Serverなどなど)があったとして、
これを一切触らずに、GCPを使ってリアルタイムのBIやAI分析基盤を作る方法と、その設計判断をまとます。
なぜ「触らない」が正解なのか
DXあるある早く言いたい。
「古い、使い勝手が悪い」「基幹システムどう刷新するか?」 ってもろもろスタートされがち。
この時点でフォーカスが「基幹システムどうしようか」っていう話で、目的と手段が逆になりがち。
そう、初手の問い立てを雑にしがち。
目的って、
本当に私たちがやりたいこと、ビジネスに価値をもたらすことって 「データの価値をどう引き出すか やん
基幹システム自体は、長年企業のビジネスを支えてきた立派な「価値ある実装」です。
悪いのはシステムそのものではなく、「データの分析も利活用もROIが見えづらくい状況」です。
サッカーやバスケットの試合で、ハーフタイムやクォーターしかスコアを見れないチームって負けそう
だとしたら、解くべき課題はシステムの刷新ではなく、「安全なデータの流路(パイプライン)を作ること」。
基幹システムはそのまま元気よく動かしておいて、裏側でデータだけを安全に横取りして持ってくる。
これこそが、今回のアイデア、設計アプローチの核心です。
具体的にどんな設計なのか、実際のコードは守秘義務なんちゃらで公開できないので参考程度に流れだけ説明します。
コードみたいくない人は文末まで一気に飛んじゃってもらってOKです。
最初に私が提案したアーキテクチャはめちゃくちゃ単純でした。
【初期案】
Google Cloudの「Datastream」を使って、オンプレSQL Serverの変更履歴(CDC)をキャッチし、そのままBigQueryにドロップする。
これ、最初はこれで完璧だと思っていました。でも、数週間後にパートナー企業からこんな要望が飛んできたんです。
「在庫が一定量を下回ったとき、即座に現場の担当者にSlackやメールで通知できませんか?」
わかる、わかりすぎる。なぜ気づけなかったオレ。これが、設計をもう一歩深く掘り下げるキッカケになりました。
初期案の「Datastream ➔ BigQuery直結」だと、データを受け取る窓口がBigQueryしかいません。
通知を出すには、BigQueryを定期的に監視(ポーリング)するバッチを別途作ってぐるぐる回すしかなくなります。
「変更があったら即座に」というリアルタイム性と、アーキテクチャの間にオーバーヘッド(遅延)が生まれてしまう。
そこで、DBの更新ログを追う「CDCルート」はそのまま活かしつつ、アプリケーション側のイベントを 「Cloud Pub/Sub」経由で流す「イベントルート」 を並走させることにしました。
両方のデータをBigQueryに集約しつつ、イベントが起きた瞬間の通知もPub/Subが複数システムに同時配信する。
最初から完璧な構成が見えてたわけじゃなく、たまたま「これやりたい」という話で気がついただけです。
全体アーキテクチャ
最終的には、このようなハイブリッド型の2ルート構成に落ち着きました。
[オンプレミス]
SQL Server ─── Datastream (CDC) ────────────────────────────→ BigQuery (raw)
↑
レガシーJava/CSV/FTP ─── Cloud Storage ─── Cloud Functions ─── Pub/Sub ┘
↓ (複数コンシューマ)
在庫アラート通知など
BigQuery (staging → mart)
↙ ↘
Vertex AI Looker Studio
(需要予測) (リアルタイムBI)
1. CDCルート(SQL Server側)
SQL Server側でCDC(変更データキャプチャ)を有効にし、Datastreamがその変更ログをリアルタイムでBigQueryへ同期します。
基幹DBにはクエリを一切投げず、ログを読むだけなので、本番稼働中のDB負荷はほぼゼロ。
※ちなみにDatastreamの出力先は、仕様上「Cloud Storage」「BigQuery」「BigLake Iceberg」のみ。Pub/Subへ直接書き込むことはできないので、イベントをトリガーにアクションを起こしたい場合は次の「イベントルート」が活躍します。
2. イベントルート(CSV・レガシーアプリ側)
Cloud StorageにアップロードされたCSVや, Javaアプリが出力するイベントを、Cloud FunctionsでトリガーしてPub/Subに流します。
Pub/Subを挟むことで、データの保存(BigQuery宛)と、業務アクション(在庫アラート宛)を完全に切り離せます(=疎結合)。
これ、例えるなら「駅の構内アナウンス」と同じです。
「〇〇電車が参ります」とアナウンスが1回流れれば、改札口も、売店も、ホームの警備員も、全員が同時にそれぞれの持ち場で必要な処理を始められますよね。
イベントルートの実装(Dataflow / Apache Beam)
Pub/Subからデータを受け取って、リアルタイムに検証・整形してBigQueryに流し込むコード(Python)の実装サンプルです。
# event_consumer.py
from typing import Optional
import apache_beam as beam
from apache_beam.options.pipeline_options import PipelineOptions
from apache_beam.io.gcp.pubsub import ReadFromPubSub
from apache_beam.io.gcp.bigquery import WriteToBigQuery
import json
def parse_and_validate(message: bytes) -> Optional[dict]:
"""Pub/Sub の生のメッセージをパース。バグったデータは None で返してドロップ"""
try:
data = json.loads(message.decode("utf-8"))
return {
"customer_code": str(data.get("customer_code", "")).strip(),
"product_code": str(data.get("product_code", "")).strip(),
"order_date": data.get("order_date"),
"sales_amount": float(data.get("sales_amount") or 0),
}
except (ValueError, KeyError):
# 実際のプロジェクトでは、パースエラー用のDLQ(Dead Letter Queue)へ流すのがお約束
return None
def run():
options = PipelineOptions(
streaming=True, # 常駐してデータを待ち受けるストリーミングモード
project="mfg-analytics-prod",
region="asia-northeast1", # 東京リージョン
runner="DataflowRunner",
)
with beam.Pipeline(options=options) as p:
(
p
| "Pub/Sub から読み取り" >> ReadFromPubSub(
subscription="projects/mfg-analytics-prod/subscriptions/erp-orders-sub"
)
| "パース・バリデーション" >> beam.Map(parse_and_validate)
| "無効行を除外" >> beam.Filter(
lambda row: row is not None
and row["customer_code"]
and row["sales_amount"] > 0
)
| "BigQuery へ書き込み" >> WriteToBigQuery(
"mfg-analytics-prod:raw.sales_orders",
write_disposition=beam.io.BigQueryDisposition.WRITE_APPEND,
create_disposition=beam.io.BigQueryDisposition.CREATE_NEVER, # スキーマは事前に定義しておく
)
)
if __name__ == "__main__":
run()
ここで重要なのが CREATE_NEVER オプション。
Dataflow側での自動テーブル作成をオフにしています。
「勝手にテーブルが作られてスキーマがバラバラになる」のを防ぐため、実務ではTerraformなどのIaCでテーブル構造をきっちりバージョン管理しておくのが、
結局一番トラブらないんじゃないかと。
BigQuery ML で「見るBI」から「予測を届けるBI」へ
従来のBIダッシュボードって、「過去に何が起きたか」を眺めるツールで、売上がガクッと落ちてから「あっ……」と気づく。
営業やスタッフワークのアクションが後手に回ってしまっては意味ないじゃん。ということで、
BigQuery ML(BQML)。
普段みんなが叩いているSQLだけで高度な機械学習(時系列予測)モデルが作れる
データサイエンティストもいよいよ首を垂れるほどの機能、Pythonで複雑なパイプラインを組んだりする必要がない。
今回は「ARIMA_PLUS」という時系列予測モデルを使い、商品ごとの過去データから自動で「30日先までの需要」を予測してみます。
-- ① 商品ごとに自動で需要予測モデルを学習させる(週1回などのバッチで十分)
CREATE OR REPLACE MODEL `mfg_analytics.models.sales_forecast`
OPTIONS (
model_type = 'ARIMA_PLUS',
time_series_timestamp_col = 'order_date',
time_series_data_col = 'daily_sales',
time_series_id_col = 'product_code' -- ここが神。商品コードごとに個別モデルを自動作成!
)
AS
SELECT
DATE(order_date) AS order_date,
product_code,
SUM(sales_amount) AS daily_sales
FROM `mfg_analytics.mart.sales_daily`
WHERE order_date >= DATE_SUB(CURRENT_DATE(), INTERVAL 365 DAY)
GROUP BY 1, 2;
-- ② 出来上がったモデルを使って、30日先までの予測値を取得する
SELECT
product_code,
forecast_timestamp AS forecast_date,
ROUND(forecast_value, 0) AS predicted_sales,
ROUND(prediction_interval_lower_bound, 0) AS lower_bound,
ROUND(prediction_interval_upper_bound, 0) AS upper_bound
FROM ML.FORECAST(
MODEL `mfg_analytics.models.sales_forecast`,
STRUCT(30 AS horizon, 0.9 AS confidence_level)
)
ORDER BY product_code, forecast_date;
This is it! このクエリ結果をLooker StudioなどのBIに紐付けておけば、ダッシュボードが自ら「来月末に在庫切れを起こしそうな商品リスト」がわかる。
ヒト科が必死にデータを深掘りしなくても、データ側から気づきを教えてくれるシステム。
これこそが、BIの本来あるべき姿だと私は思っています。
「Garbage In, Garbage Out(ゴミを入れればゴミが出る)」これAI使う際の鉄則。汝ゴミ入れるなかれ
「AIを入れたのに予測が全然当たらない」あるある。9割以上はアルゴリズムのせいではなく、データの「表記ゆれ」や「欠損」が原因です。
モデルを疑う前に、大元のデータが綺麗な状態か疑うことも必要です。
リアルタイムとクエリコストを両立させる「ちょっとした技」
ストリーミングでリアルタイムに届くデータをニアリアルタイムでダッシュボードに反映するには、Materialized View(MV)が便利かと。
-- 5分ごとに自動リフレッシュされる売上サマリー
CREATE MATERIALIZED VIEW `mfg_analytics.mart.sales_realtime_summary`
OPTIONS (
enable_refresh = true,
refresh_interval_minutes = 5,
allow_non_incremental_definition = true, -- CURRENT_DATE() を使う場合に必須
max_staleness = INTERVAL "5" MINUTE -- ここがコスト削減の肝!
)
AS
SELECT
product_code,
DATE(order_date) AS order_date,
SUM(sales_amount) AS daily_sales,
COUNT(*) AS order_count
FROM `mfg_analytics.staging.stg_sales`
WHERE order_date >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
GROUP BY 1, 2;
CURRENT_DATE() のような常に変化する関数を使ったクエリは、BigQueryの仕様上、差分だけのインクリメンタル更新ができません。そのため allow_non_incremental_definition = true を設定してフルスキャンでリフレッシュさせる必要があります。
でも、これをそのまま放置すると、「誰もダッシュボードを見ていない深夜のオフィス」でも5分おきにフルスキャンが走り続け、お財布が悲鳴を上げます。
そこで輝くのが max_staleness オプション。
「最大5分遅れのデータまでなら許容するよ」と設定することで、不要な時間帯のリフレッシュクエリを賢く間引き、ダッシュボードの超高速な応答性と驚くほどの低コストをスマートに両立させられます。
予算と工期のリアル──誰も言わない現場のホンネ
ここが一番気になるけど、みんなオブラートに包んで隠したがる部分ですよね。あえて、ストレートにお伝えします。
「GCPインフラ代」は安い。本当に高いのは……?
「Google Cloudっていくらくらいかかりますか?」って必ず聞かれます。
インフラ単体のコストはびっくりするほど安いです。
BigQueryにデータを入れておくためのストレージ代なんて、1TBで月約$20。
ギガバイト単位ならタダみたいなものです。クエリ代も前述のパーティショニングやMaterialized Viewでチューニングすれば、大した額にはなりません。
一番コストコントロールに気をつけたいのは「Dataflow」です。
今回紹介したイベントルート用のDataflowパイプラインは、ストリーミング(常駐)モードで動きます。
つまり、24時間365日サーバーのCPUを使い続けているような状態。これを知らずに起動しっぱなしにすると、請求書が届いたときに「ウッ」となります。
ここでビジネス的な判断「本当にそのデータ、ミリ秒単位のストリーミングが必要ですか?」
「1時間遅れのマイクロバッチでも、現場のオペレーションは十分回るんじゃないか?」
もしそうなら、常駐させずに定時バッチにするだけで、インフラ費用を桁違いに抑えられます。
そして、最大のホンネ。
一番お金がかかるのは、クラウドの利用料ではなく「人間の工数(開発費)」です。
歴史あるシステムのスキーマ(構造)を解き明かし、表記ゆれを直し、インフラを整備し、他部門と調整するエンジニアの時間はただではありません。
「クラウド利用料が数万円安くなるか」よりも、「いかに少ない手戻りでシンプルに開発し、エンジニアの時間を無駄にしないか」のほうが、トータルのコストに何百倍もインパクトがあります。
工期が伸びてしまう、3つの「現場あるある」早く言いたい。
「これ、半年で開発できますか?」という質問もよくあります。
結論から言うと、「何に半年をかけるかによる」です。プロジェクトで必ず直面する泥臭いハードルを挙げておきます。
1. データ品質の棚卸しで要件定義期間が100%伸びる
「よし、オンプレのデータを確認しましょう!」と蓋を開けると、仕様書に書いてない謎のフラグがあったり、値がnullだらけだったり、10年前の担当者が退職していてデータの意味が不明だったりします。データクレンジングのルール決めは、想定の「2倍」の期間がかかると見積もっておくと、後で泣かずに済みます。
2. オンプレ側のCDC有効化が「DBAの承認待ち」でストップする
Datastreamを使うには、SQL Server側でCDC機能を有効化する必要があります。これを基幹DBを管理している運用チーム(または外部の保守ベンダー)に依頼することになりますが、保守運用の観点から「本当に有効化して大丈夫か?」とセキュリティレビューや検証に時間がかかることが本当によくあります。プロジェクト開始と同時に真っ先に依頼しておくべき「超重要タスク」です。
3. 「とりあえず可視化してみる」からの要件膨張
最初の仮ダッシュボードを会議で見せた瞬間、関係者の目が一気に輝き出します。
「これに売上推移も足せる?」「支店ごとにビューを切り替えたい」「スマホ用画面も欲しい!」と要望が次々に飛んできます。
初期フェーズで「ここから先は次のフェーズ」と、スコープのデッドラインを強く握っておかないと、いつまでも本番リリースできないゾンビプロジェクトになります。
現実的なフェーズ分けのロードマップ
こうした罠を回避するために、私たちは以下のような3ステップでの「スモールスタート」を強くお勧めしています。
- Phase 1 (6〜8週間): 最小限でインパクトを出す
まずはメインの1テーブル(例:最新売上実績)だけをDatastreamでBigQueryに同期し、Looker Studioで見える化する。これで経営陣と現場に「クラウド化の恩恵」を最速で体験してもらう。 - Phase 2 (+2〜3ヶ月): 周辺データの統合と効率化
周辺マスタや、CSV連携などのパイプラインを追加。Materialized Viewを使って、ダッシュボードの描画速度とクエリコストを最適化する。 - Phase 3 (+2〜3ヶ月): 高度な予測と自動アクション
BigQuery MLでの需要予測、Pub/Sub経由のTeamsやSlackへのアラート通知など、データ主動で「アクションを起こす」仕組みをインテグレートする。
最初から100点の完璧なフルスタックを設計するのではなく、まず小さく作って(Phase 1)、社内の熱量を味方につけながら、徐々に大きな果実を収穫していくのが結局一番の近道だと考えます。
🕊 おわりに
「DX」と聞くと、なんだか今のすべてを投げ打って新しいものに飛び乗らなきゃいけないような、そんな焦りを感じるかもしれません。
「DXとは何か」という問いに対して、私たちの答えは「既存の資産(負債)から、これまで引き出せなかった価値を取り出すこと」です。
基幹システムを壊すことではない。
止めない。壊さない。それでもリアルタイム——その三つを同時に満たす設計は確かに存在します。
前提を変えると、解ける問題がある。ソフトウェアでできること、すべて。その「すべて」はまだ広がっています。
前提条件の立て方を1度だけ疑ってみる。そうするだけで、あれだけ複雑だったリプレースの方程式が、嘘みたいにスルスルと解けたりするものです。
ソフトウェアとクラウドの力は、僕たちが思っているよりもずっと優しくて、そして無限の可能性に満ちています。
それでは、世界のどこか、あるいはあなたの次のプロジェクトの現場でお会いしましょう!