wordpress na postgresie

jak może wiecie wordpress od zawsze był na mysql'u.
dodatkowo – team wordpressa nigdy nie wykazał się chęcią modyfikacji kodu aby działał na innych platformach bazodanowych – w szczególności na postgresie. a byli pytani/proszeni przez ludzi z okolic core-teamu postgresa!
ostatnio przeczytałem na blogu drixtera, że aure zrobił nieoficjalny port wordpressa na postgresa. i to nie jakąś prehistoryczną wersję tylko 2.0.4! tak więc – zachęcam do testowania 🙂

tsearch poza contribem. w core’rze

rosyjski team (oleg i teodor) przysłał pierwszą wersję patcha przenoszącego tsearcha do podstawowego postgresa.
zmiana nie jest wielka – funkcjonalność pozostaje ta sama, ale przyjemniej się teraz konfiguruje, używa – no i nie trzeba sprawdzać czy contriby są poinstalowane 🙂
zmiany:

  • tabele pg_ts_* są teraz w schemie pg_catalog. wreszcie!
  • parsery, słowniki i konfiguracje są teraz częścią systemu postgresa – tak jak tabele, klasy operatorów itd. dzięki czemu są przyporządkowane do schemy – można mieć różne w różnych schemach – np. wybór różnych parserów przez autodetekcję locale – zależny od schemy!
  • wybór aktualnej konfiguracji tsearcha odbywa się przez ustawienie zmiennej postgresa (czyli tak jak np. shared_buffers czy inne)
  • zarządzanie konfiguracją tsearcha odbywa się teraz z użyciem poleceń sql'a (create i pochodne) a nie wyrażeń insert/update/delete. pozwala to na lepszą obsługę dumpów, tworzenia, kasowania i zarządzania zależnościami
  • psql umożliwi wyświetlanie konfiguracji systemu fts poprzez polecenia \d* (\dF dokładniej)
  • dodane z automatu wszystkie dostępne stemmery snowball'owe, oraz ich odpowiednie konfiguracje
  • poprawki związane ze zwalnianiem pamięci

ogólnie – krok w dobrą stronę.
patch jest na razie w wersji alfa, ale myślę, że do 8.3 wejdzie na pewno.
można już się zapoznać z aktualizowaną na bieżąco dokumentacją – na razie na stronach olega i teodora, ale już niedługo w standardowych doc'ach postgresa.
aha. informacyjnie – prace nad wdrożeniem tsearcha do core'a są sponsorowane przez enterprisedb – producenta wersji postgresql'a zmodyfikowanej w celu zachowania wiekszej kompatybilnosci z oracle'em.

enum

na grupie pgsql-patches trwa dyskusja nad nową wersją patcha dodającego enumy.
jest kilka argumentów przeciwko enumom jako takim, kilka przeciwko temu konkretnie patchowi i całkiem sporo za tym aby enumy tym patchem włączyć do postgresa!.
wszystko wskazuje na to, że w nowym postgresie (8.3?) będzie można:

CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
CREATE TABLE person (
    name text,
    current_mood mood
);
INSERT INTO person VALUES ('Moe', 'happy');
SELECT * FROM person WHERE current_mood = 'happy';
 name | current_mood
------+--------------
 Moe  | happy
(1 row)

fajnie 🙂

jak dodać kolumnę do tabeli

teoretycznie dodanie kolumny do tabeli nie jest problemem. od daaaaaaaaaaaaaawna istnieje ALTER TABLE ADD COLUMN. czy jednak jest to zawsze bezproblemowe?
niestety nie.
otóż – dodanie pola do tabeli zakłada na nią (w całości) exclusive locka.
dla standardowego:

ALTER TABLE t ADD COLUMN c INT8;

to nie problem – taki ALTER trwa moment.
ale co jeśli robimy:

ALTER TABLE t ADD COLUMN c INT8 NOT NULL DEFAULT 123;

