Write Performance··8 min read

UpstreamAds: Write Performance Oplossen Met WAL Optimalisatie

Leer hoe we PostgreSQL write performance optimaliseerden voor UpstreamAds, waarbij we ad creative save tijden verbeterden van 500ms naar 100ms met WAL configuratie, hardware optimalisatie en connection pooling strategieën.

Categories

Database OptimalisatieWrite Performance

Tags

PostgreSQLWAL OptimalisatieWrite PerformanceHardware OptimalisatieConnection PoolingPerformance OptimalisatieUpstreamAds

About the Author

Author avatar

Rob Schoenaker

Managing Partner bij UpstreamAds en Partner bij Ludulicious B.V. met meer dan 20 jaar ervaring in softwareontwikkeling, gespecialiseerd in .NET Core, ServiceStack, C# en database design.

Share:

Het Probleem: Write Performance Doodt Gebruikerservaring

In 2022 stond UpstreamAds voor een kritieke write performance uitdaging. Adverteerders die nieuwe campagnes aanmaakten wachtten 500ms voordat hun ad creatives werden opgeslagen. Voor een write-heavy applicatie was dit volledig onacceptabel.

De Uitdaging:

  • Write-heavy workload met frequente ad creative updates
  • Adverteerders verwachten directe save bevestigingen
  • WAL configuratie geoptimaliseerd voor betrouwbaarheid, niet performance
  • Geen dedicated hardware voor write operaties

De Cijfers:

-- Deze INSERT duurde 500+ milliseconden
INSERT INTO ad_creatives (title, description, status, created_at)
VALUES ('New Diving Equipment Campaign', 'Amazing diving gear for professionals', 'active', NOW());
-- Uitvoeringstijd: 500ms per insert

De Oorzaak: Slechte WAL Configuratie

Het probleem was duidelijk uit onze monitoring:

Wat er gebeurde:

  • Default WAL instellingen geoptimaliseerd voor betrouwbaarheid, niet performance
  • Geen dedicated WAL disk
  • Checkpoint instellingen veroorzaakten I/O spikes
  • WAL constant achter tijdens piekgebruik

De Oplossing: Geoptimaliseerde WAL Configuratie

Stap 1: Optimaliseer WAL Instellingen

De eerste doorbraak kwam met geoptimaliseerde WAL configuratie:

-- Productie WAL instellingen die ons probleem oplosten
wal_level = replica
wal_buffers = 16MB                    -- Verhoogd van default 16KB
checkpoint_completion_target = 0.9    -- Vloeiendere checkpoints
checkpoint_timeout = 15min            -- Minder frequente checkpoints
max_wal_size = 4GB                    -- Meer WAL ruimte
min_wal_size = 1GB                    -- Snellere recovery
wal_compression = on                   -- Bespaar disk ruimte

Waarom Deze Instellingen Werken:

  • wal_buffers = 16MB: WAL data wordt gebufferd in geheugen voordat het naar disk wordt geschreven, vermindert I/O operaties
  • checkpoint_completion_target = 0.9: Spreidt checkpoint I/O over 90% van het checkpoint interval, voorkomt I/O spikes
  • checkpoint_timeout = 15min: Langere intervallen betekenen minder checkpoints, vermindert totale I/O overhead
  • max_wal_size = 4GB: Meer WAL ruimte laat PostgreSQL toe checkpoints uit te stellen wanneer systeem druk is
  • wal_compression = on: Comprimeert WAL data, vermindert disk I/O en storage vereisten
  • wal_level = replica: Schakelt replicatie in maar logt niet elke statement, balanceert performance en functionaliteit

Direct Resultaat: Write performance verbeterde van 500ms naar 200ms (2.5x verbetering)

Stap 2: Hardware Investering Voor WAL Performance

De configuratie wijzigingen hielpen, maar hardware optimalisatie was cruciaal:

De Hardware Investering Die Uitbetaalde:

  • Battery-backed RAID controller: Essentieel voor WAL performance
  • Separate WAL disk: Dedicated SSD voor WAL bestanden
  • RAID 1 voor WAL: Mirroring voor betrouwbaarheid
  • Snelle SSD storage: NVMe SSDs voor WAL operaties

