Efficiënte Database Opschoning: Problemen Oplossen en Toekomstige Beheerstrategieën

Oplossing (Samenvatting)

Details zijn hieronder uitgewerkt.

  1. Verwijder onnodige bestanden:
    • Verwijder logbestanden en andere bestanden die niet meer nodig zijn.
  2. Optimaliseer de database:
    REINDEX TABLE job_jobarchive;

    Als alternatief kun je de index verwijderen:

    DROP INDEX [index_name] ON job_jobarchive;
  3. Opschonen en analyseren:
    VACUUM ANALYZE job_jobarchive;
  4. Gebruik pg_repack:

    In de shell, als gebruiker postgres, voer het volgende uit om de tabel te herstructureren:

    pg_repack -d dbname -t job_jobarchive



Terwijl

VACUUM FULL job_jobarchive;

Werkt niet omdat er een foutmelding optreedt.


user_de=# VACUUM FULL job_jobarchive;
ERROR:  could not extend file "base/98628770/1388599947.114": wrote only 4096 of 8192 bytes at block 14990040
HINT:  Check free disk space.

Dus we waren een aantal dagen aan het experimenteren en zijn gegaan van

Filesystem      Size  Used Avail Use% Mounted on
tmpfs            13G  892K   13G   1% /run
/dev/md2        934G  886G  381M 100% /
tmpfs            63G     0   63G   0% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock
tmpfs           4.0M     0  4.0M   0% /sys/fs/cgroup
/dev/nvme1n1    938G  733G  158G  83% /media/backup
/dev/md1        487M   82M  381M  18% /boot

naar

Filesystem      Size  Used Avail Use% Mounted on
tmpfs            13G  1.1M   13G   1% /run
/dev/md2        934G  231G  656G  27% /
tmpfs            63G  6.2M   63G   1% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock
tmpfs           4.0M     0  4.0M   0% /sys/fs/cgroup
/dev/nvme1n1    938G  733G  158G  83% /media/backup
/dev/md1        487M   82M  381M  18% /boot
tmpfs            13G     0   13G   0% /run/user/0

Dat ging erg goed.

Maar wat heb ik precies gedaan, en wat moet ik nog automatiseren om dit in de toekomst te voorkomen?


service pgpool2 status

Inloggen

psql -h localhost -U username -d dbname

Verwijder alle records die zijn gesloten, offline zijn gezet en geen contactgegevens van bedrijven bevatten. Deze worden via XML door een derde partij gepubliceerd en vervolgens gearchiveerd.

DELETE
FROM job_jobarchive
WHERE  (email IS NULL OR email = '') AND for_index = FALSE;


SELECT COUNT(*)
FROM job_jobarchive
WHERE  (email IS NULL OR email = '') AND for_index = FALSE;

SELECT COUNT(*)
FROM job_jobarchive
WHERE  for_index = FALSE;

SELECT COUNT(id)
FROM job_jobarchive
WHERE payed  = FALSE;


De logica is dat we een query uitvoeren waarbij alle clicks en views worden geüpdatet voor gearchiveerde jobs op basis van hun jobID, zodat...



SELECT COUNT(*)
FROM job_jobarchive
WHERE payed = TRUE;

Toon alle jobs in het archief die ooit direct of indirect betaald zijn aangeklikt en in het verleden verkeer genereerden toen ze nog open waren.


user_de=> SELECT COUNT(*)
user-> FROM job_jobarchive
user-> WHERE payed = TRUE;
  count  
---------
 7410945
(1 row)


Met deze query kan ik alle jobs in het archief identificeren die ooit verkeer hebben gegenereerd. Alleen de aangeduide jobs blijven behouden voor beveiligings- en statistische doeleinden, terwijl de rest wordt verwijderd."
 

UPDATE job_jobarchive
SET payed = TRUE
WHERE job_id IN (SELECT id FROM viewjob_viewjob);

Ja, inderdaad, een kleine verwijdering was niet voldoende. Ik moest niet alleen logs en dergelijke verwijderen, maar ook meer grondig te werk gaan.

ERROR:  could not extend file "base/98628770/143848844.118": wrote only 4096 of 8192 bytes at block 15532936
HINT:  Check free disk space.



Filesystem      Size  Used Avail Use% Mounted on
tmpfs            13G  892K   13G   1% /run
/dev/md2        934G  886G  381M 100% /
tmpfs            63G     0   63G   0% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock
tmpfs           4.0M     0  4.0M   0% /sys/fs/cgroup
/dev/nvme1n1    938G  733G  158G  83% /media/backup
/dev/md1        487M   82M  381M  18% /boot


Verwijderen laste 7 dagen.


DELETE FROM job_jobarchive
WHERE added >= date_trunc('week', current_date)
  AND added < date_trunc('week', current_date) + interval '7 days';

  pg_repack -d dbname -t job_jobarchive

Het probleem is dat de database in een staat verkeert waarin VACUUM niet meer kan worden uitgevoerd. Daardoor levert het verwijderen van data geen vrijgekomen ruimte op in de database.

Ik moet dus op de een of andere manier alsnog VACUUM uitvoeren.

Mijn plan is om eerst Elastic te stoppen en de archive-index te verwijderen, aangezien deze 87 GB in beslag neemt. In deze configuratie draaien naast de databaseservers ook Elastic-instanties voor betere prestaties en horizontale schaalbaarheid.


  curl -X GET "http://138.201.8.xxx:9200/_cat/indices?v"


