«Andmed kui kood» kogemus

«Andmed kui kood» kogemus

SQL, mis võiks olla lihtsam? Igaühel meist on võimalik kirjutada lihtne päring — kirjutame select, loetleme vajalikud veerud, seejärel from, tabeli nimi, natuke tingimusi where ja kõik — kasulikud andmed on meie käsutuses, (peaaegu) sõltumatult sellest, millisest andmebaasisüsteemist me räägime (võib-olla see ei ole isegi ande süsteem)). Tulemuseks on see, et igasuguste andmeallikatega, olgu need siis relatsioonilised või mitte, saab töötada nii, nagu see oleks lihtsalt kood (kõik sellega kaasneva — version control, code review, staatiline analüüs, automaattestimine ja kõik see). Ja see ei puuduta ainult andmeid, skeeme ja migreerimist, vaid tegelikult kogu andmehoidla elu. Selles artiklis räägime igapäevastest ülesannetest ja probleemidest, millega puutuvad kokku erinevad andmebaasid, keskendudes "andmebaas kui kood".

Ja alustame otse ORM. Esimesed lahingud stiilis "SQL vs ORM" märgati juba eelkäija Venemaal.

Objekt-relatsiooniline kaardistamine

ORM-i toetajad hindavad traditsiooniliselt arendamise kiirust ja lihtsust, sõltumatust andmebaasisüsteemist ning koodi puhtust. Paljude jaoks näeb andmebaasitöö kood (ja sageli isegi andmebaas ise)

tavaliselt välja umbes nii…

@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;
    }
  ...

Mudel on varustatud nutikate annotatsioonidega, samal ajal kui hiilgav ORM genereerib ja täidab tohutult SQL-koodi. Pealegi püüavad arendajad igasuguste abstraktsioonidega ennast oma andmebaasist eemal hoida, mis viitab teatud "SQL vihkamine".

Barrikaadide teisel poolel rõhutavad puhta "handmade"-SQL-i pooldajad, et nad saavad oma andmebaasist maksimumi välja pigistada ilma täiendavate kihtideta ja abstraktsioonideta. Selle tulemusena tekivad "data-centric" projektid, kus andmebaasidega tegelevad spetsiaalselt koolitatud inimesed (neid nimetatakse "andmebaasi spetsialistideks" või "andmebaasi halduriteks" jne), ja arendajatele jääb vaid "tõmmata" valmis vaateid ja salvestatud protseduure, ilma detailidesse laskumata.

Aga mis oleks, kui võtta parimad omadused kummastki maailmast? Nii on tehtud selles suurepärases tööriistas, millel on elujõuline nimi Yesql. Toodan paar rida üldisest kontseptsioonist oma vabalt tõlgituna, aga rohkemate detailidega saab tutvuda siit.

Clojure on äge keel DSL-ide loomiseks, aga SQL on iseenesest juba äge DSL ning me ei vaja veel üht. S-väljendid on kaunid, aga siin ei lisa need midagi uut. Lõpuks saame meid ümbritsevaid sulge. Ei ole nõus? Siis oodake hetk, mil andmebaasi abstraktsioon hakkab lekima ja te alustate võitlust funktsiooniga (raw-sql)

Ja mida teha? Jätame SQL-i tavaliseks SQL-iks — üks fail ühe päringu jaoks:

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

… ning seejärel lugege see fail ning muundage see tavaliseks Clojure funktsiooniks:

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

;;; Funktsioon nimega `users-by-country` on loodud.
;;; Kasutame seda:
(users-by-country {:country_code "GB"})
;=> ({:name "Kris" :country_code "GB" ...} ...)

Järgides põhimõtet "SQL eraldi, Clojure eraldi", saad:

  • Ei mingeid süntaktilisi üllatusi. Teie andmebaas (nagu ükskõik milline teine) ei vasta SQL standardile 100% — aga Yesql jaoks pole see oluline. Te kunagi ei raiska aega funktsioonide otsimisele, mille süntaks on SQL-i omaga võrreldav. Te ei pea kunagi tagasi minema funktsiooni juurde (raw-sql "some (‘funky’ :: SYNTAX)")).
  • Parim toimetaja tugi. Teie toimetajal on juba suurepärane SQL-tugi. Hoides SQL-d SQL-ina, saate seda lihtsalt kasutada.
  • Klienditugi. Teie DBA-d saavad lugeda ja kirjutada SQL-i, mida kasutate oma Clojure projektis.
  • Lihtsam jõudluse seadistamine. Kas peate koostama plaani probleemse päringu jaoks? See ei ole probleem, kui teie päring on tavaline SQL.
  • Päringute taaskasutamine. Looge need samad SQL-failid teistesse projektidesse, sest see on lihtsalt vana hea SQL — jagage seda lihtsalt.

