Skip to content

perf(video): buffer upload metadata in a bounded bytearray, not a list[bytes] - #9583

Open
lstein wants to merge 1 commit into
invoke-ai:mainfrom
lstein:fix-9563-metadata-bytearray
Open

perf(video): buffer upload metadata in a bounded bytearray, not a list[bytes]#9583
lstein wants to merge 1 commit into
invoke-ai:mainfrom
lstein:fix-9563-metadata-bytearray

Conversation

@lstein

@lstein lstein commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes #9563 (follow-up to the non-blocking note in JPPhoto's review of #9396).

_VideoUploadStreamParser buffered the metadata part as list[bytes], one object per on_part_data callback. The multipart parser hands the field over in whatever pieces the client sent it, so a client that dribbles the field in tiny chunks makes CPython's per-object overhead dominate: MAX_UPLOAD_METADATA_SIZE bounded the payload but not the retained memory.

This accumulates into a bytearray instead, the way _header_field / _header_value in the same class already do. The buffer's own length is now what the cap checks, so the separate _metadata_size counter is gone, and _on_part_end decodes the buffer directly instead of b"".join(...).

Measured

Retained allocation (tracemalloc) for a 64 KiB metadata field, as a multiple of the payload:

chunk size before after
1 byte 8.6x 1.01x
2 bytes 21.7x 1.09x
3 bytes 15.0x 1.02x
8 bytes 6.2x 1.06x

One-byte chunks are not the worst case, contrary to the issue's estimate: CPython interns single-byte bytes objects, so only the list slot is retained. Two-byte chunks are the worst case, at ~22x. With the 1 MiB cap and two concurrent upload slots, that is ~44 MiB of avoidable retention per pair of hostile uploads; after this change it is ~2.2 MiB.

Accept/reject behaviour of the cap is unchanged: len(buffer) + len(chunk) > MAX is the same predicate as the old running counter.

Tests

  • test_upload_metadata_buffer_is_bounded_by_its_size_not_its_chunk_count[1|2|3]: feeds a 64 KiB field 1, 2 and 3 bytes per parser.write() and asserts retained allocation stays under 2x the payload. Verified load-bearing: against the previous implementation these fail with 8.6x / 21.7x / 15.0x. 64 KiB rather than the full 1 MiB because per-byte write() calls are ~11 s at 1 MiB, and the amplification ratio is size-independent.
  • test_upload_metadata_cap_counts_the_whole_field_across_chunks: a field exactly at the cap split unevenly across chunks is accepted and reassembled intact; one byte over, where no single chunk is near the cap, is still rejected with 413.

Checklist

  • The PR has a short but descriptive title, suitable for a changelog
  • Tests added / updated (if applicable)
  • Documentation added / updated (n/a)

🤖 Generated with Claude Code

https://claude.ai/code/session_01L5Y1XMvgKRCr2pu6wm8rs1

…t[bytes]

The multipart parser hands the `metadata` field over in whatever pieces the
client sent it, and `_VideoUploadStreamParser` kept one `bytes` object per
piece. Per-object overhead then dominated: a client that dribbled the field in
2-byte chunks retained ~22x the payload, so the 1 MiB `MAX_UPLOAD_METADATA_SIZE`
cap bounded payload but not memory (~22 MiB per upload, x2 concurrent slots).

Accumulate into a `bytearray` instead, the way the header buffers already do.
The buffer's own length is now the size the cap checks, so the separate
`_metadata_size` counter goes away, and `_on_part_end` decodes the buffer
directly.

Tests: feed a 64 KiB field 1, 2 and 3 bytes at a time and assert retained
allocation stays under 2x the payload (the old code retained 8.6x / 21.7x /
15.0x); and check the cap still counts the whole field across chunks.

Closes invoke-ai#9563

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5Y1XMvgKRCr2pu6wm8rs1
@github-actions github-actions Bot added api python PRs that change python files python-tests PRs that change python tests labels Sep 11, 2026
@lstein lstein added the 6.14.2 label Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

6.14.2 api python PRs that change python files python-tests PRs that change python tests

Projects

Status: 6.14.2

Development

Successfully merging this pull request may close these issues.

perf(video): buffer upload metadata in a bounded bytearray, not a list[bytes]

2 participants