Backend ArchitectureAugust 20, 20268 min read

Building a Resilient 5GB Chunked File Upload and Memory-Optimized PDF Pipeline in Django

Nguyen Dai Long

Nguyen Dai Long

Backend Lead & Software Engineer

Allowing users to upload large files—such as high-resolution training videos or multi-gigabyte document archives—poses severe operational risks for cloud containers. Traditional monolithic HTTP uploads fail frequently due to transient network drops, exhaust web worker concurrency, and trigger Out-Of-Memory (OOM) kills. Here is how we designed a robust chunked upload mechanism and memory-safe PDF processing pipeline.

1. The Anatomy of Chunked File Uploads

Instead of streaming a 5GB file in a single HTTP request, the client splits the file into uniform 5MB binary chunks using the browser's File.slice() API. Each chunk is dispatched independently with metadata including chunk index, total chunks, and a unique upload session identifier. Failed chunks are automatically retried up to three times with exponential backoff without having to restart the entire upload.

pythonndlong.site
# Django view handling atomic chunk ingestion
class ChunkedUploadView(APIView):
    def post(self, request):
        upload_id = request.data.get('upload_id')
        chunk_index = int(request.data.get('chunk_index'))
        total_chunks = int(request.data.get('total_chunks'))
        file_obj = request.FILES.get('file')

        temp_dir = os.path.join(settings.TEMP_UPLOAD_DIR, upload_id)
        os.makedirs(temp_dir, exist_ok=True)
        chunk_path = os.path.join(temp_dir, f"chunk_{chunk_index:04d}")

        with open(chunk_path, 'wb+') as destination:
            for chunk in file_obj.chunks():
                destination.write(chunk)

        if len(os.listdir(temp_dir)) == total_chunks:
            # Trigger asynchronous background assembly task
            assemble_chunks_task.delay(upload_id, total_chunks)
            return Response({'status': 'assembling'})

        return Response({'status': 'chunk_received', 'index': chunk_index})

Key Implementation Takeaways:

  • Chunk size of 5MB-10MB offers optimal balance between network overhead and retry efficiency.
  • Use temporary local scratch directories with deterministic zero-padded filenames.
  • Delegate final file assembly to Celery/background workers to keep HTTP responses instantaneous.

2. Streaming Assembly and Checksum Verification

When all chunks arrive, assembling them must not load the entire multi-gigabyte file into memory. We stream chunks sequentially into the final destination file in 64KB buffers while computing a running SHA-256 checksum. If the assembled checksum matches the client-provided checksum, the file is uploaded directly to cloud object storage (AWS S3 or GCP Cloud Storage) and local temporary artifacts are purged.

pythonndlong.site
import hashlib

def assemble_file(upload_id: str, total_chunks: int, output_path: str):
    hasher = hashlib.sha256()
    with open(output_path, 'wb') as outfile:
        for idx in range(total_chunks):
            chunk_file = os.path.join(settings.TEMP_UPLOAD_DIR, upload_id, f"chunk_{idx:04d}")
            with open(chunk_file, 'rb') as infile:
                while chunk := infile.read(65536):
                    outfile.write(chunk)
                    hasher.update(chunk)
            os.remove(chunk_file) # Clean up eagerly
    return hasher.hexdigest()

Key Implementation Takeaways:

  • Read and write in fixed 64KB buffers to keep process RAM usage under 30MB regardless of file size.
  • Clean up chunk files eagerly during assembly to prevent disk space exhaustion.
  • Verify cryptographic checksums before promoting files to permanent cloud storage.

3. Memory-Safe PDF Generation in Constrained Docker Containers

Generating comprehensive certification and reporting PDFs with hundreds of pages can quickly exceed the 512MB RAM ceiling of cloud container instances. By utilizing Python generators and streaming HTTP responses rather than accumulating huge strings in memory, we maintained container memory usage below 180MB at all times.

Key Implementation Takeaways:

  • Avoid loading entire document trees into memory when rendering large PDFs.
  • Use temporary disk-backed spooling for image-heavy document rendering.
  • Set strict memory limits in Docker compose and monitor with Prometheus.

Summary & Final Thoughts

Resilient systems anticipate network volatility. Implementing chunked uploads and buffer-based streaming pipelines completely eliminated file upload timeouts, reduced server memory footprints, and created an uninterrupted user experience even on unstable mobile connections.

#Python#Django#File Upload#Memory Optimization#Docker
Nguyen Dai Long

Written by Nguyen Dai Long

Backend Engineer & Backend Lead with 4+ years of hands-on experience building production systems, RESTful APIs, and cloud infrastructure using Python (Django), Laravel, PostgreSQL, and Google Cloud Platform.