Minu arvates on see idee väga äge ja samas väga lihtne, mille tõttu projekt on saanud palju järgijad erinevates keeltes. Proovime edasi rakendada sarnast filosoofiat, et eraldada SQL-kood kõigest muust kaugelt üle ORM-i piiri.

IDE & DB-haldurid

Alustame lihtsast igapäevasest ülesandest. Tihti peame otsima andmebaasist teatud objekte, näiteks leidma skeemist tabeli ja uurima selle struktuuri (milliseid veerge, võti, indekseid, piiranguid jne kasutatakse). Iga graafilise IDE või vähemalt mingisuguse andmebaasi haldurilt ootame eelkõige just neid oskusi. Tahame, et see toimiks kiiresti ja et ei peaks ootama pool tundi, kuni avaneb aken vajaliku teabega (eriti aeglase kaugandmebaasi ühenduse korral), ja et saadud teave oleks värske ja ajakohane, mitte vananenud vahemälu. Mida keerulisem ja suurem on andmebaas ning mida rohkem neid on, seda keerulisem on seda saavutada.

Kuid tavaliselt viskan hiire kusagile ära ja lihtsalt kirjutan koodi. Oletame, et soovime teada, millised tabelid (ja milliste omadustega) sisaldub skeemis "HR". Enamiku andmebaasi juhtimissüsteemide puhul saame soovitud tulemuse kergesti selle lihtsa päringuga information_schema's:

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

Tabelite sisu varieerub kindlasti põhjaliku andmebaasi ja iga SÜBDB võimete järgi. Näiteks MySQL-i puhul on võimalik sama juhendi abil saada selle SÜBDB spetsiifilisi tabeli parameetreid:

select table_name
     , storage_engine -- Kasutatav "mootor" ("MyISAM", "InnoDB" jne)
     , row_format     -- Rea formaat ("Fixed", "Dynamic" jne)
     , ...
  from information_schema.tables
 where schema = 'HR'

Oracle ei kasuta information_schema, kuid tal on olemas Oracle metadata, mis ei tekita suuri probleeme:

select table_name
     , pct_free       -- minimaalne vabade kohtade prosent andmeplokis (%)
     , pct_used       -- minimaalne kasutatud kohtade prosent andmeplokis (%)
     , last_analyzed  -- Viimase statistika kogumise kuupäev
     , ...
  from all_tables
 where owner = 'HR'

Sama kehtib ka ClickHouse'i kohta:

select name
     , engine -- Kasutatav "mootor" ("MergeTree", "Dictionary" jne)
     , ...
  from system.tables
 where database = 'HR'

Midagi sarnast saab teha ka Cassandra-s (kus on columnfamilies tabelite asemel ja keyspace’id skeemide asemel):

select columnfamily_name
     , compaction_strategy_class  -- Prahdihaldustrateegia
     , gc_grace_seconds           -- Prahti eluaeg
     , ...
  from system.schema_columnfamilies
 where keyspace_name = 'HR'

Enamik teisi andmebaase võimaldab samuti sarnaste päringute koostamist (isegi Mongo-s on olemas spetsiaalne süsteemi kogu, mis sisaldab teavet kõikide süsteemi kogumite kohta).

Loomulikult on sellisel viisil võimalik saada teavet mitte ainult tabelite, vaid ka iga objekti kohta. Aeg-ajalt jagavad head inimesed sellist koodi erinevatele andmebaasidele, näiteks habra-artiklite seerias "PostgreSQL andmebaaside dokumenteerimise funktsioonid" (aib, ben, gim). Loomulikult on kogu selle hulk päringute meeles hoidmine ja pidev nende sisestamine - see on "nii-nii" nauding, seetõttu on mu lemmik IDE/süntesaatoris mul ettevalmistatud komplekt snippet'e sageli kasutatavate päringute jaoks ning jääb vaid sisestada objektide nimed mallidesse.