tu pojawia się problem.
po pierwsze – not null – wymusza sprawdzenie zawartości bazy.
po drugie – ważniejsze – wyspecyfikowanie "default" przy dodawaniu pola automatycznie wstawi wartość domyślną do wszystkich rekordów obecnie w tabeli.
zasadniczo – super. o to chodzi. ale jak sobie nałożymy na to fakt iż ALTER TABLE lockuje tabelę – oops. recepta na kłopoty.
w szczególności – mając tabelę typu kilka milionów rekordów, na której non stop ktoś pracuje (np. dosyć aktywny serwis www) – zrobienie na niej takiego ALTERa to realnie sprawę ujmując wyłączenie ta
beli i serwisu.
jak więc zrobić dodanie z wyspecyfikowaniem w sposób słuszny?
no cóż – trzeba robić to krokami:
po pierwsze:

ALTER TABLE t ADD COLUMN c INT8;

to się wykona błyskawicznie.
potem (ale jako oddzielne zapytanie!):

ALTER TABLE t ALTER COLUMN c SET DEFAULT 123;

to też pójdzie szybko bo niczego nam nie zaktualizuje. po prostu – nowo wstawiane rekordy będą miały defaulta.
teraz – trzeba zaktualizować starsze. najprościej:

UPDATE t SET c = 123 WHERE c IS NULL;

w szczególności – jak danych jest więcej niż mało (powiedzmy powyżej 100,000 rekordów), to warto rozbić to na kilka oddzielnych zapytań. np:

UPDATE t SET c = 123 WHERE c IS NULL AND id BETWEEN 1 AND 100000;
UPDATE t SET c = 123 WHERE c IS NULL AND id BETWEEN 100001 AND 200000;

itd.
oczywiście – nie można do tego celu użyć funkcji – cała idea tego rozbijania polega na tym by nie robić tego wszystkiego w jednej, olbrzymiej, transakcji!.
no i na koniec pozostaje:

ALTER TABLE t ALTER COLUMN c SET NOT NULL;

to chwilę potrwa – postgres musi sprawdzić dane, ale i tak jest to krótsze i mniej blokujęce niż robienie wszystkiego za jednym zamachem.
oczywiście w tej chwili ktoś może się odezwać i powiedzieć, że w bazie <xxx> to jest prostsze, nie trzeba nic rozbijać, bla bla bla. fakt. w postgresie też nie trzeba – to co pokazałem to jedynie hint jak obejść blokadę przy dodawaniu pól z wartościami domyślnymi przy sporych tabelkach. można to zrobić jednym zapytaniem. i też zadziała. po prostu czasem nie chcemy/nie możemy mieć locka na taki czas jaki jest potrzebny ALTERowi 🙂

tsearch i synonimy

od daaaaawna w postgresie jest dostępny świetny silnik wyszukiwania pełnotekstowego – tsearch.
ma on spore możliwości, ale jest dosyć trudny przy pierwszym podejściu – trzeba poświęcić z pół godziny na to by załapać o co chodzi, skonfigurować i używać.
natomiast jak już się zrozumie co i jak – można prosto robić nowe, ciekawe rzeczy. przykładowo – dodać własne słowniki synonimów. dokładną metodę zrobienia takiego słownika i dodania go do systemu wyszukującego przedstawił w swoim blogu magnus hagander – ja to przeczytałem i polecam każdemu kto chce zobaczyć jak zrobić ciekawe rzeczy w postgresie.

currval i problemy z selectami

jak może wiecie w postgresie jest funkcja currval() zwracająca ostatnio nadane id z podanej sekwencji.

rozpatrzmy prosty przypadek:

# CREATE TABLE test (id serial PRIMARY KEY, pole int4);
CREATE TABLE
# INSERT INTO test (pole) SELECT * FROM generate_series(1, 10000);
INSERT 0 10000
# SELECT COUNT(*) FROM test;
 COUNT
-------
 10000
(1 ROW)
# SELECT currval('test_id_seq');
 currval
---------
   10000
(1 ROW)

wszystko wygląda ok. więc sprawdźmy jeszcze jeden insert mały:

