azure-eventhub migration guide: legacy checkpoint sample writes "None" metadata for partitions that were never checkpointed
メンテナーはふだん 1 日以内に返信
@hidingmyname がすでに取り組んでいます。
2026年10月29日 から。
評価
- 難易度
- 2/5
- 見積もり時間
- 1〜3時間
- 初心者へのやさしさ
- 78/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- azure, python
調査の方向性
sdk/eventhub/azure-eventhub/migration_guide.mdを開き、レガシー チェックポイントの移行スニペットを確認してください。offsetまたはsequence_numberが欠けているエントリをどのように扱うかを確認し、定義済みのEVENT_HUB_HOSTNAMEをプレフィックスに使用していることを検証してください。チェックポイントを作成できないエントリをサンプルがスキップし、定義済みの変数からBlobプレフィックスを構築すれば完了です。
索引モデルが issue の本文から書いたものです。
説明
Library name
azure-eventhub (migration_guide.md), azure-eventhub-checkpointstoreblob
Describe the bug
The "migrate checkpoint data from the legacy format" snippet at the end of
sdk/eventhub/azure-eventhub/migration_guide.md converts every blob in the
legacy EventProcessorHost container unconditionally:
# Translate each legacy checkpoint, storing offset and sequence data into correct
# blob to align with V5 BlobCheckpointStore.
for checkpoint in legacy_checkpoints:
metadata = {
"offset": str(checkpoint["offset"]),
"sequencenumber": str(checkpoint["sequence_number"]),
}
name = "{}{}".format(prefix, checkpoint["partition_id"])
container_client.upload_blob(name, data="", metadata=metadata)
The legacy (1.x) EventProcessorHost creates a lease blob for every partition
up front, before anything has been checkpointed. In
azure_storage_checkpoint_manager.py (create_lease_if_not_exists_async) a
fresh AzureBlobLease() is serialized with offset = None and
sequence_number = None, so a partition that was never checkpointed has a blob
like:
{"partition_id": "3", "sequence_number": null, "owner": null, "token": null,
"epoch": 0, "event_processor_context": null, "offset": null}
json.loads turns those into None, and the snippet writes the metadata
offset="None", sequencenumber="None".
The v5 store then can't read the container. BlobCheckpointStore.list_checkpoints
(azure-eventhub-checkpointstoreblob/.../_blobstoragecs.py, and the same line in
the -aio package) does:
"offset": str(metadata["offset"]),
"sequence_number": int(metadata["sequencenumber"]),
int("None") raises ValueError. Because list_checkpoints reads all
partitions in one call, a single unconverted partition makes the whole call
fail. EventProcessor catches the exception in its load-balancing loop, logs
"An error occurred while load-balancing and claiming ownership" and retries,
so the consumer stays up but never starts receiving on any partition it
newly claims.
A second, smaller problem in the same snippet: it defines
EVENT_HUB_HOSTNAME = os.environ["EVENT_HUB_HOSTNAME"] # <your-eventhub-namespace>.servicebus.windows.net
but builds the blob prefix from a different name that is never defined:
prefix = "{}/{}/{}/checkpoint/".format(
EVENT_HUB_FULLY_QUALIFIED_NAMESPACE.lower(),
so as written it fails with NameError before any checkpoint is uploaded.
Why it matters
This snippet is for people moving off the 1.x processor, and having some idle
or never-checkpointed partitions is normal (new partitions, low-traffic
partitions, a consumer group that was only partly used). After running the
migration as documented, the v5 processor connects, logs warnings over and
over, and receives nothing. The error points at load balancing rather than at
the migrated blobs, so it is hard to trace back to the migration step.
Suggested fix
Skip legacy entries that have no checkpoint (a v5 processor will just use its
starting_position for those partitions), and use the variable that is
actually defined:
prefix = "{}/{}/{}/checkpoint/".format(
- EVENT_HUB_FULLY_QUALIFIED_NAMESPACE.lower(),
+ EVENT_HUB_HOSTNAME.lower(),
EVENT_HUB_NAME.lower(),
EVENT_HUB_CONSUMER_GROUP.lower(),
)
# Translate each legacy checkpoint, storing offset and sequence data into correct
# blob to align with V5 BlobCheckpointStore.
for checkpoint in legacy_checkpoints:
+ # Partitions that were never checkpointed have no offset/sequence number;
+ # skip them so the V5 processor falls back to its starting position.
+ if checkpoint.get("offset") is None or checkpoint.get("sequence_number") is None:
+ continue
metadata = {
"offset": str(checkpoint["offset"]),
"sequencenumber": str(checkpoint["sequence_number"]),
}
Both fields are skipped together rather than writing only offset (as .NET
does), because the Python BlobCheckpointStore requires sequencenumber to be
present — metadata["sequencenumber"] would raise KeyError otherwise.
Upstream reference
The .NET migration guide had the same unconditional conversion and fixed it in
Azure/azure-sdk-for-net@2597c2265a ("[Event Hubs] Checkpoint Migration Sample
Tweak", Azure/azure-sdk-for-net#29129, 2022-06-06) — legacy entries with no
offset are skipped when reading, and a missing sequence number is no longer
written as a value:
--- a/sdk/eventhub/Azure.Messaging.EventHubs/MigrationGuide.md
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/MigrationGuide.md
@@ -818,10 +818,14 @@ foreach (var checkpoint in legacyCheckpoints)
{
var metadata = new Dictionary<string, string>()
{
- { offsetKey, checkpoint.Offset.ToString(CultureInfo.InvariantCulture) },
- { sequenceKey, checkpoint.SequenceNumber.ToString(CultureInfo.InvariantCulture) }
+ { offsetKey, checkpoint.Offset.ToString(CultureInfo.InvariantCulture) }
};
+ if (checkpoint.SequenceNumber.HasValue)
+ {
+ metadata[sequenceKey] = checkpoint.SequenceNumber.Value.ToString(CultureInfo.InvariantCulture);
+ }
+
BlobClient blobClient = storageClient.GetBlobClient($"{ prefix }{ checkpoint.PartitionId }");
using var content = new MemoryStream(Array.Empty<byte>());
@@ -857,7 +861,11 @@ private async Task<List<MigrationCheckpoint>> ReadLegacyCheckpoints(
await (storageClient.GetBlobClient(blobItem.Name)).DownloadToAsync(blobContentStream);
var checkpoint = JsonSerializer.Deserialize<MigrationCheckpoint>(Encoding.UTF8.GetString(blobContentStream.ToArray()));
- checkpoints.Add(checkpoint);
+
+ if (!string.IsNullOrEmpty(checkpoint.Offset))
+ {
+ checkpoints.Add(checkpoint);
+ }
}
return checkpoints;
The idea carries over directly — don't turn an empty legacy lease into a
checkpoint. The only Python-specific difference is that the v5 store here
needs both fields, so the entry should be skipped whenever either one is
missing.
I can submit a PR for this.
- 主要言語
- Python
- スター
- 5.6k
- フォーク
- 3.4k
- 平均マージ
- 2日 16時間
- マージ済み PR(30日)
- 192
環境構築
このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
Azure/azure-sdk-for-python のほかの issue
-
[Teams Chatbot]: Clarify Python dev-feed publication from a branch versus PyPI release対応中かも @jianingwang123 が 15 日前に担当しました。 オープンcustomer-reported question
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
Azure/azure-sdk-for-python#49548 · コメント 1 件 · リアクション 2 件 · 担当者 2 名 ·
メンテナーはふだん 1 日以内に返信
-
azure-ai-evaluation: a content-harm score of 0 (no harm) is parsed as NaN in _parse_content_harm_response (numeric branch uses 0 < x instead of 0 <= x)対応中かも @pujitha24 が 31 日前に担当しました。 オープンEvaluation Service Attention
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
Azure/azure-sdk-for-python#49190 · コメント 1 件 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
-
Update CODEOWNERSオープン
難易度 1/5 1時間未満 初心者へのやさしさ 90/100
Azure/azure-sdk-for-python#49183 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
-
azure-ai-evaluation: PromptyEvaluatorBase falls back to the first digit anywhere in the judge's reply, and the threshold turns a stray digit into a wrong pass/fail (15 evaluators)対応中かも @wasim-builds が 25 日前に担当しました。 オープンEvaluation Service Attention
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
Azure/azure-sdk-for-python#49153 · コメント 1 件 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
-
azure-search-documents ContentUnderstandingSkill is missing model_name and model_deployment対応中かも @wasim-builds が 25 日前に担当しました。 オープンSearch Service Attention
難易度 2/5 1〜3時間 初心者へのやさしさ 74/100
Azure/azure-sdk-for-python#48555 · コメント 1 件 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
Azure/azure-sdk-for-python の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 86/100
-
Validate finite output clamping limits in StepPredictor.prepare_clamping_params to prevent silent NaN/Inf propagation対応中かも @muskan-creates352 が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
mllam/neural-lam#848 ·
メンテナーはふだん 2 日以内に返信
-
infra
難易度 1/5 1時間未満 初心者へのやさしさ 88/100
fab-geocommuns/RNB-coeur#1166 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
deepspeedai/DeepSpeed#8951 ·
メンテナーはふだん 2 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
aws/context-ontology-accelerator#343 ·
メンテナーはふだん 1 日以内に返信