Iskustvo „baze podataka kao koda“.

Iskustvo „baze podataka kao koda“.

SQL, šta može biti jednostavnije? Svako od nas može napisati jednostavan zahtjev - kucamo izabrati, zatim navedite potrebne kolone od, ime tabele, neki uslovi u gdje i to je sve - korisni podaci su u našem džepu, i (skoro) bez obzira na to koji DBMS je u tom trenutku ispod haube (ili možda uopšte nije DBMS). Kao rezultat toga, rad s gotovo bilo kojim izvorom podataka (relacijskim i ne tako) može se razmatrati sa stanovišta običnog koda (sa svime što to podrazumijeva - kontrola verzija, pregled koda, statička analiza, autotestovi, i to je sve). I to se ne odnosi samo na same podatke, sheme i migracije, već općenito na cijeli vijek trajanja skladišta. U ovom članku ćemo govoriti o svakodnevnim zadacima i problemima rada sa različitim bazama podataka pod objektivom „baze podataka kao koda“.

I počnimo odmah od ORM. Prve bitke tipa "SQL vs ORM" zapažene su još u prošlosti predpetrska Rus'.

Objektno-relacijsko mapiranje

Pristalice ORM-a tradicionalno cijene brzinu i lakoću razvoja, nezavisnost od DBMS-a i čist kod. Za mnoge od nas, kod za rad sa bazom podataka (a često i samom bazom podataka)

obično izgleda otprilike ovako...

@Entity
@Table(name = "stock", catalog = "maindb", uniqueConstraints = {
        @UniqueConstraint(columnNames = "STOCK_NAME"),
        @UniqueConstraint(columnNames = "STOCK_CODE") })
public class Stock implements java.io.Serializable {

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "STOCK_ID", unique = true, nullable = false)
    public Integer getStockId() {
        return this.stockId;
    }
  ...

Model je okačen pametnim napomenama, a negdje iza kulisa hrabri ORM generiše i izvršava tone nekog SQL koda. Inače, programeri se svim silama trude da se izoluju od svoje baze podataka kilometrima apstrakcija, što ukazuje na neke "SQL mrzi".

S druge strane barikada, pristalice čistog “ručnog” SQL-a primjećuju sposobnost da istisnu sav sok iz svog DBMS-a bez dodatnih slojeva i apstrakcija. Kao rezultat toga, pojavljuju se „data-centric“ projekti, gdje su posebno obučeni ljudi uključeni u bazu podataka (oni su i „bazičari“, oni su i „bazičari“, oni su također „basdeneri“ itd.), a programeri samo treba da „povuku“ gotove poglede i uskladištene procedure, bez ulaženja u detalje.

Šta ako imamo najbolje od oba svijeta? Kako se to radi u prekrasnom alatu sa životno potvrđujućim imenom Yesql. Dat ću nekoliko redaka iz općeg koncepta u svom slobodnom prijevodu, a vi se možete detaljnije upoznati s njim ovdje.

Clojure je kul jezik za kreiranje DSL-ova, ali sam SQL je kul DSL i ne treba nam drugi. S-izrazi su odlični, ali ovdje ne dodaju ništa novo. Kao rezultat, dobijamo zagrade radi zagrada. Ne slažete se? Zatim sačekajte trenutak kada apstrakcija nad bazom podataka počne da curi i počnete da se borite sa funkcijom (raw-sql)

Pa šta da radim? Ostavimo SQL kao običan SQL - jedan fajl po zahtjevu:

-- name: users-by-country
select *
  from users
 where country_code = :country_code

... a zatim pročitajte ovaj fajl, pretvarajući ga u redovnu Clojure funkciju:

(defqueries "some/where/users_by_country.sql"
   {:connection db-spec})

;;; A function with the name `users-by-country` has been created.
;;; Let's use it:
(users-by-country {:country_code "GB"})
;=> ({:name "Kris" :country_code "GB" ...} ...)

Pridržavajući se principa "SQL sam, Clojure sam" dobijate:

  • Nema sintaktičkih iznenađenja. Vaša baza podataka (kao i svaka druga) nije 100% usklađena sa SQL standardom - ali to nije važno za Yesql. Nikada nećete gubiti vrijeme tražeći funkcije sa SQL ekvivalentnom sintaksom. Nikada se nećete morati vraćati na funkciju (raw-sql "neki('funky'::SINTAX)")).
  • Najbolja podrška za urednike. Vaš uređivač već ima odličnu SQL podršku. Ako sačuvate SQL kao SQL, možete ga jednostavno koristiti.
  • Timska kompatibilnost. Vaši DBA mogu čitati i pisati SQL koji koristite u svom Clojure projektu.
  • Lakše podešavanje performansi. Trebate napraviti plan za problematičan upit? Ovo nije problem kada je vaš upit običan SQL.
  • Ponovno korištenje upita. Prevucite i ispustite te iste SQL datoteke u druge projekte jer je to običan stari SQL - samo ga podijelite.

