シャーディングと再開¶

Discord は、Bot のサーバーをシャードと呼ばれる複数のゲートウェイ接続に分けます。サーバー g はシャード (g >> 22) % shard_count に属します。2500 を超えるサーバーに参加している Bot はシャーディングが必須です。シャーディングしていない接続を Discord はコード 4011 で閉じ、start() は ShardingRequired を送出します。
1 つのプロセスでのシャード¶
既定値である Bot(shard_count="auto") は、使うべきシャードの数を Discord に問い合わせ、それらをすべて実行します。ほかに必要なことはありません。bostie は Discord の Identify の上限内でシャードを開始し、すべてのシャードが同じリスナー、キャッシュ、ハンドラーにイベントを送ります。イベントがどのシャードから届いたかは event.shard_id でわかります。
複数のプロセスにまたがるシャード¶
import asyncio
import logging
import os
import bostie
from bostie.events import ShardDisconnect, ShardReady
log = logging.getLogger("my-bot")
cluster = int(os.environ.get("CLUSTER", "0"))
identify_lock = asyncio.Lock()
async def wait_for_turn(shard_id: int) -> None:
async with identify_lock:
await asyncio.sleep(5)
bot = bostie.Bot(
shard_count=16,
shard_ids=range(cluster * 4, cluster * 4 + 4),
before_identify=wait_for_turn,
session_file=f"sessions-{cluster}.json",
)
@bot.listen
async def shard_ready(event: ShardReady) -> None:
log.info("shard %d is ready", event.shard_id)
@bot.listen
async def shard_lost(event: ShardDisconnect) -> None:
action = "resuming" if event.resuming else "starting a new session"
log.info("shard %d disconnected (%s), %s", event.shard_id, event.code, action)
bot.run()
shard_count=16 で総数を固定し、shard_ids でこのプロセスが実行するシャードを選びます。shard_ids には明示的な shard_count が必要で、その ID は互いに異なり、range(shard_count) の範囲内でなければなりません。
Discord は、Bot ごとに同時に行える Identify の数を制限しています。bostie は 1 つのプロセスの中ではその上限を守りますが、プロセスをまたぐ場合は before_identify で調整します。bostie は Identify のたびにその前に before_identify(shard_id) を await するので、プロセス間で共有するロックの空きをそこで待つことができます。この例では、identify_lock がその共有ロックの代わりで、Discord が同じバケットの Identify の間に求める 5 秒間保持されます。フックが例外を送出した場合、bostie はエラーをログに出力したうえで、そのまま Identify を行います。
シャードのイベント¶
| イベント | フィールド | 送られるとき |
|---|---|---|
ShardReady |
shard_id |
シャードの新しいセッションの準備ができ、そのサーバーが届いたとき |
ShardDisconnect |
shard_id、code、reason、resuming |
接続が切れたとき。resuming の場合、シャードはセッションを再開し、取りこぼしたイベントを再送させます。そうでなければ新しいセッションを開始します |
ShardStop |
shard_id、code、reason |
close() が実行されたか Discord が終了させたために、シャードが完全に停止したとき |
ShardBackpressure |
shard_id、queued_bytes |
イベントループが追いつかないために、シャードが読み取りを一時停止したとき |
再接続と再開について、あなたがすることは何もありません。bot.latency はシャード全体のハートビート往復時間の平均で、bot.gateway.shard(shard_id).latency は 1 つのシャードの往復時間です。
再起動後に再開する¶
通常、再起動するとすべてのシャードで新しいセッションが始まり、その間の数分間のイベントは失われます。Bot(session_file="sessions.json") を指定すると、close() はセッションを開いたまま保持し、ファイルに書き出します。次の start() はそのファイルを読み込んで削除し、現在の shard_count とシャード ID に一致する保存済みのセッションをそれぞれ再開します。そのため Discord は、Bot が停止していた間に起きたイベントを再送します。読み込めないファイルはログに記録されて無視され、保存されたセッションのないシャードは Identify を行います。
bot.run() は Ctrl+C と SIGTERM で終了するので、プロセスマネージャーやデプロイによる再起動ではセッションが保存されます。クラッシュした場合は保存されず、次の起動では Identify を行います。作成から 5 秒以上経ってから再送されたインタラクションは、そのトークンではもう時間内に応答できないため、警告を出してスキップされます。