Seetõttu on selline navigeerimise ja objektide otsimise meetod palju paindlikum, säästab aega ja võimaldab saada just seda teavet ja sellisel viisil, nagu praegu vajalik (nagu on kirjeldatud postituses "Andmete eksportimine andmebaasist mis tahes vormingus: mida oskavad IntelliJ platvormi IDE-d").

Objektide toimingud

Pärast seda, kui oleme leidnud ja uurinud vajalikud objektid, on aeg nendega midagi kasulikku teha. Loomulikult, hoides sõrmi klaviatuurilt lahus.

Pole saladus, et lihtne tabeli kustutamine näeb välja peaaegu kõigis andmebaasides sama:

drop table hr.persons

Kuid tabeli loomine on juba huvitavam. Praktiselt iga andmebaasisüsteem (sealhulgas paljud NoSQL lahendused) suudab mingil kujul "create table" ja selle põhiosa ei erine eriti palju (nimi, veergude loetelu, andmetüübid), kuid ülejäänud detailid võivad olla drastiliselt erinevad ning sõltuvad konkreetse andmebaasisüsteemi sisemisest ülesehitusest ja võimalustest. Minu lemmiknäide on, et Oracle'i dokumentatsioonis on ainult "paljad" BNF-id "create table" süntaksi jaoks nakatab 31 lehekülge. Teised andmebaasisüsteemid omavad tagasihoidlikumaid võimalusi, kuid igaühel neist on samuti palju huvitavaid ja unikaalseid omadusi tabelite loomisel (postgres, mysql, cockroach, cassandra). Harva suudab mõni graafiline "wizard" uuest IDE-st (eriti universaalne) neid kõiki oskusi täielikult katta, ja kui see ka peaks toimuma, siis on see vaatepilt mitte nõrgema närvikava jaoks. Samas, õigesti ja õigeaegselt kirjutatud käsk create table võimaldab hõlpsasti kasutada kõiki neid, tagades teie andmete talletamise ja juurdepääsu usaldusväärse, optimaalse ja maksimaalselt mugavana.

Paljudes andmebaasides on ka spetsiifilised objekti tüübid, mida teistes andmebaasides ei esine. Me saame teha operatsioone mitte ainult andmebaasi objektide, vaid ka andmebaasi enda üle, näiteks "tapma" protsessi, vabastama mäluruumi, käivitama jälgimise, minema "ainult lugemis" režiimi ja palju muud.

Ja nüüd natuke joonistame

Üks levinumaid ülesandeid on ehitada diagramm andmebaasi objektidega, et ilusalt visualiseerida objekte ja nende vahelisi seoseid. Seda oskab praktiliselt iga graafiline IDE, eraldi käsurea tööriistad, spetsialiseeritud graafilised tööriistad ja mudelid. Need joonistavad midagi, "kuidas oskavad", ja veidi saab sellele protsessile mõju avaldada ainult konfiguratsioonifaili paariparametri või liidese valikute kaudu.

Kuid selle probleemi saab lahendada palju lihtsamalt, paindlikumalt ja elegantsemalt, loomulikult koodi abil. Iga keerukuse diagrammide loomiseks on meil mitmeid spetsialiseeritud markeerimiskeeli (DOT, GraphML jne), ja nende jaoks terve hulk rakendusi (GraphViz, PlantUML, Mermaid), mis oskavad selliseid juhiseid lugeda ja visualiseerida erinevates formaatides. Teavet objektide ja nende vaheliste seoste kohta teame me juba kuidas hankida.

Toome väikese näite sellest, kuidas see võiks välja näha, kasutades PlantUML ja demonstreerimist PostgreSQL andmebaasist (vasakul SQL-päring, mis genereerib PlantUML jaoks vajaliku juhise, paremal on tulemus):

«Andmed kui kood» kogemus

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'

Ja kui natuke vaeva näha, siis saab ER-mallil PlantUML jaoks luua midagi, mis sarnaneb tõelise ER-diagrammiga: SQL-päring on natuke keerulisem

SQL-päring on veidi keerulisem