root@Ubuntu-2010-groovy-64-minimal /var # curl -X DELETE "http://138.201.8.xxx:9200/archive"
{"acknowledged":true}root@Ubuntu-2010-groovy-64-minimal /var # curl -X GET "http://138.201.8.xxx:9200/_cat/indices?v"
                         
 
dan verwijderen wat er over blijft.


Filesystem      Size  Used Avail Use% Mounted on
tmpfs            13G  900K   13G   1% /run
/dev/md2        934G  799G   88G  91% /
tmpfs            63G     0   63G   0% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock
tmpfs           4.0M     0  4.0M   0% /sys/fs/cgroup
/dev/nvme1n1    938G  733G  158G  83% /media/backup
/dev/md1        487M   82M  381M  18% /boot
tmpfs            13G     0   13G   0% /run/user/0


          
                         
Er is nog steeds niet genoeg ruimte beschikbaar om VACUUM FULL of pg_repack uit te voeren.
                         
postgres@Ubuntu-2010-groovy-64-minimal:~$ pg_repack -d dbname -t job_jobarchive
INFO: repacking table "public.job_jobarchive"
ERROR: query failed: ERROR:  could not extend file "base/98628770/1333137786.97": wrote only 4096 of 8192 bytes at block 12748168
HINT:  Check free disk space.
DETAIL: query was:
postgres@Ubuntu-2010-groovy-64-minimal:~$
postgres@Ubuntu-2010-groovy-64-minimal:~$ psql -U postgres -d dbname
                         
                         
                         df -h
Filesystem      Size  Used Avail Use% Mounted on
tmpfs            13G  1.1M   13G   1% /run
/dev/md2        934G  760G  127G  86% /
tmpfs            63G  256K   63G   1% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock
tmpfs           4.0M     0  4.0M   0% /sys/fs/cgroup
/dev/nvme1n1    938G  733G  158G  83% /media/backup
/dev/md1        487M   82M  381M  18% /boot
tmpfs            13G     0   13G   0% /run/user/0
                         
                         

                                    
                          schemaname |   tablename    | table_size | bloat_size | total_size
------------+----------------+------------+------------+------------
 public     | job_jobarchive | 646 GB     | 6045 MB    | 651 GB
                         
                         
                      

Er is momenteel slechts 6 GB vrije ruimte beschikbaar. Ik moet een mechanisme ontwikkelen om alles grondig op te schonen en ongeveer 90% van de data te verwijderen. Alleen gegevens met e-mails en gegevens die aangeklikt zijn, moeten behouden blijven.

We kunnen dit doen zodra we zien dat het verwijderen van data daadwerkelijk resulteert in een afname van de databasegrootte. Op dit moment verwijder ik data, maar de grootte blijft hetzelfde. Wel heb ik een index verwijderd, wat direct 10 GB aan ruimte opleverde.

Vervolgens moet ik al mijn tabellen met statistieken één voor één nalopen en aanduiden welke records payed = True moeten worden in het archief. Dit maakt duidelijk dat deze records in het archief mogen blijven.

Jobs met een e-mailadres worden automatisch gemarkeerd als payed = True, omdat ze contactgegevens bevatten en betalende klanten vertegenwoordigen.

Payed = True in het archief betekent in dit geval dat het record belangrijk is en behouden moet blijven.

                         

                         For_index = False hoeveel?
                         
                        
                         
De oplossing om dergelijke problemen in de toekomst te voorkomen is door records die essentieel zijn te markeren met payed = true. Dit zorgt ervoor dat alleen belangrijke gegevens behouden blijven, terwijl overige data efficiënt verwijderd kan worden. Zo blijft de database beheersbaar en geoptimaliseerd.                    

+ Alle posts die momenteel een e-mailadres bevatten, zijn er 10 miljoen.

  • Tabellen die hierbij betrokken zijn:
    • viewjob_viewjob
    • applicationclickscount
    • spontaneousapplication


 
                         
 SELECT COUNT(*)  FROM job_jobarchive WHERE payed = TRUE;
  count   
----------
 18809826
        
                         
                         SELECT
    column_name,
    data_type,
    is_nullable,
    character_maximum_length
FROM
    information_schema.columns
WHERE
    table_name = 'spontaneous_spontaneousapplication';
                         
                         

                         UPDATE job_jobarchive
SET payed = TRUE
WHERE job_id IN (
    SELECT job_id
    FROM viewjob_viewjob
);
                         
                         
                         UPDATE job_jobarchive
SET payed = TRUE
WHERE job_id IN (
    SELECT job_id
    FROM job_applicationclickscount
);
                         
                         UPDATE job_jobarchive
SET payed = TRUE
WHERE job_id IN (
    SELECT job_id
    FROM spontaneous_spontaneousapplication
);
                         
                                         

                         
                         UPDATE job_jobarchive
SET payed = TRUE
WHERE email IS NOT NULL AND email != '';
    
    
       

DELETE FROM job_jobarchive
WHERE payed = FALSE

 

