Iskustvo "Baza podataka kao kod".

Iskustvo "Baza podataka kao kod".

SQL, što može biti jednostavnije? Svatko od nas može napisati jednostavan zahtjev - tipkamo odabrati, zatim navedite potrebne stupce iz, naziv tablice, neki uvjeti u gdje i to je sve - korisni podaci su u našem džepu, i (skoro) bez obzira na to koji je DBMS u tom trenutku pod haubom (ili možda uopće nije DBMS). Kao rezultat toga, rad s gotovo bilo kojim izvorom podataka (relacijskim i ne tako) može se razmatrati sa stajališ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 pohrane. U ovom ćemo članku govoriti o svakodnevnim zadacima i problemima rada s različitim bazama podataka pod lupom “baze podataka kao koda”.

I krenimo odmah od ORM. Prve bitke tipa "SQL vs ORM" primijećene su još god predpetrovska Rusija.

Objektno-relacijsko preslikavanje

Pobornici ORM-a tradicionalno cijene brzinu i jednostavnost razvoja, neovisnost o DBMS-u i čisti kod. Za mnoge od nas kod za rad s bazom podataka (a često i sama baza podataka)

to obično izgleda 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 obložen pametnim komentarima, a negdje iza kulisa hrabri ORM generira i izvršava gomilu nekog SQL koda. Usput, programeri se svim silama trude izolirati se od svoje baze podataka kilometrima apstrakcija, što ukazuje na neke "SQL mržnja".

S druge strane barikada, pristaše čistog "ručno izrađenog" SQL-a primjećuju sposobnost da iscijede 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 također “basicists”, oni su također “basicists”, oni su također “basdeneri” itd.), a programeri samo treba "izvući" gotove poglede i pohranjene procedure, bez ulaženja u detalje.

Što ako imamo najbolje od oba svijeta? Kako se to radi u prekrasnom alatu imena koje potvrđuje život Yesql. Dat ću nekoliko redaka iz općeg koncepta u svom slobodnom prijevodu, a vi se možete detaljnije upoznati s njim здесь.

Clojure je cool jezik za stvaranje DSL-ova, ali SQL je sam po sebi cool DSL i ne treba nam još jedan. S-izrazi su super, ali ne dodaju ništa novo ovdje. Kao rezultat toga, dobivamo zagrade radi zagrada. Ne slažem se? Zatim pričekajte trenutak kada apstrakcija nad bazom podataka počne curiti i počnete se boriti s funkcijom (raw-sql)

Što bih trebao napraviti? Ostavimo SQL kao obični SQL - jedna datoteka po zahtjevu:

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

... a zatim pročitajte ovu datoteku, pretvarajući je u običnu 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 načela "SQL sam po sebi, Clojure sam po sebi" dobivate:

  • Bez 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 vratiti na funkciju (raw-sql "some('funky'::SYNTAX)")).
  • Najbolja podrška za urednike. Vaš uređivač već ima izvrsnu podršku za SQL. Ako spremite SQL kao SQL, možete ga jednostavno koristiti.
  • Kompatibilnost tima. Vaši administratori baze podataka 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 regularni SQL.
  • Ponovno korištenje upita. Povucite 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, au isto vrijeme vrlo jednostavna, zahvaljujući kojoj je projekt stekao mnoge sljedbenici na raznim jezicima. Zatim ćemo pokušati primijeniti sličnu filozofiju odvajanja SQL koda od svega ostalog 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 tablicu u shemi i proučiti njezinu strukturu (koji se stupci, ključevi, indeksi, ograničenja itd. koriste). A od bilo kojeg grafičkog IDE-a ili malog DB-managera, prije svega, očekujemo upravo ove mogućnosti. Kako bi bilo brzo i ne morate čekati pola sata dok vam se ne iscrta prozor s potrebnim informacijama (pogotovo kod spore veze s udaljenom bazom podataka), a istovremeno su primljene informacije svježe i relevantne, a ne predmemorirano smeće. Štoviše, što je baza podataka složenija i veća i što je njihov broj 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 tablice (i s kojim svojstvima) sadržane u "HR" shemi. U većini DBMS-ova, željeni rezultat može se postići ovim jednostavnim upitom iz information_schema:

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