-- Pealkiri
select '@startuml
        !define Table(name,desc) class name as "desc" << (T,#FFAAAA) >&gt;
        !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
-- Tabelid
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
-- Tabelite vahelised seosed
select distinct ccu.table_name || ' "1" --&gt; "0..N" ' || tc.table_name || format(' : "A %s may have 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
-- Jalus
select '@enduml'

«Andmed kui kood» kogemus

Kui hoolikalt vaadata, kasutavad paljud visualiseerimistööriistad tõepoolest sarnaseid päringuid. Tõsi, need päringud on tavaliselt sügavalt "koodis" ise ja on keerulised mõista, rääkimata nende muutmisest.

Mõõdikud ja jälgimine

Liigume üle traditsiooniliselt keerulisele teemale – andmebaaside jõudluse jälgimine. Meenutan ühte tõelist lugu, mille rääkis "üks mu sõber". Ühel projektil elas ja töötas üks võimas DBA, keda harva keegi arendajatest isiklikult tundis, ja kellest keegi polnud kunagi isegi nägemiseks õnne saanud (kuigi kuulduste järgi töötas ta kuskil naabruses). Kella ajal "X", kui suure jaehulgiga ettevõtte tootmisseade taas "halvasti tundma hakkas", saatis ta vaikselt ekraanipilte Oracle Enterprise Managerist, kus ta kriitilised kohad hoolikalt punase markeriga esile tõi, et neid "paremini mõista" (see aitas, õrnalt öeldes, vähe). Nii tuli nende "fotode" alusel probleeme lahendada. Samas ei olnud kellelgi juurdepääsu sellele kallile (mõlemal tähendusel) Enterprise Managerile, kuna süsteem on keeruline ja kallis, äkki "arendajad leiavad millegi ja rikuvad kõik ära". Seetõttu leidsid arendajad "empriirilise" meetodi abil aeglustuse põhjuse ning laskusid patši välja andma. Kui hirmutav kiri DBA-lt ei tulnud lähiajal uuesti, hingas kõik kergendatult välja ja naasis oma praeguste ülesannete juurde (kuni uue kirja saabumiseni).

Kuid jälgimisprotsess võib olla ka lõbusam ja sõbralikum, ning mis peamine — juurdepääsetavam ja läbipaistvam kõigile. Vähemalt baastasandi osas, mis täiendaks põhimonitooringusüsteeme (mis on kindlasti kasulikud ja paljudes olukordades asendamatud). Iga andmebaas (DB) on valmis jagama teavet oma praeguse oleku ja jõudluse kohta täiesti tasuta. Sama "verine" Oracle DB puhul on praktiliselt kõike teavet jõudluse kohta võimalik saada süsteemivaadetest, alates protsessidest ja seanssidest kuni vahemälu olekuni. DBA skriptid, sektsioon "Jälgimine". Postgresql-is on samuti terve hulk süsteemivaateid, mis on mõeldud andmebaasi jälgimiseks, sealhulgas sellised hindamatud, nagu pg_stat_activity, pg_stat_database, pg_stat_bgwriter. MySQL-is on selleks isegi eraldi skeem performance_schema. Ja Mongodb-s on sisseehitatud profiler andmete kogumiseks jõudluse kohta süsteemi kogumisse system.profile.

Seega, kasutades mõnda mõõdiku kogumist (Telegraf, Metricbeat, Collectd), mis suudab sooritada kohandatud SQL-päringuid, mõõtmete salvestamise süsteemi (InfluxDB, Elasticsearch, Timescaledb) ja visuaalset tööriista (Grafana, Kibana), on võimalik luua piisavalt kerge ja paindlik jälgimissüsteem, mis on tihedalt seotud teiste süsteemi mõõtmistega (mille saadakse näiteks rakenduste serverilt, opsüsteemilt jne). Näiteks on see tehtud pgwatch2-s, kus kasutatakse kombinatsiooni InfluxDB + Grafana ja komplekti päringutest süsteemi vaadetele, millele saab samuti lisada kohandatud päringuid.

Kokku

Ja see on ainult ligikaudne loetelu, mida saab meie andmebaasiga tavalise SQL-koodi abil teha. Olen kindel, et on võimalik leida veel palju rakendusi, kirjutage kommentaaridesse. Ja sellest, kuidas (ja mis kõige tähtsam, milleks) seda kõik automatiseerida ja oma CI/CD torustikku kaasata, räägime järgmine kord.

Allikas: habr.com

Osta usaldusväärne veebihosting DDoS kaitsega, VPS VDS serverid 🔥 Osta usaldusväärne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster