Přechod z Solidu na PostgreSQL

Pavel Stěhule

Pro svého zaměstnavatele jsem řešil port IS z RDBMS Solid na RDBMS PostgreSQL 7.4.

Tento informační systém byl realizován v letech 1997-2000. V tehdejších podmínkách byla zvolen jako RDBMS systém finský systém Solid. Provozovatel po několika letech ukončil vztah díky nedostatečné podpoře prodejce, nicméně systém zůstal nadále v provozu. V únoru 2004 bylo rozhodnuto o přechodu na PostgreSQL. Po několika týdnech práce a testovacího provozu byl systém plně zprovozněn na PostgreSQL. Pozn. ačkoliv názory na Solid nebyli pozitivní (díky skutečně pomalé odezvě systému) je třeba konstatovat, že Solid byl v provozu s minimální kvalifikovanou podporou 5 let. Portace měla být provedena s minimálními změnami v kódu aplikace (což bylo možné, díky zapouzdření volání SQL do vlastní knihovny).

Charakteristika systému - databáze obsahuje informace zajména podklady pro řízení a dohled nad službami zákazníků. Se systémem pracuje maximálně 20 uživatelů, z toho však jen maximálně čtvrtina přidává a modifikuje záznamy. tj. zátěž je prakticky minimální.

Databáze sama má cca 100MB. Největší tabulky 650K záznamů.

Příprava

Převod dat

Samotný přechod nebyl nejjednodušší. Přišlo se na chybu dumpu Solidu, kdy Solid v případě exportu položek typu timestamp neexportoval čas. Dálší záludnost, příkaz COPY neakceptuje \n\r, atd. Bylo potřeba tyto znaky z dumpu nahradit (např. <cr>) a pak po importu vrátit do původní podoby. Pro automatizaci importu (pro testování) import probíhal opakovanně nakonec bylo třeba napsat několik skriptů, importujících kompletní množinu tabulek. Pro import je třeba znát závislosti tabulek (pokud nenastavim ref. integritu ....., i přesto pokud nechceme ručně vypisovat n příkazů copy). Osvědčil se následující skript

#!/bin/bash

psql intra <<EOF
CREATE OR REPLACE FUNCTION list_user_tables_sort_depend
  (owner VARCHAR, revers BOOLEAN) RETURNS SETOF VARCHAR AS '
DECLARE tabulky VARCHAR[]; i INTEGER; opakovat BOOLEAN = ''t'';
  pom VARCHAR; exportovano VARCHAR[] = ''{}''; r RECORD; mohu_exportovat BOOLEAN;

BEGIN SELECT ARRAY(SELECT tablename FROM pg_tables WHERE tableowner = owner) INTO tabulky;
  WHILE opakovat LOOP
    opakovat := ''f'';
    FOR i IN array_lower(tabulky,1) .. array_upper(tabulky,1) LOOP
      IF tabulky[i] <> '''' THEN
        mohu_exportovat := ''t'';
        FOR r IN SELECT t.relname AS z, x.relname AS nz FROM pg_catalog.pg_constraint d
          INNER JOIN pg_catalog.pg_class t on t.oid = d.conrelid
          INNER JOIN pg_catalog.pg_class x on x.oid = d.confrelid
            WHERE d.contype = ''f'' AND t.relname = tabulky[i] LOOP
          IF NOT r.nz = ANY(exportovano) THEN
            mohu_exportovat := ''f'';
          END IF;
        END LOOP;
        IF mohu_exportovat THEN
          pom := tabulky[i];
          exportovano := exportovano || tabulky[i];
          opakovat := ''t''; tabulky[i] := '''';
        END IF;
      END IF;
    END LOOP;
  END LOOP;
  IF revers THEN
    FOR i IN REVERSE array_upper(exportovano,1) .. array_lower(exportovano,1) LOOP
      RETURN NEXT exportovano[i];
    END LOOP;
  ELSE
    FOR i IN array_lower(exportovano,1) .. array_upper(exportovano,1) LOOP
      RETURN NEXT exportovano[i];
    END LOOP;
  END IF;
  RETURN;
END;
' LANGUAGE plpgsql;
EOF

if [ ! -d postgresql ]; then
  mkdir postgresql
else
  rm postgresql/*
fi;
if [ ! -d postgresql ]; then
  mkdir postgresql
else
  rm postgresql/*
fi;

./intrain.sh

DATADIR=./home/okbob/`date +%Y%m%d`
echo "BEGIN;\n" >> postgresql/import.sql
for table in `psql -At -c "SELECT * FROM list_user_tables_sort_depend('root','f');" intra`; do
  TABLE=`echo $table|tr [[:lower:]] [[:upper:]]`
  echo $TABLE
  if [ -e $DATADIR/$TABLE.dat ]; then
     cat $DATADIR/$TABLE.dat |./reformat.pl > postgresql/$TABLE.data
     echo "\copy $table from '$TABLE.data' delimiter ',' null 'NULL'" >> postgresql/import.sql
  fi
done
echo "COMMIT;" >> postgresql/import.sql

echo "BEGIN;" >> postgresql/delete.sql
for table in `psql -At -c "SELECT * FROM list_user_tables_sort_depend('root','t');" intra`; do
  echo "delete from $table;" >> postgresql/delete.sql
done
echo "COMMIT;" >> postgresql/delete.sql
cat $DATADIR/dict.sql | ./get_seq.pl >> postgresql/get_seq.sql
rm -rf ./home

Provoz

Díky možnosti logování dotazů jejichž provedení trvalo delší dobu než určenou bylo možné velice přesně a rychle vytipovat dotazy, které bylo třeba optimalizovat (přepsáním, indexy). Během třech dnů provozu pouze dotazy obsahující výraz LIKE trvají déle než 100 ms. Pro představu jedna z nejčastějších SP trvala obvykle 900-2000ms, po optimalizaci 40-80ms. Objevil se problém s dotazy a update příkazem obsahující podmínku typu

  where pk = konstanta1 OR 
    (select pk from .. wherere neco = konstatnta2)
ktere bylo třeba ručně přepsat do tvaru
  where pk IN (
    select pk from .. wherere neco = konstatnta
    union all select konstatnta1)

Zrychlení bylo z cca 140 až 200ms na 10 ms (v prvním tvaru se nepoužil index). I bez optimalizace byly základní dotazy mnohem rychlejší v PostgreSQL než v Solidu (je znát časový odstup 6 let) a to 6x až 10x. Což znatelně minimalizuje zatížení serveru a možnost zahlcení.

Závěr

Zásadně se zrychlila aplikace (běžné odezvy jsou "okamžité"), nedochází k zahlcení serveru, bez nutnosti změny hw (samotná změna hw by nebyla až takovým problémem, horší by byla pravděpodobná nutnost reinstalace sw). Po nasazení PostgreSQL vše mohlo zůstat při starém. Ačkoliv se nejedná o rozsáhlou databázi její běh je pro firmu kritický (fy. je ovšem zvyklá používat open source produkty, další systém běží nad MySQL, PostgreSQL bylo zvoleno pro svou "podobnost" se Solidem, tudíz zásahy do kódu aplikace mohli být skutečně minimální.