Bot

開発記録#199(2025/4/25)【開発のTips】「非同期設計でハマる罠と抜け道」

Python × asyncio/仮想通貨 Bot 開発向け

Yodaka

最近はMMbot作りに多くの時間を割いています。その中で「非同期プログラミング」を活用しようと試みているのですが、これが本当に失敗・エラーの連続です。それでも、失敗とその原因を特定し解決することを通して、知見が貯まってきました。本記事では、その経験を通して得た学びをまとめました。

1.なぜ非同期は“事故り”やすいのか

非同期処理は 速度とスケーラビリティ をもたらす一方、
タスクの状態が見えにくくなる “ブラックボックス化” が避けられません。

仮想通貨 Bot では ─

  • API レート制限 でタスクが詰まりやすい
  • 価格急変 でミリ秒遅延が即 PnL へ直撃

結果として 発注遅延 → 価格乖離 → 損失拡大 の連鎖を起こします。
「転ばぬ先の計測」と「転んでも壊れない設計」が肝要です。


2.典型的な罠3選

#症状原因イメージ回避ワザ
① 暗黙キャンセル1 つのタスクが例外で落ちた途端、他のタスクも静かに停止旧式 asyncio.gather(..., return_exceptions=False) が例外を握り潰すTaskGroup で例外を即バブルアップ
② 競合条件REST が先に走り板在庫がズレる並列タスクに同期点が無い順序バリア or キュー化
③ イベントループ飢餓CPU 0 %なのに板更新が 10 秒止まるI/O 待ちタスクが雪だるま化 → スケジューラ飢餓Back-Pressure を設計し列制限

Log Tips
task_id経過時間(ms) を毎イベントで記録するだけで
原因特定に掛かる時間は体感 1/2 になります。


3.抜け道と実装ヒント

3-1.TaskGroup で「生死」を束ねる

import asyncio

async def main():
    async with asyncio.TaskGroup() as tg:
        tg.create_task(worker_A())
        tg.create_task(worker_B())
        # ...

どれかが例外 ⇒ 全キャンセル ⇒ まとめて raise
“片肺運転” を防ぎ、アラート集約 も容易。


3-2.Shield + Timeout で二重防御

try:
    await asyncio.wait_for(
        asyncio.shield(critical_io()),   # ← キャンセル耐性
        timeout=<YELLOW-MASKED>)
except asyncio.TimeoutError:
    logger.warning("critical_io timed-out")

「暴走させず、放置もしない」 を両立。


3-3.Back-Pressure を前提に設計する

queue = asyncio.Queue(maxsize=100)  # 上限を必ず設定

溢れたら 古いデータ破棄 または スロットル
「全部処理する」幻想を捨て、遅延スパイラル を断ち切る。


3-4.Kubernetes 連携 Tips(GREEN 抜粋)

Probe目的実装ポイント
readinessProbeループ健全性bot が応答しても 動けている か確認
livenessProbeハング検出応答停止 ⇒ 自動再起動
PDBノード再起動対策minAvailable: 1 で穴あきを防止

4.今日から出来るチェックリスト

  • ログtask_id / latency を付与
  • 重要処理を TaskGroup + timeout に移行
  • キューの maxsize を明示し、溢れ対策を決める
  • readinessProbe で「生きてるふり」を禁止

5.まとめ & 次のステップ

速度 × 安全 を両立したいなら、
「失敗を前提に設計」→「失敗を観測しやすくする」 が最短ルート。

次回は CI パイプライン 上で pytest-asyncio を回し、
“手動デバッグの沼” から抜け出す工程を解説予定です。

👇ラジオで話したこと

「はじめての非同期プログラミング ─ つまずきポイントと安全運転のコツ」


【オープニング】

こんにちは、よだかです。
きょうは「非同期(ひどうき)プログラミングって何? そして初心者がハマりやすい落とし穴は?」を、なるべく専門用語をかみ砕いてお話しします。Python で仮想通貨 Bot を組みはじめたばかりの方に向けて、基本の“転ばぬ先の杖”をまとめました。


1️⃣ そもそも非同期って?

  • イメージ:
    キッチンで「煮込み」「炒め」「オーブン」を同時進行する感じ。
    同じコンロを 1 つしか持たないより、作業が重ならない分スピードアップ。
  • Python での道具
    asyncioイベントループ が“キッチンの指揮官”。
    料理(=タスク)に「次ここまでやったら声かけて!」と指示し、空いた時間で別の料理を進めます。

2️⃣ 初心者がつまずく三大ポイント

つまずきどうなる?原因イメージ
① 途中で止まるプログラムが固まって返事なし片方の鍋 が焦げて火災報知器が鳴ったのに、指揮官が気づかない
② 順番がぐちゃぐちゃデータが最新じゃないまま発注 → ミス取引材料(板情報)を入れ替える前に味付け(注文)しちゃう
③ 渋滞して遅いCPU はヒマなのに反応が 10 秒遅れる注文票が山積み。レジ 1 台でさばき切れず行列

チェック方法
画面に「いつ・どの料理が動いたか」を 時刻つき でプリントするだけでも、原因の八割は見えます。


3️⃣ 安全運転のコツ

  1. 「まとめて面倒を見る箱」を作る
    • Python3.11 以降は TaskGroup という“お弁当箱”が標準装備。
    • どれか 1 品が焦げたら、箱ごとコンロから下ろす → 火を止めて知らせる。
  2. タイマーを必ずセット
    • 「○秒たっても戻って来なければ強制終了」のキッチンタイマーを、
      ぜんぶの作業に付ける。放置しない・暴走させない。
  3. 皿洗い待ち行列を制限
    • 受信データを貯める “キュー” に上限を決める。
    • 溢れたら古い皿を捨てる or 料理スピードを自動で落とす。
  4. 健康診断を入れる(Kubernetes を使う人向け)
    • 生存チェック → プログラムが 応答しているか を外から確認。
    • 健康チェック → 応答はあるけど中身が壊れていないか?を追加で確認。

🛑 注意: ここで紹介していない具体的な YAML やパラメータ設定は、 公開ポリシー(YELLOW)に当たる部分なので公開資料ではマスクしています。ご理解を!


4️⃣ 今日からできる3ステップ

  1. print でもいいから すべてのタスク開始・終了を時刻つきでログに残す
  2. 重要そうな await の前後に タイマー を置く
  3. 受信データをためる queue最大サイズ を決める(例:100 件)

これだけで「知らぬ間に固まっていた」「いつのまにか遅かった」を激減できます。


【エンディング】

非同期は便利ですが、“並行する=失敗も増える”という裏面があります。
だからこそ 「転ぶ前提でガードを付ける」 のが本当の時短。
次回は、自動テストで「徹夜デバッグ」を卒業する話を予定しています。
それではまた!

-Bot