Po mom mišljenju, ideja je vrlo cool i istovremeno vrlo jednostavna, zahvaljujući čemu je projekat dobio mnoge sljedbenici na raznim jezicima. Zatim ćemo pokušati primijeniti sličnu filozofiju odvajanja SQL koda od svega ostalog što je daleko izvan ORM-a.

IDE & DB menadžeri

Počnimo s jednostavnim svakodnevnim zadatkom. Često moramo tražiti neke objekte u bazi podataka, na primjer, pronaći tabelu u šemi i proučiti njenu strukturu (koji stupci, ključevi, indeksi, ograničenja itd. se koriste). I od bilo kog grafičkog IDE-a ili malog DB-managera, prije svega, očekujemo upravo ove sposobnosti. Tako da bude brzo i da ne morate čekati pola sata dok se ne iscrta prozor sa potrebnim informacijama (posebno kod sporog povezivanja na udaljenu bazu podataka), a da pritom primljene informacije budu svježe i relevantne, a ne keširano smeće. Štaviše, što je baza podataka složenija i veća i što ih je veći, to je teže to učiniti.

Ali obično bacim miša i samo napišem kod. Recimo da trebate saznati koje su tabele (i sa kojim svojstvima) sadržane u "HR" šemi. U većini DBMS-ova, željeni rezultat se može postići ovim jednostavnim upitom iz information_schema:

select table_name
     , ...
  from information_schema.tables
 where schema = 'HR'

Od baze podataka do baze podataka, sadržaj takvih referentnih tabela varira ovisno o mogućnostima svakog DBMS-a. I, na primjer, za MySQL, iz istog priručnika možete dobiti parametre tablice specifične za ovaj DBMS:

select table_name
     , storage_engine -- Используемый "движок" ("MyISAM", "InnoDB" etc)
     , row_format     -- Формат строки ("Fixed", "Dynamic" etc)
     , ...
  from information_schema.tables
 where schema = 'HR'

Oracle ne poznaje information_schema, ali ima Oracle metapodaci, i ne nastaju veliki problemi:

select table_name
     , pct_free       -- Минимум свободного места в блоке данных (%)
     , pct_used       -- Минимум используемого места в блоке данных (%)
     , last_analyzed  -- Дата последнего сбора статистики
     , ...
  from all_tables
 where owner = 'HR'

ClickHouse nije izuzetak:

select name
     , engine -- Используемый "движок" ("MergeTree", "Dictionary" etc)
     , ...
  from system.tables
 where database = 'HR'

Nešto slično se može učiniti u Cassandri (koja ima porodice stupaca umjesto tabela i ključeve umjesto shema):

select columnfamily_name
     , compaction_strategy_class  -- Стратегия сборки мусора
     , gc_grace_seconds           -- Время жизни мусора
     , ...
  from system.schema_columnfamilies
 where keyspace_name = 'HR'

Za većinu drugih baza podataka, također možete smisliti slične upite (čak i Mongo ima posebna sistemska zbirka, koji sadrži informacije o svim zbirkama u sistemu).

Naravno, na ovaj način možete dobiti informacije ne samo o tablicama, već o bilo kojem objektu općenito. S vremena na vrijeme ljubazni ljudi dijele takav kod za različite baze podataka, kao, na primjer, u seriji članaka o habri „Funkcije za dokumentiranje PostgreSQL baza podataka“ (Ayb, Ben, teretana). Naravno, pravo je zadovoljstvo držati ovo brdo upita u glavi i stalno ih kucati, tako da u svom omiljenom IDE/editoru imam unaprijed pripremljen set isječaka za često korištene upite, a sve što ostaje je da ukucam imena objekata u šablonu.

Kao rezultat toga, ova metoda navigacije i traženja objekata je mnogo fleksibilnija, štedi puno vremena i omogućava vam da dobijete upravo one informacije u obliku u kojem su sada potrebne (kao što je, na primjer, opisano u postu "Izvoz podataka iz baze podataka u bilo kojem formatu: šta IDE mogu učiniti na IntelliJ platformi").

Operacije sa objektima