bloat_info              

                         
 WITH bloat_info AS (
    SELECT
        nspname AS schemaname,
        relname AS tablename,
        pg_size_pretty(pg_table_size(C.oid)) AS table_size,
        pg_size_pretty(pg_total_relation_size(C.oid) - pg_table_size(C.oid)) AS bloat_size,
        pg_size_pretty(pg_total_relation_size(C.oid)) AS total_size
    FROM
        pg_class C
    JOIN
        pg_namespace N ON (C.relnamespace = N.oid)
    WHERE
        C.relkind = 'r' AND N.nspname NOT IN ('pg_catalog', 'information_schema')
)
SELECT * FROM bloat_info WHERE tablename = 'your_table_name';


Server met informatie Archive leeg maken omdat  Postgresql heeft volledige disk heeft ingenomen. Zo los je het op.

PostgreSQL noodherstel: grote archieftabellen verplaatsen naar aparte disk met tablespace

Deze case beschrijft hoe ik een PostgreSQL database opnieuw stabiel kreeg toen de hoofddisk van de server bijna vol liep door zeer grote archieftabellen. Het doel was:

  • de site terug online krijgen
  • de grootste tabellen van de root disk halen
  • ruimte terug vrijmaken voor het OS
  • later retention deletes en compaction verderzetten

Situatie

De root disk zat bijna vol en PostgreSQL-tabellen namen extreem veel ruimte in. Een eerste controle toonde onder meer:

SELECT relname AS table,
         pg_size_pretty(pg_total_relation_size(relid)) AS size
  FROM pg_catalog.pg_statio_user_tables
  ORDER BY pg_total_relation_size(relid) DESC
  LIMIT 20;
  

Belangrijkste tabellen:

  • job_jobarchive ongeveer 574 GB
  • job_archiveretentionsignal ongeveer 97 GB
  • job_job ongeveer 43 GB
  • job_jobarchive_locations ongeveer 32 GB

Filesystem status op het kritiek moment:

df -h
  

Root was bijna vol, terwijl een extra disk beschikbaar was op /media/backup.

Eerste analyse

Voor job_jobarchive heb ik eerst nagegaan hoeveel rows en hoe de ruimte verdeeld was:

SELECT count(*) AS rows
  FROM job_jobarchive;

  SELECT
    pg_size_pretty(pg_relation_size('job_jobarchive')) AS table_size,
    pg_size_pretty(pg_indexes_size('job_jobarchive')) AS index_size,
    pg_size_pretty(pg_total_relation_size('job_jobarchive')) AS total_size;
  

Resultaat:

  • ongeveer 73,705,494 rows
  • heap/table: ongeveer 92 GB
  • indexen: ongeveer 3.1 GB
  • totaal: ongeveer 470 GB

Dat betekende meteen dat de ruimte niet in de indexen zat, maar vooral in TOAST. Daarna:

SELECT
    c.reltoastrelid::regclass AS toast_table
  FROM pg_class c
  WHERE c.relname = 'job_jobarchive';

  SELECT
    pg_size_pretty(pg_total_relation_size('pg_toast.pg_toast_143848844')) AS toast_total_size;
  

De TOAST-tabel bleek ongeveer 375 GB te zijn.

Daarna heb ik gekeken welke tekstvelden dat verklaarden:

SELECT
    count(*) AS total_rows,
    count(description) AS rows_with_description,
    count(raw_text) AS rows_with_raw_text,
    count(raw_text2) AS rows_with_raw_text2
  FROM job_jobarchive;

  SELECT
    round(avg(length(coalesce(description, '')))) AS avg_description_len,
    round(avg(length(coalesce(raw_text, '')))) AS avg_raw_text_len,
    round(avg(length(coalesce(raw_text2, '')))) AS avg_raw_text2_len,
    max(length(coalesce(description, ''))) AS max_description_len,
    max(length(coalesce(raw_text, ''))) AS max_raw_text_len,
    max(length(coalesce(raw_text2, ''))) AS max_raw_text2_len
  FROM job_jobarchive;
  

Conclusie:

  • description stond op alle rows
  • raw_text stond op alle rows
  • raw_text2 slechts op een minderheid
  • de echte opslagvreter was dus de combinatie description + raw_text

Waarom pg_repack eerst faalde

Na het verplaatsen van de grote archieftabel naar een aparte disk probeerde ik:

sudo -u postgres pg_repack -d vindazo_de -t job_jobarchive
  

Dat faalde met:

ERROR: could not extend file "pg_tblspc/..."
  HINT: Check free disk space.
  

De reden: pg_repack maakt een nieuwe compacte kopie van de tabel in de huidige tablespace van die tabel. Omdat job_jobarchive al in een tablespace op de aparte disk stond, moest er daar voldoende vrije ruimte zijn voor een nieuwe compacte kopie. Dat was op dat moment niet het geval.

Oplossing: tabel tijdelijk repacken op root en daarna terug verplaatsen

De effectieve oplossing was:

  1. de grote archieftabel eerst van root naar een aparte disk verplaatsen
  2. ruimte terug op root vrijmaken
  3. een tijdelijke tablespace op root gebruiken voor repack
  4. daarna eventueel opnieuw terug verplaatsen naar de aparte archive disk

Stap 1: aparte tablespace maken op extra disk

Ik heb een aparte PostgreSQL tablespace aangemaakt op /media/backup. Die disk blijft permanent bruikbaar voor archieftabellen.

