[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-post-en-\u002Fblog\u002Fupstreamads-wal-optimization-\u002Fen\u002Fblog\u002Fupstreamads-wal-optimization":3,"blog-post-surround-en-\u002Fblog\u002Fupstreamads-wal-optimization-\u002Fen\u002Fblog\u002Fupstreamads-wal-optimization":1335,"related-posts-en-\u002Fblog\u002Fupstreamads-wal-optimization-\u002Fen\u002Fblog\u002Fupstreamads-wal-optimization":1340},{"id":4,"title":5,"authors":6,"badge":13,"body":15,"categories":1288,"date":1290,"description":1291,"extension":1292,"image":1293,"meta":1295,"navigation":363,"path":1326,"readingTime":184,"seo":1327,"stem":1328,"tags":1329,"__hash__":1334},"posts_en\u002Fblog\u002F9.upstreamads-wal-optimization.md","UpstreamAds: Fixing Write Performance with WAL Optimization",[7],{"name":8,"to":9,"avatar":10,"bio":12},"Rob Schoenaker","https:\u002F\u002Flinkedin.com\u002Fin\u002Frobschoenaker",{"src":11},"\u002Fimages\u002Fteam\u002Frob.jpg","Managing Partner at UpstreamAds and Partner at Ludulicious B.V. with over 20 years of experience in software development, specializing in .NET Core, ServiceStack, C# and database design.",{"label":14},"Write Performance",{"type":16,"value":17,"toc":1261},"minimark",[18,23,27,33,49,54,90,101,105,108,113,127,131,136,139,188,193,231,237,241,244,249,275,280,294,300,304,307,332,337,385,390,413,418,422,426,429,459,463,466,610,614,628,633,637,641,644,696,700,703,874,878,895,900,904,1007,1011,1015,1026,1030,1041,1045,1056,1060,1071,1075,1086,1090,1093,1164,1168,1171,1174,1177,1197,1202,1210,1248,1251,1257],[19,20,22],"h2",{"id":21},"the-problem-write-performance-killing-user-experience","The Problem: Write Performance Killing User Experience",[24,25,26],"p",{},"In 2022, UpstreamAds faced a critical write performance issue. Advertisers creating new campaigns were waiting 500ms for their ad creatives to save. For a write-heavy application, this was completely unacceptable.",[24,28,29],{},[30,31,32],"strong",{},"The Challenge:",[34,35,36,40,43,46],"ul",{},[37,38,39],"li",{},"Write-heavy workload with frequent ad creative updates",[37,41,42],{},"Advertisers expecting instant save confirmations",[37,44,45],{},"WAL configuration optimized for reliability, not performance",[37,47,48],{},"No dedicated hardware for write operations",[24,50,51],{},[30,52,53],{},"The Numbers:",[55,56,61],"pre",{"className":57,"code":58,"language":59,"meta":60,"style":60},"language-sql shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","-- This INSERT was taking 500+ milliseconds\nINSERT INTO ad_creatives (title, description, status, created_at)\nVALUES ('New Diving Equipment Campaign', 'Amazing diving gear for professionals', 'active', NOW());\n-- Execution time: 500ms per insert\n","sql","",[62,63,64,72,78,84],"code",{"__ignoreMap":60},[65,66,69],"span",{"class":67,"line":68},"line",1,[65,70,71],{},"-- This INSERT was taking 500+ milliseconds\n",[65,73,75],{"class":67,"line":74},2,[65,76,77],{},"INSERT INTO ad_creatives (title, description, status, created_at)\n",[65,79,81],{"class":67,"line":80},3,[65,82,83],{},"VALUES ('New Diving Equipment Campaign', 'Amazing diving gear for professionals', 'active', NOW());\n",[65,85,87],{"class":67,"line":86},4,[65,88,89],{},"-- Execution time: 500ms per insert\n",[24,91,92],{},[93,94],"img",{"alt":95,"className":96,"height":98,"src":99,"width":100},"UpstreamAds WAL performance monitoring",[97],"rounded-lg",600,"https:\u002F\u002Fpicsum.photos\u002Fid\u002F11\u002F1000\u002F600",1000,[19,102,104],{"id":103},"the-root-cause-poor-wal-configuration","The Root Cause: Poor WAL Configuration",[24,106,107],{},"The problem was clear from our monitoring:",[24,109,110],{},[30,111,112],{},"What was happening:",[34,114,115,118,121,124],{},[37,116,117],{},"Default WAL settings optimized for reliability, not performance",[37,119,120],{},"No dedicated WAL disk",[37,122,123],{},"Checkpoint settings causing I\u002FO spikes",[37,125,126],{},"WAL constantly behind during peak usage",[19,128,130],{"id":129},"the-solution-optimized-wal-configuration","The Solution: Optimized WAL Configuration",[132,133,135],"h3",{"id":134},"step-1-optimize-wal-settings","Step 1: Optimize WAL Settings",[24,137,138],{},"The first breakthrough came with optimized WAL configuration:",[55,140,142],{"className":57,"code":141,"language":59,"meta":60,"style":60},"-- Production WAL settings that solved our problem\nwal_level = replica\nwal_buffers = 16MB                    -- Increased from default 16KB\ncheckpoint_completion_target = 0.9    -- Smoother checkpoints\ncheckpoint_timeout = 15min            -- Less frequent checkpoints\nmax_wal_size = 4GB                    -- More WAL space\nmin_wal_size = 1GB                    -- Faster recovery\nwal_compression = on                   -- Save disk space\n",[62,143,144,149,154,159,164,170,176,182],{"__ignoreMap":60},[65,145,146],{"class":67,"line":68},[65,147,148],{},"-- Production WAL settings that solved our problem\n",[65,150,151],{"class":67,"line":74},[65,152,153],{},"wal_level = replica\n",[65,155,156],{"class":67,"line":80},[65,157,158],{},"wal_buffers = 16MB                    -- Increased from default 16KB\n",[65,160,161],{"class":67,"line":86},[65,162,163],{},"checkpoint_completion_target = 0.9    -- Smoother checkpoints\n",[65,165,167],{"class":67,"line":166},5,[65,168,169],{},"checkpoint_timeout = 15min            -- Less frequent checkpoints\n",[65,171,173],{"class":67,"line":172},6,[65,174,175],{},"max_wal_size = 4GB                    -- More WAL space\n",[65,177,179],{"class":67,"line":178},7,[65,180,181],{},"min_wal_size = 1GB                    -- Faster recovery\n",[65,183,185],{"class":67,"line":184},8,[65,186,187],{},"wal_compression = on                   -- Save disk space\n",[24,189,190],{},[30,191,192],{},"Why These Settings Work:",[34,194,195,201,207,213,219,225],{},[37,196,197,200],{},[62,198,199],{},"wal_buffers = 16MB",": WAL data is buffered in memory before writing to disk, reducing I\u002FO operations",[37,202,203,206],{},[62,204,205],{},"checkpoint_completion_target = 0.9",": Spreads checkpoint I\u002FO over 90% of the checkpoint interval, preventing I\u002FO spikes",[37,208,209,212],{},[62,210,211],{},"checkpoint_timeout = 15min",": Longer intervals mean fewer checkpoints, reducing overall I\u002FO overhead",[37,214,215,218],{},[62,216,217],{},"max_wal_size = 4GB",": More WAL space allows PostgreSQL to delay checkpoints when system is busy",[37,220,221,224],{},[62,222,223],{},"wal_compression = on",": Compresses WAL data, reducing disk I\u002FO and storage requirements",[37,226,227,230],{},[62,228,229],{},"wal_level = replica",": Enables replication but doesn't log every statement, balancing performance and functionality",[24,232,233,236],{},[30,234,235],{},"Immediate Result:"," Write performance improved from 500ms to 200ms (2.5x improvement)",[132,238,240],{"id":239},"step-2-hardware-investment-for-wal-performance","Step 2: Hardware Investment for WAL Performance",[24,242,243],{},"The configuration changes helped, but hardware optimization was crucial:",[24,245,246],{},[30,247,248],{},"The Hardware Investment That Paid Off:",[34,250,251,257,263,269],{},[37,252,253,256],{},[30,254,255],{},"Battery-backed RAID controller",": Essential for WAL performance",[37,258,259,262],{},[30,260,261],{},"Separate WAL disk",": Dedicated SSD for WAL files",[37,264,265,268],{},[30,266,267],{},"RAID 1 for WAL",": Mirroring for reliability",[37,270,271,274],{},[30,272,273],{},"Fast SSD storage",": NVMe SSDs for WAL operations",[24,276,277],{},[30,278,279],{},"Why This Hardware Matters:",[34,281,282,285,288,291],{},[37,283,284],{},"Battery-backed RAID ensures data integrity during power failures",[37,286,287],{},"Dedicated WAL disk eliminates I\u002FO contention with data files",[37,289,290],{},"RAID 1 provides redundancy without performance penalty",[37,292,293],{},"NVMe SSDs provide low-latency, high-throughput storage",[24,295,296,299],{},[30,297,298],{},"Result:"," Write performance improved to 150ms (3.3x improvement)",[132,301,303],{"id":302},"step-3-connection-pooling-optimization","Step 3: Connection Pooling Optimization",[24,305,306],{},"With better WAL performance, connection management became the bottleneck:",[55,308,310],{"className":57,"code":309,"language":59,"meta":60,"style":60},"-- Connection settings optimization\nmax_connections = 200\nshared_preload_libraries = 'pg_stat_statements'\ntrack_activity_query_size = 2048\n",[62,311,312,317,322,327],{"__ignoreMap":60},[65,313,314],{"class":67,"line":68},[65,315,316],{},"-- Connection settings optimization\n",[65,318,319],{"class":67,"line":74},[65,320,321],{},"max_connections = 200\n",[65,323,324],{"class":67,"line":80},[65,325,326],{},"shared_preload_libraries = 'pg_stat_statements'\n",[65,328,329],{"class":67,"line":86},[65,330,331],{},"track_activity_query_size = 2048\n",[24,333,334],{},[30,335,336],{},"Using PgBouncer for Connection Pooling:",[55,338,342],{"className":339,"code":340,"language":341,"meta":60,"style":60},"language-ini shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","# pgbouncer.ini\n[databases]\nupstreamads = host=localhost port=5432 dbname=upstreamads pool_size=100\n\n[pgbouncer]\npool_mode = transaction\nmax_client_conn = 1000\ndefault_pool_size = 25\n","ini",[62,343,344,349,354,359,365,370,375,380],{"__ignoreMap":60},[65,345,346],{"class":67,"line":68},[65,347,348],{},"# pgbouncer.ini\n",[65,350,351],{"class":67,"line":74},[65,352,353],{},"[databases]\n",[65,355,356],{"class":67,"line":80},[65,357,358],{},"upstreamads = host=localhost port=5432 dbname=upstreamads pool_size=100\n",[65,360,361],{"class":67,"line":86},[65,362,364],{"emptyLinePlaceholder":363},true,"\n",[65,366,367],{"class":67,"line":166},[65,368,369],{},"[pgbouncer]\n",[65,371,372],{"class":67,"line":172},[65,373,374],{},"pool_mode = transaction\n",[65,376,377],{"class":67,"line":178},[65,378,379],{},"max_client_conn = 1000\n",[65,381,382],{"class":67,"line":184},[65,383,384],{},"default_pool_size = 25\n",[24,386,387],{},[30,388,389],{},"Why This Works:",[34,391,392,398,404,410],{},[37,393,394,397],{},[62,395,396],{},"pool_mode = transaction",": Reuses connections across transactions",[37,399,400,403],{},[62,401,402],{},"pool_size = 100",": Maintains 100 persistent connections to PostgreSQL",[37,405,406,409],{},[62,407,408],{},"max_client_conn = 1000",": Handles up to 1000 client connections",[37,411,412],{},"Eliminates connection overhead for frequent writes",[24,414,415,417],{},[30,416,298],{}," Write performance improved to 120ms (4.2x improvement)",[19,419,421],{"id":420},"the-game-changer-write-ahead-logging-monitoring","The Game Changer: Write-Ahead Logging Monitoring",[132,423,425],{"id":424},"the-problem-wal-performance-degradation","The Problem: WAL Performance Degradation",[24,427,428],{},"Even with optimization, WAL performance was inconsistent:",[55,430,432],{"className":57,"code":431,"language":59,"meta":60,"style":60},"-- Problem: WAL performance varied based on system load\nSELECT \n    pg_current_wal_lsn(),\n    pg_walfile_name(pg_current_wal_lsn()),\n    pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), '0\u002F0'));\n",[62,433,434,439,444,449,454],{"__ignoreMap":60},[65,435,436],{"class":67,"line":68},[65,437,438],{},"-- Problem: WAL performance varied based on system load\n",[65,440,441],{"class":67,"line":74},[65,442,443],{},"SELECT \n",[65,445,446],{"class":67,"line":80},[65,447,448],{},"    pg_current_wal_lsn(),\n",[65,450,451],{"class":67,"line":86},[65,452,453],{},"    pg_walfile_name(pg_current_wal_lsn()),\n",[65,455,456],{"class":67,"line":166},[65,457,458],{},"    pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), '0\u002F0'));\n",[132,460,462],{"id":461},"the-solution-automated-wal-monitoring","The Solution: Automated WAL Monitoring",[24,464,465],{},"We implemented comprehensive WAL monitoring:",[55,467,469],{"className":57,"code":468,"language":59,"meta":60,"style":60},"-- WAL performance monitoring view\nCREATE VIEW wal_performance AS\nSELECT \n    pg_current_wal_lsn() as current_lsn,\n    pg_walfile_name(pg_current_wal_lsn()) as current_wal_file,\n    pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), '0\u002F0')) as wal_size,\n    (SELECT count(*) FROM pg_stat_activity WHERE state = 'active') as active_connections,\n    (SELECT count(*) FROM pg_stat_activity WHERE state = 'idle in transaction') as idle_transactions;\n\n-- Automated WAL maintenance function\nCREATE OR REPLACE FUNCTION auto_wal_maintenance()\nRETURNS void AS $$\nBEGIN\n    -- Check WAL size and trigger maintenance if needed\n    IF (SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), '0\u002F0')) > 2 * 1024 * 1024 * 1024 THEN\n        PERFORM pg_switch_wal();\n    END IF;\n    \n    -- Update WAL statistics\n    PERFORM pg_stat_reset();\nEND;\n$$ LANGUAGE plpgsql;\n\n-- Schedule WAL maintenance every 5 minutes\nSELECT cron.schedule('wal-maintenance', '*\u002F5 * * * *', 'SELECT auto_wal_maintenance();');\n",[62,470,471,476,481,485,490,495,500,505,510,515,521,527,533,539,545,551,557,563,569,575,581,587,593,598,604],{"__ignoreMap":60},[65,472,473],{"class":67,"line":68},[65,474,475],{},"-- WAL performance monitoring view\n",[65,477,478],{"class":67,"line":74},[65,479,480],{},"CREATE VIEW wal_performance AS\n",[65,482,483],{"class":67,"line":80},[65,484,443],{},[65,486,487],{"class":67,"line":86},[65,488,489],{},"    pg_current_wal_lsn() as current_lsn,\n",[65,491,492],{"class":67,"line":166},[65,493,494],{},"    pg_walfile_name(pg_current_wal_lsn()) as current_wal_file,\n",[65,496,497],{"class":67,"line":172},[65,498,499],{},"    pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), '0\u002F0')) as wal_size,\n",[65,501,502],{"class":67,"line":178},[65,503,504],{},"    (SELECT count(*) FROM pg_stat_activity WHERE state = 'active') as active_connections,\n",[65,506,507],{"class":67,"line":184},[65,508,509],{},"    (SELECT count(*) FROM pg_stat_activity WHERE state = 'idle in transaction') as idle_transactions;\n",[65,511,513],{"class":67,"line":512},9,[65,514,364],{"emptyLinePlaceholder":363},[65,516,518],{"class":67,"line":517},10,[65,519,520],{},"-- Automated WAL maintenance function\n",[65,522,524],{"class":67,"line":523},11,[65,525,526],{},"CREATE OR REPLACE FUNCTION auto_wal_maintenance()\n",[65,528,530],{"class":67,"line":529},12,[65,531,532],{},"RETURNS void AS $$\n",[65,534,536],{"class":67,"line":535},13,[65,537,538],{},"BEGIN\n",[65,540,542],{"class":67,"line":541},14,[65,543,544],{},"    -- Check WAL size and trigger maintenance if needed\n",[65,546,548],{"class":67,"line":547},15,[65,549,550],{},"    IF (SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), '0\u002F0')) > 2 * 1024 * 1024 * 1024 THEN\n",[65,552,554],{"class":67,"line":553},16,[65,555,556],{},"        PERFORM pg_switch_wal();\n",[65,558,560],{"class":67,"line":559},17,[65,561,562],{},"    END IF;\n",[65,564,566],{"class":67,"line":565},18,[65,567,568],{},"    \n",[65,570,572],{"class":67,"line":571},19,[65,573,574],{},"    -- Update WAL statistics\n",[65,576,578],{"class":67,"line":577},20,[65,579,580],{},"    PERFORM pg_stat_reset();\n",[65,582,584],{"class":67,"line":583},21,[65,585,586],{},"END;\n",[65,588,590],{"class":67,"line":589},22,[65,591,592],{},"$$ LANGUAGE plpgsql;\n",[65,594,596],{"class":67,"line":595},23,[65,597,364],{"emptyLinePlaceholder":363},[65,599,601],{"class":67,"line":600},24,[65,602,603],{},"-- Schedule WAL maintenance every 5 minutes\n",[65,605,607],{"class":67,"line":606},25,[65,608,609],{},"SELECT cron.schedule('wal-maintenance', '*\u002F5 * * * *', 'SELECT auto_wal_maintenance();');\n",[24,611,612],{},[30,613,389],{},[34,615,616,619,622,625],{},[37,617,618],{},"Monitors WAL size and performance metrics",[37,620,621],{},"Automatically switches WAL files when they get too large",[37,623,624],{},"Resets statistics to maintain accurate monitoring",[37,626,627],{},"Runs maintenance during off-peak hours",[24,629,630,632],{},[30,631,298],{}," Consistent write performance of 100ms (5x improvement)",[19,634,636],{"id":635},"the-final-optimization-write-batching-strategy","The Final Optimization: Write Batching Strategy",[132,638,640],{"id":639},"the-problem-individual-write-operations","The Problem: Individual Write Operations",[24,642,643],{},"Even with optimized WAL, individual writes were still slow:",[55,645,649],{"className":646,"code":647,"language":648,"meta":60,"style":60},"language-csharp shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F Problem: Individual writes for each ad creative\nforeach (var creative in adCreatives)\n{\n    await _database.ExecuteAsync(\n        \"INSERT INTO ad_creatives (title, description, status) VALUES (@title, @description, @status)\",\n        creative\n    );\n    \u002F\u002F Each insert takes 100ms\n}\n","csharp",[62,650,651,656,661,666,671,676,681,686,691],{"__ignoreMap":60},[65,652,653],{"class":67,"line":68},[65,654,655],{},"\u002F\u002F Problem: Individual writes for each ad creative\n",[65,657,658],{"class":67,"line":74},[65,659,660],{},"foreach (var creative in adCreatives)\n",[65,662,663],{"class":67,"line":80},[65,664,665],{},"{\n",[65,667,668],{"class":67,"line":86},[65,669,670],{},"    await _database.ExecuteAsync(\n",[65,672,673],{"class":67,"line":166},[65,674,675],{},"        \"INSERT INTO ad_creatives (title, description, status) VALUES (@title, @description, @status)\",\n",[65,677,678],{"class":67,"line":172},[65,679,680],{},"        creative\n",[65,682,683],{"class":67,"line":178},[65,684,685],{},"    );\n",[65,687,688],{"class":67,"line":184},[65,689,690],{},"    \u002F\u002F Each insert takes 100ms\n",[65,692,693],{"class":67,"line":512},[65,694,695],{},"}\n",[132,697,699],{"id":698},"the-solution-batch-write-operations","The Solution: Batch Write Operations",[24,701,702],{},"We implemented batch writing:",[55,704,706],{"className":646,"code":705,"language":648,"meta":60,"style":60},"\u002F\u002F Solution: Batch writes for multiple ad creatives\npublic async Task\u003Cint> BatchInsertAdCreatives(List\u003CAdCreative> creatives)\n{\n    using var connection = _connectionPool.GetConnection();\n    using var transaction = connection.BeginTransaction();\n    \n    try\n    {\n        \u002F\u002F Use COPY for bulk inserts\n        using var writer = connection.BeginBinaryImport(\n            \"COPY ad_creatives (title, description, status, created_at) FROM STDIN WITH BINARY\"\n        );\n        \n        foreach (var creative in creatives)\n        {\n            writer.StartRow();\n            writer.Write(creative.Title);\n            writer.Write(creative.Description);\n            writer.Write(creative.Status);\n            writer.Write(DateTime.UtcNow);\n        }\n        \n        writer.Complete();\n        transaction.Commit();\n        \n        return creatives.Count;\n    }\n    catch\n    {\n        transaction.Rollback();\n        throw;\n    }\n}\n",[62,707,708,713,718,722,727,732,736,741,746,751,756,761,766,771,776,781,786,791,796,801,806,811,815,820,825,829,835,841,847,852,858,864,869],{"__ignoreMap":60},[65,709,710],{"class":67,"line":68},[65,711,712],{},"\u002F\u002F Solution: Batch writes for multiple ad creatives\n",[65,714,715],{"class":67,"line":74},[65,716,717],{},"public async Task\u003Cint> BatchInsertAdCreatives(List\u003CAdCreative> creatives)\n",[65,719,720],{"class":67,"line":80},[65,721,665],{},[65,723,724],{"class":67,"line":86},[65,725,726],{},"    using var connection = _connectionPool.GetConnection();\n",[65,728,729],{"class":67,"line":166},[65,730,731],{},"    using var transaction = connection.BeginTransaction();\n",[65,733,734],{"class":67,"line":172},[65,735,568],{},[65,737,738],{"class":67,"line":178},[65,739,740],{},"    try\n",[65,742,743],{"class":67,"line":184},[65,744,745],{},"    {\n",[65,747,748],{"class":67,"line":512},[65,749,750],{},"        \u002F\u002F Use COPY for bulk inserts\n",[65,752,753],{"class":67,"line":517},[65,754,755],{},"        using var writer = connection.BeginBinaryImport(\n",[65,757,758],{"class":67,"line":523},[65,759,760],{},"            \"COPY ad_creatives (title, description, status, created_at) FROM STDIN WITH BINARY\"\n",[65,762,763],{"class":67,"line":529},[65,764,765],{},"        );\n",[65,767,768],{"class":67,"line":535},[65,769,770],{},"        \n",[65,772,773],{"class":67,"line":541},[65,774,775],{},"        foreach (var creative in creatives)\n",[65,777,778],{"class":67,"line":547},[65,779,780],{},"        {\n",[65,782,783],{"class":67,"line":553},[65,784,785],{},"            writer.StartRow();\n",[65,787,788],{"class":67,"line":559},[65,789,790],{},"            writer.Write(creative.Title);\n",[65,792,793],{"class":67,"line":565},[65,794,795],{},"            writer.Write(creative.Description);\n",[65,797,798],{"class":67,"line":571},[65,799,800],{},"            writer.Write(creative.Status);\n",[65,802,803],{"class":67,"line":577},[65,804,805],{},"            writer.Write(DateTime.UtcNow);\n",[65,807,808],{"class":67,"line":583},[65,809,810],{},"        }\n",[65,812,813],{"class":67,"line":589},[65,814,770],{},[65,816,817],{"class":67,"line":595},[65,818,819],{},"        writer.Complete();\n",[65,821,822],{"class":67,"line":600},[65,823,824],{},"        transaction.Commit();\n",[65,826,827],{"class":67,"line":606},[65,828,770],{},[65,830,832],{"class":67,"line":831},26,[65,833,834],{},"        return creatives.Count;\n",[65,836,838],{"class":67,"line":837},27,[65,839,840],{},"    }\n",[65,842,844],{"class":67,"line":843},28,[65,845,846],{},"    catch\n",[65,848,850],{"class":67,"line":849},29,[65,851,745],{},[65,853,855],{"class":67,"line":854},30,[65,856,857],{},"        transaction.Rollback();\n",[65,859,861],{"class":67,"line":860},31,[65,862,863],{},"        throw;\n",[65,865,867],{"class":67,"line":866},32,[65,868,840],{},[65,870,872],{"class":67,"line":871},33,[65,873,695],{},[24,875,876],{},[30,877,389],{},[34,879,880,886,889,892],{},[37,881,882,885],{},[62,883,884],{},"COPY"," command is much faster than individual INSERTs",[37,887,888],{},"Binary format reduces data transfer overhead",[37,890,891],{},"Single transaction reduces WAL overhead",[37,893,894],{},"Batch processing reduces connection overhead",[24,896,897,899],{},[30,898,298],{}," Batch writes improved to 20ms per creative (25x improvement for batches)",[19,901,903],{"id":902},"performance-results-summary","Performance Results Summary",[905,906,907,923],"table",{},[908,909,910],"thead",{},[911,912,913,917,920],"tr",{},[914,915,916],"th",{},"Optimization Step",[914,918,919],{},"Write Time",[914,921,922],{},"Improvement",[924,925,926,940,953,966,979,992],"tbody",{},[911,927,928,934,937],{},[929,930,931],"td",{},[30,932,933],{},"Original (Default WAL)",[929,935,936],{},"500ms",[929,938,939],{},"Baseline",[911,941,942,947,950],{},[929,943,944],{},[30,945,946],{},"Optimized WAL Settings",[929,948,949],{},"200ms",[929,951,952],{},"2.5x faster",[911,954,955,960,963],{},[929,956,957],{},[30,958,959],{},"Hardware Optimization",[929,961,962],{},"150ms",[929,964,965],{},"3.3x faster",[911,967,968,973,976],{},[929,969,970],{},[30,971,972],{},"Connection Pooling",[929,974,975],{},"120ms",[929,977,978],{},"4.2x faster",[911,980,981,986,989],{},[929,982,983],{},[30,984,985],{},"WAL Monitoring",[929,987,988],{},"100ms",[929,990,991],{},"5x faster",[911,993,994,999,1002],{},[929,995,996],{},[30,997,998],{},"Batch Writing",[929,1000,1001],{},"20ms",[929,1003,1004],{},[30,1005,1006],{},"25x faster",[19,1008,1010],{"id":1009},"key-lessons-learned","Key Lessons Learned",[132,1012,1014],{"id":1013},"_1-wal-configuration-is-critical-for-write-performance","1. WAL Configuration Is Critical for Write Performance",[34,1016,1017,1020,1023],{},[37,1018,1019],{},"Default settings are optimized for reliability, not performance",[37,1021,1022],{},"Proper WAL configuration can dramatically improve write performance",[37,1024,1025],{},"Balance reliability with performance based on your needs",[132,1027,1029],{"id":1028},"_2-hardware-investment-pays-off","2. Hardware Investment Pays Off",[34,1031,1032,1035,1038],{},[37,1033,1034],{},"Battery-backed RAID controllers are essential for WAL performance",[37,1036,1037],{},"Dedicated WAL disks eliminate I\u002FO contention",[37,1039,1040],{},"Fast SSDs provide the low latency needed for write operations",[132,1042,1044],{"id":1043},"_3-connection-pooling-reduces-overhead","3. Connection Pooling Reduces Overhead",[34,1046,1047,1050,1053],{},[37,1048,1049],{},"PgBouncer eliminates connection overhead for frequent writes",[37,1051,1052],{},"Transaction-level pooling is ideal for write-heavy workloads",[37,1054,1055],{},"Proper connection limits prevent resource exhaustion",[132,1057,1059],{"id":1058},"_4-monitoring-prevents-performance-degradation","4. Monitoring Prevents Performance Degradation",[34,1061,1062,1065,1068],{},[37,1063,1064],{},"WAL performance can degrade over time",[37,1066,1067],{},"Automated monitoring and maintenance prevent issues",[37,1069,1070],{},"Regular statistics updates maintain accurate monitoring",[132,1072,1074],{"id":1073},"_5-batch-operations-scale-better","5. Batch Operations Scale Better",[34,1076,1077,1080,1083],{},[37,1078,1079],{},"Individual writes don't scale well",[37,1081,1082],{},"Batch operations reduce WAL overhead",[37,1084,1085],{},"COPY command is much faster than individual INSERTs",[19,1087,1089],{"id":1088},"implementation-checklist","Implementation Checklist",[24,1091,1092],{},"If you're facing similar write performance issues:",[34,1094,1097,1110,1119,1128,1137,1146,1155],{"className":1095},[1096],"contains-task-list",[37,1098,1101,1105,1106,1109],{"className":1099},[1100],"task-list-item",[1102,1103],"input",{"disabled":363,"type":1104},"checkbox"," ",[30,1107,1108],{},"Optimize WAL settings",": Configure for your workload",[37,1111,1113,1105,1115,1118],{"className":1112},[1100],[1102,1114],{"disabled":363,"type":1104},[30,1116,1117],{},"Invest in hardware",": Battery-backed RAID, dedicated WAL disk",[37,1120,1122,1105,1124,1127],{"className":1121},[1100],[1102,1123],{"disabled":363,"type":1104},[30,1125,1126],{},"Implement connection pooling",": Use PgBouncer for write-heavy workloads",[37,1129,1131,1105,1133,1136],{"className":1130},[1100],[1102,1132],{"disabled":363,"type":1104},[30,1134,1135],{},"Add WAL monitoring",": Track performance and automate maintenance",[37,1138,1140,1105,1142,1145],{"className":1139},[1100],[1102,1141],{"disabled":363,"type":1104},[30,1143,1144],{},"Consider batch operations",": Use COPY for bulk inserts",[37,1147,1149,1105,1151,1154],{"className":1148},[1100],[1102,1150],{"disabled":363,"type":1104},[30,1152,1153],{},"Monitor write performance",": Track response times and resource usage",[37,1156,1158,1105,1160,1163],{"className":1157},[1100],[1102,1159],{"disabled":363,"type":1104},[30,1161,1162],{},"Test under load",": Ensure performance holds during peak usage",[19,1165,1167],{"id":1166},"summary","Summary",[24,1169,1170],{},"Optimizing write performance in PostgreSQL requires a comprehensive approach. By combining optimized WAL configuration, hardware investment, connection pooling, automated monitoring, and batch operations, we achieved a 25x performance improvement for UpstreamAds write operations.",[24,1172,1173],{},"The key was understanding that write performance isn't just about database configuration—it's about creating a complete system optimized for write-heavy workloads, from hardware to application code.",[24,1175,1176],{},"If this article helped you understand write performance optimization, we can help you implement these techniques in your own applications. At Ludulicious, we specialize in:",[34,1178,1179,1185,1191],{},[37,1180,1181,1184],{},[30,1182,1183],{},"Write Performance Optimization",": WAL tuning and hardware optimization",[37,1186,1187,1190],{},[30,1188,1189],{},"Database Performance Optimization",": From slow queries to indexing strategies",[37,1192,1193,1196],{},[30,1194,1195],{},"Custom Development",": Tailored solutions for your specific use case",[24,1198,1199],{},[30,1200,1201],{},"Ready to optimize your write performance?",[24,1203,1204,1209],{},[1205,1206,1208],"a",{"href":1207},"\u002Fcontact","Contact us"," for a free consultation, or check out our other optimization guides:",[34,1211,1212,1218,1224,1230,1236,1242],{},[37,1213,1214],{},[1205,1215,1217],{"href":1216},"\u002Fblog\u002Fpostgresql-performance-strategy","PostgreSQL Performance Tuning: Strategic Lessons from Production",[37,1219,1220],{},[1205,1221,1223],{"href":1222},"\u002Fblog\u002Fduikersgids-spatial-search-optimization","Duikersgids: How I Made Spatial Search 55x Faster",[37,1225,1226],{},[1205,1227,1229],{"href":1228},"\u002Fblog\u002Frijmwoordenboek-phonetic-search-optimization","Rijmwoordenboek: Solving the 3-Second Phonetic Search Problem",[37,1231,1232],{},[1205,1233,1235],{"href":1234},"\u002Fblog\u002Frijmwoordenboek-caching-optimization","Rijmwoordenboek: Serving Pages Under 15ms with Better Caching",[37,1237,1238],{},[1205,1239,1241],{"href":1240},"\u002Fblog\u002Fupstreamads-fulltext-search-optimization","UpstreamAds: From 1.2s to 35ms Full-Text Search",[37,1243,1244],{},[1205,1245,1247],{"href":1246},"\u002Fblog\u002Fpostgresql-configuration-optimization","PostgreSQL Configuration: The Settings That Matter",[1249,1250],"hr",{},[24,1252,1253],{},[1254,1255,1256],"em",{},"This optimization case study is based on real production experience with UpstreamAds. All performance numbers are from actual production systems.",[1258,1259,1260],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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":60,"searchDepth":74,"depth":74,"links":1262},[1263,1264,1265,1270,1274,1278,1279,1286,1287],{"id":21,"depth":74,"text":22},{"id":103,"depth":74,"text":104},{"id":129,"depth":74,"text":130,"children":1266},[1267,1268,1269],{"id":134,"depth":80,"text":135},{"id":239,"depth":80,"text":240},{"id":302,"depth":80,"text":303},{"id":420,"depth":74,"text":421,"children":1271},[1272,1273],{"id":424,"depth":80,"text":425},{"id":461,"depth":80,"text":462},{"id":635,"depth":74,"text":636,"children":1275},[1276,1277],{"id":639,"depth":80,"text":640},{"id":698,"depth":80,"text":699},{"id":902,"depth":74,"text":903},{"id":1009,"depth":74,"text":1010,"children":1280},[1281,1282,1283,1284,1285],{"id":1013,"depth":80,"text":1014},{"id":1028,"depth":80,"text":1029},{"id":1043,"depth":80,"text":1044},{"id":1058,"depth":80,"text":1059},{"id":1073,"depth":80,"text":1074},{"id":1088,"depth":74,"text":1089},{"id":1166,"depth":74,"text":1167},[1289,14],"Database Optimization","2025-01-17","Learn how we optimized PostgreSQL write performance for UpstreamAds, improving ad creative save times from 500ms to 100ms using WAL configuration, hardware optimization, and connection pooling strategies.","md",{"src":1294},"https:\u002F\u002Fpicsum.photos\u002Fid\u002F11\u002F640\u002F360",{"schema":1296},{"type":1297,"name":5,"description":1298,"image":1294,"author":1299,"datePublished":1290,"dateModified":1290,"publisher":1300,"steps":1303,"totalTime":1322,"estimatedCost":1323},"HowTo","Learn how to optimize PostgreSQL WAL (Write-Ahead Logging) for high-write workloads, improving write performance by 300% using WAL configuration, checkpoint tuning, and storage optimization.",{"name":8,"url":9},{"name":1301,"url":1302},"Ludulicious B.V.","https:\u002F\u002Fludulicious.nl",[1304,1307,1310,1313,1316,1319],{"name":1305,"text":1306},"Analyze Write Performance Issues","Identify WAL-related bottlenecks in high-write workloads",{"name":1308,"text":1309},"Optimize WAL Configuration","Configure WAL buffers, segments, and checkpoint settings",{"name":1311,"text":1312},"Tune Checkpoint Parameters","Adjust checkpoint timing and completion targets",{"name":1314,"text":1315},"Optimize Storage Configuration","Configure storage for optimal WAL performance",{"name":1317,"text":1318},"Implement WAL Archiving","Set up WAL archiving for backup and recovery",{"name":1320,"text":1321},"Monitor WAL Performance","Track WAL performance metrics and optimize further","PT1D",{"currency":1324,"value":1325},"EUR","4000","\u002Fblog\u002Fupstreamads-wal-optimization",{"title":5,"description":1291},"blog\u002F9.upstreamads-wal-optimization",[1330,1331,14,959,972,1332,1333],"PostgreSQL","WAL Optimization","Performance Optimization","UpstreamAds","nBGuJPeDDSjKGPmyoJG8NfbgZFxw9yrcwUJBF-rxUaw",[1336,1339],{"title":1241,"path":1240,"stem":1337,"description":1338,"children":-1},"blog\u002F8.upstreamads-fulltext-search-optimization","Learn how we optimized PostgreSQL full-text search for UpstreamAds, reducing search times from 1.2 seconds to 35ms using pre-computed tsvector indexes, multi-language strategies, and partial indexing.",null,[]]