Symptom
Backup restore fails on devices with a real content library (multi-GB backups) even though the same operation works on a small demo device. Because the failure mode is an OOM kill / request timeout, it shows up as "restore doesn't work" reports rather than a clean Sentry stack.
Root cause 1 — restore loads the whole upload into RAM (fixed here)
RecoverViewMixin.post (the /api/v1|v2/recover endpoint) staged the uploaded archive with:
with open(location, 'wb') as f:
f.write(file_upload.read()) # entire archive into memory
A backup is every image + video asset on the device, so file_upload.read() allocates a multi-GB bytes object and OOM-kills the worker on a 1 GB Pi. The HTML recover view (settings_recover) was already fixed to stream via file_upload.chunks(); the API path was left behind. PR streams the API path the same way.
Root cause 2 — restore is synchronous and can time out (follow-up)
Even with streaming, recover() extracts the whole archive inside the request handler. Extracting a multi-GB library to a slow SD card can exceed the request/proxy timeout — the same failure the download side hit and solved by streaming (#2987) / async iteration (#3073). The restore equivalent would be to move extraction to a background Celery task with progress, or at least confirm the request timeout is generous enough. Not addressed here.
Root cause 3 — no ENOSPC handling on restore (follow-up)
A large restore that fills the SD card (staging the upload, or during extraction) raises an uncaught OSError → 500. Reusing the is_disk_full helper (added in #3133) to return an actionable "disk full" message would make this a clean operator error instead of a crash.
Note on the download/generate side
Already hardened: stream_backup / astream_backup stream the tar.gz with a flat memory footprint and no SD staging (#2987, #3073), and gzip level was dropped to 1 to keep generation within the browser timeout. The remaining API create_backup path still stages the whole archive to disk before returning a filename, which needs free SD space — worth revisiting but lower impact than the restore path.
Symptom
Backup restore fails on devices with a real content library (multi-GB backups) even though the same operation works on a small demo device. Because the failure mode is an OOM kill / request timeout, it shows up as "restore doesn't work" reports rather than a clean Sentry stack.
Root cause 1 — restore loads the whole upload into RAM (fixed here)
RecoverViewMixin.post(the/api/v1|v2/recoverendpoint) staged the uploaded archive with:A backup is every image + video asset on the device, so
file_upload.read()allocates a multi-GBbytesobject and OOM-kills the worker on a 1 GB Pi. The HTML recover view (settings_recover) was already fixed to stream viafile_upload.chunks(); the API path was left behind. PR streams the API path the same way.Root cause 2 — restore is synchronous and can time out (follow-up)
Even with streaming,
recover()extracts the whole archive inside the request handler. Extracting a multi-GB library to a slow SD card can exceed the request/proxy timeout — the same failure the download side hit and solved by streaming (#2987) / async iteration (#3073). The restore equivalent would be to move extraction to a background Celery task with progress, or at least confirm the request timeout is generous enough. Not addressed here.Root cause 3 — no ENOSPC handling on restore (follow-up)
A large restore that fills the SD card (staging the upload, or during extraction) raises an uncaught
OSError→ 500. Reusing theis_disk_fullhelper (added in #3133) to return an actionable "disk full" message would make this a clean operator error instead of a crash.Note on the download/generate side
Already hardened:
stream_backup/astream_backupstream the tar.gz with a flat memory footprint and no SD staging (#2987, #3073), and gzip level was dropped to 1 to keep generation within the browser timeout. The remaining APIcreate_backuppath still stages the whole archive to disk before returning a filename, which needs free SD space — worth revisiting but lower impact than the restore path.