sudo mkdir -p /media/backup/pg_tblspc_archive_cold
  sudo chown postgres:postgres /media/backup/pg_tblspc_archive_cold
  sudo chmod 700 /media/backup/pg_tblspc_archive_cold

  sudo -u postgres psql -d postgres -c "CREATE TABLESPACE archive_cold LOCATION '/media/backup/pg_tblspc_archive_cold';"
  

Stap 2: grote archieftabel naar aparte disk verplaatsen

sudo -u postgres psql -d vindazo_de -c "ALTER TABLE job_jobarchive SET TABLESPACE archive_cold;"
  

Daarmee verhuisde de tabel fysiek naar de extra disk, en de root disk kreeg opnieuw ademruimte.

Stap 3: tijdelijke repack tablespace op root maken

Omdat pg_repack op de archive disk faalde door te weinig vrije ruimte, heb ik tijdelijk een tablespace op root gemaakt:

sudo mkdir -p /var/lib/postgresql/ts_repack_root
  sudo chown postgres:postgres /var/lib/postgresql/ts_repack_root
  sudo chmod 700 /var/lib/postgresql/ts_repack_root

  sudo -u postgres psql -d postgres -c "CREATE TABLESPACE repack_root LOCATION '/var/lib/postgresql/ts_repack_root';"
  

Stap 4: job_jobarchive repacken naar repack_root

sudo -u postgres pg_repack -d vindazo_de -t job_jobarchive --tablespace=repack_root
  

Belangrijk: --tablespace=repack_root is niet alleen tijdelijke scratch space. Het eindresultaat van de gerepackte tabel komt effectief in die opgegeven tablespace terecht.

Dat betekent:

  • de tabel wordt compact herschreven
  • de oude grote versie verdwijnt
  • ruimte in de vorige tablespace komt vrij

Na deze stap kwam ongeveer 100 GB vrij.

Stap 5: controleren waar tabellen nu staan

SELECT
    c.relname,
    COALESCE(t.spcname, 'pg_default') AS tablespace
  FROM pg_class c
  LEFT JOIN pg_tablespace t ON t.oid = c.reltablespace
  WHERE c.relname IN (
    'job_jobarchive',
    'job_archiveretentionsignal',
    'job_jobarchive_locations',
    'job_jobarchive_categories'
  )
  ORDER BY c.relname;
  

Retention deletes opnieuw opstarten

Om oude archive rows die al op decision='delete' stonden effectief te verwijderen, gebruikte ik deze Django command:

cd /home/sites/deployment/vindazo2/branches/de_vindazo
  python manage.py purge_archive_retention_deletes --batch-size=500 --delete-signals --yes
  

Belangrijk:

  • --delete-signals verwijdert ook de signal rows uit job_archiveretentionsignal
  • zonder --yes is het alleen dry-run

Vooraf kun je controleren hoeveel nog gemarkeerd staat:

SELECT decision, count(*)
  FROM job_archiveretentionsignal
  GROUP BY decision
  ORDER BY decision;

  SELECT count(*)
  FROM job_jobarchive a
  JOIN job_archiveretentionsignal s ON s.job_id = a.job_id
  WHERE s.decision = 'delete';
  

Retention decisions opnieuw berekenen

Als de evaluatie opnieuw gestart moest worden:

cd /home/sites/deployment/vindazo2/branches/de_vindazo
  python manage.py evaluate_archive_retention --min-age-days 180
  

Voor alles volledig opnieuw herberekenen:

python manage.py evaluate_archive_retention --min-age-days 180 --recheck-all
  

Controle van ontbrekende database-indexen

Tijdens troubleshooting bleek dat er veel PostgreSQL-indexen ontbraken, onder meer op job_archiveretentionsignal. Een snelle check kan via:

python manage.py sqldiff job -t
  

Of voor alle apps:

python manage.py sqldiff -a -t
  

Daarnaast is deze SQL nuttig om huidige indexen te inspecteren:

SELECT schemaname, tablename, indexname, indexdef
  FROM pg_indexes
  WHERE tablename IN ('job_job', 'job_archiveretentionsignal', 'job_jobarchive_locations')
  ORDER BY tablename, indexname;
  

Belangrijke lessen

  • Gewone VACUUM of autovacuum geeft meestal geen OS-space terug. Het maakt intern vrije ruimte, maar krimpt het bestand niet.
  • pg_repack en VACUUM FULL herschrijven de tabel wel. Daarvoor is tijdelijke extra ruimte nodig.
  • Als pg_repack faalt in een tablespace, controleer dan vrije ruimte in die specifieke tablespace.
  • Gebruik tablespaces om grote archieftabellen van de root disk te halen.
  • Draai grote repacks één voor één, niet parallel.
  • Controleer eerst of het probleem in indexen, heap of TOAST zit. In deze case zat het echte probleem bijna volledig in TOAST.

Belangrijkste commando’s uit deze case

Schijfruimte controleren

df -h
  

Grootste tabellen zoeken

SELECT relname AS table,
         pg_size_pretty(pg_total_relation_size(relid)) AS size
  FROM pg_catalog.pg_statio_user_tables
  ORDER BY pg_total_relation_size(relid) DESC
  LIMIT 20;
  

Tablespace op aparte disk maken

