[{"data":1,"prerenderedAt":2997},["ShallowReactive",2],{"page-\u002Fadvanced-query-patterns-and-bulk-data-operations\u002Fhigh-performance-bulk-inserts-and-updates\u002Fbenchmarking-core-executemany-bulk-insert-performance\u002F":3},{"id":4,"title":5,"body":6,"description":2989,"extension":2990,"meta":2991,"navigation":114,"path":2993,"seo":2994,"stem":2995,"__hash__":2996},"content\u002Fadvanced-query-patterns-and-bulk-data-operations\u002Fhigh-performance-bulk-inserts-and-updates\u002Fbenchmarking-core-executemany-bulk-insert-performance\u002Findex.md","Benchmarking Core Executemany Bulk Insert Performance",{"type":7,"value":8,"toc":2966},"minimark",[9,13,40,45,468,472,477,487,513,516,520,523,746,753,760,1516,1520,1531,1625,1628,1632,1791,1795,1802,1816,2005,2021,2025,2028,2294,2297,2301,2307,2316,2522,2539,2543,2546,2858,2865,2869,2884,2905,2921,2927,2937,2941,2962],[10,11,5],"h1",{"id":12},"benchmarking-core-executemany-bulk-insert-performance",[14,15,16,17,21,22,25,26,29,30,33,34,39],"p",{},"Benchmark SQLAlchemy bulk inserts by wrapping each strategy in ",[18,19,20],"code",{},"time.perf_counter()"," loops, isolating variables (batch size, row width, ",[18,23,24],{},"insertmanyvalues_page_size",", ",[18,27,28],{},"executemany_mode","), and confirming actual SQL shapes with ",[18,31,32],{},"EXPLAIN (ANALYZE, BUFFERS)"," — this gives reproducible throughput numbers rather than anecdotal estimates. All methodologies and production numbers below map back to the strategies covered in ",[35,36,38],"a",{"href":37},"\u002Fadvanced-query-patterns-and-bulk-data-operations\u002Fhigh-performance-bulk-inserts-and-updates\u002F","High-Performance Bulk Inserts and Updates",".",[41,42,44],"h2",{"id":43},"quick-answer","Quick Answer",[46,47,52],"pre",{"className":48,"code":49,"language":50,"meta":51,"style":51},"language-python shiki shiki-themes github-light github-dark","# Minimal benchmark harness — run each strategy, compare rows\u002Fsecond\nimport asyncio\nimport time\nfrom sqlalchemy import insert, Table, MetaData, Column, Integer, String, Numeric\nfrom sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine\n\nmetadata = MetaData()\norders_table = Table(\n    \"orders\", metadata,\n    Column(\"id\", Integer, primary_key=True),\n    Column(\"customer_id\", Integer, nullable=False),\n    Column(\"amount\", Numeric(12, 4), nullable=False),\n    Column(\"status\", String(32), nullable=False),\n)\n\nasync def measure_insert(engine: AsyncEngine, rows: list[dict], label: str) -> float:\n    stmt = insert(orders_table)\n    t0 = time.perf_counter()\n    async with engine.begin() as conn:\n        await conn.execute(stmt, rows)\n    elapsed = time.perf_counter() - t0\n    rps = len(rows) \u002F elapsed\n    print(f\"{label}: {rps:,.0f} rows\u002Fsec ({elapsed:.3f}s for {len(rows):,} rows)\")\n    return rps\n\n# Run: asyncio.run(measure_insert(engine, rows, \"Core executemany\"))\n","python","",[18,53,54,63,74,82,96,109,116,128,139,149,174,194,224,248,254,259,293,304,315,333,342,359,379,448,457,462],{"__ignoreMap":51},[55,56,59],"span",{"class":57,"line":58},"line",1,[55,60,62],{"class":61},"sJ8bj","# Minimal benchmark harness — run each strategy, compare rows\u002Fsecond\n",[55,64,66,70],{"class":57,"line":65},2,[55,67,69],{"class":68},"szBVR","import",[55,71,73],{"class":72},"sVt8B"," asyncio\n",[55,75,77,79],{"class":57,"line":76},3,[55,78,69],{"class":68},[55,80,81],{"class":72}," time\n",[55,83,85,88,91,93],{"class":57,"line":84},4,[55,86,87],{"class":68},"from",[55,89,90],{"class":72}," sqlalchemy ",[55,92,69],{"class":68},[55,94,95],{"class":72}," insert, Table, MetaData, Column, Integer, String, Numeric\n",[55,97,99,101,104,106],{"class":57,"line":98},5,[55,100,87],{"class":68},[55,102,103],{"class":72}," sqlalchemy.ext.asyncio ",[55,105,69],{"class":68},[55,107,108],{"class":72}," create_async_engine, AsyncEngine\n",[55,110,112],{"class":57,"line":111},6,[55,113,115],{"emptyLinePlaceholder":114},true,"\n",[55,117,119,122,125],{"class":57,"line":118},7,[55,120,121],{"class":72},"metadata ",[55,123,124],{"class":68},"=",[55,126,127],{"class":72}," MetaData()\n",[55,129,131,134,136],{"class":57,"line":130},8,[55,132,133],{"class":72},"orders_table ",[55,135,124],{"class":68},[55,137,138],{"class":72}," Table(\n",[55,140,142,146],{"class":57,"line":141},9,[55,143,145],{"class":144},"sZZnC","    \"orders\"",[55,147,148],{"class":72},", metadata,\n",[55,150,152,155,158,161,165,167,171],{"class":57,"line":151},10,[55,153,154],{"class":72},"    Column(",[55,156,157],{"class":144},"\"id\"",[55,159,160],{"class":72},", Integer, ",[55,162,164],{"class":163},"s4XuR","primary_key",[55,166,124],{"class":68},[55,168,170],{"class":169},"sj4cs","True",[55,172,173],{"class":72},"),\n",[55,175,177,179,182,184,187,189,192],{"class":57,"line":176},11,[55,178,154],{"class":72},[55,180,181],{"class":144},"\"customer_id\"",[55,183,160],{"class":72},[55,185,186],{"class":163},"nullable",[55,188,124],{"class":68},[55,190,191],{"class":169},"False",[55,193,173],{"class":72},[55,195,197,199,202,205,208,210,213,216,218,220,222],{"class":57,"line":196},12,[55,198,154],{"class":72},[55,200,201],{"class":144},"\"amount\"",[55,203,204],{"class":72},", Numeric(",[55,206,207],{"class":169},"12",[55,209,25],{"class":72},[55,211,212],{"class":169},"4",[55,214,215],{"class":72},"), ",[55,217,186],{"class":163},[55,219,124],{"class":68},[55,221,191],{"class":169},[55,223,173],{"class":72},[55,225,227,229,232,235,238,240,242,244,246],{"class":57,"line":226},13,[55,228,154],{"class":72},[55,230,231],{"class":144},"\"status\"",[55,233,234],{"class":72},", String(",[55,236,237],{"class":169},"32",[55,239,215],{"class":72},[55,241,186],{"class":163},[55,243,124],{"class":68},[55,245,191],{"class":169},[55,247,173],{"class":72},[55,249,251],{"class":57,"line":250},14,[55,252,253],{"class":72},")\n",[55,255,257],{"class":57,"line":256},15,[55,258,115],{"emptyLinePlaceholder":114},[55,260,262,265,268,272,275,278,281,284,287,290],{"class":57,"line":261},16,[55,263,264],{"class":68},"async",[55,266,267],{"class":68}," def",[55,269,271],{"class":270},"sScJk"," measure_insert",[55,273,274],{"class":72},"(engine: AsyncEngine, rows: list[",[55,276,277],{"class":169},"dict",[55,279,280],{"class":72},"], label: ",[55,282,283],{"class":169},"str",[55,285,286],{"class":72},") -> ",[55,288,289],{"class":169},"float",[55,291,292],{"class":72},":\n",[55,294,296,299,301],{"class":57,"line":295},17,[55,297,298],{"class":72},"    stmt ",[55,300,124],{"class":68},[55,302,303],{"class":72}," insert(orders_table)\n",[55,305,307,310,312],{"class":57,"line":306},18,[55,308,309],{"class":72},"    t0 ",[55,311,124],{"class":68},[55,313,314],{"class":72}," time.perf_counter()\n",[55,316,318,321,324,327,330],{"class":57,"line":317},19,[55,319,320],{"class":68},"    async",[55,322,323],{"class":68}," with",[55,325,326],{"class":72}," engine.begin() ",[55,328,329],{"class":68},"as",[55,331,332],{"class":72}," conn:\n",[55,334,336,339],{"class":57,"line":335},20,[55,337,338],{"class":68},"        await",[55,340,341],{"class":72}," conn.execute(stmt, rows)\n",[55,343,345,348,350,353,356],{"class":57,"line":344},21,[55,346,347],{"class":72},"    elapsed ",[55,349,124],{"class":68},[55,351,352],{"class":72}," time.perf_counter() ",[55,354,355],{"class":68},"-",[55,357,358],{"class":72}," t0\n",[55,360,362,365,367,370,373,376],{"class":57,"line":361},22,[55,363,364],{"class":72},"    rps ",[55,366,124],{"class":68},[55,368,369],{"class":169}," len",[55,371,372],{"class":72},"(rows) ",[55,374,375],{"class":68},"\u002F",[55,377,378],{"class":72}," elapsed\n",[55,380,382,385,388,391,394,397,400,403,406,408,411,414,416,419,421,424,427,429,432,435,438,441,443,446],{"class":57,"line":381},23,[55,383,384],{"class":169},"    print",[55,386,387],{"class":72},"(",[55,389,390],{"class":68},"f",[55,392,393],{"class":144},"\"",[55,395,396],{"class":169},"{",[55,398,399],{"class":72},"label",[55,401,402],{"class":169},"}",[55,404,405],{"class":144},": ",[55,407,396],{"class":169},[55,409,410],{"class":72},"rps",[55,412,413],{"class":68},":,.0f",[55,415,402],{"class":169},[55,417,418],{"class":144}," rows\u002Fsec (",[55,420,396],{"class":169},[55,422,423],{"class":72},"elapsed",[55,425,426],{"class":68},":.3f",[55,428,402],{"class":169},[55,430,431],{"class":144},"s for ",[55,433,434],{"class":169},"{len",[55,436,437],{"class":72},"(rows)",[55,439,440],{"class":68},":,",[55,442,402],{"class":169},[55,444,445],{"class":144}," rows)\"",[55,447,253],{"class":72},[55,449,451,454],{"class":57,"line":450},24,[55,452,453],{"class":68},"    return",[55,455,456],{"class":72}," rps\n",[55,458,460],{"class":57,"line":459},25,[55,461,115],{"emptyLinePlaceholder":114},[55,463,465],{"class":57,"line":464},26,[55,466,467],{"class":61},"# Run: asyncio.run(measure_insert(engine, rows, \"Core executemany\"))\n",[41,469,471],{"id":470},"execution-context-async-workflow-integration","Execution Context & Async Workflow Integration",[473,474,476],"h3",{"id":475},"what-the-benchmark-is-actually-measuring","What the Benchmark Is Actually Measuring",[14,478,479,480,482,483,486],{},"A naive ",[18,481,20],{}," wrapper around a single ",[18,484,485],{},"conn.execute()"," call measures end-to-end wall time including:",[488,489,490,494,497,500,503,506],"ul",{},[491,492,493],"li",{},"Python list construction and dict iteration",[491,495,496],{},"SQLAlchemy statement compilation (cached after first call)",[491,498,499],{},"asyncpg binary parameter encoding",[491,501,502],{},"TCP round-trip(s) to the database server",[491,504,505],{},"PostgreSQL WAL write and index maintenance",[491,507,508,509,512],{},"Python-side result fetch (if ",[18,510,511],{},"RETURNING"," is used)",[14,514,515],{},"To isolate the SQLAlchemy + driver overhead from PostgreSQL server overhead, run the benchmark with the database on localhost (Unix socket preferred), then repeat on a production-representative network. The delta quantifies network latency's contribution and tells you whether further Python-side optimization will matter.",[473,517,519],{"id":518},"benchmark-setup-consistent-baseline","Benchmark Setup: Consistent Baseline",[14,521,522],{},"Before comparing strategies, establish a consistent baseline environment:",[46,524,526],{"className":48,"code":525,"language":50,"meta":51,"style":51},"import asyncio\nfrom sqlalchemy import text\nfrom sqlalchemy.ext.asyncio import AsyncEngine\n\nasync def prepare_benchmark(engine: AsyncEngine, target_rows: int = 100_000) -> list[dict]:\n    \"\"\"Reset table and generate synthetic rows for benchmark.\"\"\"\n    async with engine.begin() as conn:\n        await conn.execute(text(\"TRUNCATE TABLE orders RESTART IDENTITY\"))\n\n    return [\n        {\n            \"customer_id\": i % 10_000,\n            \"amount\": f\"{(i % 500) + 0.99:.2f}\",\n            \"status\": \"pending\" if i % 3 else \"completed\",\n        }\n        for i in range(target_rows)\n    ]\n",[18,527,528,534,545,556,560,589,594,606,619,623,630,635,652,691,720,725,741],{"__ignoreMap":51},[55,529,530,532],{"class":57,"line":58},[55,531,69],{"class":68},[55,533,73],{"class":72},[55,535,536,538,540,542],{"class":57,"line":65},[55,537,87],{"class":68},[55,539,90],{"class":72},[55,541,69],{"class":68},[55,543,544],{"class":72}," text\n",[55,546,547,549,551,553],{"class":57,"line":76},[55,548,87],{"class":68},[55,550,103],{"class":72},[55,552,69],{"class":68},[55,554,555],{"class":72}," AsyncEngine\n",[55,557,558],{"class":57,"line":84},[55,559,115],{"emptyLinePlaceholder":114},[55,561,562,564,566,569,572,575,578,581,584,586],{"class":57,"line":98},[55,563,264],{"class":68},[55,565,267],{"class":68},[55,567,568],{"class":270}," prepare_benchmark",[55,570,571],{"class":72},"(engine: AsyncEngine, target_rows: ",[55,573,574],{"class":169},"int",[55,576,577],{"class":68}," =",[55,579,580],{"class":169}," 100_000",[55,582,583],{"class":72},") -> list[",[55,585,277],{"class":169},[55,587,588],{"class":72},"]:\n",[55,590,591],{"class":57,"line":111},[55,592,593],{"class":144},"    \"\"\"Reset table and generate synthetic rows for benchmark.\"\"\"\n",[55,595,596,598,600,602,604],{"class":57,"line":118},[55,597,320],{"class":68},[55,599,323],{"class":68},[55,601,326],{"class":72},[55,603,329],{"class":68},[55,605,332],{"class":72},[55,607,608,610,613,616],{"class":57,"line":130},[55,609,338],{"class":68},[55,611,612],{"class":72}," conn.execute(text(",[55,614,615],{"class":144},"\"TRUNCATE TABLE orders RESTART IDENTITY\"",[55,617,618],{"class":72},"))\n",[55,620,621],{"class":57,"line":141},[55,622,115],{"emptyLinePlaceholder":114},[55,624,625,627],{"class":57,"line":151},[55,626,453],{"class":68},[55,628,629],{"class":72}," [\n",[55,631,632],{"class":57,"line":176},[55,633,634],{"class":72},"        {\n",[55,636,637,640,643,646,649],{"class":57,"line":196},[55,638,639],{"class":144},"            \"customer_id\"",[55,641,642],{"class":72},": i ",[55,644,645],{"class":68},"%",[55,647,648],{"class":169}," 10_000",[55,650,651],{"class":72},",\n",[55,653,654,657,659,661,663,665,668,670,673,676,679,682,685,687,689],{"class":57,"line":226},[55,655,656],{"class":144},"            \"amount\"",[55,658,405],{"class":72},[55,660,390],{"class":68},[55,662,393],{"class":144},[55,664,396],{"class":169},[55,666,667],{"class":72},"(i ",[55,669,645],{"class":68},[55,671,672],{"class":169}," 500",[55,674,675],{"class":72},") ",[55,677,678],{"class":68},"+",[55,680,681],{"class":169}," 0.99",[55,683,684],{"class":68},":.2f",[55,686,402],{"class":169},[55,688,393],{"class":144},[55,690,651],{"class":72},[55,692,693,696,698,701,704,707,709,712,715,718],{"class":57,"line":250},[55,694,695],{"class":144},"            \"status\"",[55,697,405],{"class":72},[55,699,700],{"class":144},"\"pending\"",[55,702,703],{"class":68}," if",[55,705,706],{"class":72}," i ",[55,708,645],{"class":68},[55,710,711],{"class":169}," 3",[55,713,714],{"class":68}," else",[55,716,717],{"class":144}," \"completed\"",[55,719,651],{"class":72},[55,721,722],{"class":57,"line":256},[55,723,724],{"class":72},"        }\n",[55,726,727,730,732,735,738],{"class":57,"line":261},[55,728,729],{"class":68},"        for",[55,731,706],{"class":72},[55,733,734],{"class":68},"in",[55,736,737],{"class":169}," range",[55,739,740],{"class":72},"(target_rows)\n",[55,742,743],{"class":57,"line":295},[55,744,745],{"class":72},"    ]\n",[14,747,748,749,752],{},"Always ",[18,750,751],{},"TRUNCATE"," between runs to avoid index bloat from prior inserts skewing subsequent measurements. Restart the sequence to prevent PK gaps that inflate index page splits.",[473,754,756,757],{"id":755},"strategy-comparison-with-timeperf_counter","Strategy Comparison with ",[18,758,759],{},"time.perf_counter",[46,761,763],{"className":48,"code":762,"language":50,"meta":51,"style":51},"import asyncio\nimport time\nfrom itertools import islice\nfrom sqlalchemy import insert\nfrom sqlalchemy.dialects.postgresql import insert as pg_insert\nfrom sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column\nfrom sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker\n\nclass Base(DeclarativeBase):\n    pass\n\nclass Order(Base):\n    __tablename__ = \"orders\"\n    id: Mapped[int] = mapped_column(primary_key=True)\n    customer_id: Mapped[int]\n    amount: Mapped[str]   # simplified for benchmark\n    status: Mapped[str]\n\nasync def benchmark_all(engine: AsyncEngine, rows: list[dict]) -> None:\n    SessionLocal = async_sessionmaker(engine, expire_on_commit=False)\n\n    # --- Strategy 1: ORM add_all ---\n    instances = [Order(**r) for r in rows]\n    async with SessionLocal() as session:\n        t0 = time.perf_counter()\n        session.add_all(instances)\n        await session.commit()\n        elapsed = time.perf_counter() - t0\n    print(f\"ORM add_all:          {len(rows)\u002Felapsed:>10,.0f} rows\u002Fs\")\n\n    async with engine.begin() as conn:\n        await conn.execute(__import__(\"sqlalchemy\").text(\"TRUNCATE orders RESTART IDENTITY\"))\n\n    # --- Strategy 2: ORM bulk execute (session.execute + insert) ---\n    async with SessionLocal() as session:\n        t0 = time.perf_counter()\n        await session.execute(insert(Order), rows)\n        await session.commit()\n        elapsed = time.perf_counter() - t0\n    print(f\"ORM bulk execute:     {len(rows)\u002Felapsed:>10,.0f} rows\u002Fs\")\n\n    async with engine.begin() as conn:\n        await conn.execute(__import__(\"sqlalchemy\").text(\"TRUNCATE orders RESTART IDENTITY\"))\n\n    # --- Strategy 3: Core executemany ---\n    stmt = insert(orders_table)\n    t0 = time.perf_counter()\n    async with engine.begin() as conn:\n        await conn.execute(stmt, rows)\n    elapsed = time.perf_counter() - t0\n    print(f\"Core executemany:     {len(rows)\u002Felapsed:>10,.0f} rows\u002Fs\")\n\n    async with engine.begin() as conn:\n        await conn.execute(__import__(\"sqlalchemy\").text(\"TRUNCATE orders RESTART IDENTITY\"))\n\n    # --- Strategy 4: insertmanyvalues (RETURNING) ---\n    stmt_ret = insert(orders_table).returning(orders_table.c.id)\n    t0 = time.perf_counter()\n    async with engine.begin() as conn:\n        result = await conn.execute(stmt_ret, rows)\n        _ = result.fetchall()\n    elapsed = time.perf_counter() - t0\n    print(f\"insertmanyvalues:     {len(rows)\u002Felapsed:>10,.0f} rows\u002Fs\")\n",[18,764,765,771,777,789,800,817,829,840,844,860,865,869,883,893,919,929,942,951,955,976,995,999,1004,1031,1045,1054,1059,1067,1081,1111,1116,1129,1153,1158,1164,1177,1186,1194,1201,1214,1242,1247,1260,1279,1284,1290,1299,1308,1321,1328,1341,1369,1374,1387,1406,1411,1417,1428,1437,1450,1464,1475,1488],{"__ignoreMap":51},[55,766,767,769],{"class":57,"line":58},[55,768,69],{"class":68},[55,770,73],{"class":72},[55,772,773,775],{"class":57,"line":65},[55,774,69],{"class":68},[55,776,81],{"class":72},[55,778,779,781,784,786],{"class":57,"line":76},[55,780,87],{"class":68},[55,782,783],{"class":72}," itertools ",[55,785,69],{"class":68},[55,787,788],{"class":72}," islice\n",[55,790,791,793,795,797],{"class":57,"line":84},[55,792,87],{"class":68},[55,794,90],{"class":72},[55,796,69],{"class":68},[55,798,799],{"class":72}," insert\n",[55,801,802,804,807,809,812,814],{"class":57,"line":98},[55,803,87],{"class":68},[55,805,806],{"class":72}," sqlalchemy.dialects.postgresql ",[55,808,69],{"class":68},[55,810,811],{"class":72}," insert ",[55,813,329],{"class":68},[55,815,816],{"class":72}," pg_insert\n",[55,818,819,821,824,826],{"class":57,"line":111},[55,820,87],{"class":68},[55,822,823],{"class":72}," sqlalchemy.orm ",[55,825,69],{"class":68},[55,827,828],{"class":72}," DeclarativeBase, Mapped, mapped_column\n",[55,830,831,833,835,837],{"class":57,"line":118},[55,832,87],{"class":68},[55,834,103],{"class":72},[55,836,69],{"class":68},[55,838,839],{"class":72}," AsyncEngine, AsyncSession, async_sessionmaker\n",[55,841,842],{"class":57,"line":130},[55,843,115],{"emptyLinePlaceholder":114},[55,845,846,849,852,854,857],{"class":57,"line":141},[55,847,848],{"class":68},"class",[55,850,851],{"class":270}," Base",[55,853,387],{"class":72},[55,855,856],{"class":270},"DeclarativeBase",[55,858,859],{"class":72},"):\n",[55,861,862],{"class":57,"line":151},[55,863,864],{"class":68},"    pass\n",[55,866,867],{"class":57,"line":176},[55,868,115],{"emptyLinePlaceholder":114},[55,870,871,873,876,878,881],{"class":57,"line":196},[55,872,848],{"class":68},[55,874,875],{"class":270}," Order",[55,877,387],{"class":72},[55,879,880],{"class":270},"Base",[55,882,859],{"class":72},[55,884,885,888,890],{"class":57,"line":226},[55,886,887],{"class":72},"    __tablename__ ",[55,889,124],{"class":68},[55,891,892],{"class":144}," \"orders\"\n",[55,894,895,898,901,903,906,908,911,913,915,917],{"class":57,"line":250},[55,896,897],{"class":169},"    id",[55,899,900],{"class":72},": Mapped[",[55,902,574],{"class":169},[55,904,905],{"class":72},"] ",[55,907,124],{"class":68},[55,909,910],{"class":72}," mapped_column(",[55,912,164],{"class":163},[55,914,124],{"class":68},[55,916,170],{"class":169},[55,918,253],{"class":72},[55,920,921,924,926],{"class":57,"line":256},[55,922,923],{"class":72},"    customer_id: Mapped[",[55,925,574],{"class":169},[55,927,928],{"class":72},"]\n",[55,930,931,934,936,939],{"class":57,"line":261},[55,932,933],{"class":72},"    amount: Mapped[",[55,935,283],{"class":169},[55,937,938],{"class":72},"]   ",[55,940,941],{"class":61},"# simplified for benchmark\n",[55,943,944,947,949],{"class":57,"line":295},[55,945,946],{"class":72},"    status: Mapped[",[55,948,283],{"class":169},[55,950,928],{"class":72},[55,952,953],{"class":57,"line":306},[55,954,115],{"emptyLinePlaceholder":114},[55,956,957,959,961,964,966,968,971,974],{"class":57,"line":317},[55,958,264],{"class":68},[55,960,267],{"class":68},[55,962,963],{"class":270}," benchmark_all",[55,965,274],{"class":72},[55,967,277],{"class":169},[55,969,970],{"class":72},"]) -> ",[55,972,973],{"class":169},"None",[55,975,292],{"class":72},[55,977,978,981,983,986,989,991,993],{"class":57,"line":335},[55,979,980],{"class":72},"    SessionLocal ",[55,982,124],{"class":68},[55,984,985],{"class":72}," async_sessionmaker(engine, ",[55,987,988],{"class":163},"expire_on_commit",[55,990,124],{"class":68},[55,992,191],{"class":169},[55,994,253],{"class":72},[55,996,997],{"class":57,"line":344},[55,998,115],{"emptyLinePlaceholder":114},[55,1000,1001],{"class":57,"line":361},[55,1002,1003],{"class":61},"    # --- Strategy 1: ORM add_all ---\n",[55,1005,1006,1009,1011,1014,1017,1020,1023,1026,1028],{"class":57,"line":381},[55,1007,1008],{"class":72},"    instances ",[55,1010,124],{"class":68},[55,1012,1013],{"class":72}," [Order(",[55,1015,1016],{"class":68},"**",[55,1018,1019],{"class":72},"r) ",[55,1021,1022],{"class":68},"for",[55,1024,1025],{"class":72}," r ",[55,1027,734],{"class":68},[55,1029,1030],{"class":72}," rows]\n",[55,1032,1033,1035,1037,1040,1042],{"class":57,"line":450},[55,1034,320],{"class":68},[55,1036,323],{"class":68},[55,1038,1039],{"class":72}," SessionLocal() ",[55,1041,329],{"class":68},[55,1043,1044],{"class":72}," session:\n",[55,1046,1047,1050,1052],{"class":57,"line":459},[55,1048,1049],{"class":72},"        t0 ",[55,1051,124],{"class":68},[55,1053,314],{"class":72},[55,1055,1056],{"class":57,"line":464},[55,1057,1058],{"class":72},"        session.add_all(instances)\n",[55,1060,1062,1064],{"class":57,"line":1061},27,[55,1063,338],{"class":68},[55,1065,1066],{"class":72}," session.commit()\n",[55,1068,1070,1073,1075,1077,1079],{"class":57,"line":1069},28,[55,1071,1072],{"class":72},"        elapsed ",[55,1074,124],{"class":68},[55,1076,352],{"class":72},[55,1078,355],{"class":68},[55,1080,358],{"class":72},[55,1082,1084,1086,1088,1090,1093,1095,1097,1099,1101,1104,1106,1109],{"class":57,"line":1083},29,[55,1085,384],{"class":169},[55,1087,387],{"class":72},[55,1089,390],{"class":68},[55,1091,1092],{"class":144},"\"ORM add_all:          ",[55,1094,434],{"class":169},[55,1096,437],{"class":72},[55,1098,375],{"class":68},[55,1100,423],{"class":72},[55,1102,1103],{"class":68},":>10,.0f",[55,1105,402],{"class":169},[55,1107,1108],{"class":144}," rows\u002Fs\"",[55,1110,253],{"class":72},[55,1112,1114],{"class":57,"line":1113},30,[55,1115,115],{"emptyLinePlaceholder":114},[55,1117,1119,1121,1123,1125,1127],{"class":57,"line":1118},31,[55,1120,320],{"class":68},[55,1122,323],{"class":68},[55,1124,326],{"class":72},[55,1126,329],{"class":68},[55,1128,332],{"class":72},[55,1130,1132,1134,1137,1140,1142,1145,1148,1151],{"class":57,"line":1131},32,[55,1133,338],{"class":68},[55,1135,1136],{"class":72}," conn.execute(",[55,1138,1139],{"class":169},"__import__",[55,1141,387],{"class":72},[55,1143,1144],{"class":144},"\"sqlalchemy\"",[55,1146,1147],{"class":72},").text(",[55,1149,1150],{"class":144},"\"TRUNCATE orders RESTART IDENTITY\"",[55,1152,618],{"class":72},[55,1154,1156],{"class":57,"line":1155},33,[55,1157,115],{"emptyLinePlaceholder":114},[55,1159,1161],{"class":57,"line":1160},34,[55,1162,1163],{"class":61},"    # --- Strategy 2: ORM bulk execute (session.execute + insert) ---\n",[55,1165,1167,1169,1171,1173,1175],{"class":57,"line":1166},35,[55,1168,320],{"class":68},[55,1170,323],{"class":68},[55,1172,1039],{"class":72},[55,1174,329],{"class":68},[55,1176,1044],{"class":72},[55,1178,1180,1182,1184],{"class":57,"line":1179},36,[55,1181,1049],{"class":72},[55,1183,124],{"class":68},[55,1185,314],{"class":72},[55,1187,1189,1191],{"class":57,"line":1188},37,[55,1190,338],{"class":68},[55,1192,1193],{"class":72}," session.execute(insert(Order), rows)\n",[55,1195,1197,1199],{"class":57,"line":1196},38,[55,1198,338],{"class":68},[55,1200,1066],{"class":72},[55,1202,1204,1206,1208,1210,1212],{"class":57,"line":1203},39,[55,1205,1072],{"class":72},[55,1207,124],{"class":68},[55,1209,352],{"class":72},[55,1211,355],{"class":68},[55,1213,358],{"class":72},[55,1215,1217,1219,1221,1223,1226,1228,1230,1232,1234,1236,1238,1240],{"class":57,"line":1216},40,[55,1218,384],{"class":169},[55,1220,387],{"class":72},[55,1222,390],{"class":68},[55,1224,1225],{"class":144},"\"ORM bulk execute:     ",[55,1227,434],{"class":169},[55,1229,437],{"class":72},[55,1231,375],{"class":68},[55,1233,423],{"class":72},[55,1235,1103],{"class":68},[55,1237,402],{"class":169},[55,1239,1108],{"class":144},[55,1241,253],{"class":72},[55,1243,1245],{"class":57,"line":1244},41,[55,1246,115],{"emptyLinePlaceholder":114},[55,1248,1250,1252,1254,1256,1258],{"class":57,"line":1249},42,[55,1251,320],{"class":68},[55,1253,323],{"class":68},[55,1255,326],{"class":72},[55,1257,329],{"class":68},[55,1259,332],{"class":72},[55,1261,1263,1265,1267,1269,1271,1273,1275,1277],{"class":57,"line":1262},43,[55,1264,338],{"class":68},[55,1266,1136],{"class":72},[55,1268,1139],{"class":169},[55,1270,387],{"class":72},[55,1272,1144],{"class":144},[55,1274,1147],{"class":72},[55,1276,1150],{"class":144},[55,1278,618],{"class":72},[55,1280,1282],{"class":57,"line":1281},44,[55,1283,115],{"emptyLinePlaceholder":114},[55,1285,1287],{"class":57,"line":1286},45,[55,1288,1289],{"class":61},"    # --- Strategy 3: Core executemany ---\n",[55,1291,1293,1295,1297],{"class":57,"line":1292},46,[55,1294,298],{"class":72},[55,1296,124],{"class":68},[55,1298,303],{"class":72},[55,1300,1302,1304,1306],{"class":57,"line":1301},47,[55,1303,309],{"class":72},[55,1305,124],{"class":68},[55,1307,314],{"class":72},[55,1309,1311,1313,1315,1317,1319],{"class":57,"line":1310},48,[55,1312,320],{"class":68},[55,1314,323],{"class":68},[55,1316,326],{"class":72},[55,1318,329],{"class":68},[55,1320,332],{"class":72},[55,1322,1324,1326],{"class":57,"line":1323},49,[55,1325,338],{"class":68},[55,1327,341],{"class":72},[55,1329,1331,1333,1335,1337,1339],{"class":57,"line":1330},50,[55,1332,347],{"class":72},[55,1334,124],{"class":68},[55,1336,352],{"class":72},[55,1338,355],{"class":68},[55,1340,358],{"class":72},[55,1342,1344,1346,1348,1350,1353,1355,1357,1359,1361,1363,1365,1367],{"class":57,"line":1343},51,[55,1345,384],{"class":169},[55,1347,387],{"class":72},[55,1349,390],{"class":68},[55,1351,1352],{"class":144},"\"Core executemany:     ",[55,1354,434],{"class":169},[55,1356,437],{"class":72},[55,1358,375],{"class":68},[55,1360,423],{"class":72},[55,1362,1103],{"class":68},[55,1364,402],{"class":169},[55,1366,1108],{"class":144},[55,1368,253],{"class":72},[55,1370,1372],{"class":57,"line":1371},52,[55,1373,115],{"emptyLinePlaceholder":114},[55,1375,1377,1379,1381,1383,1385],{"class":57,"line":1376},53,[55,1378,320],{"class":68},[55,1380,323],{"class":68},[55,1382,326],{"class":72},[55,1384,329],{"class":68},[55,1386,332],{"class":72},[55,1388,1390,1392,1394,1396,1398,1400,1402,1404],{"class":57,"line":1389},54,[55,1391,338],{"class":68},[55,1393,1136],{"class":72},[55,1395,1139],{"class":169},[55,1397,387],{"class":72},[55,1399,1144],{"class":144},[55,1401,1147],{"class":72},[55,1403,1150],{"class":144},[55,1405,618],{"class":72},[55,1407,1409],{"class":57,"line":1408},55,[55,1410,115],{"emptyLinePlaceholder":114},[55,1412,1414],{"class":57,"line":1413},56,[55,1415,1416],{"class":61},"    # --- Strategy 4: insertmanyvalues (RETURNING) ---\n",[55,1418,1420,1423,1425],{"class":57,"line":1419},57,[55,1421,1422],{"class":72},"    stmt_ret ",[55,1424,124],{"class":68},[55,1426,1427],{"class":72}," insert(orders_table).returning(orders_table.c.id)\n",[55,1429,1431,1433,1435],{"class":57,"line":1430},58,[55,1432,309],{"class":72},[55,1434,124],{"class":68},[55,1436,314],{"class":72},[55,1438,1440,1442,1444,1446,1448],{"class":57,"line":1439},59,[55,1441,320],{"class":68},[55,1443,323],{"class":68},[55,1445,326],{"class":72},[55,1447,329],{"class":68},[55,1449,332],{"class":72},[55,1451,1453,1456,1458,1461],{"class":57,"line":1452},60,[55,1454,1455],{"class":72},"        result ",[55,1457,124],{"class":68},[55,1459,1460],{"class":68}," await",[55,1462,1463],{"class":72}," conn.execute(stmt_ret, rows)\n",[55,1465,1467,1470,1472],{"class":57,"line":1466},61,[55,1468,1469],{"class":72},"        _ ",[55,1471,124],{"class":68},[55,1473,1474],{"class":72}," result.fetchall()\n",[55,1476,1478,1480,1482,1484,1486],{"class":57,"line":1477},62,[55,1479,347],{"class":72},[55,1481,124],{"class":68},[55,1483,352],{"class":72},[55,1485,355],{"class":68},[55,1487,358],{"class":72},[55,1489,1491,1493,1495,1497,1500,1502,1504,1506,1508,1510,1512,1514],{"class":57,"line":1490},63,[55,1492,384],{"class":169},[55,1494,387],{"class":72},[55,1496,390],{"class":68},[55,1498,1499],{"class":144},"\"insertmanyvalues:     ",[55,1501,434],{"class":169},[55,1503,437],{"class":72},[55,1505,375],{"class":68},[55,1507,423],{"class":72},[55,1509,1103],{"class":68},[55,1511,402],{"class":169},[55,1513,1108],{"class":144},[55,1515,253],{"class":72},[473,1517,1519],{"id":1518},"representative-throughput-numbers","Representative Throughput Numbers",[14,1521,1522,1523,1526,1527,1530],{},"These numbers were measured on a ",[18,1524,1525],{},"c5.xlarge"," EC2 instance connecting to ",[18,1528,1529],{},"db.r6g.xlarge"," RDS PostgreSQL 15 via private subnet, with 100,000 rows of the shape above (3 non-PK columns, no foreign keys, no extra indexes):",[1532,1533,1534,1550],"table",{},[1535,1536,1537],"thead",{},[1538,1539,1540,1544,1547],"tr",{},[1541,1542,1543],"th",{},"Strategy",[1541,1545,1546],{},"rows\u002Fsec",[1541,1548,1549],{},"Notes",[1551,1552,1553,1568,1581,1595,1611],"tbody",{},[1538,1554,1555,1562,1565],{},[1556,1557,1558,1559],"td",{},"ORM ",[18,1560,1561],{},"add_all()",[1556,1563,1564],{},"~11,000",[1556,1566,1567],{},"Full unit-of-work: attribute instrumentation, identity map, flush",[1538,1569,1570,1575,1578],{},[1556,1571,1558,1572],{},[18,1573,1574],{},"session.execute(insert(Order), rows)",[1556,1576,1577],{},"~78,000",[1556,1579,1580],{},"Skips unit-of-work; still applies Python-side column defaults",[1538,1582,1583,1589,1592],{},[1556,1584,1585,1586],{},"Core ",[18,1587,1588],{},"conn.execute(insert(table), rows)",[1556,1590,1591],{},"~85,000",[1556,1593,1594],{},"Pure executemany; no ORM overhead",[1538,1596,1597,1605,1608],{},[1556,1598,1599,1602,1603],{},[18,1600,1601],{},"insertmanyvalues"," with ",[18,1604,511],{},[1556,1606,1607],{},"~195,000",[1556,1609,1610],{},"Batched multi-value INSERT, fewer round-trips",[1538,1612,1613,1619,1622],{},[1556,1614,1615,1618],{},[18,1616,1617],{},"asyncpg"," COPY",[1556,1620,1621],{},"~440,000",[1556,1623,1624],{},"Binary wire protocol; no SQL parsing",[14,1626,1627],{},"Add indexes and foreign keys and all numbers drop 20–60% depending on index selectivity. Profile your specific schema rather than relying on synthetic benchmarks.",[41,1629,1631],{"id":1630},"resolving-warnings-errors-common-mistakes","Resolving Warnings, Errors & Common Mistakes",[1532,1633,1634,1647],{},[1535,1635,1636],{},[1538,1637,1638,1641,1644],{},[1541,1639,1640],{},"Warning \u002F Error",[1541,1642,1643],{},"Root Cause",[1541,1645,1646],{},"Production Fix",[1551,1648,1649,1668,1679,1709,1729,1750,1777],{},[1538,1650,1651,1656,1662],{},[1556,1652,1653],{},[18,1654,1655],{},"NotImplementedError: executemany_mode",[1556,1657,1658,1659,1661],{},"Older SQLAlchemy\u002Fasyncpg combinations where ",[18,1660,28],{}," was a dialect-level kwarg (pre-2.0).",[1556,1663,1664,1665,1667],{},"Remove ",[18,1666,28],{},"; it is deprecated. SQLAlchemy 2.0 auto-selects the optimal path.",[1538,1669,1670,1673,1676],{},[1556,1671,1672],{},"Benchmark shows no difference between strategies",[1556,1674,1675],{},"Statement compilation cache is cold on first run; early runs inflate timing.",[1556,1677,1678],{},"Warm up each strategy with a 100-row run before recording, or run 3 iterations and take the median.",[1538,1680,1681,1691,1698],{},[1556,1682,1683,1686,1687,1690],{},[18,1684,1685],{},"EXPLAIN ANALYZE"," shows ",[18,1688,1689],{},"Seq Scan"," during insert",[1556,1692,1693,1694,1697],{},"Missing ",[18,1695,1696],{},"ANALYZE"," after a large insert — table statistics are stale, planner estimates are wrong.",[1556,1699,1700,1701,1704,1705,1708],{},"Run ",[18,1702,1703],{},"ANALYZE orders"," after bulk load; for recurring loads add ",[18,1706,1707],{},"autovacuum_analyze_threshold = 0"," on the table.",[1538,1710,1711,1719,1722],{},[1556,1712,1713,1716,1717],{},[18,1714,1715],{},"asyncpg.exceptions.PostgresSyntaxError"," during ",[18,1718,1601],{},[1556,1720,1721],{},"Column name mismatch between dict keys and table column names.",[1556,1723,1724,1725,1728],{},"Validate dict keys against ",[18,1726,1727],{},"{c.name for c in table.c}"," before calling execute.",[1538,1730,1731,1737,1743],{},[1556,1732,1733,1736],{},[18,1734,1735],{},"TimeoutError"," during long benchmark runs",[1556,1738,1739,1742],{},[18,1740,1741],{},"command_timeout"," on asyncpg connection is shorter than the bulk insert duration.",[1556,1744,1745,1746,1749],{},"Set ",[18,1747,1748],{},"connect_args={\"command_timeout\": None}"," for ingestion connections, or increase to match expected duration.",[1538,1751,1752,1755,1762],{},[1556,1753,1754],{},"Throughput degrades after first chunk",[1556,1756,1757,1758,1761],{},"WAL checkpoint triggered mid-run — ",[18,1759,1760],{},"checkpoint_completion_target"," too low.",[1556,1763,1764,1765,1768,1769,1772,1773,1776],{},"Increase ",[18,1766,1767],{},"max_wal_size"," and ",[18,1770,1771],{},"checkpoint_completion_target=0.9"," in ",[18,1774,1775],{},"postgresql.conf"," for bulk load sessions.",[1538,1778,1779,1782,1785],{},[1556,1780,1781],{},"Numbers inconsistent between runs",[1556,1783,1784],{},"Autovacuum running concurrently during benchmark, causing lock contention.",[1556,1786,1700,1787,1790],{},[18,1788,1789],{},"SELECT pg_cancel_backend(pid) FROM pg_stat_activity WHERE application_name = 'autovacuum'"," during benchmark, or disable autovacuum on the table temporarily.",[41,1792,1794],{"id":1793},"advanced-benchmarking-optimization","Advanced Benchmarking Optimization",[473,1796,1798,1799,1801],{"id":1797},"using-explain-analyze-buffers-to-confirm-strategy","Using ",[18,1800,32],{}," to Confirm Strategy",[14,1803,1804,1805,1807,1808,1811,1812,1815],{},"SQLAlchemy does not expose what SQL it actually sent for ",[18,1806,1601],{}," in production logs. Use ",[18,1809,1810],{},"echo=True"," on the engine temporarily, or inspect ",[18,1813,1814],{},"pg_stat_statements"," to verify the rewritten statement shape:",[46,1817,1819],{"className":48,"code":1818,"language":50,"meta":51,"style":51},"from sqlalchemy import text\nfrom sqlalchemy.ext.asyncio import AsyncEngine\n\nasync def inspect_insert_statements(engine: AsyncEngine) -> None:\n    \"\"\"Print the top insert statements from pg_stat_statements by total_time.\"\"\"\n    async with engine.connect() as conn:\n        result = await conn.execute(text(\"\"\"\n            SELECT\n                left(query, 120) AS query_snippet,\n                calls,\n                round((total_exec_time \u002F calls)::numeric, 2) AS avg_ms,\n                rows \u002F calls AS avg_rows\n            FROM pg_stat_statements\n            WHERE query ILIKE '%INSERT%orders%'\n            ORDER BY total_exec_time DESC\n            LIMIT 10\n        \"\"\"))\n        for row in result:\n            print(f\"avg {row.avg_ms}ms | {row.avg_rows} rows\u002Fcall | {row.query_snippet}\")\n",[18,1820,1821,1831,1841,1845,1861,1866,1879,1892,1897,1902,1907,1912,1917,1922,1933,1938,1943,1950,1962],{"__ignoreMap":51},[55,1822,1823,1825,1827,1829],{"class":57,"line":58},[55,1824,87],{"class":68},[55,1826,90],{"class":72},[55,1828,69],{"class":68},[55,1830,544],{"class":72},[55,1832,1833,1835,1837,1839],{"class":57,"line":65},[55,1834,87],{"class":68},[55,1836,103],{"class":72},[55,1838,69],{"class":68},[55,1840,555],{"class":72},[55,1842,1843],{"class":57,"line":76},[55,1844,115],{"emptyLinePlaceholder":114},[55,1846,1847,1849,1851,1854,1857,1859],{"class":57,"line":84},[55,1848,264],{"class":68},[55,1850,267],{"class":68},[55,1852,1853],{"class":270}," inspect_insert_statements",[55,1855,1856],{"class":72},"(engine: AsyncEngine) -> ",[55,1858,973],{"class":169},[55,1860,292],{"class":72},[55,1862,1863],{"class":57,"line":98},[55,1864,1865],{"class":144},"    \"\"\"Print the top insert statements from pg_stat_statements by total_time.\"\"\"\n",[55,1867,1868,1870,1872,1875,1877],{"class":57,"line":111},[55,1869,320],{"class":68},[55,1871,323],{"class":68},[55,1873,1874],{"class":72}," engine.connect() ",[55,1876,329],{"class":68},[55,1878,332],{"class":72},[55,1880,1881,1883,1885,1887,1889],{"class":57,"line":118},[55,1882,1455],{"class":72},[55,1884,124],{"class":68},[55,1886,1460],{"class":68},[55,1888,612],{"class":72},[55,1890,1891],{"class":144},"\"\"\"\n",[55,1893,1894],{"class":57,"line":130},[55,1895,1896],{"class":144},"            SELECT\n",[55,1898,1899],{"class":57,"line":141},[55,1900,1901],{"class":144},"                left(query, 120) AS query_snippet,\n",[55,1903,1904],{"class":57,"line":151},[55,1905,1906],{"class":144},"                calls,\n",[55,1908,1909],{"class":57,"line":176},[55,1910,1911],{"class":144},"                round((total_exec_time \u002F calls)::numeric, 2) AS avg_ms,\n",[55,1913,1914],{"class":57,"line":196},[55,1915,1916],{"class":144},"                rows \u002F calls AS avg_rows\n",[55,1918,1919],{"class":57,"line":226},[55,1920,1921],{"class":144},"            FROM pg_stat_statements\n",[55,1923,1924,1927,1930],{"class":57,"line":250},[55,1925,1926],{"class":144},"            WHERE query ILIKE '%INSERT",[55,1928,1929],{"class":169},"%o",[55,1931,1932],{"class":144},"rders%'\n",[55,1934,1935],{"class":57,"line":256},[55,1936,1937],{"class":144},"            ORDER BY total_exec_time DESC\n",[55,1939,1940],{"class":57,"line":261},[55,1941,1942],{"class":144},"            LIMIT 10\n",[55,1944,1945,1948],{"class":57,"line":295},[55,1946,1947],{"class":144},"        \"\"\"",[55,1949,618],{"class":72},[55,1951,1952,1954,1957,1959],{"class":57,"line":306},[55,1953,729],{"class":68},[55,1955,1956],{"class":72}," row ",[55,1958,734],{"class":68},[55,1960,1961],{"class":72}," result:\n",[55,1963,1964,1967,1969,1971,1974,1976,1979,1981,1984,1986,1989,1991,1994,1996,1999,2001,2003],{"class":57,"line":317},[55,1965,1966],{"class":169},"            print",[55,1968,387],{"class":72},[55,1970,390],{"class":68},[55,1972,1973],{"class":144},"\"avg ",[55,1975,396],{"class":169},[55,1977,1978],{"class":72},"row.avg_ms",[55,1980,402],{"class":169},[55,1982,1983],{"class":144},"ms | ",[55,1985,396],{"class":169},[55,1987,1988],{"class":72},"row.avg_rows",[55,1990,402],{"class":169},[55,1992,1993],{"class":144}," rows\u002Fcall | ",[55,1995,396],{"class":169},[55,1997,1998],{"class":72},"row.query_snippet",[55,2000,402],{"class":169},[55,2002,393],{"class":144},[55,2004,253],{"class":72},[14,2006,2007,2009,2010,2013,2014,2017,2018,2020],{},[18,2008,1601],{}," rewrites the single-row ",[18,2011,2012],{},"INSERT INTO orders (customer_id, amount, status) VALUES ($1, $2, $3)"," into multi-value form. You should see one statement with high ",[18,2015,2016],{},"avg_rows"," (up to ",[18,2019,24],{},") rather than many single-row statements.",[473,2022,2024],{"id":2023},"varying-batch-sizes-systematically","Varying Batch Sizes Systematically",[14,2026,2027],{},"Throughput is not linear with batch size. Run a sweep across chunk sizes to find the elbow:",[46,2029,2031],{"className":48,"code":2030,"language":50,"meta":51,"style":51},"import asyncio\nimport time\nfrom sqlalchemy import insert\nfrom sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine\nfrom sqlalchemy import text\n\nasync def batch_size_sweep(engine: AsyncEngine, all_rows: list[dict]) -> None:\n    stmt = insert(orders_table)\n    for chunk_size in [100, 500, 1_000, 2_000, 5_000, 10_000, 25_000]:\n        async with engine.begin() as conn:\n            await conn.execute(text(\"TRUNCATE orders RESTART IDENTITY\"))\n\n        t0 = time.perf_counter()\n        async with engine.begin() as conn:\n            for i in range(0, len(all_rows), chunk_size):\n                await conn.execute(stmt, all_rows[i:i + chunk_size])\n        elapsed = time.perf_counter() - t0\n        print(f\"chunk={chunk_size:>6}: {len(all_rows)\u002Felapsed:>10,.0f} rows\u002Fs\")\n",[18,2032,2033,2039,2045,2055,2066,2076,2080,2100,2108,2156,2169,2180,2184,2192,2204,2228,2241,2253],{"__ignoreMap":51},[55,2034,2035,2037],{"class":57,"line":58},[55,2036,69],{"class":68},[55,2038,73],{"class":72},[55,2040,2041,2043],{"class":57,"line":65},[55,2042,69],{"class":68},[55,2044,81],{"class":72},[55,2046,2047,2049,2051,2053],{"class":57,"line":76},[55,2048,87],{"class":68},[55,2050,90],{"class":72},[55,2052,69],{"class":68},[55,2054,799],{"class":72},[55,2056,2057,2059,2061,2063],{"class":57,"line":84},[55,2058,87],{"class":68},[55,2060,103],{"class":72},[55,2062,69],{"class":68},[55,2064,2065],{"class":72}," AsyncEngine, create_async_engine\n",[55,2067,2068,2070,2072,2074],{"class":57,"line":98},[55,2069,87],{"class":68},[55,2071,90],{"class":72},[55,2073,69],{"class":68},[55,2075,544],{"class":72},[55,2077,2078],{"class":57,"line":111},[55,2079,115],{"emptyLinePlaceholder":114},[55,2081,2082,2084,2086,2089,2092,2094,2096,2098],{"class":57,"line":118},[55,2083,264],{"class":68},[55,2085,267],{"class":68},[55,2087,2088],{"class":270}," batch_size_sweep",[55,2090,2091],{"class":72},"(engine: AsyncEngine, all_rows: list[",[55,2093,277],{"class":169},[55,2095,970],{"class":72},[55,2097,973],{"class":169},[55,2099,292],{"class":72},[55,2101,2102,2104,2106],{"class":57,"line":130},[55,2103,298],{"class":72},[55,2105,124],{"class":68},[55,2107,303],{"class":72},[55,2109,2110,2113,2116,2118,2121,2124,2126,2129,2131,2134,2136,2139,2141,2144,2146,2149,2151,2154],{"class":57,"line":141},[55,2111,2112],{"class":68},"    for",[55,2114,2115],{"class":72}," chunk_size ",[55,2117,734],{"class":68},[55,2119,2120],{"class":72}," [",[55,2122,2123],{"class":169},"100",[55,2125,25],{"class":72},[55,2127,2128],{"class":169},"500",[55,2130,25],{"class":72},[55,2132,2133],{"class":169},"1_000",[55,2135,25],{"class":72},[55,2137,2138],{"class":169},"2_000",[55,2140,25],{"class":72},[55,2142,2143],{"class":169},"5_000",[55,2145,25],{"class":72},[55,2147,2148],{"class":169},"10_000",[55,2150,25],{"class":72},[55,2152,2153],{"class":169},"25_000",[55,2155,588],{"class":72},[55,2157,2158,2161,2163,2165,2167],{"class":57,"line":151},[55,2159,2160],{"class":68},"        async",[55,2162,323],{"class":68},[55,2164,326],{"class":72},[55,2166,329],{"class":68},[55,2168,332],{"class":72},[55,2170,2171,2174,2176,2178],{"class":57,"line":176},[55,2172,2173],{"class":68},"            await",[55,2175,612],{"class":72},[55,2177,1150],{"class":144},[55,2179,618],{"class":72},[55,2181,2182],{"class":57,"line":196},[55,2183,115],{"emptyLinePlaceholder":114},[55,2185,2186,2188,2190],{"class":57,"line":226},[55,2187,1049],{"class":72},[55,2189,124],{"class":68},[55,2191,314],{"class":72},[55,2193,2194,2196,2198,2200,2202],{"class":57,"line":250},[55,2195,2160],{"class":68},[55,2197,323],{"class":68},[55,2199,326],{"class":72},[55,2201,329],{"class":68},[55,2203,332],{"class":72},[55,2205,2206,2209,2211,2213,2215,2217,2220,2222,2225],{"class":57,"line":256},[55,2207,2208],{"class":68},"            for",[55,2210,706],{"class":72},[55,2212,734],{"class":68},[55,2214,737],{"class":169},[55,2216,387],{"class":72},[55,2218,2219],{"class":169},"0",[55,2221,25],{"class":72},[55,2223,2224],{"class":169},"len",[55,2226,2227],{"class":72},"(all_rows), chunk_size):\n",[55,2229,2230,2233,2236,2238],{"class":57,"line":261},[55,2231,2232],{"class":68},"                await",[55,2234,2235],{"class":72}," conn.execute(stmt, all_rows[i:i ",[55,2237,678],{"class":68},[55,2239,2240],{"class":72}," chunk_size])\n",[55,2242,2243,2245,2247,2249,2251],{"class":57,"line":295},[55,2244,1072],{"class":72},[55,2246,124],{"class":68},[55,2248,352],{"class":72},[55,2250,355],{"class":68},[55,2252,358],{"class":72},[55,2254,2255,2258,2260,2262,2265,2267,2270,2273,2275,2277,2279,2282,2284,2286,2288,2290,2292],{"class":57,"line":306},[55,2256,2257],{"class":169},"        print",[55,2259,387],{"class":72},[55,2261,390],{"class":68},[55,2263,2264],{"class":144},"\"chunk=",[55,2266,396],{"class":169},[55,2268,2269],{"class":72},"chunk_size",[55,2271,2272],{"class":68},":>6",[55,2274,402],{"class":169},[55,2276,405],{"class":144},[55,2278,434],{"class":169},[55,2280,2281],{"class":72},"(all_rows)",[55,2283,375],{"class":68},[55,2285,423],{"class":72},[55,2287,1103],{"class":68},[55,2289,402],{"class":169},[55,2291,1108],{"class":144},[55,2293,253],{"class":72},[14,2295,2296],{},"Typical results: throughput rises steeply from 100 to ~2,000 rows per chunk, then plateaus or slightly decreases above 10,000 due to memory allocation and WAL pressure. The optimal chunk size for your workload sits at the plateau's left edge.",[41,2298,2300],{"id":2299},"advanced-profiling-techniques","Advanced Profiling Techniques",[473,2302,2304,2305],{"id":2303},"correlating-python-timing-with-pg_stat_statements","Correlating Python Timing with ",[18,2306,1814],{},[14,2308,2309,2310,2312,2313,2315],{},"Python-side ",[18,2311,20],{}," measures wall time including event loop scheduling latency, GIL contention, and network jitter. For a clean server-side view, cross-reference with ",[18,2314,1814],{},":",[46,2317,2319],{"className":48,"code":2318,"language":50,"meta":51,"style":51},"from sqlalchemy import text\nfrom sqlalchemy.ext.asyncio import AsyncEngine\n\nasync def server_side_timing(engine: AsyncEngine, label: str) -> None:\n    \"\"\"Print server-side execution stats for recent INSERT statements.\"\"\"\n    async with engine.connect() as conn:\n        result = await conn.execute(text(\"\"\"\n            SELECT\n                left(query, 100)      AS query_preview,\n                calls,\n                round(mean_exec_time::numeric, 2) AS mean_ms,\n                rows \u002F calls          AS rows_per_call\n            FROM pg_stat_statements\n            WHERE query ILIKE '%INSERT%orders%'\n            ORDER BY total_exec_time DESC\n            LIMIT 5\n        \"\"\"))\n        print(f\"\\n=== {label} ===\")\n        for row in result:\n            print(f\"  {row.mean_ms}ms avg | {row.rows_per_call} rows\u002Fcall | {row.query_preview}\")\n",[18,2320,2321,2331,2341,2345,2365,2370,2382,2394,2398,2403,2407,2412,2417,2421,2429,2433,2438,2444,2471,2481],{"__ignoreMap":51},[55,2322,2323,2325,2327,2329],{"class":57,"line":58},[55,2324,87],{"class":68},[55,2326,90],{"class":72},[55,2328,69],{"class":68},[55,2330,544],{"class":72},[55,2332,2333,2335,2337,2339],{"class":57,"line":65},[55,2334,87],{"class":68},[55,2336,103],{"class":72},[55,2338,69],{"class":68},[55,2340,555],{"class":72},[55,2342,2343],{"class":57,"line":76},[55,2344,115],{"emptyLinePlaceholder":114},[55,2346,2347,2349,2351,2354,2357,2359,2361,2363],{"class":57,"line":84},[55,2348,264],{"class":68},[55,2350,267],{"class":68},[55,2352,2353],{"class":270}," server_side_timing",[55,2355,2356],{"class":72},"(engine: AsyncEngine, label: ",[55,2358,283],{"class":169},[55,2360,286],{"class":72},[55,2362,973],{"class":169},[55,2364,292],{"class":72},[55,2366,2367],{"class":57,"line":98},[55,2368,2369],{"class":144},"    \"\"\"Print server-side execution stats for recent INSERT statements.\"\"\"\n",[55,2371,2372,2374,2376,2378,2380],{"class":57,"line":111},[55,2373,320],{"class":68},[55,2375,323],{"class":68},[55,2377,1874],{"class":72},[55,2379,329],{"class":68},[55,2381,332],{"class":72},[55,2383,2384,2386,2388,2390,2392],{"class":57,"line":118},[55,2385,1455],{"class":72},[55,2387,124],{"class":68},[55,2389,1460],{"class":68},[55,2391,612],{"class":72},[55,2393,1891],{"class":144},[55,2395,2396],{"class":57,"line":130},[55,2397,1896],{"class":144},[55,2399,2400],{"class":57,"line":141},[55,2401,2402],{"class":144},"                left(query, 100)      AS query_preview,\n",[55,2404,2405],{"class":57,"line":151},[55,2406,1906],{"class":144},[55,2408,2409],{"class":57,"line":176},[55,2410,2411],{"class":144},"                round(mean_exec_time::numeric, 2) AS mean_ms,\n",[55,2413,2414],{"class":57,"line":196},[55,2415,2416],{"class":144},"                rows \u002F calls          AS rows_per_call\n",[55,2418,2419],{"class":57,"line":226},[55,2420,1921],{"class":144},[55,2422,2423,2425,2427],{"class":57,"line":250},[55,2424,1926],{"class":144},[55,2426,1929],{"class":169},[55,2428,1932],{"class":144},[55,2430,2431],{"class":57,"line":256},[55,2432,1937],{"class":144},[55,2434,2435],{"class":57,"line":261},[55,2436,2437],{"class":144},"            LIMIT 5\n",[55,2439,2440,2442],{"class":57,"line":295},[55,2441,1947],{"class":144},[55,2443,618],{"class":72},[55,2445,2446,2448,2450,2452,2454,2457,2460,2462,2464,2466,2469],{"class":57,"line":306},[55,2447,2257],{"class":169},[55,2449,387],{"class":72},[55,2451,390],{"class":68},[55,2453,393],{"class":144},[55,2455,2456],{"class":169},"\\n",[55,2458,2459],{"class":144},"=== ",[55,2461,396],{"class":169},[55,2463,399],{"class":72},[55,2465,402],{"class":169},[55,2467,2468],{"class":144}," ===\"",[55,2470,253],{"class":72},[55,2472,2473,2475,2477,2479],{"class":57,"line":317},[55,2474,729],{"class":68},[55,2476,1956],{"class":72},[55,2478,734],{"class":68},[55,2480,1961],{"class":72},[55,2482,2483,2485,2487,2489,2492,2494,2497,2499,2502,2504,2507,2509,2511,2513,2516,2518,2520],{"class":57,"line":335},[55,2484,1966],{"class":169},[55,2486,387],{"class":72},[55,2488,390],{"class":68},[55,2490,2491],{"class":144},"\"  ",[55,2493,396],{"class":169},[55,2495,2496],{"class":72},"row.mean_ms",[55,2498,402],{"class":169},[55,2500,2501],{"class":144},"ms avg | ",[55,2503,396],{"class":169},[55,2505,2506],{"class":72},"row.rows_per_call",[55,2508,402],{"class":169},[55,2510,1993],{"class":144},[55,2512,396],{"class":169},[55,2514,2515],{"class":72},"row.query_preview",[55,2517,402],{"class":169},[55,2519,393],{"class":144},[55,2521,253],{"class":72},[14,2523,1700,2524,2527,2528,2531,2532,2534,2535,2538],{},[18,2525,2526],{},"SELECT pg_stat_statements_reset()"," before each benchmark strategy to isolate its statistics. If ",[18,2529,2530],{},"rows_per_call"," is 1 for an insert you expect to be batched, ",[18,2533,1601],{}," is not activating — check that ",[18,2536,2537],{},".returning()"," is present and the dialect supports it.",[473,2540,2542],{"id":2541},"profiling-index-maintenance-cost","Profiling Index Maintenance Cost",[14,2544,2545],{},"Index maintenance during bulk inserts is often the dominant cost for tables with non-trivial indexes. Isolate this by benchmarking with and without indexes:",[46,2547,2549],{"className":48,"code":2548,"language":50,"meta":51,"style":51},"from sqlalchemy import text\nfrom sqlalchemy.ext.asyncio import AsyncEngine\nimport time\n\nasync def benchmark_with_without_indexes(engine: AsyncEngine, rows: list[dict]) -> None:\n    stmt = __import__(\"sqlalchemy\").insert(orders_table)\n\n    # Without indexes\n    async with engine.begin() as conn:\n        await conn.execute(text(\"DROP INDEX IF EXISTS idx_orders_customer\"))\n        await conn.execute(text(\"TRUNCATE orders RESTART IDENTITY\"))\n    t0 = time.perf_counter()\n    async with engine.begin() as conn:\n        await conn.execute(stmt, rows)\n    no_idx_time = time.perf_counter() - t0\n\n    # With index\n    async with engine.begin() as conn:\n        await conn.execute(text(\"CREATE INDEX idx_orders_customer ON orders(customer_id)\"))\n        await conn.execute(text(\"TRUNCATE orders RESTART IDENTITY\"))\n    t0 = time.perf_counter()\n    async with engine.begin() as conn:\n        await conn.execute(stmt, rows)\n    with_idx_time = time.perf_counter() - t0\n\n    overhead_pct = (with_idx_time - no_idx_time) \u002F no_idx_time * 100\n    print(f\"Index overhead: {overhead_pct:.1f}% slower ({no_idx_time:.2f}s → {with_idx_time:.2f}s)\")\n",[18,2550,2551,2561,2571,2577,2581,2600,2616,2620,2625,2637,2648,2658,2666,2678,2684,2697,2701,2706,2718,2729,2739,2747,2759,2765,2778,2782,2808],{"__ignoreMap":51},[55,2552,2553,2555,2557,2559],{"class":57,"line":58},[55,2554,87],{"class":68},[55,2556,90],{"class":72},[55,2558,69],{"class":68},[55,2560,544],{"class":72},[55,2562,2563,2565,2567,2569],{"class":57,"line":65},[55,2564,87],{"class":68},[55,2566,103],{"class":72},[55,2568,69],{"class":68},[55,2570,555],{"class":72},[55,2572,2573,2575],{"class":57,"line":76},[55,2574,69],{"class":68},[55,2576,81],{"class":72},[55,2578,2579],{"class":57,"line":84},[55,2580,115],{"emptyLinePlaceholder":114},[55,2582,2583,2585,2587,2590,2592,2594,2596,2598],{"class":57,"line":98},[55,2584,264],{"class":68},[55,2586,267],{"class":68},[55,2588,2589],{"class":270}," benchmark_with_without_indexes",[55,2591,274],{"class":72},[55,2593,277],{"class":169},[55,2595,970],{"class":72},[55,2597,973],{"class":169},[55,2599,292],{"class":72},[55,2601,2602,2604,2606,2609,2611,2613],{"class":57,"line":111},[55,2603,298],{"class":72},[55,2605,124],{"class":68},[55,2607,2608],{"class":169}," __import__",[55,2610,387],{"class":72},[55,2612,1144],{"class":144},[55,2614,2615],{"class":72},").insert(orders_table)\n",[55,2617,2618],{"class":57,"line":118},[55,2619,115],{"emptyLinePlaceholder":114},[55,2621,2622],{"class":57,"line":130},[55,2623,2624],{"class":61},"    # Without indexes\n",[55,2626,2627,2629,2631,2633,2635],{"class":57,"line":141},[55,2628,320],{"class":68},[55,2630,323],{"class":68},[55,2632,326],{"class":72},[55,2634,329],{"class":68},[55,2636,332],{"class":72},[55,2638,2639,2641,2643,2646],{"class":57,"line":151},[55,2640,338],{"class":68},[55,2642,612],{"class":72},[55,2644,2645],{"class":144},"\"DROP INDEX IF EXISTS idx_orders_customer\"",[55,2647,618],{"class":72},[55,2649,2650,2652,2654,2656],{"class":57,"line":176},[55,2651,338],{"class":68},[55,2653,612],{"class":72},[55,2655,1150],{"class":144},[55,2657,618],{"class":72},[55,2659,2660,2662,2664],{"class":57,"line":196},[55,2661,309],{"class":72},[55,2663,124],{"class":68},[55,2665,314],{"class":72},[55,2667,2668,2670,2672,2674,2676],{"class":57,"line":226},[55,2669,320],{"class":68},[55,2671,323],{"class":68},[55,2673,326],{"class":72},[55,2675,329],{"class":68},[55,2677,332],{"class":72},[55,2679,2680,2682],{"class":57,"line":250},[55,2681,338],{"class":68},[55,2683,341],{"class":72},[55,2685,2686,2689,2691,2693,2695],{"class":57,"line":256},[55,2687,2688],{"class":72},"    no_idx_time ",[55,2690,124],{"class":68},[55,2692,352],{"class":72},[55,2694,355],{"class":68},[55,2696,358],{"class":72},[55,2698,2699],{"class":57,"line":261},[55,2700,115],{"emptyLinePlaceholder":114},[55,2702,2703],{"class":57,"line":295},[55,2704,2705],{"class":61},"    # With index\n",[55,2707,2708,2710,2712,2714,2716],{"class":57,"line":306},[55,2709,320],{"class":68},[55,2711,323],{"class":68},[55,2713,326],{"class":72},[55,2715,329],{"class":68},[55,2717,332],{"class":72},[55,2719,2720,2722,2724,2727],{"class":57,"line":317},[55,2721,338],{"class":68},[55,2723,612],{"class":72},[55,2725,2726],{"class":144},"\"CREATE INDEX idx_orders_customer ON orders(customer_id)\"",[55,2728,618],{"class":72},[55,2730,2731,2733,2735,2737],{"class":57,"line":335},[55,2732,338],{"class":68},[55,2734,612],{"class":72},[55,2736,1150],{"class":144},[55,2738,618],{"class":72},[55,2740,2741,2743,2745],{"class":57,"line":344},[55,2742,309],{"class":72},[55,2744,124],{"class":68},[55,2746,314],{"class":72},[55,2748,2749,2751,2753,2755,2757],{"class":57,"line":361},[55,2750,320],{"class":68},[55,2752,323],{"class":68},[55,2754,326],{"class":72},[55,2756,329],{"class":68},[55,2758,332],{"class":72},[55,2760,2761,2763],{"class":57,"line":381},[55,2762,338],{"class":68},[55,2764,341],{"class":72},[55,2766,2767,2770,2772,2774,2776],{"class":57,"line":450},[55,2768,2769],{"class":72},"    with_idx_time ",[55,2771,124],{"class":68},[55,2773,352],{"class":72},[55,2775,355],{"class":68},[55,2777,358],{"class":72},[55,2779,2780],{"class":57,"line":459},[55,2781,115],{"emptyLinePlaceholder":114},[55,2783,2784,2787,2789,2792,2794,2797,2799,2802,2805],{"class":57,"line":464},[55,2785,2786],{"class":72},"    overhead_pct ",[55,2788,124],{"class":68},[55,2790,2791],{"class":72}," (with_idx_time ",[55,2793,355],{"class":68},[55,2795,2796],{"class":72}," no_idx_time) ",[55,2798,375],{"class":68},[55,2800,2801],{"class":72}," no_idx_time ",[55,2803,2804],{"class":68},"*",[55,2806,2807],{"class":169}," 100\n",[55,2809,2810,2812,2814,2816,2819,2821,2824,2827,2829,2832,2834,2837,2839,2841,2844,2846,2849,2851,2853,2856],{"class":57,"line":1061},[55,2811,384],{"class":169},[55,2813,387],{"class":72},[55,2815,390],{"class":68},[55,2817,2818],{"class":144},"\"Index overhead: ",[55,2820,396],{"class":169},[55,2822,2823],{"class":72},"overhead_pct",[55,2825,2826],{"class":68},":.1f",[55,2828,402],{"class":169},[55,2830,2831],{"class":144},"% slower (",[55,2833,396],{"class":169},[55,2835,2836],{"class":72},"no_idx_time",[55,2838,684],{"class":68},[55,2840,402],{"class":169},[55,2842,2843],{"class":144},"s → ",[55,2845,396],{"class":169},[55,2847,2848],{"class":72},"with_idx_time",[55,2850,684],{"class":68},[55,2852,402],{"class":169},[55,2854,2855],{"class":144},"s)\"",[55,2857,253],{"class":72},[14,2859,2860,2861,2864],{},"A single B-tree index on a narrow column typically adds 20–40% overhead. For tables with 5+ indexes, the index maintenance cost can exceed the raw insert cost. In those cases, consider dropping indexes before bulk load and rebuilding with ",[18,2862,2863],{},"CREATE INDEX CONCURRENTLY"," after commit — a standard ETL pattern.",[41,2866,2868],{"id":2867},"frequently-asked-questions","Frequently Asked Questions",[14,2870,2871,2875,2876,2879,2880,2883],{},[2872,2873,2874],"strong",{},"How do I measure only the database time, excluding Python serialization?","\nRecord the time immediately before and after ",[18,2877,2878],{},"await conn.execute(stmt, chunk)",", with the dict list pre-built outside the timed block. For server-side measurement, use ",[18,2881,2882],{},"pg_stat_statements.mean_exec_time"," per call — this excludes all client-side overhead including network RTT and Python result parsing.",[14,2885,2886,2892,2893,2895,2896,2898,2899,2901,2902,2904],{},[2872,2887,2888,2889,2891],{},"Should I use ",[18,2890,28],{}," in SQLAlchemy 2.0?","\nNo. ",[18,2894,28],{}," was an asyncpg-dialect-level parameter in SQLAlchemy 1.4. In 2.0 it is replaced by ",[18,2897,1601],{},", which is dialect-agnostic and automatically activated when ",[18,2900,511],{}," is present. If you see ",[18,2903,28],{}," in existing code, remove it during your 1.4 → 2.0 migration.",[14,2906,2907,2910,2911,2913,2914,2917,2918,2920],{},[2872,2908,2909],{},"What is a realistic baseline to expect on RDS PostgreSQL?","\nOn ",[18,2912,1529],{}," RDS over a private VPC connection, Core ",[18,2915,2916],{},"executemany"," with asyncpg typically delivers 70,000–120,000 rows\u002Fsec for 3–5 column rows without extra indexes. Add two additional indexes and expect a 30–50% reduction. ",[18,2919,1617],{}," COPY on the same hardware delivers 350,000–500,000 rows\u002Fsec. These ranges widen significantly with row width, index selectivity, and network RTT.",[14,2922,2923,2926],{},[2872,2924,2925],{},"How do I ensure my benchmark reflects production conditions?","\nUse the same instance class, same Postgres version, same indexes, same autovacuum settings, and same connection pool configuration as production. Run the benchmark during a simulated concurrent read load — bulk inserts under concurrent OLTP traffic behave differently than in an idle database due to lock contention and buffer cache pressure.",[14,2928,2929,2932,2933,2936],{},[2872,2930,2931],{},"Why does my benchmark show higher throughput on the second run than the first?","\nPostgreSQL's buffer cache is cold on the first run — the table and index pages must be loaded from disk. Subsequent runs find those pages already in shared buffers and operate entirely in memory. Always warm up with at least one full pass before recording results, and consider running benchmarks with ",[18,2934,2935],{},"pg_prewarm"," to pre-load table pages for a controlled comparison.",[41,2938,2940],{"id":2939},"related","Related",[488,2942,2943,2948,2955],{},[491,2944,2945,2947],{},[35,2946,38],{"href":37}," — complete guide to all bulk strategies including ON CONFLICT upsert, synchronize_session, and asyncpg COPY.",[491,2949,2950,2954],{},[35,2951,2953],{"href":2952},"\u002Fadvanced-query-patterns-and-bulk-data-operations\u002Fhigh-performance-bulk-inserts-and-updates\u002Fbatch-inserting-millions-of-rows-with-sqlalchemy-coreexecute\u002F","Batch Inserting Millions of Rows with SQLAlchemy Core"," — memory-safe generator patterns and per-chunk transaction management for production-scale inserts.",[491,2956,2957,2961],{},[35,2958,2960],{"href":2959},"\u002Fasync-engines-dialects-and-connection-pooling\u002Fconfiguring-async-engines-and-connection-pools\u002F","Configuring Async Engines and Connection Pools"," — pool sizing and driver configuration that directly affects bulk insert benchmark results.",[2963,2964,2965],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":51,"searchDepth":65,"depth":65,"links":2967},[2968,2969,2976,2977,2982,2987,2988],{"id":43,"depth":65,"text":44},{"id":470,"depth":65,"text":471,"children":2970},[2971,2972,2973,2975],{"id":475,"depth":76,"text":476},{"id":518,"depth":76,"text":519},{"id":755,"depth":76,"text":2974},"Strategy Comparison with time.perf_counter",{"id":1518,"depth":76,"text":1519},{"id":1630,"depth":65,"text":1631},{"id":1793,"depth":65,"text":1794,"children":2978},[2979,2981],{"id":1797,"depth":76,"text":2980},"Using EXPLAIN (ANALYZE, BUFFERS) to Confirm Strategy",{"id":2023,"depth":76,"text":2024},{"id":2299,"depth":65,"text":2300,"children":2983},[2984,2986],{"id":2303,"depth":76,"text":2985},"Correlating Python Timing with pg_stat_statements",{"id":2541,"depth":76,"text":2542},{"id":2867,"depth":65,"text":2868},{"id":2939,"depth":65,"text":2940},"Benchmark SQLAlchemy bulk inserts by wrapping each strategy in time.perf_counter() loops, isolating variables (batch size, row width, insertmanyvalues_page_size, executemany_mode), and confirming actual SQL shapes with EXPLAIN (ANALYZE, BUFFERS) — this gives reproducible throughput numbers rather than anecdotal estimates. All methodologies and production numbers below map back to the strategies covered in High-Performance Bulk Inserts and Updates.","md",{"date":2992},"2026-06-18","\u002Fadvanced-query-patterns-and-bulk-data-operations\u002Fhigh-performance-bulk-inserts-and-updates\u002Fbenchmarking-core-executemany-bulk-insert-performance",{"title":5,"description":2989},"advanced-query-patterns-and-bulk-data-operations\u002Fhigh-performance-bulk-inserts-and-updates\u002Fbenchmarking-core-executemany-bulk-insert-performance\u002Findex","Nl9hDvO6Eu6rP-lZcgi-5QKHIw5xH1sLe5d-r7J6xJw",1781810028980]