はじめに
こんにちは。
GadgetStreemでは現在、ガジェット価格の上昇圧力を一つの数字で確認できる独自指数、
GPI(Gadget Pressure Index/ガジェット価格圧力指数)

を開発しています。
これまでの開発日誌では、次の段階まで進みました。
- Vol.1:Docker、PostgreSQL、FastAPIなどの基盤構築
- Vol.2:USD/JPYのデータソース選定とCollector設計
- Vol.3:USD/JPY Collectorの実装と、PostgreSQLへの実データ保存
Vol.3までで、
Federal Reserve H.10
↓
USD/JPY Collector
↓
データ検証
↓
PostgreSQLへ保存
という最初のデータパイプラインが形になりました。
しかし、市場データを一度取得できたからといって、そのままGPIを毎週公開できるわけではありません。
次に必要なのは、
Collectorを継続的に動かし、正常に取得できなかった場合にも気付ける仕組み
です。
今回のVol.4では、USD/JPY Collectorを「動くプログラム」から「継続運用できるシステム」へ進化させるための設計をご紹介します。
一度成功することと、毎週成功することは違う
開発中にコマンドを実行し、ドル円データを取得できたとします。
取得成功
保存成功
これだけなら、Collectorの基本機能は動いています。
しかし、実際の運用では次のような問題が起こります。
- データ提供元へ接続できない
- 通信が途中で切れる
- XMLやCSVの形式が変わる
- 最新週のデータがまだ公開されていない
- 欠損値しか含まれていない
- データベースへ接続できない
- Collector自体は終了したが、一件も保存されていない
- スケジュールされた処理が実行されなかった
つまり、
エラーが出なかった
=正しい最新データを取得できた
とは限りません。
GPIを継続公開するには、単なる成功・失敗だけでなく、
- いつ実行したのか
- どの期間を取得したのか
- 何件追加したのか
- 最新の観測日はいつか
- 欠損がなかったか
- 前回成功したのはいつか
まで管理する必要があります。
H.10の更新頻度に合わせる
USD/JPY Collectorが利用するFederal Reserve BoardのH.10は、リアルタイムの為替配信ではありません。
二国間為替レートは、通常、前営業週の金曜日までのデータが米国東部時間の月曜日16時15分に更新されます。
月曜日が米国の祝日の場合は、次の営業日に公開されます。
そのため、GPI側でも秒単位や分単位で何度も取得する必要はありません。
初期運用では、
Federal Reserveによる週次更新
↓
一定時間を空けてCollectorを実行
↓
新しい観測値を確認
↓
GPIの週次計算へ利用
という流れにします。
提供元の公表時刻直後は、更新処理やアクセス集中が発生する可能性もあります。
そのため、公開予定時刻ぴったりではなく、ある程度余裕を持って取得する方が安全です。
「観測日」と「取得日」を分ける
市場データを扱ううえで重要なのが、日付を一つだけにしないことです。
GadgetStreem Intelligenceでは、少なくとも次の日時を分けて管理します。
| 日時 | 意味 |
|---|---|
| 観測日時 | 為替レートが示す対象日時 |
| 公表日時 | 提供元がデータを公開した日時 |
| 取得日時 | GSIがデータを取得した日時 |
| 保存日時 | PostgreSQLへ記録した日時 |
例えば、金曜日の為替データが翌週月曜日に公開され、GSIが火曜日に取得した場合、
観測日:金曜日
公表日:月曜日
取得日:火曜日
となります。
これらをすべて「火曜日のドル円」として保存してしまうと、GPIの計算期間がずれてしまいます。
そのため、GPIではデータが何を示す日時なのかを最優先で管理します。
データの鮮度を判定する
Collectorが正常に動いていても、取得できた値が古ければ、最新の市場状況を表しているとは限りません。
そこで、観測値へ鮮度の状態を付けます。
初期案は次のとおりです。
| 状態 | 意味 |
|---|---|
| Fresh | 想定どおり最新データを取得できている |
| Delayed | 提供元の更新が通常より遅れている |
| Stale | 前回値から一定期間更新されていない |
| Unavailable | 利用可能な観測値が存在しない |
| Invalid | 形式・単位・内容に問題がある |
重要なのは、古い値を最新値のように見せないことです。
前回値を暫定利用する場合でも、
stale = true
のような状態を付け、公開画面にも
前回取得値を使用しています
と表示します。
Collectorの実行履歴を残す
観測値だけでなく、Collectorを実行した履歴も保存します。
一回の実行を表すCollection Runには、次のような情報を記録します。
run_id
source_id
開始日時
終了日時
対象期間
処理結果
新規追加件数
変更なし件数
Revision追加件数
スキップ件数
失敗件数
エラー概要
例えば、次のような結果です。
Status:Success
Inserted:5
Unchanged:5
Revised:0
Skipped:2
Failed:0
これにより、
Collectorは終了したが、実際には何件処理したのか
を確認できます。
一件も保存されていない場合でも、それが
- 新しいデータがなかった
- すべて登録済みだった
- 欠損値しかなかった
- 解析に失敗した
のどれなのかを区別できます。
成功・警告・失敗を分ける
Collectorの結果を、単純な成功・失敗の二択にはしません。
初期のステータス案は次のとおりです。
Success
必要なデータを正常に取得・検証・保存できた状態です。
Success with warnings
処理自体は完了したものの、次のような注意事項がある状態です。
- 一部に欠損値があった
- 想定より観測件数が少なかった
- 新しい観測値がなかった
- データの公開が遅れている
- 前回値と比べて極端な変化があった
Failed
Collectorを正常に完了できなかった状態です。
- HTTP接続エラー
- タイムアウト
- ZIP展開失敗
- XML解析失敗
- 対象系列が見つからない
- DB保存失敗
Partial failure
複数件のうち、一部だけ処理できなかった状態です。
GPIでは部分失敗を無視せず、どの観測値が失敗したかを記録します。
エラーは種類ごとに分ける
すべてのエラーを
取得に失敗しました
とだけ記録しても、原因を特定できません。
そこで、エラーを役割ごとに分けます。
NetworkError
HttpError
TimeoutError
ArchiveError
ParseError
ValidationError
PersistenceError
ConfigurationError
例えば、HTTP 503で提供元が一時的に利用できない場合と、XML内から日本円の系列を見つけられない場合では、対応方法が異なります。
- 一時的な通信障害:時間を空けて再試行
- XML構造の変更:コードと仕様の確認が必要
- 不正な数値:データを隔離して人間が確認
- DB障害:保存処理をロールバック
エラーの種類を分けることで、無意味な再試行や、誤ったデータ保存を防ぎます。
何でも再試行すればよいわけではない
通信エラーが発生した場合は、再試行が有効です。
ただし、すぐに何十回もアクセスすると、提供元へ負荷をかける可能性があります。
そこで、再試行は回数を制限し、待ち時間を少しずつ長くします。
概念的には次のような流れです。
1回目:通常実行
失敗
↓
少し待って2回目
失敗
↓
さらに待って3回目
失敗
↓
処理を停止して通知
一方で、
- XMLの構造が変わった
- 必須の系列が存在しない
- 単位が想定と異なる
- 設定が不正
といった問題は、何度実行しても直らない可能性が高いため、自動再試行の対象にしません。
再試行できるエラーと、人間の確認が必要なエラーを分ける
ことが重要です。
データベースへの保存は中途半端にしない
10件の観測値を保存している途中でエラーが発生した場合、
最初の6件だけ保存
残り4件は未保存
となると、その週のデータが不完全になります。
そこで、関連する保存処理はPostgreSQLのトランザクション内で実行します。
処理全体が成功した場合だけ確定し、途中で問題が発生した場合は元の状態へ戻します。
また、同一キーの挿入競合を安全に扱う仕組みとして、PostgreSQLにはINSERT ... ON CONFLICTが用意されています。ON CONFLICT DO UPDATEは、競合時の挿入または更新を原子的に実行できます。
ただし、GSIでは過去値を無言で上書きしません。
同じ値ならunchanged、公式値が変わっていれば新しいRevisionを追加するという独自ルールを優先します。
自動実行と手動実行の両方を残す
Collectorは最終的に自動実行します。
しかし、自動実行だけにすると、障害調査や過去データの再取得が難しくなります。
そのため、次の両方を残します。
自動実行
決められた曜日・時刻に、最新データを取得します。
手動実行
必要に応じて、開発者がCLIから実行します。
gsi collect-usd-jpy
将来的には対象期間を指定できるようにします。
gsi collect-usd-jpy \
--from 2026-07-01 \
--to 2026-07-31
これにより、
- 自動取得に失敗した期間の再実行
- 過去データの追加取得
- 新しいCollectorの動作確認
- 障害復旧後の再処理
が可能になります。
GitHub Actionsのスケジュールは「正確な時刻」ではない
初期段階では、GitHub Actionsを使って定期実行する案もあります。
GitHub Actionsでは、cron形式のscheduleイベントでワークフローを自動実行できます。
ただし、GitHub公式ドキュメントでは、負荷が高い時間帯にはスケジュール実行が遅延し、状況によってはキューから削除される可能性もあると説明されています。また、スケジュールワークフローはデフォルトブランチ上のファイルだけが対象です。
そのため、GPIではGitHub Actionsを
正確な時刻を保証する市場データ配信基盤
としては扱いません。
開発・検証段階では十分に活用できますが、本番運用では、
- 遅れても問題のない余裕ある時刻に設定
- 実行されなかったことを検知
- 手動再実行を可能にする
- 必要に応じて専用スケジューラーへ移行
という方針にします。
毎時0分を避ける
GitHubは、毎時ちょうどの時間帯はスケジュール実行が集中し、遅延しやすいと案内しています。
そのため、例えば、
毎週火曜日 6:00
ではなく、
毎週火曜日 6:17
のように、実行が集中しにくい時刻を選びます。
また、GitHub ActionsのcronはUTC基準なので、JST(日本時間)との時差も考慮する必要があります。
日本時間の実行時刻を決める場合は、
JST = UTC+9時間
として設定します。
Dockerの再起動だけでは解決しない
コンテナが異常終了した場合、Dockerには再起動ポリシーがあります。
on-failureでは異常終了時だけ再起動し、alwaysやunless-stoppedでは条件に応じて継続的に再起動できます。
ただし、CollectorはAPIサーバーのように常時起動するプロセスではなく、
開始
↓
取得
↓
保存
↓
終了
という一回限りのジョブです。
そのため、単純にコンテナを再起動し続けると、設定ミスやデータ形式変更でも無限に失敗を繰り返す恐れがあります。
Collectorでは、コンテナ再起動よりも、
- ジョブ単位の再試行
- 最大試行回数
- 実行履歴
- エラー通知
- 手動再実行
を重視します。
正常に動いているかを監視する
APIにはすでに、
/health/live
/health/ready
というヘルスチェックがあります。
今後はCollectorにも、運用状態を確認できる情報を追加します。
例えば、
最終実行日時
最終成功日時
最新観測日
現在のデータ鮮度
直近のエラー
連続失敗回数
です。
将来的な管理画面では、次のような表示を想定しています。
USD/JPY
Status:Fresh
Latest observation:2026-07-17
Last successful collection:2026-07-21 06:17 JST
Consecutive failures:0
問題がある場合は、
Status:Stale
Latest observation:2026-07-10
Last error:No new observation was found
のように表示します。
「処理が成功した」だけでは通知しない
毎週正常に動くたびに通知すると、重要な警告が埋もれてしまいます。
そのため、原則として正常時は履歴だけを残し、次のような場合に通知します。
- Collectorが失敗した
- 一定時間、新しい観測値がない
- 連続して複数回失敗した
- 値が想定範囲から大きく外れた
- データ形式が変わった
- DB保存に失敗した
- スケジュール自体が実行されなかった
通知先は、初期段階ではGitHub Actionsの失敗通知やGitHub Issueを利用し、将来的にメールやSlackなどを検討します。
異常値は自動削除しない
ドル円が前週から大きく変化した場合、データ取得ミスの可能性もあります。
しかし、本当に市場が急変している可能性もあります。
そのため、
大きく変動した
↓
自動的に削除
とはしません。
代わりに、
quality_status = warning
として保存候補に残し、人間が確認します。
確認内容は次のとおりです。
- Federal Reserveの公式ページと一致するか
- 日付や単位が入れ替わっていないか
- 小数点の位置が正しいか
- 別の通貨系列を取得していないか
- 前後の観測値と整合するか
データが公式情報と一致していれば、急激な変化であっても正しい市場データとして採用します。
GPIを公開してよい状態か判断する
将来的にGPIを毎週自動計算するようになっても、Collectorに問題がある状態で通常値を公開してはいけません。
公開前には、各構成要素の状態を確認します。
USD/JPY:Fresh
DRAM:Fresh
NAND:Fresh
GPU需給:Fresh
海上運賃:Delayed
一部のデータが遅れている場合は、Rule Bookに従って、
- 正式値を公開
- Provisional(暫定値)として公開
- 前回値をStale表示付きで使用
- 公開を停止
のどれにするかを判断します。
この判断をCollectorの都合だけで変更せず、GPI全体の共通ルールとして管理します。
今回確定した運用方針
USD/JPY Collectorの初期運用では、次の方針を採用します。
- Federal Reserve H.10の更新頻度に合わせて取得する
- 観測日、公表日、取得日を分ける
- Collectorの実行履歴を保存する
- 成功・警告・失敗を区別する
- 通信障害だけを制限付きで再試行する
- データ形式変更などは人間の確認対象とする
- 同一値は重複保存しない
- 訂正値はRevisionとして残す
- データ鮮度を公開情報へ含める
- 自動実行と手動実行の両方を残す
- 正式なGPIを公開できない状態を検知する
- 正常時より、異常時の通知を優先する
まだ実装していないもの
今回のVol.4は、Collectorの継続運用設計が中心でした。
現時点ですべての機能が本番稼働しているわけではありません。
今後、段階的に追加するものは次のとおりです。
- Collection Run専用テーブル
- データ鮮度判定
- 自動スケジュール
- 制限付き再試行
- 連続失敗の検知
- GitHub Issueまたはメール通知
- 管理用ステータスAPI
- Collector監視画面
- 本番用スケジューラー
一度にすべてを実装するのではなく、テストと運用結果を確認しながら追加する予定です。
次のVol.5で紹介すること
次回のGPI開発日誌 Vol.5では、今回設計した運用ルールを実装します。
主な内容は、
- Collection Runテーブル
- 最終成功日時の保存
- Fresh/Stale判定
- Collectorの自動実行
- 再試行
- 実行されなかった場合の検知
- GitHub Actionsからの手動実行
- 障害時の通知
です。
これが完成すればUSD/JPY Collectorは、
手動で動かせるプログラム
から、
毎週のデータ更新を任せられる運用システム
へ進化します。
まとめ
Vol.3では、Federal Reserve H.10からUSD/JPYを取得し、PostgreSQLへ保存する最初のデータパイプラインを作りました。
しかし、指数を継続公開するには、
一度動くことより、異常に気付きながら動き続けられること
が重要です。
今回のVol.4では、
- 取得履歴
- データ鮮度
- 欠損検知
- 再試行
- 自動実行
- エラー分類
- 障害通知
- GPI公開可否の判断
という継続運用の設計を行いました。
GadgetStreem Intelligenceは、単に市場データを集めるだけでなく、
データが正しく更新されているかまで説明できるプラットフォーム
を目指します。
次回は、この運用設計を実際のコードとデータベースへ落とし込みます。
GPIが継続的に更新される指数として完成するまで、引き続き開発過程を公開していきます。
ぜひ一緒に成長を見守っていただけるとうれしいです。
※GPIは現在開発中の実験的な市場分析指標です。
個別製品の将来価格、値上げ、値下げ、在庫状況、購入利益を保証するものではありません。
また、投資判断を目的とした金融指標ではありません。



コメント