Nakon što smo pronašli i proučili potrebne predmete, vrijeme je da s njima učinimo nešto korisno. Naravno, takođe bez skidanja prstiju sa tastature.

Nije tajna da će jednostavno brisanje tabele izgledati isto u skoro svim bazama podataka:

drop table hr.persons

Ali sa stvaranjem stola postaje zanimljivije. Gotovo svaki DBMS (uključujući mnoge NoSQL) može „kreirati tabelu“ u ovom ili onom obliku, a njen glavni dio će se čak i neznatno razlikovati (naziv, lista kolona, ​​tipovi podataka), ali ostali detalji mogu se drastično razlikovati i ovisiti o interni uređaj i mogućnosti određenog DBMS-a. Moj omiljeni primjer je da u Oracle dokumentaciji postoje samo "goli" BNF-ovi za sintaksu "kreiraj tablicu" zauzimaju 31 stranicu. Drugi DBMS-ovi imaju skromnije mogućnosti, ali svaki od njih također ima mnogo zanimljivih i jedinstvenih karakteristika za kreiranje tabela (postgres, mysql, žohar, cassandra). Malo je vjerovatno da će neki grafički „čarobnjak“ iz drugog IDE-a (posebno univerzalnog) moći u potpunosti pokriti sve ove sposobnosti, a čak i ako može, to neće biti spektakl za one sa slabim srcem. Istovremeno, korektno i blagovremeno napisana izjava kreirati tabelu omogućit će vam jednostavno korištenje svih njih, učiniti pohranu i pristup vašim podacima pouzdanim, optimalnim i što udobnijim.

Također, mnogi DBMS-ovi imaju svoje specifične tipove objekata koji nisu dostupni u drugim DBMS-ovima. Štoviše, možemo izvoditi operacije ne samo na objektima baze podataka, već i na samom DBMS-u, na primjer, „ubiti“ proces, osloboditi dio memorije, omogućiti praćenje, prebaciti se na način rada „samo za čitanje“ i još mnogo toga.

Sada da crtamo malo

Jedan od najčešćih zadataka je da napravite dijagram sa objektima baze podataka i vidite objekte i veze između njih u prelepoj slici. Gotovo svaki grafički IDE, odvojeni uslužni programi "komandne linije", specijalizovani grafički alati i modeli mogu to učiniti. Oni će vam nešto nacrtati „najbolje što mogu“, a na ovaj proces možete malo uticati samo uz pomoć nekoliko parametara u konfiguracionoj datoteci ili checkbox-a u interfejsu.

Ali ovaj problem se može riješiti mnogo jednostavnije, fleksibilnije i elegantnije, naravno uz pomoć koda. Za kreiranje dijagrama bilo koje složenosti imamo nekoliko specijaliziranih jezika za označavanje (DOT, GraphML itd.), a za njih čitav niz aplikacija (GraphViz, PlantUML, Mermaid) koje mogu čitati takve upute i vizualizirati ih u raznim formatima . Pa, već znamo kako doći do informacija o objektima i vezama između njih.

Evo malog primjera kako bi ovo moglo izgledati, koristeći PlantUML i demo baza podataka za PostgreSQL (na lijevoj strani je SQL upit koji će generirati potrebnu instrukciju za PlantUML, a desno je rezultat):

Iskustvo „baze podataka kao koda“.

select '@startuml'||chr(10)||'hide methods'||chr(10)||'hide stereotypes' union all
select distinct ccu.table_name || ' --|> ' ||
       tc.table_name as val
  from table_constraints as tc
  join key_column_usage as kcu
    on tc.constraint_name = kcu.constraint_name
  join constraint_column_usage as ccu
    on ccu.constraint_name = tc.constraint_name
 where tc.constraint_type = 'FOREIGN KEY'
   and tc.table_name ~ '.*' union all
select '@enduml'

A ako pokušate malo, onda na osnovu ER šablon za PlantUML možete dobiti nešto vrlo slično stvarnom dijagramu hitne pomoći:

SQL upit je malo složeniji

