Generowanie danych losowych dla PostgreSQL z wykorzystaniem EuroDB
Generowanie danych losowych dla PostgreSQL to temat niezwykle interesujący dla wielu osób związanych ze światem IT. Prawdopodobnie wiąże się to ściśle ze wzrostem znaczenia metodyki DevOps oraz związanego z nim rygoru testowania. W poprzedniej części cyklu omawialiśmy, jak można podejść do tego problemu od strony wykorzystania samego PostgreSQL. Zgodnie z zapowiedziami, w tym artykule zaprezentujemy […]
Generowanie danych losowych dla PostgreSQL to temat niezwykle interesujący dla wielu osób związanych ze światem IT. Prawdopodobnie wiąże się to ściśle ze wzrostem znaczenia metodyki DevOps oraz związanego z nim rygoru testowania. W poprzedniej części cyklu omawialiśmy, jak można podejść do tego problemu od strony wykorzystania samego PostgreSQL. Zgodnie z zapowiedziami, w tym artykule zaprezentujemy rozwinięcie tego tematu w oparciu o jeden z ciekawszych komponentów EuroDB – moduł do generowania danych.
EuroDB Data Generator to stosunkowo proste, lecz bardzo potężne narzędzie umożliwiające łatwe generowanie ogromnych ilości danych losowych dla PostgreSQL. Można powiedzieć, że w pod tym względem doskonale wpisuje się w jedno z założeń – „do one thing and do it well” – przyświecającego twórcom Unixa.
Data Generator działa w oparciu o pojęcie generatorów dla poszczególnych typów danych. Narzędzie rozpoznaje bogatą listę zdefiniowanych typów znanych z silnika PostgreSQL, które pozwalają na generowanie danych bez potrzeby pisania ani jednej linijki kodu. Jedynym potrzebnym argumentem będzie plik z definicjami tabel, dla których mają zostać stworzone dane. Wykorzystując standardowe narzędzie pg_dump
można w prosty sposób stworzyć taki plik.
Podstawowe generowanie danych losowych dla PostgreSQL
By zademonstrować działanie Data Generatora, posłużę się prostym schematem, dla którego wygeneruję dane testowe:
CREATE TABLE app_user( id serial PRIMARY KEY, username text NOT NULL, firstname text, lastname text, email text NOT NULL, birthday date, is_active boolean NOT NULL, created timestamp NOT NULL, last_login timestamp NOT NULL, last_ip inet NOT NULL );
Dla pełności, poniżej zaprezentuję jeszcze, w jaki sposób można wyciągnąć te informacje z działającego serwera korzystającego z silnika PostgreSQL:
$ pg_dump -s -O -x -d NAZWA_BAZY
Opcje kolejno odpowiadają za wypisywanie definicji poszczególnych obiektów (-s) oraz ograniczają dane przez zablokowanie dodawania informacji o właścicielach (-O), a także o rolach i uprawnieniach (-x).
Mając plik wejściowy z definicją tabeli jak wyżej o nazwie app_user.sql
, możemy po prostu wywołać:
eurodb-datagenerator app_user.sql
Doprowadzi to do wygenerowania danych zgodnie z domyślnymi ustawieniami programu. Na tym etapie istotne jest jeszcze sterowanie ilością wygenerowanych rekordów. Domyślna wartość odpowiadającego za to parametru --size
to 100. Przyjrzyjmy się jednak przykładowym wynikom dla analogicznego polecenia z ograniczeniem ilości generowanych danych:
-- eurodb-datagenerator --size 5 app_user.sql -- data generated by /usr/bin/eurodb-datagenerator version 2.0.0 (r792 on 2014-03-23) for postgresql -- fill table app_user (5) \echo # filling table app_user (5) COPY app_user (id,username,firstname,lastname,email,birthday,is_active,created,last_login,last_ip) FROM STDIN (ENCODING 'UTF-8'); 1 username firstname_1_ lastname_1 email_1_1_1_1_1_ 2019-05-18 FALSE 2019-05-20 13:59:45 2019-05-20 14:03:45 0.0.0.4 2 username_2_2_2_ firstname_2_2_2 lastname_5_ email_2_2_2 2019-05-18 TRUE 2019-05-20 13:59:45 2019-05-20 13:59:45 0.0.0.3 3 username_3_3_ firstname_1_ lastname_3_3_3_ email_3_3_3_3_3 2019-05-17 TRUE 2019-05-20 14:00:45 2019-05-20 13:59:45 0.0.0.2 4 username_4_ firstname_5_5_5 lastname_4_4_ email_4_4_ 2019-05-20 FALSE 2019-05-20 14:01:45 2019-05-20 14:00:45 0.0.0.0 5 username_ firstname_5_5_5 lastname_3_3_3_ email_5_5_5_5_ 2019-05-19 FALSE 2019-05-20 14:01:45 2019-05-20 14:02:45 0.0.0.3 \. -- restart sequences ALTER SEQUENCE app_user_id_seq RESTART WITH 6; -- analyze modified tables ANALYZE app_user;
Jak widać, Data Generator korzysta z postgresowego polecenia COPY
, które jest zdecydowanie szybsze od standardowego INSERT
. Jest to szczególnie istotne, gdy generujemy dużo większe ilości danych niż w prezentowanych tu przykładach.
Kolejną rzeczą, którą widać, jest format danych. Dla kolumn tekstowych domyślnym zachowaniem jest po prostu wykorzystanie nazwy kolumny oraz dodatkowych losowych znaków. Daty urodzenia również rzucają się w oczy – zakres, z którego pochodzą, jest zdecydowanie zbyt bliski, aby można go było uznać za potencjalne urodziny prawdziwej osoby korzystającej z naszej aplikacji.
Pomimo pewnych uwag wygenerowaliśmy jednak dane na podstawie samej definicji tabeli w SQL, bez potrzeby pisania własnego skryptu. Moglibyśmy w tym momencie skończyć – bez żadnego wysiłku udało nam się odtworzyć funkcjonalnie równoważne dane do tych z poprzedniego postu, a nawet zrobić nieco więcej. Przyjrzyjmy się jednak jeszcze dokładniej innym możliwościom EuroDB Data Generatora.
Poprawianie wygenerowanych danych przy pomocy dyrektyw
Jak widać, włączenie programu do procesu testowania w jego najprostszej formie jest bardzo łatwe. Wiadomo jednak, że prawdziwe dane produkcyjne nie wyglądają w ten sposób i byłoby dobrze, gdyby dane testowe lepiej je odzwierciedlały w środowisku testowym. Szczególnie jeśli aplikacja, z której korzystamy, ma być testowana w najdokładniejszy sposób.
Z pomocą przychodzą nam dyrektywy Data Generatora, które służą do sterowania generowaniem. W ten sposób możemy na przykład określić rozkład, który mają przyjąć dane, stworzyć w deklaracyjny sposób własny generator czy też odpowiednio zmodyfikować argumenty wbudowanych generatorów.
Wykorzystanie wyrażeń regularnych
Korzystając z naszego przykładu, spróbujmy zmienić zachowanie Data Generatora podczas tworzenia adresu e-mail. By to zrobić, wystarczy umieścić komentarz SQL przy definicji konkretnej kolumny:
email text NOT NULL, --df: pattern='[a-z]{3,8}\.[a-z]{3,8}@(qmail|inbox)\.com'
Na początku komentarza znajduje się magiczny ciąg znaków, który pozwala na sterowanie generowaniem. Następnie deklarujemy, który z wbudowanych generatorów ma zostać wykorzystany oraz przekazujemy do niego odpowiedni argument.
Kolejne wywołanie daje bardziej satysfakcjonujący wynik, który wygląda jak adres e-mail:
-- eurodb-datagenerator --size 5 app_user.SQL COPY app_user (id,username,firstname,lastname,email,birthday,is_active,created,last_login,last_ip) FROM STDIN (ENCODING 'UTF-8'); 1 username firstname_1_ lastname_5_ [email protected] 2019-05-17 FALSE 2019-05-20 16:29:43 2019-05-20 16:31:43 0.0.0.3 2 username_2_2_2_ firstname lastname_3_3_3_ [email protected] 2019-05-17 FALSE 2019-05-20 16:29:43 2019-05-20 16:30:43 0.0.0.1 3 username_3_3_ firstname lastname_3_3_3_ [email protected] 2019-05-16 TRUE 2019-05-20 16:29:43 2019-05-20 16:29:43 0.0.0.4 4 username_4_ firstname_5_5_5 lastname_3_3_3_ [email protected] 2019-05-16 FALSE 2019-05-20 16:30:43 2019-05-20 16:30:43 0.0.0.2 5 username_ firstname_5_5_5 lastname_5_ [email protected] 2019-05-17 TRUE 2019-05-20 16:31:43 2019-05-20 16:29:43 0.0.0.0 \.
Jak można się domyślić, wbudowany generator pattern
działa w oparciu o wzorzec regularny, do którego zostaną wygenerowane wartości. W tym wypadku wykorzystaliśmy bardzo prostego regexpa, który opisuje niewielki podzbiór ze wszystkich możliwych adresów e-mail. W zależności od potrzeb można zdefiniować bardziej rozbudowane wzorce.
Wykorzystanie list słów
Spróbujmy teraz stworzyć bardziej rozróżnialne wartości dla kolumny username
. W tym celu możemy skorzystać na przykład z listy słów, które można znaleźć w pakiecie words
. W EuroLinuxie oraz podobnych systemach po jego instalacji uzyskujemy dostęp do pokaźniej listy angielskich słów w katalogu /usr/share/dict/
. By wykorzystać te dane podczas generowania, wystarczy dopisać:
--df English: word=/usr/share/dict/words sub=uniform username text NOT NULL, --df: text=English length=1 lenvar=0
Aby uzyskać bardziej interesujące rezultaty:
COPY app_user (id,username,firstname,lastname,email,birthday,is_active,created,last_login,last_ip) FROM STDIN (ENCODING 'UTF-8'); 1 smackee firstname_1_ lastname_1 [email protected] 2019-05-20 TRUE 2019-05-20 17:03:57 2019-05-20 17:05:57 0.0.0.4 2 shivaite firstname_4_ lastname [email protected] 2019-05-20 FALSE 2019-05-20 17:06:57 2019-05-20 17:03:57 0.0.0.0 3 barish firstname_2_2_2 lastname [email protected] 2019-05-18 TRUE 2019-05-20 17:05:57 2019-05-20 17:06:57 0.0.0.2 4 syncategorematic firstname_1_ lastname_3_3_3_ [email protected] 2019-05-20 FALSE 2019-05-20 17:06:57 2019-05-20 17:03:57 0.0.0.3 5 slugs firstname_4_ lastname_1 [email protected] 2019-05-17 TRUE 2019-05-20 17:03:57 2019-05-20 17:03:57 0.0.0.4 \.
W tym momencie warto jeszcze wytłumaczyć poszczególne elementy składni. Otóż pierwsza deklaracja w komentarzu deklaruje makro English
, które posłuży w następnej deklaracji jako źródło danych do generatora text
. Przy pomocy argumentu sub definiujemy rozkład, z jakim mają się pojawiać poszczególne elementy pliku. W tym wypadku jest to rozkład jednostajny (uniform
). Stąd mając do dyspozycji na przykład prawdziwe dane z jakiejś kolumny oraz rozkład jaki przyjmują w bazie, moglibyśmy wygenerować bardzo podobne wyniki. Jednym z przykładów może być rozkład wieku użytkowników, który można wymodelować przy pomocy dostępnych funkcji.
Określanie zakresu dat
Wróćmy więc do generowania dat z zadanego przedziału, tak aby uwiarygodnić to, co zostanie wygenerowane. Zakładając, że z aplikacji mogą korzystać tylko osoby pełnoletnie, należy podać argumenty w następujący sposób:
birthday date, -- df: start=1955-05-21 end=2001-05-21 sub=power alpha=0.5
Parametry start
i end
oczywiście definiują zakres. Przy pomocy sub=power
określamy dystrybucję, której parametrem w tym wypadku będzie alpha
. Dzięki temu wyniki będą bardziej interesujące niż standardowy rozkład jednostajny.
\echo # filling table app_user (5) COPY app_user (id,username,firstname,lastname,email,birthday,is_active,created,last_login,last_ip) FROM STDIN (ENCODING 'UTF-8'); 1 bivector firstname_5_5_5 lastname_4_4_ [email protected] 1999-11-14 TRUE 2019-05-21 10:16:52 2019-05-21 10:17:52 0.0.0.4 2 shallows firstname_1_ lastname_5_ [email protected] 2000-12-17 FALSE 2019-05-21 10:14:52 2019-05-21 10:16:52 0.0.0.2 3 Boule firstname_5_5_5 lastname_3_3_3_ [email protected] 1999-11-21 FALSE 2019-05-21 10:13:52 2019-05-21 10:14:52 0.0.0.3 4 metasoma firstname_1_ lastname_4_4_ [email protected] 1996-03-14 TRUE 2019-05-21 10:13:52 2019-05-21 10:15:52 0.0.0.2 5 entrochite firstname lastname_4_4_ [email protected] 1997-03-25 FALSE 2019-05-21 10:17:52 2019-05-21 10:14:52 0.0.0.3 \.
Na koniec można by jeszcze wygenerować imiona i nazwiska, które wyglądają bardziej naturalnie. Z pomocą przyjdą nam odpowiednie listy słów. Można oczywiście skorzystać z własnoręcznie skompletowanej listy popularnych imion czy nazwisk. Ja skorzystam z udostępnianej w domenie publicznej listy Moby Project zawierającej angielskie imiona. Dla nazwisk skorzystam z listy nazw miejscowości, co powinno dać całkiem wiarygodne rezultaty. Podsumowując, nasze reguły wyglądają więc następująco:
CREATE TABLE app_user( id serial PRIMARY KEY, --df English: word=/usr/share/dict/words sub=uniform username text NOT NULL, --df: text=English length=1 lenvar=0 --df Names: word=/tmp/names.txt sub=uniform firstname text, --df: text=Names length=1 lenvar=0 --df Places: word=/tmp/places.txt sub=uniform lastname text, --df: text=Places length=1 lenvar=0 email text NOT NULL, --df: pattern='[a-z]{3,8}\.[a-z]{3,8}@(qmail|inbox)\.com' birthday date, -- df: start=1955-05-21 end=2001-05-21 sub=power alpha=0.5 is_active boolean NOT NULL, created timestamp NOT NULL, last_login timestamp NOT NULL, last_ip inet NOT NULL );
Natomiast po uruchomieniu generatora otrzymamy następujące wyniki:
\echo # filling table app_user (5) COPY app_user (id,username,firstname,lastname,email,birthday,is_active,created,last_login,last_ip) FROM STDIN (ENCODING 'UTF-8'); 1 metagalactic John Bramwell [email protected] 1988-12-21 FALSE 2019-06-04 10:08:48 2019-06-04 10:06:48 0.0.0.3 2 splinter-bar Mary Lacoste [email protected] 1988-01-11 TRUE 2019-06-04 10:07:48 2019-06-04 10:07:48 0.0.0.1 3 distinctions Diana Farnam [email protected] 1993-01-12 TRUE 2019-06-04 10:07:48 2019-06-04 10:07:48 0.0.0.4 4 sow Wilson Orford [email protected] 1967-02-23 TRUE 2019-06-04 10:06:48 2019-06-04 10:08:48 0.0.0.2 5 Magi Bran Eustis [email protected] 1985-07-12 FALSE 2019-06-04 10:09:48 2019-06-04 10:07:48 0.0.0.2 \.
Bonus – generator w generatorze
Ostatnią bardzo przydatną funkcją, którą chciałbym tutaj pokazać, jest możliwość osadzania generatora w generatorze. W przypadku generowania adresów e-mail znów można skorzystać z listy słów, aby adres e-mail lepiej zapadał w pamięć. Dodatkowo wykorzystajmy do tego celu prezentowany już generator text
.
--df English: word=/usr/share/dict/words email text NOT NULL, -- df: pattern='[:text=English length=1 lenvar=0:](\.\d{2,4})?@(qmail|inbox).com'
Jak widać, by zagnieździć inny generator, wystarczy umieścić go pomiędzy [: i :].
\echo # filling table app_user (5) COPY app_user (id,username,firstname,lastname,email,birthday,is_active,created,last_login,last_ip) FROM STDIN (ENCODING 'UTF-8'); 1 achymous firstname_2_2_2 lastname_5_ [email protected] 1992-05-19 FALSE 2019-05-21 10:35:07 2019-05-21 10:38:07 0.0.0.1 2 alundum firstname_1_ lastname_4_4_ [email protected] 1988-04-16 FALSE 2019-05-21 10:35:07 2019-05-21 10:39:07 0.0.0.3 3 coranoch firstname_4_ lastname_3_3_3_ [email protected] 1999-07-02 FALSE 2019-05-21 10:39:07 2019-05-21 10:39:07 0.0.0.3 4 colored firstname lastname_3_3_3_ [email protected] 1993-10-10 FALSE 2019-05-21 10:36:07 2019-05-21 10:36:07 0.0.0.0 5 bestripe firstname lastname_4_4_ [email protected] 1987-03-31 TRUE 2019-05-21 10:37:07 2019-05-21 10:35:07 0.0.0.3 \.
Podsumowanie
Mam nadzieję, że udało mi się Was zaciekawić potężnymi możliwościami, jakie daje wykorzystanie EuroDB Data Generatora podczas generowania danych losowych. Warto wspomnieć, że ten artykuł opisuje tylko niektóre funkcje i parametry konfiguracyjne, które pozwalają na wygenerowanie dokładnie takich danych, jakich wymaga projekt. Dostępnych możliwości jest o wiele więcej, niż można zmieścić w krótkiej formie artykułu. Dlatego w tym miejscu chciałbym zachęcić jeszcze do zapoznania się z naszymi autoryzowanymi kursami administracyjnymi, podczas których można zgłębić tematykę generowania danych oraz nauczyć się w pełni wykorzystywać możliwości wszystkich modułów dodatkowych EuroDB.