# INSERT INTO test(pole) VALUES (12313);
INSERT 0 1
# SELECT currval('test_id_seq');
 currval
---------
   10001
(1 ROW)

nadal wszystko ok. i teraz:

# SELECT * FROM test WHERE id = currval('test_id_seq');
  id   | pole
-------+-------
 10001 | 12313
(1 ROW)
TIME: 93.358 ms

działa, ale coś wolno, sprawdźmy:

# EXPLAIN analyze SELECT * FROM test WHERE id = currval('test_id_seq');
                                            QUERY PLAN
--------------------------------------------------------------------------------------------------
 Seq Scan ON test  (cost=0.00..200.02 ROWS=1 width=8) (actual TIME=64.375..64.379 ROWS=1 loops=1)
   FILTER: (id = currval('test_id_seq'::regclass))
 Total runtime: 64.431 ms
(3 ROWS)

seq scan? sprawdźmy więc ręcznie podaną wartość:

# EXPLAIN analyze SELECT * FROM test WHERE id = 10001;
                                                   QUERY PLAN
----------------------------------------------------------------------------------------------------------------
 INDEX Scan USING test_pkey ON test  (cost=0.00..8.02 ROWS=1 width=8) (actual TIME=0.029..0.033 ROWS=1 loops=1)
   INDEX Cond: (id = 10001)
 Total runtime: 0.086 ms
(3 ROWS)

hmm .. tu jest dobrze. skąd seq scan? aby nie przynudzać z każdym zapytaniem, powiem tyle, że nie jest to kwestia błędnych typów czy czegoś tak oczywistego. problemem jest zmienność funkcji.

dokładniej: funkcja currval() jest zadeklarowana jako “volatile" – co oznacza, że jej wynik ma prawo zmienić się w czasie pojedynczego skanu tabeli. tak jak np. random(). to oznacza, że nie można użyć jej jako dostarczyciela wartości i potem tą wartością przeszukać indeksy.

cóż więc można zrobić – no cóż. trzeba powiedzieć postgresowi, że interesuje nas tylko pierwsza zwrócona wartość currvala – idealnie do tego nadają się podzapytania:

# EXPLAIN analyze SELECT * FROM test WHERE id = (SELECT currval('test_id_seq'));
                                                   QUERY PLAN
----------------------------------------------------------------------------------------------------------------
 INDEX Scan USING test_pkey ON test  (cost=0.01..8.03 ROWS=1 width=8) (actual TIME=0.047..0.050 ROWS=1 loops=1)
   INDEX Cond: (id = $0)
   InitPlan
     ->  RESULT  (cost=0.00..0.01 ROWS=1 width=0) (actual TIME=0.010..0.012 ROWS=1 loops=1)
 Total runtime: 0.187 ms
(5 ROWS)

jak widać jest już zdecydowanie lepiej.

ile jest wart postgres

dziś jest dzień postgresql'owy, więc kolejna informacja o nim.

jest taki program: sloccount. służy do liczenia ilości linii kodu, oraz estymowaniu kosztów stworzenia programu.

odpaliłem go na źródłach postgresa 8.2. oto wynik:

Totals grouped by language (dominant language first):
ansic:       479298 (94.01%)
yacc:         14698 (2.88%)
sh:            7805 (1.53%)
lex:           5349 (1.05%)
perl:          2608 (0.51%)
asm:             65 (0.01%)
python:          12 (0.00%)
<br/>
Total Physical Source Lines of Code (SLOC)                = 509,835
Development Effort Estimate, Person-Years (Person-Months) = 139.26 (1,671.14)
 (Basic COCOMO model, Person-Months = 2.4 * (KSLOC**1.05))
Schedule Estimate, Years (Months)                         = 3.50 (41.95)
 (Basic COCOMO model, Months = 2.5 * (person-months**0.38))
Estimated Average Number of Developers (Effort/Schedule)  = 39.84
Total Estimated Cost to Develop                           = $ 18,812,337
 (average salary = $56,286/year, overhead = 2.40).