-- Шапка
select '@startuml
        !define Table(name,desc) class name as "desc" << (T,#FFAAAA) >>
        !define primary_key(x) <b>x</b>
        !define unique(x) <color:green>x</color>
        !define not_null(x) <u>x</u>
        hide methods
        hide stereotypes'
 union all
-- Таблицы
select format('Table(%s, "%s n information about %s") {'||chr(10), table_name, table_name, table_name) ||
       (select string_agg(column_name || ' ' || upper(udt_name), chr(10))
          from information_schema.columns
         where table_schema = 'public'
           and table_name = t.table_name) || chr(10) || '}'
  from information_schema.tables t
 where table_schema = 'public'
 union all
-- Связи между таблицами
select distinct ccu.table_name || ' "1" --> "0..N" ' || tc.table_name || format(' : "A %s may haven many %s"', ccu.table_name, tc.table_name)
  from information_schema.table_constraints as tc
  join information_schema.key_column_usage as kcu on tc.constraint_name = kcu.constraint_name
  join information_schema.constraint_column_usage as ccu on ccu.constraint_name = tc.constraint_name
 where tc.constraint_type = 'FOREIGN KEY'
   and ccu.constraint_schema = 'public'
   and tc.table_name ~ '.*'
 union all
-- Подвал
select '@enduml'

Iskustvo „baze podataka kao koda“.

Ako pažljivo pogledate, ispod haube mnogi alati za vizualizaciju također koriste slične upite. Istina, ovi zahtjevi su obično duboki „uklopljene“ u kod same aplikacije i teško ih je razumjeti, da ne spominjem bilo kakvu njihovu modifikaciju.

metrika i praćenje

Pređimo na tradicionalno složenu temu - praćenje performansi baze podataka. Sjećam se male istinite priče koju mi ​​je ispričao “jedan od mojih prijatelja”. Na drugom projektu živio je određeni moćni DBA, i malo ko od programera ga je lično poznavao, ili ga je ikada lično vidio (uprkos činjenici da je, prema glasinama, radio negdje u susjednoj zgradi). U satu „X“, kada je proizvodni sistem velikog trgovca ponovo počeo da se „oseća loše“, u tišini je poslao screenshotove grafikona iz Oracle Enterprise Manager-a, na kojima je pažljivo označio kritična mesta crvenim markerom za „razumljivost“ ( ovo, blago rečeno, nije puno pomoglo). I na osnovu ove "foto kartice" morao sam da tretiram. Istovremeno, niko nije imao pristup dragocenom (u oba smisla te reči) Enterprise Manageru, jer sistem je složen i skup, odjednom "programeri naiđu na nešto i sve pokvare." Stoga su programeri "empirijski" pronašli lokaciju i uzrok kočnica i pustili zakrpu. Da prijeteće pismo iz DBA ne stigne ponovo u bliskoj budućnosti, onda bi svi odahnuli i vratili se svojim trenutnim zadacima (do novog Pisma).

Ali proces praćenja može izgledati zabavnije i prijateljskije, i što je najvažnije, pristupačnije i transparentnije za sve. Barem njegov osnovni dio, kao dodatak glavnim sistemima praćenja (koji su svakako korisni i u mnogim slučajevima nezamjenjivi). Svaki DBMS može slobodno i apsolutno besplatno dijeliti informacije o svom trenutnom stanju i performansama. U istom „krvavom“ Oracle DB-u, gotovo sve informacije o performansama mogu se dobiti iz sistemskih pogleda, u rasponu od procesa i sesija do stanja keša bafera (na primjer, DBA skripte, odjeljak "Monitoring"). Postgresql takođe ima čitavu gomilu sistemskih pogleda za praćenje baze podataka, posebno one koje su neophodne u svakodnevnom životu svakog DBA, kao npr pg_stat_activity, pg_stat_database, pg_stat_bgwriter. MySQL čak ima zasebnu šemu za ovo. performance_schema. A In Mongo ugrađen profiler agregira podatke o performansama u sistemsku kolekciju system.profile.

Dakle, naoružani nekom vrstom sakupljača metrika (Telegraf, Metricbeat, Collectd) koji može izvoditi prilagođene sql upite, skladištenje ovih metrika (InfluxDB, Elasticsearch, Timescaledb) i vizualizator (Grafana, Kibana), možete dobiti prilično jednostavan i fleksibilan sistem praćenja koji će biti blisko integrisan sa drugim sistemskim metrikama (dobijenim, na primer, sa servera aplikacija, iz OS-a, itd.). Kao što se, na primjer, radi u pgwatch2, koji koristi kombinaciju InfluxDB + Grafana i skup upita za sistemske poglede, kojima se također može pristupiti dodajte prilagođene upite.

Ukupno

A ovo je samo približna lista onoga što se može učiniti s našom bazom podataka koristeći uobičajeni SQL kod. Sigurna sam da možete naći još mnogo primjena, pišite u komentarima. A mi ćemo razgovarati o tome kako (i najvažnije zašto) sve ovo automatizirati i uključiti u svoj CI/CD kanal sljedeći put.

izvor: www.habr.com

Dodajte komentar