# Backup Interviews: The One Detail That Separates Seniors—Resumable, Chunked Uploads

<img src="https://bugfree-s3.s3.amazonaws.com/mermaid_diagrams/image_1770228971239.png" alt="Resumable Chunked Uploads" width="700" style="max-width:100%;height:auto" />

# Backup Interviews: The One Detail That Separates Seniors—Resumable, Chunked Uploads

In cloud backup systems the simplest reliability lever is also the one interviewers love to ask about: resumable, chunked uploads. Backups regularly fail mid-transfer — Wi‑Fi drops, laptops sleep, cellular blips. If you upload whole files atomically, you often restart from zero and miss backup windows. Splitting data into chunks and making uploads resumable changes the game.

Below is a practical, interview-friendly design pattern you can explain and defend.

## Problem

- Long transfers are brittle: network interruptions, client restarts, and transient server errors will abort uploads.
- Re-uploading entire files wastes time, bandwidth, and energy, and increases collision with backup windows.

## High-level solution

1. Split files into fixed-size chunks.
2. Hash each chunk and use an idempotent API like `putChunk(backupId, chunkIndex, checksum)`.
3. Persist progress (which chunks are stored) in per-backup metadata so the client can resume exactly where it stopped.
4. Add exponential backoff, integrity checks, and duplicate handling on the server.

This combination makes uploads resumable, verifiable, and efficient.

## Core components

### 1) Chunking

- Use fixed-size chunks (e.g., 4–16 MiB). Fixed size simplifies indexing and retry logic. Consider variable chunking only for dedup-heavy designs.
- Compute a checksum (SHA-256 or a strong hash) per chunk. This enables integrity checks and idempotency.

Trade-offs:
- Small chunks: better resume granularity, larger index/metadata.
- Large chunks: fewer round trips, worse restart cost on failure.

### 2) Idempotent chunk upload API

Provide a server API like:

```text
putChunk(backupId, chunkIndex, checksum, data)
```

Server behavior:
- If chunkIndex already stored with the same checksum: return success (idempotent).
- If stored with different checksum: reject (checksum mismatch) to avoid corruption.
- If not present: store chunk and mark it in metadata.

This avoids duplicate work and ensures safe retries.

### 3) Persisted progress (metadata)

Keep a per-backup metadata object that tracks which chunk indices are already accepted. Minimal metadata schema:

```json
{
  "backupId": "...",
  "fileId": "...",
  "chunkSize": 4194304,
  "chunks": {
    "0": "sha256:...",
    "1": "sha256:...",
    "4": "sha256:..."
  },
  "status": "in_progress"
}
```

The client can query this metadata and resume uploading only missing chunks. Persist metadata atomically (or use a compare-and-set) so progress is never lost.

### 4) Retries and backoff

- Use exponential backoff with jitter for transient errors.
- On client restart, re-check metadata and only upload missing chunks.
- Limit retry budget per chunk to avoid infinite loops.

### 5) Integrity and verification

- Server verifies chunk checksum on receive; reject corrupted uploads.
- Optionally: server computes its own checksum and cross-checks client-supplied checksum.
- After all chunks uploaded, perform a final composition step that verifies the assembled file hash matches the expected file hash (if provided).

## Example resumable upload flow (client)

1. Split file into chunkCount chunks and compute checksums.
2. Request or create `backupId` and read metadata about already-uploaded chunks.
3. For each missing chunk:
   - `putChunk(backupId, chunkIndex, checksum, data)` with retries/backoff
   - On success, record progress locally or rely on server metadata
4. When all chunks present, call `finalizeBackup(backupId, expectedDigest)` which triggers server-side verification and composition.

Pseudocode:

```python
for i, chunk in enumerate(chunks):
    if server_has_chunk(backupId, i):
        continue
    attempt = 0
    while attempt < MAX_ATTEMPTS:
        try:
            putChunk(backupId, i, checksum(chunk), chunk)
            break
        except TransientError:
            sleep(exponential_backoff(attempt))
            attempt += 1
    if attempt == MAX_ATTEMPTS:
        raise UploadFailed
finalizeBackup(backupId, fileChecksum)
```

## Server-side considerations

- Atomic metadata updates: use compare-and-swap to avoid races when multiple clients upload the same backup.
- Garbage collection: remove orphaned chunks after a timeout or when a backup is abandoned.
- Authorization: ensure clients can only write chunks for their backups.
- Storage layout: store chunks keyed by (backupId, chunkIndex) or by checksum (content-addressed) to enable deduplication.
- Concurrency: allow parallel chunk uploads to speed up large backups; throttle to control IO.

## Additional enhancements

- Content-addressed storage: store chunks by checksum to deduplicate across backups and users (watch multi-tenant privacy/legal constraints).
- Client-side encryption: encrypt chunks before upload; store per-backup metadata for decryption keys (or use zero-knowledge patterns).
- Partial restores: let clients request ranges or subsets of chunks for faster restores.

## Interview-ready takeaway

Reliability in backups is not a single promise you make to users — it's a protocol you design and implement: chunk, hash, idempotent put, persist progress, retry with backoff, and verify. Explain the trade-offs (chunk size, metadata complexity, concurrency) and you've shown the mentality of a senior engineer: think beyond "it works once" to "it recovers gracefully."

#CloudComputing #SystemDesign #DevOps