Waarom Deze Hardware Belangrijk Is:

  • Battery-backed RAID zorgt voor data integriteit tijdens stroomuitval
  • Dedicated WAL disk elimineert I/O contention met data bestanden
  • RAID 1 biedt redundantie zonder performance penalty
  • NVMe SSDs bieden lage latency, hoge throughput storage

Resultaat: Write performance verbeterde naar 150ms (3.3x verbetering)

Stap 3: Connection Pooling Optimalisatie

Met betere WAL performance werd connection management de bottleneck:

-- Connection instellingen optimalisatie
max_connections = 200
shared_preload_libraries = 'pg_stat_statements'
track_activity_query_size = 2048

PgBouncer Gebruiken Voor Connection Pooling:

# pgbouncer.ini
[databases]
upstreamads = host=localhost port=5432 dbname=upstreamads pool_size=100

[pgbouncer]
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 25

Waarom Dit Werkt:

  • pool_mode = transaction: Hergebruikt connections over transacties
  • pool_size = 100: Onderhoudt 100 persistente connections naar PostgreSQL
  • max_client_conn = 1000: Handelt tot 1000 client connections
  • Elimineert connection overhead voor frequente writes

Resultaat: Write performance verbeterde naar 120ms (4.2x verbetering)

De Game Changer: Write-Ahead Logging Monitoring

Het Probleem: WAL Performance Degradatie

Zelfs met optimalisatie was WAL performance inconsistent:

-- Probleem: WAL performance varieerde op basis van systeembelasting
SELECT 
    pg_current_wal_lsn(),
    pg_walfile_name(pg_current_wal_lsn()),
    pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), '0/0'));

De Oplossing: Geautomatiseerde WAL Monitoring

We implementeerden uitgebreide WAL monitoring:

-- WAL performance monitoring view
CREATE VIEW wal_performance AS
SELECT 
    pg_current_wal_lsn() as current_lsn,
    pg_walfile_name(pg_current_wal_lsn()) as current_wal_file,
    pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), '0/0')) as wal_size,
    (SELECT count(*) FROM pg_stat_activity WHERE state = 'active') as active_connections,
    (SELECT count(*) FROM pg_stat_activity WHERE state = 'idle in transaction') as idle_transactions;

-- Geautomatiseerde WAL maintenance functie
CREATE OR REPLACE FUNCTION auto_wal_maintenance()
RETURNS void AS $$
BEGIN
    -- Controleer WAL grootte en trigger maintenance indien nodig
    IF (SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), '0/0')) > 2 * 1024 * 1024 * 1024 THEN
        PERFORM pg_switch_wal();
    END IF;
    
    -- Update WAL statistieken
    PERFORM pg_stat_reset();
END;
$$ LANGUAGE plpgsql;

-- Plan WAL maintenance elke 5 minuten
SELECT cron.schedule('wal-maintenance', '*/5 * * * *', 'SELECT auto_wal_maintenance();');

Waarom Dit Werkt:

  • Monitort WAL grootte en performance metrics
  • Schakelt automatisch WAL bestanden wanneer ze te groot worden
  • Reset statistieken om accurate monitoring te behouden
  • Draait maintenance tijdens off-peak uren

Resultaat: Consistente write performance van 100ms (5x verbetering)

De Finale Optimalisatie: Write Batching Strategie

Het Probleem: Individuele Write Operaties

Zelfs met geoptimaliseerde WAL waren individuele writes nog steeds langzaam:

// Probleem: Individuele writes voor elke ad creative
foreach (var creative in adCreatives)
{
    await _database.ExecuteAsync(
        "INSERT INTO ad_creatives (title, description, status) VALUES (@title, @description, @status)",
        creative
    );
    // Elke insert duurt 100ms
}

De Oplossing: Batch Write Operaties

We implementeerden batch writing:

// Oplossing: Batch writes voor meerdere ad creatives
public async Task<int> BatchInsertAdCreatives(List<AdCreative> creatives)
{
    using var connection = _connectionPool.GetConnection();
    using var transaction = connection.BeginTransaction();
    
    try
    {
        // Gebruik COPY voor bulk inserts
        using var writer = connection.BeginBinaryImport(
            "COPY ad_creatives (title, description, status, created_at) FROM STDIN WITH BINARY"
        );
        
        foreach (var creative in creatives)
        {
            writer.StartRow();
            writer.Write(creative.Title);
            writer.Write(creative.Description);
            writer.Write(creative.Status);
            writer.Write(DateTime.UtcNow);
        }
        
        writer.Complete();
        transaction.Commit();
        
        return creatives.Count;
    }
    catch
    {
        transaction.Rollback();
        throw;
    }
}

