Scenario advanced

Optimising a Slow Data Processing Pipeline

Reduce a 10-million-row CSV batch job from 4 hours to under 30 minutes using chunked bulk inserts and multiprocessing.

2 min read ~15 min to complete
Steps
2 Services Used
~15 min Duration
Advanced Difficulty
The Situation

Senior Python developer / backend engineering interviews

Context: A batch job that processes 10 million CSV rows runs for 4 hours. Each row is parsed, validated, transformed, and inserted into a database. How would you reduce the run time to under 30 minutes?

# Current slow approach
import csv
import psycopg2

conn = psycopg2.connect(...)
cur = conn.cursor()

with open("data.csv") as f:
    reader = csv.DictReader(f)
    for row in reader:                    # 10M iterations
        validated = validate(row)
        transformed = transform(validated)
        cur.execute(                       # 10M individual INSERT statements!
            "INSERT INTO records VALUES (%s, %s, %s)",
            (transformed["id"], transformed["name"], transformed["value"])
        )
    conn.commit()
# Optimised approach — chunked bulk insert + multiprocessing

import csv
import psycopg2
from psycopg2.extras import execute_values
from concurrent.futures import ProcessPoolExecutor
import itertools

def process_chunk(rows: list[dict]) -> list[tuple]:
    """Validate and transform a chunk of rows — runs in subprocess."""
    result = []
    for row in rows:
        try:
            validated = validate(row)
            transformed = transform(validated)
            result.append((transformed["id"], transformed["name"], transformed["value"]))
        except ValueError:
            pass  # Skip invalid rows
    return result

def chunked(iterable, size):
    it = iter(iterable)
    while chunk := list(itertools.islice(it, size)):
        yield chunk

CHUNK_SIZE = 10_000

with open("data.csv") as f:
    reader = csv.DictReader(f)
    with ProcessPoolExecutor(max_workers=8) as pool:
        conn = psycopg2.connect(...)
        cur = conn.cursor()

        for processed_chunk in pool.map(process_chunk, chunked(reader, CHUNK_SIZE)):
            execute_values(
                cur,
                "INSERT INTO records (id, name, value) VALUES %s ON CONFLICT DO NOTHING",
                processed_chunk,
                page_size=1000
            )
        conn.commit()

# Results:
# Before: 4 hours (10M individual inserts, single thread)
# After:  ~18 minutes (bulk insert + 8 parallel processes)
Services Used
PythonPostgreSQL
Prerequisites
  • Python 3.10+
  • Basic understanding of async programming
What You Learned
  • Chunked bulk inserts vs row-by-row inserts
  • ProcessPoolExecutor for parallel CPU work
  • Measuring throughput improvements

Add More Questions to This Guide

Know a question that should be here? Share it and help the community!

Open Google Form