sudo mkdir -p /media/backup/pg_tblspc_archive_cold
  sudo chown postgres:postgres /media/backup/pg_tblspc_archive_cold
  sudo chmod 700 /media/backup/pg_tblspc_archive_cold
  sudo -u postgres psql -d postgres -c "CREATE TABLESPACE archive_cold LOCATION '/media/backup/pg_tblspc_archive_cold';"
  

Tabel verplaatsen naar aparte disk

sudo -u postgres psql -d vindazo_de -c "ALTER TABLE job_jobarchive SET TABLESPACE archive_cold;"
  

Tijdelijke repack tablespace op root maken

sudo mkdir -p /var/lib/postgresql/ts_repack_root
  sudo chown postgres:postgres /var/lib/postgresql/ts_repack_root
  sudo chmod 700 /var/lib/postgresql/ts_repack_root
  sudo -u postgres psql -d postgres -c "CREATE TABLESPACE repack_root LOCATION '/var/lib/postgresql/ts_repack_root';"
  

Tabel repacken naar repack_root

sudo -u postgres pg_repack -d vindazo_de -t job_jobarchive --tablespace=repack_root
  

Retention delete uitvoeren

cd /home/sites/deployment/vindazo2/branches/de_vindazo
  python manage.py purge_archive_retention_deletes --batch-size=500 --delete-signals --yes
  

Retention beslissingen herberekenen

python manage.py evaluate_archive_retention --min-age-days 180
  

Samenvatting

De kern van het herstel was niet simpelweg “vacuum draaien”, maar:

  1. begrijpen waar de ruimte echt zat
  2. de grootste archieftabel naar een aparte disk verplaatsen
  3. tijdelijke werkruimte voorzien voor pg_repack
  4. retention deletes opnieuw opstarten
  5. missing indexes en verdere optimalisatie daarna aanpakken

In deze case bleek de grootste boosdoener niet de indexen te zijn, maar enorme TOAST-opslag door de combinatie van description en raw_text op tientallen miljoenen archive rows.

PostgreSQL archiefopruiming op twee productie-servers: hoe we honderden GB terugwonnen

Deze case beschrijft hoe we op twee productie-omgevingen grote hoeveelheden schijfruimte hebben teruggewonnen door PostgreSQL archieftabellen, retention-signals, sessies en logging op te ruimen.

De algemene aanpak was op beide servers hetzelfde:

  1. eerst analyseren welke database echt actief is
  2. daarna bepalen welke tabellen de meeste ruimte innemen
  3. retention-data en overbodige signal rows verwijderen
  4. pas daarna VACUUM FULL of pg_repack gebruiken
  5. ook niet-database oorzaken zoals PostgreSQL logbestanden en Django sessions meenemen

1. Eerste les: verifieer altijd de echte actieve database

De eerste valkuil was eenvoudig: handmatig inloggen op PostgreSQL betekent niet automatisch dat je dezelfde database bekijkt als de webapplicatie.

Daarom moet de analyse altijd beginnen vanuit Django zelf:

python manage.py dbshell
  

Of expliciet:

python manage.py shell -c "from django.conf import settings; print(settings.DATABASES['default'])"
  

Belangrijke les: als je handmatig in de verkeerde database inlogt, analyseer en vacuum je de verkeerde omgeving.

2. Controleer waar PostgreSQL fysiek draait

Op een van de servers draaide PostgreSQL niet op de root filesystem, maar op een aparte mount. Dat verklaarde meteen waarom de grootste druk niet op / zat, maar op een andere schijf.

De juiste controle is:

sudo -u postgres psql -d postgres -c "SHOW data_directory;"
  

Daarna kun je die locatie koppelen aan het juiste filesystem met:

df -h /pad/uit/data_directory
  

Dit is belangrijk, want anders kun je denken dat een databaseprobleem op root zit, terwijl de echte datadir elders staat.

3. Analyseer eerst de grootste tabellen

Op beide servers begon de analyse met dezelfde query:

SELECT relname AS table,
         pg_size_pretty(pg_total_relation_size(relid)) AS size
  FROM pg_catalog.pg_statio_user_tables
  ORDER BY pg_total_relation_size(relid) DESC
  LIMIT 20;
  

De grootste tabellen waren typisch:

  • job_jobarchive
  • job_archiveretentionsignal
  • job_jobarchive_locations
  • django_session
  • crawler_page
  • mailing_message

Belangrijke les: niet alleen archieftabellen zijn groot. Ook sessies, mailingtabellen en crawllogs kunnen tientallen GB innemen.

4. Retention-signals analyseren voor je iets verwijdert

Voor archief-retentie gebruikten we eerst deze queries:

SELECT decision, count(*)
  FROM job_archiveretentionsignal
  GROUP BY decision
  ORDER BY decision;
  
SELECT count(*)
  FROM job_jobarchive a
  JOIN job_archiveretentionsignal s ON s.job_id = a.job_id
  WHERE s.decision = 'delete';
  

Dat gaf een belangrijk onderscheid:

  • soms waren er nog miljoenen rows met decision='delete'
  • maar de gekoppelde archive-vacatures zelf waren al weg

In zo’n geval bevat job_archiveretentionsignal alleen nog restdata, en is het logisch om vooral die signal rows op te schonen.

5. Wanneer pure SQL beter is dan een management command

Als de gekoppelde archive rows al verwijderd zijn, is pure SQL vaak sneller en eenvoudiger dan een ORM-gestuurde cleanup.

Voorbeeld:

DELETE FROM job_archiveretentionsignal
  WHERE decision = 'delete';
  

Dit is efficiënter dan een langlopende management command als:

  • de archive rows al weg zijn
  • je alleen nog delete-signals wilt opruimen

Na de delete volgt compaction:

VACUUM FULL VERBOSE ANALYZE job_archiveretentionsignal;
  

6. Django sessions groeien niet vanzelf weg

Een tweede grote verrassing was hoe groot django_session was geworden. In één geval nam die tabel tientallen GB in.

Django verwijdert expired sessions niet automatisch. Daarvoor moet je expliciet:

python manage.py clearsessions
  

Of, als je iedereen mag uitloggen:

TRUNCATE TABLE django_session;
  

Daarna opnieuw fysiek compacter maken:

VACUUM FULL VERBOSE ANALYZE django_session;
  

7. Wanneer pg_repack goed werkt, en wanneer niet

pg_repack is ideaal wanneer je tabellen compact wilt herschrijven zonder langdurige blokkering, maar het is niet altijd beschikbaar op oudere of afwijkend geconfigureerde servers.

Typische command:

sudo -u postgres pg_repack -d DBNAME -t job_jobarchive
  

Als pg_repack niet beschikbaar is, blijft de veilige fallback:

VACUUM FULL VERBOSE ANALYZE job_jobarchive;
  

Belangrijk verschil:

  • pg_repack houdt de tabel grotendeels beschikbaar tijdens de operatie
  • VACUUM FULL lockt de tabel volledig

8. Groot verborgen probleem: PostgreSQL logfiles

Op een van de servers bleek een enorm deel van de gebruikte schijfruimte niet in de tabellen te zitten, maar in PostgreSQL logbestanden onder pg_log.

Dat was zichtbaar via de datadir-analyse: de logmap was groter dan de databestanden zelf.

Controle van de log settings:

SHOW logging_collector;
  SHOW log_directory;
  SHOW log_statement;
  SHOW log_min_duration_statement;
  SHOW log_duration;
  SHOW log_min_messages;
  SHOW log_min_error_statement;
  

De instellingen waren niet extreem verbose, maar:

  • logging_collector stond aan
  • logs werden lokaal binnen PGDATA geschreven
  • er was geen cleanup-retentie

Op andere servers stond logging_collector uit. Daardoor groeide daar geen gigantische lokale logmap.

Praktische cleanup:

sudo -u postgres psql -d postgres -c "SELECT pg_rotate_logfile();"
  sudo find /pad/naar/pg_log -type f -mtime +7 -delete
  

Belangrijke les: als pg_log binnen de PostgreSQL datadir staat, kan logging zelf de schijf vullen waarop ook je database draait.

9. Resultaten

Na de opruiming op de eerste server daalde het gebruik van de betrokken schijf van zeer hoog naar comfortabel laag. Er kwam ruwweg meer dan de helft van de gebruikte ruimte terug vrij.

Op de tweede server daalde de bezetting van de root filesystem eveneens sterk, met meer dan honderd GB extra vrije ruimte als resultaat.

De exacte getallen verschillen per omgeving, maar het patroon was hetzelfde:

  • retention-signals opruimen
  • sessies opschonen
  • zware tabellen compact maken
  • overmatige PostgreSQL logs verwijderen

10. Veiligste volgorde van werken

De veiligste werkwijze was uiteindelijk deze:

  1. controleer eerst welke database de applicatie echt gebruikt
  2. controleer waar PostgreSQL fysiek draait via SHOW data_directory;
  3. haal de grootste tabellen op
  4. analyseer job_archiveretentionsignal en kijk of archive rows echt nog bestaan
  5. verwijder eerst delete-signals of andere obvious restdata
  6. ruim Django sessions op
  7. ruim overmatige PostgreSQL logs op als die binnen PGDATA zitten
  8. gebruik pas daarna VACUUM FULL of pg_repack

11. Handige SQL en commands uit deze case

Actieve database via Django

python manage.py shell -c "from django.conf import settings; print(settings.DATABASES['default'])"
  

PostgreSQL datadir

sudo -u postgres psql -d postgres -c "SHOW data_directory;"
  

Grootste tabellen

SELECT relname AS table,
         pg_size_pretty(pg_total_relation_size(relid)) AS size
  FROM pg_catalog.pg_statio_user_tables
  ORDER BY pg_total_relation_size(relid) DESC
  LIMIT 20;
  

Retention status

SELECT decision, count(*)
  FROM job_archiveretentionsignal
  GROUP BY decision
  ORDER BY decision;
  
SELECT count(*)
  FROM job_jobarchive a
  JOIN job_archiveretentionsignal s ON s.job_id = a.job_id
  WHERE s.decision = 'delete';
  

Delete signal rows wanneer archive rows al weg zijn

DELETE FROM job_archiveretentionsignal
  WHERE decision = 'delete';
  

Django sessions opschonen

python manage.py clearsessions
  

VACUUM FULL voorbeelden

VACUUM FULL VERBOSE ANALYZE job_archiveretentionsignal;
  VACUUM FULL VERBOSE ANALYZE django_session;
  VACUUM FULL VERBOSE ANALYZE job_jobarchive;
  VACUUM FULL VERBOSE ANALYZE job_jobarchive_locations;
  VACUUM FULL VERBOSE ANALYZE job_jobarchive_categories;
  

