Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

azure-eventhub migration guide: legacy checkpoint sample writes "None" metadata for partitions that were never checkpointed

オープン 初心者向け
#49,723 コメント 2 件 リアクション 1 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

@hidingmyname がすでに取り組んでいます。

2026年10月29日 から。

  • #49759 @hidingmyname による — オープン
  • #49763 @huong8373tt-beep による — オープン

評価

難易度
2/5
見積もり時間
1〜3時間
初心者へのやさしさ
78/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
azure, python
領域
documentation

調査の方向性

sdk/eventhub/azure-eventhub/migration_guide.mdを開き、レガシー チェックポイントの移行スニペットを確認してください。offsetまたはsequence_numberが欠けているエントリをどのように扱うかを確認し、定義済みのEVENT_HUB_HOSTNAMEをプレフィックスに使用していることを検証してください。チェックポイントを作成できないエントリをサンプルがスキップし、定義済みの変数からBlobプレフィックスを構築すれば完了です。

索引モデルが issue の本文から書いたものです。

説明

Client customer-reported Event Hubs needs-team-attention question Service Attention
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

環境構築

Codespaces で開く

このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

Azure/azure-sdk-for-python のほかの issue

Azure/azure-sdk-for-python の issue をすべて見る

似ている issue

Python の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。