Skip to content

Add opt-in append_blob write mode for WASB task logs - #71064

Closed
bujjibabukatta wants to merge 8 commits into
apache:mainfrom
bujjibabukatta:fix/#70867
Closed

bujjibabukatta wants to merge 8 commits into
apache:mainfrom
bujjibabukatta:fix/#70867

Conversation

@bujjibabukatta

Copy link
Copy Markdown
Contributor

Summary
WASB task logs are rewritten from scratch on every upload -- the whole
existing blob is downloaded, the new segment appended in memory, and the
full result re-uploaded. For a long-running task this means O(N^2) traffic
across N log writes instead of O(N).

Root cause
WasbRemoteLogIO.write() always calls hook.load_string(..., overwrite=True),
which replaces the entire blob object regardless of how much of it is
actually new.

Fix
Added an opt-in write_mode="append_blob" that uses Azure's native AppendBlob
API (append_block() with a position guard) to append only the new segment,
instead of rewriting the whole object. Falls back to the existing
block-blob rewrite path for any blob already written as a block blob, since
Azure doesn't support converting a blob's type in place. Segments over the
4 MiB per-block limit are chunked automatically, and a concurrent-writer
position mismatch fails safely rather than writing at the wrong offset.

closes: #70867

Was generative AI tooling used ?

  • Yes - Claude

Generated-by: Claude following the guidelines

@bmanan7

bmanan7 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Thanks for picking this up. Nice to see it go straight to append_block() with appendpos_condition rather than upload_blob(blob_type="AppendBlob"), since that distinction is easy to miss, and the block-blob fallback for existing objects is exactly right.

One thing I think is worth handling before this lands: the chunk loop can leave a lifecycle partially applied, and nothing records how much of it landed.

Take a segment larger than the 4 MiB block limit, say 10 MiB split into A1, A2, A3:

  1. append_block(A1, offset=0) commits. The blob is now 4 MiB.
  2. append_block(A2, offset=4MiB) hits a transient error, so write() returns False.
  3. upload() sees has_uploaded=False and correctly leaves the local file intact so the segment can be retried (the behaviour added in Fix duplicated task logs in the WASB log handler #70860).

The blob now holds A1, while the local file still holds A1+A2+A3. On the next upload to the same key, offset is read as properties.size, which is 4 MiB, and the whole local file is appended from there. A1 ends up stored twice.

appendpos_condition does not catch this: the blob really is 4 MiB at that point, so the guard passes. It protects against writing at the wrong offset, not against re-sending content that already landed. The block-blob path never had this problem because its single overwrite=True PUT is atomic, so a lifecycle could not end half-applied.

It needs a second upload against the same key to surface, so in practice a deferrable resume or a reschedule poke whose individual lifecycle writes more than 4 MiB. Rare, but it is the same class of duplication that #70860 just fixed, which is why I thought it worth raising.

The awkward part is that write() receives the log string and returns a bool, so it has no way to tell upload() "4 of 10 MiB committed" or to trim the local file itself. A few directions that might work:

  • Retry the remaining chunks in place before giving up, which narrows the window without closing it.
  • Return bytes written, or move the chunking up into upload(), so the committed prefix can be trimmed from the local file and the "local file is exactly what has not been uploaded" invariant holds again.
  • Simplest option: do not chunk, and fall back to the block-blob path for segments over the limit, keeping the append path atomic.

Drafted-by: Claude Code (Opus 5); reviewed by @bmanan7 before posting

@bujjibabukatta

Copy link
Copy Markdown
Contributor Author

Hi @bmanan7 — good catch. Went with your second option: _write_append_blob
now returns bytes committed, and upload() trims the local file to just
the uncommitted part on a partial failure, so a retry doesn't resend what
already landed. Also caught that chunk boundaries could split a multi-byte
character, so fixed that too. Added tests for both.

@potiuk potiuk added the ready for maintainer review Set after triaging when all criteria pass. label Aug 13, 2026

@aaron-y-chen aaron-y-chen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution! :)

Could you help resolve the conflict before we get into the review process?

@potiuk

potiuk commented Sep 25, 2026

Copy link
Copy Markdown
Member

Hello @bujjibabukatta - thank you for your contributions to Apache Airflow!

The Airflow community has introduced a limit of 5 open pull requests at a time for contributors without write access to the repository. You currently have 16 open pull requests, so - as a one-time step of introducing the limit - we closed the ones where maintainers have not engaged yet:

These pull requests stay open because maintainers are already engaged in them - they count towards your limit:

This is not a judgement of you or of your changes. We never told contributors before that opening many pull requests at once was a problem, so there is nothing to feel bad about - and nothing is lost: your branches, commits and the review history stay where they are.

What we ask you to do is to make your first prioritization decision: choose which of the pull requests above matter most to you, and reopen them (up to 5 open at a time, including the ones still open) with the "Reopen pull request" button or gh pr reopen <PR_NUMBER> --repo apache/airflow. Reopen the ones you are ready to follow through - keep them rebased, respond to review comments and fix failing checks.

While your pull requests are waiting for review, the most valuable thing you can do is help in other ways - reviewing other contributors' pull requests, helping with issues, and taking part in the discussions on the devlist and Slack.

Why we introduced the limit, what it means for you and how to reopen or restore a pull request is explained in https://github.com/apache/airflow/blob/main/contributing-docs/32_open_pull_request_limit.rst.


Drafted-by: Claude Code (Opus 5); reviewed by @potiuk before posting

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:logging area:providers closed because of open PR limit Closed as a one-time step of introducing the open pull request limit provider:microsoft-azure Azure-related issues ready for maintainer review Set after triaging when all criteria pass.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WASB task log uploads rewrite the whole log each time instead of appending

4 participants