PostgreSQL logs roteren en opruimen

sudo -u postgres psql -d postgres -c "SELECT pg_rotate_logfile();"
  sudo find /pad/naar/pg_log -type f -mtime +7 -delete
  

Conclusie

De grootste fout die je in dit soort situaties kunt maken, is blind gaan vacuumen zonder eerst te begrijpen waar de ruimte echt zit.

Je moet eerst weten:

  • welke database actief is
  • op welke schijf PostgreSQL werkelijk draait
  • welke tabellen nog zinvolle data bevatten
  • welke tabellen alleen nog restdata of signal rows bevatten
  • of niet-databasebestanden zoals pg_log het echte probleem zijn

In deze cases bleek de grootste winst te komen uit:

  • het verwijderen van oude retention-signals
  • het opschonen van Django sessions
  • het compacter maken van zware tabellen via VACUUM FULL of pg_repack
  • het opruimen van PostgreSQL logs op servers waar lokaal verzameld werd

Het resultaat was dat beide productie-omgevingen weer veel veiliger qua vrije schijfruimte kwamen te staan, zonder blinde of onnodig destructieve stappen.

PostgreSQL archiefopruiming op NL: waarom VACUUM FULL hier beter werkte dan pg_repack

In deze case ging het om een grote productie-database waar archieftabellen, retention-signals, sessies en andere zware tabellen veel ruimte innamen. De eerste gedachte was om pg_repack te gebruiken, maar in de praktijk bleek VACUUM FULL hier de betere keuze.

De reden was eenvoudig:

  • pg_repack houdt de tabel grotendeels online, maar blijft wachten als er nog actieve queries op de tabel lopen
  • daardoor kan een live site of achtergrondproces de operatie eindeloos ophouden
  • VACUUM FULL neemt zelf de lock, stopt het verkeer op die tabel expliciet, en werkt daardoor in deze situatie voorspelbaarder en sneller

Voor een druk gebruikte productieomgeving is dat een belangrijk verschil. pg_repack is eleganter als de tabel rustig is, maar als de tabel constant gebruikt wordt, kan VACUUM FULL in de praktijk juist sneller klaar zijn omdat het niet blijft hangen op terugkerende queries.

Situatie vóór de cleanup

Filesystem gebruik vóór de operatie:

df -h
  Filesystem                           Size  Used Avail Use% Mounted on
  tmpfs                                 13G  1.3G   12G  11% /run
  /dev/md2                             1.8T  780G  886G  47% /
  tmpfs                                 63G  636K   63G   1% /dev/shm
  tmpfs                                5.0M     0  5.0M   0% /run/lock
  /dev/md1                             989M  385M  554M  41% /boot
  //u266537.your-storagebox.de/backup  2.0T  798G  1.2T  40% /mnt/backupbox
  tmpfs                                 13G     0   13G   0% /run/user/0
  

Grootste tabellen vóór de cleanup:

SELECT relname AS table,
         pg_size_pretty(pg_total_relation_size(relid)) AS size
  FROM pg_catalog.pg_statio_user_tables
  ORDER BY pg_total_relation_size(relid) DESC
  LIMIT 20;
  
table                         | size
  -----------------------------+---------
  job_jobarchive               | 455 GB
  job_archiveretentionsignal   | 53 GB
  job_job                      | 42 GB
  mailing_queue                | 32 GB
  trafficinsights_httplogevent | 22 GB
  django_session               | 20 GB
  crawler_page                 | 13 GB
  job_jobarchive_locations     | 12 GB
  

Belangrijke retention-analyse

Voor we begonnen met compaction, werd eerst gecontroleerd of de retention-signals nog gekoppeld waren aan echte archive rows.

SELECT decision, count(*)
  FROM job_archiveretentionsignal
  GROUP BY decision
  ORDER BY decision;
  

Resultaat:

decision | count
  ---------+----------
  delete   | 62363096
  keep     | 19946581
  

Daarna werd gecontroleerd of die delete-signals nog aan echte archive rows hingen:

SELECT count(*)
  FROM job_jobarchive a
  JOIN job_archiveretentionsignal s ON s.job_id = a.job_id
  WHERE s.decision = 'delete';
  

Resultaat:

count
  ------
  0
  

Dit was een cruciale observatie. Het betekende:

  • de archive-vacatures met decision='delete' waren al verwijderd
  • maar de signal rows zelf stonden nog in job_archiveretentionsignal
  • die tabel bevatte dus enorme hoeveelheden restdata

Waarom pg_repack hier minder geschikt bleek

Bij gebruik van pg_repack kwam een bekend probleem naar boven: het commando bleef wachten op actieve transacties op de tabel.

De typische melding was:

NOTICE: Waiting for 1 transactions to finish. First PID: ...
  

Dat betekent in de praktijk:

  • de tabel wordt nog gebruikt door webverkeer of een achtergrondjob
  • pg_repack wil netjes wachten
  • maar als nieuwe queries blijven binnenkomen, kan die wachttijd heel lang oplopen

Voor een productie-omgeving waar de tabel actief gebruikt wordt, maakt dat pg_repack soms minder praktisch dan het op papier lijkt.