Od baze do baze podataka, sadržaj takvih referentnih tablica varira ovisno o mogućnostima svakog DBMS-a. I, na primjer, za MySQL, iz iste referentne knjige 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 informacijsku shemu, ali je poznaje Oracle metapodaci, i nema velikih problema:

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

ClickHouse nije iznimka:

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

Nešto slično može se učiniti u Cassandri (koja ima obitelji stupaca umjesto tablica i prostore ključeva 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 zbirka sustava, koji sadrži podatke o svim zbirkama u sustavu).

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 habra članaka “Funkcije za dokumentiranje PostgreSQL baza podataka” (Ayb, Ben, teretana). Naravno, držati cijelo ovo brdo upita u glavi i stalno ih tipkati je takav užitak, tako da u svom omiljenom IDE/editoru imam unaprijed pripremljen set isječaka za često korištene upite, i sve što preostaje je utipkati imena objekata u predložak.

Kao rezultat toga, ova metoda navigacije i traženja objekata mnogo je fleksibilnija, štedi puno vremena i omogućuje vam da dobijete točno 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: što IDE mogu učiniti na IntelliJ platformi").

Operacije s objektima

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

Nije tajna da će jednostavno brisanje tablice izgledati isto u gotovo svim bazama podataka:

drop table hr.persons

Ali s izradom stola postaje zanimljivije. Gotovo svaki DBMS (uključujući mnoge NoSQL) može "stvoriti tablicu" u ovom ili onom obliku, a njen glavni dio će se čak malo razlikovati (naziv, popis stupaca, tipovi podataka), ali ostali detalji mogu se dramatično razlikovati i ovise o interni uređaj i mogućnosti konkretnog DBMS-a. Moj omiljeni primjer je da u Oracle dokumentaciji postoje samo "goli" BNF-ovi za sintaksu "stvori tablicu" zauzimaju 31 stranicu. Ostali DBMS-ovi imaju skromnije mogućnosti, ali svaki od njih također ima mnogo zanimljivih i jedinstvenih značajki za izradu tablica (postgres, mysql, bubašvaba, Cassandra). Malo je vjerojatno da će bilo koji grafički “čarobnjak” iz drugog IDE-a (pogotovo univerzalnog) moći u potpunosti pokriti sve te mogućnosti, a čak i ako može, to neće biti spektakl za one sa slabim srcem. Istovremeno, ispravno i pravodobno napisana izjava kreirati tablicu omogućit će vam jednostavno korištenje svih njih, učiniti pohranu i pristup vašim podacima pouzdanim, optimalnim i što ugodnijim.

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 neko područje memorije, omogućiti praćenje, prebaciti se u način rada "samo za čitanje" i još mnogo toga.

Sada malo crtajmo

Jedan od najčešćih zadataka je izgraditi dijagram s objektima baze podataka i vidjeti objekte i veze između njih na lijepoj slici. Gotovo svaki grafički IDE, zasebni uslužni programi "komandne linije", specijalizirani grafički alati i modeliri to mogu učiniti. Oni će vam nacrtati nešto “kako znaju”, a na taj proces možete malo utjecati samo uz pomoć nekoliko parametara u konfiguracijskoj datoteci ili potvrdnim okvirima u sučelju.

Ali ovaj se problem može riješiti puno jednostavnije, fleksibilnije i elegantnije, i naravno uz pomoć koda. Za izradu 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 različitim formatima . Pa, već znamo kako doći do informacija o objektima i vezama među njima.

Evo malog primjera kako bi ovo moglo izgledati, koristeći PlantUML i demo baza podataka za PostgreSQL (s lijeve strane je SQL upit koji će generirati potrebnu instrukciju za PlantUML, a s desne je rezultat):