SLOCCount, Copyright (C) 2001-2004 David A. Wheeler
SLOCCount is Open Source Software/Free Software, licensed under the GNU GPL.
SLOCCount comes with ABSOLUTELY NO WARRANTY, and you are welcome to
redistribute it under certain conditions as specified by the GNU GPL license;
see the documentation for details.
Please credit this data as "generated using David A. Wheeler's 'SLOCCount'."

pl/scheme

(hurra, (dzięki (projektowi (pl/scheme))) (wreszcie) (będziemy (się (mogli))) (w postgresie) (cieszyć (z kodu (składającego się (z samych (nawiasów)))))) 🙂

funkcje ułatwiające migracje z oracle’a

przeczytałem właśnie o interesującym module – orafce. moduł ten zawiera ponad 100 funkcji których celem jest emulowanie funkcji bazodanowych z oracle'a i plvision (taki pakiet do oracle'a).

funkcje w orafce są kompatybilne na poziomie api z wersjami z oracle'a co powoduje, że ich użycie mocno uprości ewentualną migrację między tymi platformami.

wśród funkcji zawartych w pakiecie są m.in.:

  • kilka funkcji podstawowych – typu next_day, last_day
  • funkcje kalendarza biznesowego – bazujące na pakiecie plvdate z plvision
  • spory zestaw funkcji do pracy na stringach
  • obsługa komunikacji między procesowej

zwrócić należy uwagę na fakt iż nie ma w tym pakiecie niczego czego nie dałoby się uzyskać w postgresie inaczej – jednakże sensem istnienia tego pakietu nie jest dodawanie funkcjonalności, a ułatwianie migracji.

nowości w postgresie 8.2 – … advisory locks

miałem o tym napisać później, ale skoro merlin już opisał advisory locki, nie pozostaje mi nic innego niż
opisać je u siebie.

advisory locki są to zupełnie nowe locki nie powiązane z żadnymi fizycznymi obiektami w bazie. służą one do
przekazywania informacji między sesjami i transakcjami.

najbardziej szczególną cechą tych locków jest to, że działają poza mechanizmem transakcji. dzięki temu możliwe jest używanie
ich do celów do których standardowe locki sie nie nadawały.

przykładem użycia z mojego podwórka jest zakładanie locka na konkretne źródło rss'ów w dnewsach. do tej pory musiałem mieć
oddzielną tabelkę i w niej trzymać wpisy. do tego dochodziły kwestie czyszczenia wpisów gdyby program ściągający dane padł i nie
skasował swojego locka.

z drugiej strony – mogłem uzyć locków wewnątrz transakcji, ale to by oznaczało trzymanie długo trwających transakcji – a one z
definicji są złe (psują systemy replikacyjne).

advisory locki robią to co trzeba.

sesja (połącznie) które potrzebuje zakłada locka (locka zakłada sie na “liczbę" z zakresu int8, albo na parę liczb int4). lock
zostaje zdjęty gdy wywołam funkcję zdejmującą lub gdy padnie połączenie!

advisory locki są trzymane w pamięci dzięki czemu są baaaaaardzo szybkie – merlin przeprowadził prosty test zakładając 1000
advisory locków w około 6 milisekund!

dodatkowo – dzięki temu, że są trzymane w pamięci – odpadaję wszelkie bolączki mvcc – puchnące tabele, wolny odczyt,
vacuumowanie itd. advisory locki są błyskawiczne i całkowicie bezpieczne.

przykładowe użycie:

# SELECT pg_advisory_lock(123);<br/>
 pg_advisory_lock<br/>
------------------<br/>
''<br/>
(1 ROW)<br/>
# SELECT pg_advisory_unlock(123);<br/>
 pg_advisory_unlock<br/>
--------------------<br/>
 t<br/>
(1 ROW)

oczywiście advisory locki zapewniają wszystkie standardowe metody dostępu:

  • pobranie locka blokujące
  • pobranie nieblokujące
  • locki exclusive
  • locki shared

polecam przyjrzenie
się tej funkcjonalności – ma ona sporo niecałkiem oczywistych zastosowań.