feat(temp-upload): hour-bucketed shared uploads with best-effort cleanup - #4479
Draft
zhoujh01 wants to merge 1 commit into
Draft
feat(temp-upload): hour-bucketed shared uploads with best-effort cleanup#4479zhoujh01 wants to merge 1 commit into
zhoujh01 wants to merge 1 commit into
Conversation
zhoujh01
force-pushed
the
feat/temp-upload-paged-cleanup
branch
from
August 29, 2026 07:49
06ff745 to
5a9d709
Compare
zhoujh01
marked this pull request as ready for review
August 29, 2026 09:21
zhoujh01
marked this pull request as draft
August 29, 2026 09:48
zhoujh01
force-pushed
the
feat/temp-upload-paged-cleanup
branch
from
August 29, 2026 10:37
5a9d709 to
0b9f89a
Compare
Group shared uploads into hourly buckets so the upload root never becomes one
huge flat directory (which made cleanup enumerate hundreds of thousands of
entries and take minutes), and rework cleanup to be bounded and best-effort.
Storage layout:
- New uploads: viking://upload/<YYYYMMDDHH>/<uuid>/{content,meta}, UTC hour.
upload_id is <YYYYMMDDHH>-<uuid>; the 10-digit hour prefix is the bucket and a
format marker, and the in-bucket directory is just the uuid (no repeated
prefix). temp_file_id stays shared_<upload_id> (external contract unchanged).
- Legacy flat <13-digit-ms>-<uuid> uploads stay readable; _read_shared_meta
picks exactly one path by id format, no fallback probe.
Cleanup (best-effort, off the request path, oldest-first via name-ascending ls):
- module-level due_at (epoch) + pending throttle so requests don't pile up
duplicate jobs;
- YYYYMMDDHH bucket expires at bucket_start + 3600 + ttl and is removed whole
(rm -r); scan stops at the first live bucket;
- legacy flat uploads expire by created_at + ttl and are removed individually;
- other/malformed dirs are removed only when temp_upload.cleanup_invalid_dirs is
enabled, and legacy flat uploads are never treated as invalid;
- every deletion logs kind/uri/elapsed_ms.
Shared upload writes, failure rollback, and cleanup deletes use
auto_pathlock=False; VikingFS.rm/write_file/write_file_bytes gain an
auto_pathlock parameter (default True, no behavior change for other callers).
Add temp_upload.cleanup_invalid_dirs config flag (default False).
Co-authored-by: TRAE CLI <traecli@bytedance.com>
zhoujh01
force-pushed
the
feat/temp-upload-paged-cleanup
branch
from
August 29, 2026 10:53
0b9f89a to
320dc67
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
对 shared 临时上传(shared TempUpload)做三件事:(1) 按 UTC 小时分桶存储、
(2) 把清理与本地磁盘 IO 移出请求路径(异步化)、(3) 重写清理为有界、best-effort 的方式。
改造前的问题:
viking://upload/下,该目录会膨胀到数十万条子目录;清理任务一次性枚举全量目录、耗时数分钟,线上曾观测到
listed达 70 万+、单轮清理 250s~45min,并伴随大量"already pending"。
_save_shared在请求路径内同步执行清理,且上传/解析过程中的本地磁盘读写是阻塞调用,会拖慢Core worker 的事件循环。
本 PR 通过小时分桶把单层目录规模控制在有界范围,并将清理与本地 IO 移出请求路径,从根本上解决
上述问题。
Human Involvement
Related Issue
Type of Change
Changes Made
1. 存储分桶
viking://upload/<YYYYMMDDHH>/<uuid>/{content,meta}(UTC 小时)。upload_id为<YYYYMMDDHH>-<uuid>,10 位小时前缀既是存储桶也是新格式标记;桶内子目录只用<uuid>,不重复前缀。对外
temp_file_id(shared_<upload_id>)契约保持不变。<13位ms>-<uuid>上传在 TTL 内仍可读;_read_shared_meta根据upload_id格式直接选定唯一路径读取,不做回退探测(新格式走桶、老格式走扁平)。2. 异步化 / 移出请求路径
_save_shared写完后仅调用_schedule_shared_cleanup(ctx)入队,不等待;实际清理在专用后台线程
ov-shared-upload-cleanup(单 worker、daemon、通过asyncio.run执行)中运行。due_at(epoch 秒)+pending集合 + 有界队列Queue(maxsize=100)。同一 account 已在 pending、或
due_at未到时跳过入队;队列满时释放 pending 并 WARNING;worker的
finally无条件释放 pending。(TempUploadStore非单例,故节流状态放在模块级。)_stream_upload_to_local_temp/_save_local/_resolve_local/
_resolve_shared/ResolvedTempUpload.cleanup中的本地磁盘读写(open/write/close/unlink/read_bytes/mkstemp/json 写入)统一用
asyncio.to_thread执行,避免慢本地存储阻塞 Core worker事件循环。
3. 清理重写(best-effort、oldest-first、按名称升序
ls)YYYYMMDDHH桶在bucket_start + 3600 + ttl后过期,整桶rm -r删除,扫描遇到首个未过期桶即停止并把该桶过期时刻记为
due_at;created_at + ttl单个删除;temp_upload.cleanup_invalid_dirs=true时删除,且遗留扁平上传永远不会被当作非法目录;删除失败不中断本轮扫描;
kind/uri/elapsed_ms日志,清理完成输出listed/scanned/removed/next_cleanup_at汇总;list/删除失败为 WARNING;
due_at,让下一次请求重试而不是被陈旧节流卡住。4. pathlock 与配置
auto_pathlock=False,避免清理与 pathlock 相互阻塞;
VikingFS.rm/write_file/write_file_bytes新增auto_pathlock参数(默认True,其它调用方行为不变)。
temp_upload.cleanup_invalid_dirs配置开关(默认False)。Testing
tests/server/test_temp_upload_store_async_io.py(17 passed)覆盖:_save_local/_resolve_local关键磁盘操作确实走asyncio.to_thread;due_at未到跳过、到期提交、队列满释放 pending;删除失败不中断扫描、遗留扁平永不被当非法、list/删除失败清
due_at;upload_id格式判定,新格式读桶路径、老格式读扁平路径,各只读一次。Checklist
Additional Notes
ttl + 不到 1 小时(清理本就是best-effort,以此换取按桶删除带来的数量级性能提升)。
ls,依赖“分桶后顶层目录很小(约 TTL 小时数 + 1 个桶)”这一前提。若存在大量历史遗留的扁平目录,首轮清理仍会较慢,属历史数据的一次性开销。
upload/目录,即使被清空,目录 inode 也不会自动收缩;建议在服务空闲时对旧目录做一次重建
(
mv upload upload.old && mkdir upload && rm -rf upload.old)。cleanup_invalid_dirs无每轮删除上限,启用时会在一轮内清理全部非法目录;若存在大量历史非法目录,首轮清理开销会较大。
due_at/pending为进程内状态;多进程/重启后状态丢失只会导致额外的清理触发,不影响正确性。