[{"data":1,"prerenderedAt":1974},["ShallowReactive",2],{"post-\u002Fblog\u002Fresume-interrupted-multipart-upload":3,"blog-index":1892},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"author":11,"tags":12,"guide":16,"related":17,"body":20,"_type":1886,"_id":1887,"_source":1888,"_file":1889,"_stem":1890,"_extension":1891},"\u002Fblog\u002Fresume-interrupted-multipart-upload","blog",false,"","Resuming a multipart upload you never meant to abandon","An upload ID never expires, so a crashed upload is resumable without the state file — measured: the part list is the real state, and it can lie about the ETag.","2026-11-13","Clement Baranger",[13,14,15],"s3","uploads","multipart","incomplete-multipart-uploads",[18,19],"multipart-5mb-floor-10000-parts","list-pagination-1000-pages",{"type":21,"children":22,"toc":1877},"root",[23,31,38,120,126,155,213,242,251,272,278,291,518,551,583,614,654,664,699,711,757,792,798,815,825,1130,1154,1178,1460,1494,1500,1565,1571,1719,1725,1871],{"type":24,"tag":25,"props":26,"children":27},"element","p",{},[28],{"type":29,"value":30},"text","A 400 GB upload dies at 03:40 when the machine reboots, and the temporary file holding the part ETags goes with it. The obvious conclusion is that the transfer restarts from zero. It does not: the parts are on the server, the upload ID has no expiry, and the only thing lost was a list the server hands back in two calls.",{"type":24,"tag":32,"props":33,"children":35},"h2",{"id":34},"the-short-version",[36],{"type":29,"value":37},"The short version",{"type":24,"tag":39,"props":40,"children":41},"ul",{},[42,48,53,67,72,84],{"type":24,"tag":43,"props":44,"children":45},"li",{},[46],{"type":29,"value":47},"A multipart upload keeps three things alive between calls: an upload ID, the numbered parts already sent, and the ETag each part returned.",{"type":24,"tag":43,"props":49,"children":50},{},[51],{"type":29,"value":52},"The upload ID does not expire. Nothing removes it until you complete or abort the upload, so a crash leaves a resumable upload rather than a corpse.",{"type":24,"tag":43,"props":54,"children":55},{},[56,58,65],{"type":29,"value":57},"Recovery is two reads and one write: list the in-progress uploads, list that upload's parts, send only the missing numbers. A local state file is convenience, not authority — ",{"type":24,"tag":59,"props":60,"children":62},"code",{"className":61},[],[63],{"type":29,"value":64},"ListParts",{"type":29,"value":66}," returns the same ETag map.",{"type":24,"tag":43,"props":68,"children":69},{},[70],{"type":29,"value":71},"Parts are billed as storage until completed or aborted, and re-sending instead of resuming costs the wall-clock time: 400 GB is about 8.9 hours at 100 Mbit\u002Fs.",{"type":24,"tag":43,"props":73,"children":74},{},[75,77,82],{"type":29,"value":76},"The part list is a claim, not a proof. Measured on SeaweedFS 4.50, re-sending a part number left the previous ETag in ",{"type":24,"tag":59,"props":78,"children":80},{"className":79},[],[81],{"type":29,"value":64},{"type":29,"value":83}," in 15 of 30 trials, and the completed object held the older bytes with no error.",{"type":24,"tag":43,"props":85,"children":86},{},[87,89,95,97,103,105,111,112,118],{"type":29,"value":88},"A resume needs ",{"type":24,"tag":59,"props":90,"children":92},{"className":91},[],[93],{"type":29,"value":94},"s3:ListMultipartUploadParts",{"type":29,"value":96}," and ",{"type":24,"tag":59,"props":98,"children":100},{"className":99},[],[101],{"type":29,"value":102},"s3:ListBucketMultipartUploads",{"type":29,"value":104},"; a credential that can only ",{"type":24,"tag":59,"props":106,"children":108},{"className":107},[],[109],{"type":29,"value":110},"GetObject",{"type":29,"value":96},{"type":24,"tag":59,"props":113,"children":115},{"className":114},[],[116],{"type":29,"value":117},"PutObject",{"type":29,"value":119}," fails on the first recovery call.",{"type":24,"tag":32,"props":121,"children":123},{"id":122},"what-actually-happens",[124],{"type":29,"value":125},"What actually happens",{"type":24,"tag":25,"props":127,"children":128},{},[129,131,137,139,145,147,153],{"type":29,"value":130},"Three API calls wrap a loop. ",{"type":24,"tag":59,"props":132,"children":134},{"className":133},[],[135],{"type":29,"value":136},"CreateMultipartUpload",{"type":29,"value":138}," returns an opaque upload ID, each ",{"type":24,"tag":59,"props":140,"children":142},{"className":141},[],[143],{"type":29,"value":144},"UploadPart",{"type":29,"value":146}," carries a part number and returns an ETag, and ",{"type":24,"tag":59,"props":148,"children":150},{"className":149},[],[151],{"type":29,"value":152},"CompleteMultipartUpload",{"type":29,"value":154}," takes the ascending list of numbers with their ETags. Everything between those calls lives on the server, addressed by upload ID and key. The AWS upload guide states the consequence: \"After you initiate a multipart upload, there is no expiry; you must explicitly complete or stop the multipart upload.\" No timer, no garbage collection, no eviction of a stale ID — a part sits there until something completes or aborts it, billed as stored bytes for every hour in between.",{"type":24,"tag":25,"props":156,"children":157},{},[158,160,166,168,174,175,181,183,188,190,196,198,204,205,211],{"type":29,"value":159},"What the client loses in a crash is not the upload but the part-number-to-ETag map, which the same guide tells you to keep: \"For each part upload, you must record the part number and the ETag value.\" The server holds that map too, reachable two ways. ",{"type":24,"tag":59,"props":161,"children":163},{"className":162},[],[164],{"type":29,"value":165},"ListMultipartUploads",{"type":29,"value":167}," returns in-progress uploads per bucket, 1,000 at a time, paged with ",{"type":24,"tag":59,"props":169,"children":171},{"className":170},[],[172],{"type":29,"value":173},"key-marker",{"type":29,"value":96},{"type":24,"tag":59,"props":176,"children":178},{"className":177},[],[179],{"type":29,"value":180},"upload-id-marker",{"type":29,"value":182}," — that is how a restart finds an ID nobody wrote down. ",{"type":24,"tag":59,"props":184,"children":186},{"className":185},[],[187],{"type":29,"value":64},{"type":29,"value":189}," returns one upload's parts, 1,000 at a time, paged with ",{"type":24,"tag":59,"props":191,"children":193},{"className":192},[],[194],{"type":29,"value":195},"part-number-marker",{"type":29,"value":197},", with ",{"type":24,"tag":59,"props":199,"children":201},{"className":200},[],[202],{"type":29,"value":203},"IsTruncated",{"type":29,"value":96},{"type":24,"tag":59,"props":206,"children":208},{"className":207},[],[209],{"type":29,"value":210},"NextPartNumberMarker",{"type":29,"value":212}," when there are more.",{"type":24,"tag":25,"props":214,"children":215},{},[216,218,225,227,233,235,240],{"type":29,"value":217},"Both ceilings behave like ",{"type":24,"tag":219,"props":220,"children":222},"a",{"href":221},"\u002Fblog\u002Flist-pagination-1000-pages",[223],{"type":29,"value":224},"the 1,000-entry page that breaks listing loops",{"type":29,"value":226},": a ",{"type":24,"tag":59,"props":228,"children":230},{"className":229},[],[231],{"type":29,"value":232},"MaxParts",{"type":29,"value":234}," of 1,000 that reports truncation instead of returning everything. A 400 GB object at 16 MiB parts is 25,600 parts, so 26 ",{"type":24,"tag":59,"props":236,"children":238},{"className":237},[],[239],{"type":29,"value":64},{"type":29,"value":241}," pages — code that reads the first page rebuilds a completion list missing 24,600 entries.",{"type":24,"tag":25,"props":243,"children":244},{},[245],{"type":24,"tag":246,"props":247,"children":250},"img",{"alt":248,"src":249},"A multipart upload surviving a process crash: the upload ID and the parts stay on the server, so a restart lists the in-progress upload, lists its parts, sends only the missing part number and completes from that list — with the measured case where re-sending a part number left the previous ETag in the part list","\u002Fdiagrams\u002Fresume-interrupted-multipart-upload.svg",[],{"type":24,"tag":25,"props":252,"children":253},{},[254,256,262,264,270],{"type":29,"value":255},"Upload IDs are not a format to validate: the same protocol produced 72 characters on SeaweedFS (",{"type":24,"tag":59,"props":257,"children":259},{"className":258},[],[260],{"type":29,"value":261},"1c1651d2b4add570726720ab31276b5ee10f9f39_01a1d493…",{"type":29,"value":263},") and 63 on moto (",{"type":24,"tag":59,"props":265,"children":267},{"className":266},[],[268],{"type":29,"value":269},"GnUwtRrY1uqTe9iQs25CPTiGkCSz7ToEgGW8MySMeOYEjCz1x3fJFkDHA",{"type":29,"value":271},").",{"type":24,"tag":32,"props":273,"children":275},{"id":274},"where-it-bites",[276],{"type":29,"value":277},"Where it bites",{"type":24,"tag":25,"props":279,"children":280},{},[281,283,289],{"type":29,"value":282},"Two probes ran against SeaweedFS 4.50 and moto 5.2.5 with ",{"type":24,"tag":59,"props":284,"children":286},{"className":285},[],[287],{"type":29,"value":288},"boto3",{"type":29,"value":290}," 1.44.2: 5 MiB + 5 MiB + 1 KiB, the first two parts sent, the process then treated as dead.",{"type":24,"tag":292,"props":293,"children":294},"table",{},[295,319],{"type":24,"tag":296,"props":297,"children":298},"thead",{},[299],{"type":24,"tag":300,"props":301,"children":302},"tr",{},[303,309,314],{"type":24,"tag":304,"props":305,"children":306},"th",{},[307],{"type":29,"value":308},"Step",{"type":24,"tag":304,"props":310,"children":311},{},[312],{"type":29,"value":313},"SeaweedFS 4.50",{"type":24,"tag":304,"props":315,"children":316},{},[317],{"type":29,"value":318},"moto 5.2.5",{"type":24,"tag":320,"props":321,"children":322},"tbody",{},[323,346,366,400,425,450,496],{"type":24,"tag":300,"props":324,"children":325},{},[326,337,342],{"type":24,"tag":327,"props":328,"children":329},"td",{},[330,335],{"type":24,"tag":59,"props":331,"children":333},{"className":332},[],[334],{"type":29,"value":165},{"type":29,"value":336}," finds the key",{"type":24,"tag":327,"props":338,"children":339},{},[340],{"type":29,"value":341},"yes, 1 upload",{"type":24,"tag":327,"props":343,"children":344},{},[345],{"type":29,"value":341},{"type":24,"tag":300,"props":347,"children":348},{},[349,357,362],{"type":24,"tag":327,"props":350,"children":351},{},[352],{"type":24,"tag":59,"props":353,"children":355},{"className":354},[],[356],{"type":29,"value":64},{"type":24,"tag":327,"props":358,"children":359},{},[360],{"type":29,"value":361},"2 parts, not truncated",{"type":24,"tag":327,"props":363,"children":364},{},[365],{"type":29,"value":361},{"type":24,"tag":300,"props":367,"children":368},{},[369,378,395],{"type":24,"tag":327,"props":370,"children":371},{},[372],{"type":24,"tag":59,"props":373,"children":375},{"className":374},[],[376],{"type":29,"value":377},"MaxParts=1",{"type":24,"tag":327,"props":379,"children":380},{},[381,386,388,393],{"type":24,"tag":59,"props":382,"children":384},{"className":383},[],[385],{"type":29,"value":203},{"type":29,"value":387}," true, ",{"type":24,"tag":59,"props":389,"children":391},{"className":390},[],[392],{"type":29,"value":210},{"type":29,"value":394}," 1",{"type":24,"tag":327,"props":396,"children":397},{},[398],{"type":29,"value":399},"same",{"type":24,"tag":300,"props":401,"children":402},{},[403,408,417],{"type":24,"tag":327,"props":404,"children":405},{},[406],{"type":29,"value":407},"Complete with a wrong ETag",{"type":24,"tag":327,"props":409,"children":410},{},[411],{"type":24,"tag":59,"props":412,"children":414},{"className":413},[],[415],{"type":29,"value":416},"400 InvalidPart",{"type":24,"tag":327,"props":418,"children":419},{},[420],{"type":24,"tag":59,"props":421,"children":423},{"className":422},[],[424],{"type":29,"value":416},{"type":24,"tag":300,"props":426,"children":427},{},[428,433,442],{"type":24,"tag":327,"props":429,"children":430},{},[431],{"type":29,"value":432},"Parts out of order",{"type":24,"tag":327,"props":434,"children":435},{},[436],{"type":24,"tag":59,"props":437,"children":439},{"className":438},[],[440],{"type":29,"value":441},"400 InvalidPartOrder",{"type":24,"tag":327,"props":443,"children":444},{},[445],{"type":24,"tag":59,"props":446,"children":448},{"className":447},[],[449],{"type":29,"value":416},{"type":24,"tag":300,"props":451,"children":452},{},[453,470,479],{"type":24,"tag":327,"props":454,"children":455},{},[456,461,463,468],{"type":24,"tag":59,"props":457,"children":459},{"className":458},[],[460],{"type":29,"value":64},{"type":29,"value":462},", ",{"type":24,"tag":59,"props":464,"children":466},{"className":465},[],[467],{"type":29,"value":144},{"type":29,"value":469}," after abort",{"type":24,"tag":327,"props":471,"children":472},{},[473],{"type":24,"tag":59,"props":474,"children":476},{"className":475},[],[477],{"type":29,"value":478},"404 NoSuchUpload",{"type":24,"tag":327,"props":480,"children":481},{},[482,488,490],{"type":24,"tag":59,"props":483,"children":485},{"className":484},[],[486],{"type":29,"value":487},"404",{"type":29,"value":489},", then ",{"type":24,"tag":59,"props":491,"children":493},{"className":492},[],[494],{"type":29,"value":495},"500 Internal Server Error",{"type":24,"tag":300,"props":497,"children":498},{},[499,509,514],{"type":24,"tag":327,"props":500,"children":501},{},[502,504],{"type":29,"value":503},"Second ",{"type":24,"tag":59,"props":505,"children":507},{"className":506},[],[508],{"type":29,"value":152},{"type":24,"tag":327,"props":510,"children":511},{},[512],{"type":29,"value":513},"accepted, no error",{"type":24,"tag":327,"props":515,"children":516},{},[517],{"type":29,"value":513},{"type":24,"tag":519,"props":520,"children":524},"pre",{"className":521,"code":522,"language":523,"meta":7,"style":7},"language-bash shiki shiki-themes github-light-high-contrast github-dark-high-contrast","python3 scripts\u002Fresume-probe.py http:\u002F\u002F127.0.0.1:18334\n","bash",[525],{"type":24,"tag":59,"props":526,"children":527},{"__ignoreMap":7},[528],{"type":24,"tag":529,"props":530,"children":533},"span",{"class":531,"line":532},"line",1,[534,540,546],{"type":24,"tag":529,"props":535,"children":537},{"style":536},"--shiki-default:#702C00;--shiki-dark:#FFB757",[538],{"type":29,"value":539},"python3",{"type":24,"tag":529,"props":541,"children":543},{"style":542},"--shiki-default:#032563;--shiki-dark:#ADDCFF",[544],{"type":29,"value":545}," scripts\u002Fresume-probe.py",{"type":24,"tag":529,"props":547,"children":548},{"style":542},[549],{"type":29,"value":550}," http:\u002F\u002F127.0.0.1:18334\n",{"type":24,"tag":25,"props":552,"children":553},{},[554,560,562,567,569,574,576,581],{"type":24,"tag":555,"props":556,"children":557},"strong",{},[558],{"type":29,"value":559},"The abort codes are not interchangeable.",{"type":29,"value":561}," SeaweedFS answers ",{"type":24,"tag":59,"props":563,"children":565},{"className":564},[],[566],{"type":29,"value":478},{"type":29,"value":568}," — \"The specified multipart upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed.\" moto answers ",{"type":24,"tag":59,"props":570,"children":572},{"className":571},[],[573],{"type":29,"value":495},{"type":29,"value":575}," to ",{"type":24,"tag":59,"props":577,"children":579},{"className":578},[],[580],{"type":29,"value":144},{"type":29,"value":582}," after an abort, so a retry loop on 5xx waits forever on an upload that no longer exists.",{"type":24,"tag":25,"props":584,"children":585},{},[586,597,599,605,607,612],{"type":24,"tag":555,"props":587,"children":588},{},[589,595],{"type":24,"tag":59,"props":590,"children":592},{"className":591},[],[593],{"type":29,"value":594},"InvalidPartOrder",{"type":29,"value":596}," is not universal.",{"type":29,"value":598}," AWS documents it separately: \"The list of parts was not in ascending order.\" moto collapses it into ",{"type":24,"tag":59,"props":600,"children":602},{"className":601},[],[603],{"type":29,"value":604},"InvalidPart",{"type":29,"value":606},". Sorting the list makes it unreachable; merging a ",{"type":24,"tag":59,"props":608,"children":610},{"className":609},[],[611],{"type":29,"value":64},{"type":29,"value":613}," page with a local map brings it back.",{"type":24,"tag":25,"props":615,"children":616},{},[617,622,624,630,632,638,639,644,646,652],{"type":24,"tag":555,"props":618,"children":619},{},[620],{"type":29,"value":621},"A second completion succeeds.",{"type":29,"value":623}," AWS documents ",{"type":24,"tag":59,"props":625,"children":627},{"className":626},[],[628],{"type":29,"value":629},"NoSuchUpload",{"type":29,"value":631}," for an upload that was \"aborted or completed\"; both endpoints returned success. A retried completion looks identical to a first one, so treat ",{"type":24,"tag":59,"props":633,"children":635},{"className":634},[],[636],{"type":29,"value":637},"200",{"type":29,"value":96},{"type":24,"tag":59,"props":640,"children":642},{"className":641},[],[643],{"type":29,"value":629},{"type":29,"value":645}," as the same terminal state and confirm with a ",{"type":24,"tag":59,"props":647,"children":649},{"className":648},[],[650],{"type":29,"value":651},"HEAD",{"type":29,"value":653}," on the key.",{"type":24,"tag":25,"props":655,"children":656},{},[657,662],{"type":24,"tag":555,"props":658,"children":659},{},[660],{"type":29,"value":661},"Re-sending a part number is not reliably an overwrite.",{"type":29,"value":663}," The documentation says \"if you upload a new part using the same part number as a previously uploaded part, the previously uploaded part gets overwritten\", so a resume that guesses \"part 2 failed, send it again\" assumes new bytes win. SeaweedFS 4.50 does not always agree:",{"type":24,"tag":519,"props":665,"children":667},{"className":521,"code":666,"language":523,"meta":7,"style":7},"python3 scripts\u002Fresume-overwrite-probe.py http:\u002F\u002F127.0.0.1:18334 10 0\n",[668],{"type":24,"tag":59,"props":669,"children":670},{"__ignoreMap":7},[671],{"type":24,"tag":529,"props":672,"children":673},{"class":531,"line":532},[674,678,683,688,694],{"type":24,"tag":529,"props":675,"children":676},{"style":536},[677],{"type":29,"value":539},{"type":24,"tag":529,"props":679,"children":680},{"style":542},[681],{"type":29,"value":682}," scripts\u002Fresume-overwrite-probe.py",{"type":24,"tag":529,"props":684,"children":685},{"style":542},[686],{"type":29,"value":687}," http:\u002F\u002F127.0.0.1:18334",{"type":24,"tag":529,"props":689,"children":691},{"style":690},"--shiki-default:#023B95;--shiki-dark:#91CBFF",[692],{"type":29,"value":693}," 10",{"type":24,"tag":529,"props":695,"children":696},{"style":690},[697],{"type":29,"value":698}," 0\n",{"type":24,"tag":25,"props":700,"children":701},{},[702,704,709],{"type":29,"value":703},"Each trial uploads a block as part 1, uploads a different block as part 1 again, reads the ETag from ",{"type":24,"tag":59,"props":705,"children":707},{"className":706},[],[708],{"type":29,"value":64},{"type":29,"value":710},", completes with it and hashes the stored object. Across 30 trials the part list kept the previous ETag in 15 — 4\u002F10 and 6\u002F10 with no pause, 5\u002F10 with a one-second pause before the read. Every one of those completions stored the first block. moto was stale 0 times in 15.",{"type":24,"tag":25,"props":712,"children":713},{},[714,716,721,723,728,730,735,736,741,743,748,750,755],{"type":29,"value":715},"The failure is silent: ",{"type":24,"tag":59,"props":717,"children":719},{"className":718},[],[720],{"type":29,"value":144},{"type":29,"value":722}," returns ",{"type":24,"tag":59,"props":724,"children":726},{"className":725},[],[727],{"type":29,"value":637},{"type":29,"value":729}," with a new ETag, ",{"type":24,"tag":59,"props":731,"children":733},{"className":732},[],[734],{"type":29,"value":152},{"type":29,"value":722},{"type":24,"tag":59,"props":737,"children":739},{"className":738},[],[740],{"type":29,"value":637},{"type":29,"value":742},", the object is the right length, and the bytes are the earlier attempt. Complete with the ETag returned by the ",{"type":24,"tag":59,"props":744,"children":746},{"className":745},[],[747],{"type":29,"value":144},{"type":29,"value":749}," you just made, never with a value re-read from ",{"type":24,"tag":59,"props":751,"children":753},{"className":752},[],[754],{"type":29,"value":64},{"type":29,"value":756}," in the same session.",{"type":24,"tag":25,"props":758,"children":759},{},[760,762,767,769,775,777,782,784,790],{"type":29,"value":761},"Two more outcomes to wire for: a resume after your lifecycle rule fired fails at the first call with ",{"type":24,"tag":59,"props":763,"children":765},{"className":764},[],[766],{"type":29,"value":629},{"type":29,"value":768}," — normal, not a bug — and a resumed job racing an object written under the same key can be refused with ",{"type":24,"tag":59,"props":770,"children":772},{"className":771},[],[773],{"type":29,"value":774},"412 Precondition Failed",{"type":29,"value":776}," now that conditional writes apply to ",{"type":24,"tag":59,"props":778,"children":780},{"className":779},[],[781],{"type":29,"value":152},{"type":29,"value":783},". The part-size arithmetic that sets how many pages a resume walks is ",{"type":24,"tag":219,"props":785,"children":787},{"href":786},"\u002Fblog\u002Fmultipart-5mb-floor-10000-parts",[788],{"type":29,"value":789},"its own set of limits",{"type":29,"value":791},".",{"type":24,"tag":32,"props":793,"children":795},{"id":794},"doing-it-right",[796],{"type":29,"value":797},"Doing it right",{"type":24,"tag":25,"props":799,"children":800},{},[801,806,808,813],{"type":24,"tag":555,"props":802,"children":803},{},[804],{"type":29,"value":805},"1. Write the upload ID down before the first part.",{"type":29,"value":807}," Endpoint, bucket, key, upload ID, fsynced. The part map is optional because ",{"type":24,"tag":59,"props":809,"children":811},{"className":810},[],[812],{"type":29,"value":64},{"type":29,"value":814}," rebuilds it.",{"type":24,"tag":25,"props":816,"children":817},{},[818,823],{"type":24,"tag":555,"props":819,"children":820},{},[821],{"type":29,"value":822},"2. Find the upload by key prefix.",{"type":29,"value":824}," Never scan a whole bucket; the pagination has two tokens, not one.",{"type":24,"tag":519,"props":826,"children":830},{"className":827,"code":828,"language":829,"meta":7,"style":7},"language-python shiki shiki-themes github-light-high-contrast github-dark-high-contrast","def find_upload(s3, bucket, key):\n    kw = {\"Bucket\": bucket, \"Prefix\": key}\n    while True:\n        page = s3.list_multipart_uploads(**kw)\n        for u in page.get(\"Uploads\", []):\n            if u[\"Key\"] == key:\n                return u[\"UploadId\"]\n        if not page.get(\"IsTruncated\"):\n            return None\n        kw[\"KeyMarker\"], kw[\"UploadIdMarker\"] = page[\"NextKeyMarker\"], page[\"NextUploadIdMarker\"]\n","python",[831],{"type":24,"tag":59,"props":832,"children":833},{"__ignoreMap":7},[834,855,894,913,941,975,1009,1032,1060,1074],{"type":24,"tag":529,"props":835,"children":836},{"class":531,"line":532},[837,843,849],{"type":24,"tag":529,"props":838,"children":840},{"style":839},"--shiki-default:#A0111F;--shiki-dark:#FF9492",[841],{"type":29,"value":842},"def",{"type":24,"tag":529,"props":844,"children":846},{"style":845},"--shiki-default:#622CBC;--shiki-dark:#DBB7FF",[847],{"type":29,"value":848}," find_upload",{"type":24,"tag":529,"props":850,"children":852},{"style":851},"--shiki-default:#0E1116;--shiki-dark:#F0F3F6",[853],{"type":29,"value":854},"(s3, bucket, key):\n",{"type":24,"tag":529,"props":856,"children":858},{"class":531,"line":857},2,[859,864,869,874,879,884,889],{"type":24,"tag":529,"props":860,"children":861},{"style":851},[862],{"type":29,"value":863},"    kw ",{"type":24,"tag":529,"props":865,"children":866},{"style":839},[867],{"type":29,"value":868},"=",{"type":24,"tag":529,"props":870,"children":871},{"style":851},[872],{"type":29,"value":873}," {",{"type":24,"tag":529,"props":875,"children":876},{"style":542},[877],{"type":29,"value":878},"\"Bucket\"",{"type":24,"tag":529,"props":880,"children":881},{"style":851},[882],{"type":29,"value":883},": bucket, ",{"type":24,"tag":529,"props":885,"children":886},{"style":542},[887],{"type":29,"value":888},"\"Prefix\"",{"type":24,"tag":529,"props":890,"children":891},{"style":851},[892],{"type":29,"value":893},": key}\n",{"type":24,"tag":529,"props":895,"children":897},{"class":531,"line":896},3,[898,903,908],{"type":24,"tag":529,"props":899,"children":900},{"style":839},[901],{"type":29,"value":902},"    while",{"type":24,"tag":529,"props":904,"children":905},{"style":690},[906],{"type":29,"value":907}," True",{"type":24,"tag":529,"props":909,"children":910},{"style":851},[911],{"type":29,"value":912},":\n",{"type":24,"tag":529,"props":914,"children":916},{"class":531,"line":915},4,[917,922,926,931,936],{"type":24,"tag":529,"props":918,"children":919},{"style":851},[920],{"type":29,"value":921},"        page ",{"type":24,"tag":529,"props":923,"children":924},{"style":839},[925],{"type":29,"value":868},{"type":24,"tag":529,"props":927,"children":928},{"style":851},[929],{"type":29,"value":930}," s3.list_multipart_uploads(",{"type":24,"tag":529,"props":932,"children":933},{"style":839},[934],{"type":29,"value":935},"**",{"type":24,"tag":529,"props":937,"children":938},{"style":851},[939],{"type":29,"value":940},"kw)\n",{"type":24,"tag":529,"props":942,"children":944},{"class":531,"line":943},5,[945,950,955,960,965,970],{"type":24,"tag":529,"props":946,"children":947},{"style":839},[948],{"type":29,"value":949},"        for",{"type":24,"tag":529,"props":951,"children":952},{"style":851},[953],{"type":29,"value":954}," u ",{"type":24,"tag":529,"props":956,"children":957},{"style":839},[958],{"type":29,"value":959},"in",{"type":24,"tag":529,"props":961,"children":962},{"style":851},[963],{"type":29,"value":964}," page.get(",{"type":24,"tag":529,"props":966,"children":967},{"style":542},[968],{"type":29,"value":969},"\"Uploads\"",{"type":24,"tag":529,"props":971,"children":972},{"style":851},[973],{"type":29,"value":974},", []):\n",{"type":24,"tag":529,"props":976,"children":978},{"class":531,"line":977},6,[979,984,989,994,999,1004],{"type":24,"tag":529,"props":980,"children":981},{"style":839},[982],{"type":29,"value":983},"            if",{"type":24,"tag":529,"props":985,"children":986},{"style":851},[987],{"type":29,"value":988}," u[",{"type":24,"tag":529,"props":990,"children":991},{"style":542},[992],{"type":29,"value":993},"\"Key\"",{"type":24,"tag":529,"props":995,"children":996},{"style":851},[997],{"type":29,"value":998},"] ",{"type":24,"tag":529,"props":1000,"children":1001},{"style":839},[1002],{"type":29,"value":1003},"==",{"type":24,"tag":529,"props":1005,"children":1006},{"style":851},[1007],{"type":29,"value":1008}," key:\n",{"type":24,"tag":529,"props":1010,"children":1012},{"class":531,"line":1011},7,[1013,1018,1022,1027],{"type":24,"tag":529,"props":1014,"children":1015},{"style":839},[1016],{"type":29,"value":1017},"                return",{"type":24,"tag":529,"props":1019,"children":1020},{"style":851},[1021],{"type":29,"value":988},{"type":24,"tag":529,"props":1023,"children":1024},{"style":542},[1025],{"type":29,"value":1026},"\"UploadId\"",{"type":24,"tag":529,"props":1028,"children":1029},{"style":851},[1030],{"type":29,"value":1031},"]\n",{"type":24,"tag":529,"props":1033,"children":1035},{"class":531,"line":1034},8,[1036,1041,1046,1050,1055],{"type":24,"tag":529,"props":1037,"children":1038},{"style":839},[1039],{"type":29,"value":1040},"        if",{"type":24,"tag":529,"props":1042,"children":1043},{"style":839},[1044],{"type":29,"value":1045}," not",{"type":24,"tag":529,"props":1047,"children":1048},{"style":851},[1049],{"type":29,"value":964},{"type":24,"tag":529,"props":1051,"children":1052},{"style":542},[1053],{"type":29,"value":1054},"\"IsTruncated\"",{"type":24,"tag":529,"props":1056,"children":1057},{"style":851},[1058],{"type":29,"value":1059},"):\n",{"type":24,"tag":529,"props":1061,"children":1063},{"class":531,"line":1062},9,[1064,1069],{"type":24,"tag":529,"props":1065,"children":1066},{"style":839},[1067],{"type":29,"value":1068},"            return",{"type":24,"tag":529,"props":1070,"children":1071},{"style":690},[1072],{"type":29,"value":1073}," None\n",{"type":24,"tag":529,"props":1075,"children":1077},{"class":531,"line":1076},10,[1078,1083,1088,1093,1098,1102,1106,1111,1116,1121,1126],{"type":24,"tag":529,"props":1079,"children":1080},{"style":851},[1081],{"type":29,"value":1082},"        kw[",{"type":24,"tag":529,"props":1084,"children":1085},{"style":542},[1086],{"type":29,"value":1087},"\"KeyMarker\"",{"type":24,"tag":529,"props":1089,"children":1090},{"style":851},[1091],{"type":29,"value":1092},"], kw[",{"type":24,"tag":529,"props":1094,"children":1095},{"style":542},[1096],{"type":29,"value":1097},"\"UploadIdMarker\"",{"type":24,"tag":529,"props":1099,"children":1100},{"style":851},[1101],{"type":29,"value":998},{"type":24,"tag":529,"props":1103,"children":1104},{"style":839},[1105],{"type":29,"value":868},{"type":24,"tag":529,"props":1107,"children":1108},{"style":851},[1109],{"type":29,"value":1110}," page[",{"type":24,"tag":529,"props":1112,"children":1113},{"style":542},[1114],{"type":29,"value":1115},"\"NextKeyMarker\"",{"type":24,"tag":529,"props":1117,"children":1118},{"style":851},[1119],{"type":29,"value":1120},"], page[",{"type":24,"tag":529,"props":1122,"children":1123},{"style":542},[1124],{"type":29,"value":1125},"\"NextUploadIdMarker\"",{"type":24,"tag":529,"props":1127,"children":1128},{"style":851},[1129],{"type":29,"value":1031},{"type":24,"tag":25,"props":1131,"children":1132},{},[1133,1138,1140,1145,1147,1152],{"type":24,"tag":555,"props":1134,"children":1135},{},[1136],{"type":29,"value":1137},"3. Rebuild the part map from the server, all pages of it.",{"type":29,"value":1139}," Page on ",{"type":24,"tag":59,"props":1141,"children":1143},{"className":1142},[],[1144],{"type":29,"value":210},{"type":29,"value":1146}," until ",{"type":24,"tag":59,"props":1148,"children":1150},{"className":1149},[],[1151],{"type":29,"value":203},{"type":29,"value":1153}," is false.",{"type":24,"tag":25,"props":1155,"children":1156},{},[1157,1162,1164,1169,1171,1176],{"type":24,"tag":555,"props":1158,"children":1159},{},[1160],{"type":29,"value":1161},"4. Send only the gaps, and trust the response over the list.",{"type":29,"value":1163}," Keep the ETag that ",{"type":24,"tag":59,"props":1165,"children":1167},{"className":1166},[],[1168],{"type":29,"value":144},{"type":29,"value":1170}," returns; re-reading ",{"type":24,"tag":59,"props":1172,"children":1174},{"className":1173},[],[1175],{"type":29,"value":64},{"type":29,"value":1177}," to confirm is the call measured above returning stale data.",{"type":24,"tag":519,"props":1179,"children":1181},{"className":827,"code":1180,"language":829,"meta":7,"style":7},"def resume(s3, bucket, key, uid, parts_have, total_parts, body_of):\n    for n in range(1, total_parts + 1):\n        if n in parts_have:\n            continue\n        parts_have[n] = {\"PartNumber\": n, \"ETag\": s3.upload_part(\n            Bucket=bucket, Key=key, UploadId=uid, PartNumber=n, Body=body_of(n))[\"ETag\"]}\n    return [{\"PartNumber\": n, \"ETag\": parts_have[n][\"ETag\"]} for n in sorted(parts_have)]\n",[1182],{"type":24,"tag":59,"props":1183,"children":1184},{"__ignoreMap":7},[1185,1202,1252,1272,1280,1316,1398],{"type":24,"tag":529,"props":1186,"children":1187},{"class":531,"line":532},[1188,1192,1197],{"type":24,"tag":529,"props":1189,"children":1190},{"style":839},[1191],{"type":29,"value":842},{"type":24,"tag":529,"props":1193,"children":1194},{"style":845},[1195],{"type":29,"value":1196}," resume",{"type":24,"tag":529,"props":1198,"children":1199},{"style":851},[1200],{"type":29,"value":1201},"(s3, bucket, key, uid, parts_have, total_parts, body_of):\n",{"type":24,"tag":529,"props":1203,"children":1204},{"class":531,"line":857},[1205,1210,1215,1219,1224,1229,1234,1239,1244,1248],{"type":24,"tag":529,"props":1206,"children":1207},{"style":839},[1208],{"type":29,"value":1209},"    for",{"type":24,"tag":529,"props":1211,"children":1212},{"style":851},[1213],{"type":29,"value":1214}," n ",{"type":24,"tag":529,"props":1216,"children":1217},{"style":839},[1218],{"type":29,"value":959},{"type":24,"tag":529,"props":1220,"children":1221},{"style":690},[1222],{"type":29,"value":1223}," range",{"type":24,"tag":529,"props":1225,"children":1226},{"style":851},[1227],{"type":29,"value":1228},"(",{"type":24,"tag":529,"props":1230,"children":1231},{"style":690},[1232],{"type":29,"value":1233},"1",{"type":24,"tag":529,"props":1235,"children":1236},{"style":851},[1237],{"type":29,"value":1238},", total_parts ",{"type":24,"tag":529,"props":1240,"children":1241},{"style":839},[1242],{"type":29,"value":1243},"+",{"type":24,"tag":529,"props":1245,"children":1246},{"style":690},[1247],{"type":29,"value":394},{"type":24,"tag":529,"props":1249,"children":1250},{"style":851},[1251],{"type":29,"value":1059},{"type":24,"tag":529,"props":1253,"children":1254},{"class":531,"line":896},[1255,1259,1263,1267],{"type":24,"tag":529,"props":1256,"children":1257},{"style":839},[1258],{"type":29,"value":1040},{"type":24,"tag":529,"props":1260,"children":1261},{"style":851},[1262],{"type":29,"value":1214},{"type":24,"tag":529,"props":1264,"children":1265},{"style":839},[1266],{"type":29,"value":959},{"type":24,"tag":529,"props":1268,"children":1269},{"style":851},[1270],{"type":29,"value":1271}," parts_have:\n",{"type":24,"tag":529,"props":1273,"children":1274},{"class":531,"line":915},[1275],{"type":24,"tag":529,"props":1276,"children":1277},{"style":839},[1278],{"type":29,"value":1279},"            continue\n",{"type":24,"tag":529,"props":1281,"children":1282},{"class":531,"line":943},[1283,1288,1292,1296,1301,1306,1311],{"type":24,"tag":529,"props":1284,"children":1285},{"style":851},[1286],{"type":29,"value":1287},"        parts_have[n] ",{"type":24,"tag":529,"props":1289,"children":1290},{"style":839},[1291],{"type":29,"value":868},{"type":24,"tag":529,"props":1293,"children":1294},{"style":851},[1295],{"type":29,"value":873},{"type":24,"tag":529,"props":1297,"children":1298},{"style":542},[1299],{"type":29,"value":1300},"\"PartNumber\"",{"type":24,"tag":529,"props":1302,"children":1303},{"style":851},[1304],{"type":29,"value":1305},": n, ",{"type":24,"tag":529,"props":1307,"children":1308},{"style":542},[1309],{"type":29,"value":1310},"\"ETag\"",{"type":24,"tag":529,"props":1312,"children":1313},{"style":851},[1314],{"type":29,"value":1315},": s3.upload_part(\n",{"type":24,"tag":529,"props":1317,"children":1318},{"class":531,"line":977},[1319,1324,1328,1333,1338,1342,1347,1352,1356,1361,1366,1370,1375,1380,1384,1389,1393],{"type":24,"tag":529,"props":1320,"children":1321},{"style":536},[1322],{"type":29,"value":1323},"            Bucket",{"type":24,"tag":529,"props":1325,"children":1326},{"style":839},[1327],{"type":29,"value":868},{"type":24,"tag":529,"props":1329,"children":1330},{"style":851},[1331],{"type":29,"value":1332},"bucket, ",{"type":24,"tag":529,"props":1334,"children":1335},{"style":536},[1336],{"type":29,"value":1337},"Key",{"type":24,"tag":529,"props":1339,"children":1340},{"style":839},[1341],{"type":29,"value":868},{"type":24,"tag":529,"props":1343,"children":1344},{"style":851},[1345],{"type":29,"value":1346},"key, ",{"type":24,"tag":529,"props":1348,"children":1349},{"style":536},[1350],{"type":29,"value":1351},"UploadId",{"type":24,"tag":529,"props":1353,"children":1354},{"style":839},[1355],{"type":29,"value":868},{"type":24,"tag":529,"props":1357,"children":1358},{"style":851},[1359],{"type":29,"value":1360},"uid, ",{"type":24,"tag":529,"props":1362,"children":1363},{"style":536},[1364],{"type":29,"value":1365},"PartNumber",{"type":24,"tag":529,"props":1367,"children":1368},{"style":839},[1369],{"type":29,"value":868},{"type":24,"tag":529,"props":1371,"children":1372},{"style":851},[1373],{"type":29,"value":1374},"n, ",{"type":24,"tag":529,"props":1376,"children":1377},{"style":536},[1378],{"type":29,"value":1379},"Body",{"type":24,"tag":529,"props":1381,"children":1382},{"style":839},[1383],{"type":29,"value":868},{"type":24,"tag":529,"props":1385,"children":1386},{"style":851},[1387],{"type":29,"value":1388},"body_of(n))[",{"type":24,"tag":529,"props":1390,"children":1391},{"style":542},[1392],{"type":29,"value":1310},{"type":24,"tag":529,"props":1394,"children":1395},{"style":851},[1396],{"type":29,"value":1397},"]}\n",{"type":24,"tag":529,"props":1399,"children":1400},{"class":531,"line":1011},[1401,1406,1411,1415,1419,1423,1428,1432,1437,1442,1446,1450,1455],{"type":24,"tag":529,"props":1402,"children":1403},{"style":839},[1404],{"type":29,"value":1405},"    return",{"type":24,"tag":529,"props":1407,"children":1408},{"style":851},[1409],{"type":29,"value":1410}," [{",{"type":24,"tag":529,"props":1412,"children":1413},{"style":542},[1414],{"type":29,"value":1300},{"type":24,"tag":529,"props":1416,"children":1417},{"style":851},[1418],{"type":29,"value":1305},{"type":24,"tag":529,"props":1420,"children":1421},{"style":542},[1422],{"type":29,"value":1310},{"type":24,"tag":529,"props":1424,"children":1425},{"style":851},[1426],{"type":29,"value":1427},": parts_have[n][",{"type":24,"tag":529,"props":1429,"children":1430},{"style":542},[1431],{"type":29,"value":1310},{"type":24,"tag":529,"props":1433,"children":1434},{"style":851},[1435],{"type":29,"value":1436},"]} ",{"type":24,"tag":529,"props":1438,"children":1439},{"style":839},[1440],{"type":29,"value":1441},"for",{"type":24,"tag":529,"props":1443,"children":1444},{"style":851},[1445],{"type":29,"value":1214},{"type":24,"tag":529,"props":1447,"children":1448},{"style":839},[1449],{"type":29,"value":959},{"type":24,"tag":529,"props":1451,"children":1452},{"style":690},[1453],{"type":29,"value":1454}," sorted",{"type":24,"tag":529,"props":1456,"children":1457},{"style":851},[1458],{"type":29,"value":1459},"(parts_have)]\n",{"type":24,"tag":25,"props":1461,"children":1462},{},[1463,1468,1470,1476,1478,1484,1486,1492],{"type":24,"tag":555,"props":1464,"children":1465},{},[1466],{"type":29,"value":1467},"5. Complete in ascending order, abort when you mean it, keep a backstop.",{"type":29,"value":1469}," ",{"type":24,"tag":59,"props":1471,"children":1473},{"className":1472},[],[1474],{"type":29,"value":1475},"sorted()",{"type":29,"value":1477}," is the whole ordering rule. On give-up, ",{"type":24,"tag":59,"props":1479,"children":1481},{"className":1480},[],[1482],{"type":29,"value":1483},"abort_multipart_upload",{"type":29,"value":1485}," frees the parts and their bill; for crashes nobody handles, add ",{"type":24,"tag":59,"props":1487,"children":1489},{"className":1488},[],[1490],{"type":29,"value":1491},"AbortIncompleteMultipartUpload",{"type":29,"value":1493}," with a day count longer than the longest plausible restart window — the AWS example uses 7.",{"type":24,"tag":32,"props":1495,"children":1497},{"id":1496},"how-s3-explorer-fits",[1498],{"type":29,"value":1499},"How S3 Explorer fits",{"type":24,"tag":25,"props":1501,"children":1502},{},[1503,1505,1510,1512,1517,1519,1524,1526,1532,1533,1539,1540,1546,1548,1553,1555,1563],{"type":29,"value":1504},"A resume needs three things from the endpoint before any code path is worth debugging: credentials that can list, the right bucket, and the number of calls the recovery cost. S3 Explorer's ",{"type":24,"tag":555,"props":1506,"children":1507},{},[1508],{"type":29,"value":1509},"test connection",{"type":29,"value":1511}," reports how many buckets those credentials actually see before a profile is saved, ",{"type":24,"tag":555,"props":1513,"children":1514},{},[1515],{"type":29,"value":1516},"bucket list and folder navigation",{"type":29,"value":1518}," open the target key without a CLI guess, and the ",{"type":24,"tag":555,"props":1520,"children":1521},{},[1522],{"type":29,"value":1523},"per-request metrics",{"type":29,"value":1525}," view counts the ",{"type":24,"tag":59,"props":1527,"children":1529},{"className":1528},[],[1530],{"type":29,"value":1531},"LIST",{"type":29,"value":462},{"type":24,"tag":59,"props":1534,"children":1536},{"className":1535},[],[1537],{"type":29,"value":1538},"GET",{"type":29,"value":96},{"type":24,"tag":59,"props":1541,"children":1543},{"className":1542},[],[1544],{"type":29,"value":1545},"PUT",{"type":29,"value":1547}," calls a session generated — the number that shows whether a recovery loop is paging or hammering. Its ",{"type":24,"tag":555,"props":1549,"children":1550},{},[1551],{"type":29,"value":1552},"provider picker",{"type":29,"value":1554}," carries 25 presets plus a Custom tile, so AWS and a self-hosted endpoint can be compared in one window. ",{"type":24,"tag":219,"props":1556,"children":1560},{"href":1557,"rel":1558},"https:\u002F\u002Fs3explorer.accessdevops.com",[1559],"nofollow",[1561],{"type":29,"value":1562},"S3 Explorer",{"type":29,"value":1564}," runs on Windows, macOS and Linux.",{"type":24,"tag":32,"props":1566,"children":1568},{"id":1567},"checklist",[1569],{"type":29,"value":1570},"Checklist",{"type":24,"tag":39,"props":1572,"children":1575},{"className":1573},[1574],"contains-task-list",[1576,1595,1619,1647,1656,1672,1687,1703],{"type":24,"tag":43,"props":1577,"children":1580},{"className":1578},[1579],"task-list-item",[1581,1587,1589,1594],{"type":24,"tag":1582,"props":1583,"children":1586},"input",{"disabled":1584,"type":1585},true,"checkbox",[],{"type":29,"value":1588}," The upload ID reaches durable storage before the first ",{"type":24,"tag":59,"props":1590,"children":1592},{"className":1591},[],[1593],{"type":29,"value":144},{"type":29,"value":791},{"type":24,"tag":43,"props":1596,"children":1598},{"className":1597},[1579],[1599,1602,1604,1610,1612,1618],{"type":24,"tag":1582,"props":1600,"children":1601},{"disabled":1584,"type":1585},[],{"type":29,"value":1603}," Recovery finds the upload by prefix and follows ",{"type":24,"tag":59,"props":1605,"children":1607},{"className":1606},[],[1608],{"type":29,"value":1609},"NextKeyMarker",{"type":29,"value":1611}," with ",{"type":24,"tag":59,"props":1613,"children":1615},{"className":1614},[],[1616],{"type":29,"value":1617},"NextUploadIdMarker",{"type":29,"value":791},{"type":24,"tag":43,"props":1620,"children":1622},{"className":1621},[1579],[1623,1626,1627,1632,1634,1640,1641,1646],{"type":24,"tag":1582,"props":1624,"children":1625},{"disabled":1584,"type":1585},[],{"type":29,"value":1469},{"type":24,"tag":59,"props":1628,"children":1630},{"className":1629},[],[1631],{"type":29,"value":64},{"type":29,"value":1633}," is paged on ",{"type":24,"tag":59,"props":1635,"children":1637},{"className":1636},[],[1638],{"type":29,"value":1639},"PartNumberMarker",{"type":29,"value":1146},{"type":24,"tag":59,"props":1642,"children":1644},{"className":1643},[],[1645],{"type":29,"value":203},{"type":29,"value":1153},{"type":24,"tag":43,"props":1648,"children":1650},{"className":1649},[1579],[1651,1654],{"type":24,"tag":1582,"props":1652,"children":1653},{"disabled":1584,"type":1585},[],{"type":29,"value":1655}," Completion uses one merged map, sorted ascending, never the first page alone.",{"type":24,"tag":43,"props":1657,"children":1659},{"className":1658},[1579],[1660,1663,1665,1670],{"type":24,"tag":1582,"props":1661,"children":1662},{"disabled":1584,"type":1585},[],{"type":29,"value":1664}," A re-sent part completes with the ETag from that ",{"type":24,"tag":59,"props":1666,"children":1668},{"className":1667},[],[1669],{"type":29,"value":144},{"type":29,"value":1671}," response, not a re-read.",{"type":24,"tag":43,"props":1673,"children":1675},{"className":1674},[1579],[1676,1679,1680,1685],{"type":24,"tag":1582,"props":1677,"children":1678},{"disabled":1584,"type":1585},[],{"type":29,"value":1469},{"type":24,"tag":59,"props":1681,"children":1683},{"className":1682},[],[1684],{"type":29,"value":629},{"type":29,"value":1686}," on the first recovery call is handled as \"gone, start over\", not alerted on.",{"type":24,"tag":43,"props":1688,"children":1690},{"className":1689},[1579],[1691,1694,1696,1701],{"type":24,"tag":1582,"props":1692,"children":1693},{"disabled":1584,"type":1585},[],{"type":29,"value":1695}," No retry loop on 5xx without a ",{"type":24,"tag":59,"props":1697,"children":1699},{"className":1698},[],[1700],{"type":29,"value":651},{"type":29,"value":1702}," on the key first.",{"type":24,"tag":43,"props":1704,"children":1706},{"className":1705},[1579],[1707,1710,1712,1717],{"type":24,"tag":1582,"props":1708,"children":1709},{"disabled":1584,"type":1585},[],{"type":29,"value":1711}," A lifecycle ",{"type":24,"tag":59,"props":1713,"children":1715},{"className":1714},[],[1716],{"type":29,"value":1491},{"type":29,"value":1718}," rule exists, with a day count longer than the restart window.",{"type":24,"tag":32,"props":1720,"children":1722},{"id":1721},"sources",[1723],{"type":29,"value":1724},"Sources",{"type":24,"tag":39,"props":1726,"children":1727},{},[1728,1759,1777,1809,1836],{"type":24,"tag":43,"props":1729,"children":1730},{},[1731,1738,1740,1745,1746,1751,1753,1758],{"type":24,"tag":219,"props":1732,"children":1735},{"href":1733,"rel":1734},"https:\u002F\u002Fdocs.aws.amazon.com\u002FAmazonS3\u002Flatest\u002Fuserguide\u002Fmpuoverview.html",[1559],[1736],{"type":29,"value":1737},"Amazon S3 — multipart upload overview",{"type":29,"value":1739},": \"no expiry\" for upload IDs, the ETag recording requirement, the same-part-number overwrite rule, ",{"type":24,"tag":59,"props":1741,"children":1743},{"className":1742},[],[1744],{"type":29,"value":94},{"type":29,"value":96},{"type":24,"tag":59,"props":1747,"children":1749},{"className":1748},[],[1750],{"type":29,"value":102},{"type":29,"value":1752},", conditional writes on ",{"type":24,"tag":59,"props":1754,"children":1756},{"className":1755},[],[1757],{"type":29,"value":152},{"type":29,"value":791},{"type":24,"tag":43,"props":1760,"children":1761},{},[1762,1768,1769,1775],{"type":24,"tag":219,"props":1763,"children":1766},{"href":1764,"rel":1765},"https:\u002F\u002Fdocs.aws.amazon.com\u002FAmazonS3\u002Flatest\u002FAPI\u002FAPI_ListParts.html",[1559],[1767],{"type":29,"value":64},{"type":29,"value":96},{"type":24,"tag":219,"props":1770,"children":1773},{"href":1771,"rel":1772},"https:\u002F\u002Fdocs.aws.amazon.com\u002FAmazonS3\u002Flatest\u002FAPI\u002FAPI_ListMultipartUploads.html",[1559],[1774],{"type":29,"value":165},{"type":29,"value":1776},": 1,000 per response, and the pagination fields.",{"type":24,"tag":43,"props":1778,"children":1779},{},[1780,1787,1789,1794,1796,1801,1802,1807],{"type":24,"tag":219,"props":1781,"children":1784},{"href":1782,"rel":1783},"https:\u002F\u002Fdocs.aws.amazon.com\u002FAmazonS3\u002Flatest\u002FAPI\u002FAPI_Error.html",[1559],[1785],{"type":29,"value":1786},"Amazon S3 — error code catalogue",{"type":29,"value":1788},": ",{"type":24,"tag":59,"props":1790,"children":1792},{"className":1791},[],[1793],{"type":29,"value":629},{"type":29,"value":1795}," (404), ",{"type":24,"tag":59,"props":1797,"children":1799},{"className":1798},[],[1800],{"type":29,"value":604},{"type":29,"value":462},{"type":24,"tag":59,"props":1803,"children":1805},{"className":1804},[],[1806],{"type":29,"value":594},{"type":29,"value":1808}," (400).",{"type":24,"tag":43,"props":1810,"children":1811},{},[1812,1819,1820,1834],{"type":24,"tag":219,"props":1813,"children":1816},{"href":1814,"rel":1815},"https:\u002F\u002Fdocs.aws.amazon.com\u002FAmazonS3\u002Flatest\u002Fuserguide\u002Fabort-mpu.html",[1559],[1817],{"type":29,"value":1818},"Aborting a multipart upload",{"type":29,"value":96},{"type":24,"tag":219,"props":1821,"children":1824},{"href":1822,"rel":1823},"https:\u002F\u002Fdocs.aws.amazon.com\u002FAmazonS3\u002Flatest\u002Fuserguide\u002Fmpu-abort-incomplete-mpu-lifecycle-config.html",[1559],[1825,1827,1832],{"type":29,"value":1826},"the ",{"type":24,"tag":59,"props":1828,"children":1830},{"className":1829},[],[1831],{"type":29,"value":1491},{"type":29,"value":1833}," rule",{"type":29,"value":1835},": parts are billed until aborted; the 7-day example.",{"type":24,"tag":43,"props":1837,"children":1838},{},[1839,1841,1846,1848,1854,1856,1862,1863,1869],{"type":29,"value":1840},"Measured 13 November 2026 with ",{"type":24,"tag":59,"props":1842,"children":1844},{"className":1843},[],[1845],{"type":29,"value":288},{"type":29,"value":1847}," 1.44.2 against SeaweedFS 4.50 (",{"type":24,"tag":59,"props":1849,"children":1851},{"className":1850},[],[1852],{"type":29,"value":1853},"weed server -s3",{"type":29,"value":1855},") and moto 5.2.5, using ",{"type":24,"tag":59,"props":1857,"children":1859},{"className":1858},[],[1860],{"type":29,"value":1861},"scripts\u002Fresume-probe.py",{"type":29,"value":96},{"type":24,"tag":59,"props":1864,"children":1866},{"className":1865},[],[1867],{"type":29,"value":1868},"scripts\u002Fresume-overwrite-probe.py",{"type":29,"value":1870}," in this repository — both print the exact code and message the endpoint returned.",{"type":24,"tag":1872,"props":1873,"children":1874},"style",{},[1875],{"type":29,"value":1876},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":7,"searchDepth":857,"depth":857,"links":1878},[1879,1880,1881,1882,1883,1884,1885],{"id":34,"depth":857,"text":37},{"id":122,"depth":857,"text":125},{"id":274,"depth":857,"text":277},{"id":794,"depth":857,"text":797},{"id":1496,"depth":857,"text":1499},{"id":1567,"depth":857,"text":1570},{"id":1721,"depth":857,"text":1724},"markdown","content:blog:resume-interrupted-multipart-upload.md","content","blog\u002Fresume-interrupted-multipart-upload.md","blog\u002Fresume-interrupted-multipart-upload","md",[1893,1901,1908,1914,1921,1926,1930,1935,1941,1946,1948,1956,1963,1969],{"_path":1894,"title":1895,"description":1896,"tags":1897},"\u002Fblog\u002Fabac-object-tags-vs-prefix-policies","Object tags or prefixes: which access model survives growth","Prefixes decide what a principal can list, tags decide what it can read. Two AWS limits break tag-only designs, and prefix policies run out at about 60 tenants.",[13,1898,1899,1900],"iam","governance","access-control",{"_path":1902,"title":1903,"description":1904,"tags":1905},"\u002Fblog\u002Fassumerole-chaining-one-hour-ceiling","Chained AssumeRole sessions stop at one hour, silently","A role that assumes another role gets 3600 seconds, whatever its MaxSessionDuration allows. The exact error, and the batch job that dies at minute 61.",[13,1898,1906,1907],"sts","aws",{"_path":1909,"title":1910,"description":1911,"tags":1912},"\u002Fblog\u002Fbucket-policy-allow-still-denied","When two policies allow and the request still fails","S3 returns AccessDenied even when the bucket and IAM policies both allow. Here is the five-gate evaluation order and where the hidden deny sits.",[13,1898,1913,1900],"security",{"_path":1915,"title":1916,"description":1917,"tags":1918},"\u002Fblog\u002Fcrc64nvme-new-integrity-default","CRC64NVME is now the S3 default, your SDK sends CRC32","S3 stores a CRC64NVME checksum on every new object, while the CLI and the SDKs default to different algorithms. Measured on a live endpoint.",[13,1919,1920],"integrity","api",{"_path":221,"title":1922,"description":1923,"tags":1924},"S3 LIST returns 1,000 entries a page, folders included","One ListObjectsV2 response holds at most 1,000 entries, and a folder rollup spends one of them. Measured on two endpoints, plus the loop that stops too early.",[13,1920,1925],"performance",{"_path":786,"title":1927,"description":1928,"tags":1929},"Multipart upload: the 5 MiB floor and the 10,000-part ceiling","A single PUT stops at 5 GiB. Splitting a 60 GB file moves the failure rather than removing it: the 5 MiB part floor is checked last, after every byte is sent.",[13,14,15],{"_path":1931,"title":1932,"description":1933,"tags":1934},"\u002Fblog\u002Fpresigned-post-enforce-size-content-type","Presigned POST: cap size and type before the upload starts","A presigned PUT signs a URL, not a byte count. A POST policy adds a size range and a content-type condition that S3 enforces while the body arrives.",[13,1913,14],{"_path":1936,"title":1937,"description":1938,"tags":1939},"\u002Fblog\u002Fpresigned-url-leakage","Presigned URLs leak into logs, referrers and chat","A presigned URL is a working credential stored as text. Where the copy ends up, what it still grants after redaction, and the 15-minute fix.",[13,1913,1940],"presigned-urls",{"_path":1942,"title":1943,"description":1944,"tags":1945},"\u002Fblog\u002Fpresigned-url-seven-day-ceiling","Presigned URLs expire at seven days. Then what?","Seven days is not a setting: it is how long the SigV4 signing key lives. What happens at that wall, and the three patterns that outlive it.",[13,1913,1940],{"_path":4,"title":8,"description":9,"tags":1947},[13,14,15],{"_path":1949,"title":1950,"description":1951,"tags":1952},"\u002Fblog\u002Fs3-explorer-walkthrough","S3 Explorer walkthrough: zero to browsing in 5 minutes","A step-by-step walkthrough of S3 Explorer against a live S3 endpoint: pick a provider, test, browse buckets, inspect metadata, read request metrics.",[13,1953,1954,1955],"tutorial","tooling","seaweedfs",{"_path":1957,"title":1958,"description":1959,"tags":1960},"\u002Fblog\u002Fs3-in-2027-egress-bill-changes","S3 in 2027: the egress bill changes on 12 January","The EU Data Act removes switching fees in January 2027 — but not everyday egress. The five things you need to know about your buckets before that date.",[13,1961,1899,1962],"pricing","cloud",{"_path":1964,"title":1965,"description":1966,"tags":1967},"\u002Fblog\u002Fs3-total-cost-of-ownership","The real cost of S3: 8 providers compared, egress included","Storage is 20% of your bill. We modelled 8 S3 providers across 4 workloads — backup, app, media, DR — with storage, egress, requests and replication.",[13,1961,1962,1968],"cost-optimisation",{"_path":1970,"title":1971,"description":1972,"tags":1973},"\u002Fblog\u002Fwelcome","Why we write about S3, and how this blog is made","Object storage is everywhere and most advice about it is stale. What this blog covers, how each article is sourced, and what we measure to know it helped.",[13,1962],1794568659400]