Streaming with generators: I read line by line, validate and write in batches.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | # ====================================================================== # Q: You have to import a 5 GB CSV with 30 million rows. How do you do it # without running out of memory? # # Streaming with generators: I read line by line, validate and write in batches. # Never readlines() or loading the whole file with pandas.read_csv. For a clean # import the fastest option is PostgreSQL COPY into a staging table and then # INSERT ... SELECT with an upsert. # ====================================================================== import csv from itertools import batched # Python 3.12+ from pydantic import BaseModel, ValidationError class EventRow(BaseModel): external_id: str host: str severity: int occurred_at: datetime def parse(path): with open(path, newline='', encoding='utf-8') as f: for lineno, raw in enumerate(csv.DictReader(f), start=2): try: yield EventRow(**raw).model_dump(), None except ValidationError as e: yield None, {'line': lineno, 'raw': raw, 'errors': e.errors()} def import_file(session, path, import_id): for chunk in batched(parse(path), 5000): good = [row for row, err in chunk if row] bad = [err for row, err in chunk if err] if good: upsert_events(session, good) if bad: session.execute(insert(RejectedRow), [{'import_id': import_id, **b} for b in bad]) session.commit() # small transactions, not one huge one |