Waarom Dit Werkt:

  • COPY commando is veel sneller dan individuele INSERTs
  • Binary formaat vermindert data transfer overhead
  • Single transaction vermindert WAL overhead
  • Batch processing vermindert connection overhead

Resultaat: Batch writes verbeterden naar 20ms per creative (25x verbetering voor batches)

Performance Resultaten Samenvatting

Optimalisatie StapWrite TijdVerbetering
Origineel (Default WAL)500msBaseline
Geoptimaliseerde WAL Instellingen200ms2.5x sneller
Hardware Optimalisatie150ms3.3x sneller
Connection Pooling120ms4.2x sneller
WAL Monitoring100ms5x sneller
Batch Writing20ms25x sneller

Belangrijkste Lessen Geleerd

1. WAL Configuratie Is Kritiek Voor Write Performance

  • Default instellingen zijn geoptimaliseerd voor betrouwbaarheid, niet performance
  • Juiste WAL configuratie kan write performance dramatisch verbeteren
  • Balanceer betrouwbaarheid met performance op basis van je behoeften

2. Hardware Investering Betaalt Zich Uit

  • Battery-backed RAID controllers zijn essentieel voor WAL performance
  • Dedicated WAL disks elimineren I/O contention
  • Snelle SSDs bieden de lage latency nodig voor write operaties

3. Connection Pooling Vermindert Overhead

  • PgBouncer elimineert connection overhead voor frequente writes
  • Transaction-level pooling is ideaal voor write-heavy workloads
  • Juiste connection limits voorkomen resource uitputting

4. Monitoring Voorkomt Performance Degradatie

  • WAL performance kan over tijd degraderen
  • Geautomatiseerde monitoring en maintenance voorkomen problemen
  • Regelmatige statistiek updates behouden accurate monitoring

5. Batch Operaties Schalen Beter

  • Individuele writes schalen niet goed
  • Batch operaties verminderen WAL overhead
  • COPY commando is veel sneller dan individuele INSERTs

Implementatie Checklist

Als je vergelijkbare write performance problemen hebt:

  • Optimaliseer WAL instellingen: Configureer voor je workload
  • Investeer in hardware: Battery-backed RAID, dedicated WAL disk
  • Implementeer connection pooling: Gebruik PgBouncer voor write-heavy workloads
  • Voeg WAL monitoring toe: Track performance en automatiseer maintenance
  • Overweeg batch operaties: Gebruik COPY voor bulk inserts
  • Monitor write performance: Track responsetijden en resource gebruik
  • Test onder belasting: Zorg dat performance behouden blijft tijdens piekgebruik

Samenvatting

Het optimaliseren van write performance in PostgreSQL vereist een uitgebreide aanpak. Door geoptimaliseerde WAL configuratie, hardware investering, connection pooling, geautomatiseerde monitoring en batch operaties te combineren, bereikten we een 25x performance verbetering voor UpstreamAds write operaties.

De sleutel was begrijpen dat write performance niet alleen gaat over database configuratie—het gaat over het creëren van een compleet systeem geoptimaliseerd voor write-heavy workloads, van hardware tot applicatie code.

Als dit artikel je hielp write performance optimalisatie te begrijpen, kunnen we je helpen deze technieken te implementeren in je eigen applicaties. Bij Ludulicious specialiseren we ons in:

  • Write Performance Optimalisatie: WAL tuning en hardware optimalisatie
  • Database Performance Optimalisatie: Van langzame queries tot indexering strategieën
  • Custom Development: Op maat gemaakte oplossingen voor je specifieke use case

Klaar om je write performance te optimaliseren?

Neem contact op voor een gratis consultatie, of bekijk onze andere optimalisatie gidsen:


Deze optimalisatie case study is gebaseerd op echte productie ervaring met UpstreamAds. Alle performance cijfers zijn van echte productie systemen.