Iskustvo "Baza podataka kao kod".

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 malo pokušate, onda na temelju ER predložak za PlantUML možete dobiti nešto vrlo slično pravom ER dijagramu:

SQL upit je malo kompliciraniji

-- Шапка
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 "Baza podataka kao kod".

Ako bolje pogledate, ispod haube mnogi alati za vizualizaciju također koriste slične upite. Istina, ti su zahtjevi obično duboki "ugrađeni" u kod same aplikacije i teško ih je razumjeti, da ne spominjemo njihove izmjene.

Mjerni podaci i praćenje

Prijeđimo na tradicionalno složenu temu - praćenje performansi baze podataka. Sjećam se male istinite priče koju mi ​​je ispričao "jedan moj prijatelj". Na drugom projektu živio je određeni moćni DBA, a malo ga je programera poznavalo osobno ili ga je ikada osobno vidjelo (unatoč činjenici da je, prema glasinama, radio negdje u susjednoj zgradi). U satu “X”, kada se proizvodni sustav velikog trgovca ponovno počeo “lošiti”, u tišini je poslao screenshotove grafova iz Oracle Enterprise Managera, na kojima je kritična mjesta pažljivo istaknuo crvenim markerom radi “razumljivosti” ( ovo, blago rečeno, nije puno pomoglo). I na temelju ove "foto karte" koju sam morao liječiti. Istovremeno, nitko nije imao pristup dragocjenom (u oba smisla riječi) Enterprise Manageru, jer sustav je složen i skup, odjednom se "programeri spotaknu o nešto i sve pokvare." Stoga su programeri "empirijski" pronašli mjesto i uzrok kočnica i izdali zakrpu. Da prijeteći dopis DBA opet ne stigne u dogledno vrijeme, onda bi svi odahnuli i vratili se sadašnjim poslovima (do novog Dopisa).

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 nadzornim sustavima (koji su svakako korisni iu mnogim slučajevima nezamjenjivi). Svaki DBMS može slobodno i apsolutno besplatno dijeliti informacije o svom trenutnom stanju i performansama. U istoj "krvavoj" Oracle DB, gotovo sve informacije o performansama mogu se dobiti iz prikaza sustava, u rasponu od procesa i sesija do stanja međuspremnika (na primjer, DBA skripte, odjeljak "Praćenje"). Postgresql također ima čitavu hrpu prikaza sustava za praćenje baze podataka, posebno onih koji su nezamjenjivi u svakodnevnom životu svakog DBA, kao što su pg_stat_activity, pg_stat_baza_podataka, pg_stat_bgwriter. MySQL čak ima zasebnu shemu za to. shema_izvedbe. A U Mongo ugrađen profiler prikuplja podatke o izvedbi u zbirku sustava sustav.profil.

Dakle, naoružani nekom vrstom sakupljača metrika (Telegraf, Metricbeat, Collectd) koji može izvoditi prilagođene sql upite, pohranom tih metrika (InfluxDB, Elasticsearch, Timescaledb) i vizualizatorom (Grafana, Kibana), možete dobiti prilično jednostavan i fleksibilan sustav praćenja koji će biti usko integriran s drugim metrikama cijelog sustava (dobivenim, na primjer, s aplikacijskog poslužitelja, iz OS-a itd.). Kao što je, na primjer, to učinjeno u pgwatch2, koji koristi kombinaciju InfluxDB + Grafana i skup upita za preglede sustava, kojima se također može pristupiti dodajte prilagođene upite.

Ukupno

A ovo je samo približan popis onoga što se može učiniti s našom bazom podataka korištenjem uobičajenog SQL koda. Siguran sam da možete pronaći još mnogo drugih upotreba, napišite u komentarima. Razgovarat ćemo o tome kako (i što je najvažnije zašto) sve ovo automatizirati i sljedeći put uključiti u svoj CI/CD kanal.

Izvor: www.habr.com

Dodajte komentar