VACUUM FULL werkte in deze case beter omdat:

  • het zelf de lock neemt
  • de tabel duidelijk blokkeert voor de duur van de operatie
  • de cleanup daardoor voorspelbaar doorgaat
  • de operatie niet blijft hangen op een stroom van live queries

Uitgevoerde VACUUM FULL-commands

De cleanup werd uitgevoerd met deze commando’s, één voor één:

sudo -u postgres psql -d vindazo_nl -c "VACUUM FULL VERBOSE ANALYZE job_archiveretentionsignal;"
  sudo -u postgres psql -d vindazo_nl -c "VACUUM FULL VERBOSE ANALYZE django_session;"
  sudo -u postgres psql -d vindazo_nl -c "VACUUM FULL VERBOSE ANALYZE job_jobarchive;"
  sudo -u postgres psql -d vindazo_nl -c "VACUUM FULL VERBOSE ANALYZE job_jobarchive_locations;"
  sudo -u postgres psql -d vindazo_nl -c "VACUUM FULL VERBOSE ANALYZE job_jobarchive_categories;"
  

Daarna ook nog voor enkele aanvullende grote tabellen:

sudo -u postgres psql -d vindazo_nl -c "VACUUM FULL VERBOSE ANALYZE crawler_page;"
  sudo -u postgres psql -d vindazo_nl -c "VACUUM FULL VERBOSE ANALYZE mailing_message;"
  sudo -u postgres psql -d vindazo_nl -c "VACUUM FULL VERBOSE ANALYZE mailing_queue;"
  

Belangrijk: deze werden bewust niet parallel gedraaid. Elke tabel werd volledig afgewerkt voor de volgende startte.

Situatie na de cleanup

Filesystem gebruik na de operatie:

df -h
  Filesystem                           Size  Used Avail Use% Mounted on
  tmpfs                                 13G  1.3G   12G  11% /run
  /dev/md2                             1.8T  378G  1.3T  23% /
  tmpfs                                 63G  440K   63G   1% /dev/shm
  tmpfs                                5.0M     0  5.0M   0% /run/lock
  /dev/md1                             989M  385M  554M  41% /boot
  //u266537.your-storagebox.de/backup  2.0T  798G  1.2T  40% /mnt/backupbox
  tmpfs                                 13G     0   13G   0% /run/user/0
  

Dat betekent:

  • gebruik op / daalde van 780 GB naar 378 GB
  • vrije ruimte steeg van 886 GB naar 1.3 TB
  • bezetting daalde van 47% naar 23%

Netto winst:

  • ongeveer 402 GB teruggewonnen

Tabelgroottes na de cleanup

De grootste tabellen na afloop:

table                         | size
  -----------------------------+---------
  job_jobarchive               | 129 GB
  job_job                      | 42 GB
  mailing_queue                | 32 GB
  trafficinsights_httplogevent | 22 GB
  crawler_page                 | 13 GB
  job_archiveretentionsignal   | 7247 MB
  trafficinsights_pathstatssnapshot | 4218 MB
  job_jobarchive_locations     | 2706 MB
  job_legacyjoblookup          | 2621 MB
  icrm_cv                      | 2247 MB
  viewjob_viewjob              | 1643 MB
  

Belangrijkste dalingen:

  • job_jobarchive: van 455 GB naar 129 GB
  • job_archiveretentionsignal: van 53 GB naar ongeveer 7.2 GB
  • job_jobarchive_locations: van 12 GB naar ongeveer 2.7 GB

Waarom deze aanpak werkte

Deze cleanup werkte goed omdat:

  1. eerst inhoudelijk werd gecontroleerd wat al verwijderd was
  2. daarna duidelijk werd dat job_archiveretentionsignal vooral nog restdata bevatte
  3. de zwaarste tabellen sequentieel werden aangepakt
  4. VACUUM FULL de lock- en wachttijdproblemen van pg_repack vermeed

Met andere woorden: pg_repack is technisch elegant, maar op een actieve productie-tabel kan VACUUM FULL soms efficiënter zijn omdat het de operatie meteen afdwingt in plaats van te blijven wachten op terugkerende queries.

Belangrijke les uit deze case

Gebruik pg_repack vooral wanneer:

  • de tabel relatief rustig is
  • je de site zoveel mogelijk online wilt houden
  • je geen blokkering wilt op live verkeer

Gebruik VACUUM FULL eerder wanneer:

  • de tabel toch voortdurend gebruikt wordt
  • pg_repack blijft wachten op actieve transacties
  • je een voorspelbare, harde cleanup wilt
  • tijdelijke blokkering van de tabel aanvaardbaar is

Conclusie

In deze NL-case bleek VACUUM FULL uiteindelijk de betere praktische keuze te zijn. Niet omdat het mooier is, maar omdat het in een drukke productieomgeving betrouwbaarder en sneller klaar was dan pg_repack.

Het resultaat was groot:

  • meer dan 400 GB vrije ruimte teruggewonnen
  • job_jobarchive sterk verkleind
  • job_archiveretentionsignal van tientallen GB naar enkele GB teruggebracht
  • de root filesystem van een risicovolle naar een comfortabele bezettingsgraad gebracht

De belangrijkste les: niet blind kiezen voor de “mooiste” tool, maar voor de aanpak die in jouw productiepatroon het betrouwbaarst doorwerkt.

Comments