Blog » Analizy i porady » Bazy danych

Generowanie danych losowych dla PostgreSQL z wykorzystaniem EuroDB

Generowanie danych losowych dla PostgreSQL

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_	rsdjqqe.hanvsp@inbox.com	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_	qvxqjn.vxkqzg@inbox.com	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_	qleu.nglj@qmail.com	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_	fdnse.rulvr@inbox.com	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_	gkmhm.xrwfmb@inbox.com	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	sdoeiovx.wsd@qmail.com	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	ntmjtct.musck@qmail.com	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	xmkwa.hlxjajkf@inbox.com	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_	ykpsbezn.jpluf@qmail.com	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	wxrlmyjv.abomukj@qmail.com	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_	dqddtfi.sjr@qmail.com	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_	brzp.bmaillx@inbox.com	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_	evchf.nfi@inbox.com	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_	hftt.ovjfxfg@qmail.com	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_	ohvqczqj.wyvzyc@inbox.com	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    cbfpxhh.oicl@inbox.com    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     icqvcbh.oixie@inbox.com   1988-01-11    TRUE     2019-06-04    10:07:48    2019-06-04    10:07:48    0.0.0.1
3       distinctions    Diana Farnam     fvmrj.tmluercy@qmail.com  1993-01-12    TRUE     2019-06-04    10:07:48    2019-06-04    10:07:48    0.0.0.4
4       sow             Wilson Orford    yvldbcvg.qao@qmail.com    1967-02-23    TRUE     2019-06-04    10:06:48    2019-06-04    10:08:48    0.0.0.2
5       Magi            Bran Eustis      tripb.flin@inbox.com      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_	Adib.7080@qmail.com	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_	evasible.86@inbox.com	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_	manhunter.9963@inbox.com	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_	country.8663@qmail.com	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_	pappyri@inbox.com	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.

Chcesz być na bieżąco? Obserwuj nasz profil w serwisie LinkedIn.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *