Eksperienca «Database as Code»

Eksperienca «Database as Code»

SQL, çfarë mund të jetë më e thjeshtë? Çdo prej nesh mund të shkruajë një kërkesë të thjeshtë — shkruajmë select, rendisim kolonat e nevojshme, pastaj from, emri i tabelës, pak kushte në where dhe gjithçka — të dhëna të dobishme kemi në xhep, ndonjëherë (gati) pa marrë parasysh se cila DBMS është në sfond (apo ndoshta nuk është një DBMS fare). Si rezultat, puna me pothuajse çdo burim të dhënash (relational dhe jo aq shumë) mund të shqyrtohet nga këndvështrimi i kodit të zakonshëm (me të gjitha pasojat — version kontrolle, rishikimi i kodit, analiza statike, teste automatike dhe gjithë kjo). Kjo nuk ka të bëjë vetëm me të dhënat, skemat dhe migrimet, por në përgjithësi me gjithë aktivitetin e depozitës. Në këtë artikull do të flasim për detyrat dhe problemet e përditshme në punën me DB të ndryshme nën këndvështrimin e "database as code".

Dhe do të fillojmë me ORM. Luftërat e para në stilin "SQL vs ORM" janë vërejtur qysh në Rusinë para Pjetrit.

Mapimi objekt-relacional

Përkrahësit e ORM zakonisht e vlerësojnë shpejtësinë dhe thjeshtësinë e zhvillimit, pavarësinë nga DBMS dhe pastërtinë e kodit. Për shumë prej nesh, kodi i punës me DB (dhe shpesh vetë DB)

zakonisht duket më shumë si kjo…

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

Modeli është mbushur me annotime të zgjuara, ndërsa diku pas skenave ORM-i trim gjeneron dhe ekzekuton ton me ndonjë SQL. Në fakt, zhvilluesit po përpiqen në çdo mënyrë të tërheqin veten nga baza e të dhënave të tyre me kilometra abstraksionesh, që flet për një farë "SQL urrejtje".

Në anën tjetër të barrikadave, përkrahësit e SQL-it "handmade" theksojnë mundësinë për të nxjerrë të gjithë potencialin nga baza e të dhënave pa shtresa dhe abstraksione të tjera. Si rezultat, lindin projekte "data-centric", ku baza merret me njerëz të trajnuar posaçërisht (ata janë "bazas", "bazovikë", "bazdënës", etj.), dhe zhvilluesit vetëm duhet të "tërheqin" pamje dhe procedura të ruajtura, pa u thelluar në detaje.

E çfarë nëse marrim më të mirën nga të dy botët? Si është bërë në mjetin e mrekullueshëm me një emër që afirmon jetën Yesql. Do t'ju jap disa fjalë nga koncepti i përgjithshëm në përkthimin tim të lirë, ndërsa mund të njihet më në detaje. këtu.

Clojure është një gjuhë e shkëlqyer për krijimin e DSL-ve, por SQL vetë është një DSL i mrekullueshëm, dhe nuk na nevojitet një tjetër. S-shprehjet janë të bukura, por këtu ato nuk sjellin asgjë të re. Në fund, e marrim parasysh kullimin e thjeshtë të gozhdave. Nuk jeni dakord? Atëherë prisni momentin kur abstraksioni mbi DB do të fillojë të derdhë, dhe do të filloni të luftoni me funksionin. (raw-sql)

Çfarë duhet të bëjmë? Le të lëmë SQL-në si SQL të zakonshëm - një skedar për një kërkesë:

-- emri: përdoruesit sipas vendit
select *
  from users
 where country_code = :country_code

… dhe pastaj lexoni këtë skedar, duke e shndërruar atë në një funksion të zakonshëm Clojure:

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

;;; Një funksion me emrin `users-by-country` është krijuar.
;;; Le ta përdorim:
(users-by-country {:country_code "GB"})
;=>( {:name "Kris" :country_code "GB" ...} ...)

Duke u mbajtur pas parimit "SQL veç e veç, Clojure veç e veç", ju merrni:

  • Asnjë surprizë sintaksore. Baza juaj e të dhënave (si çdo tjetër) nuk i përmbush standardet SQL 100% — por për Yesql kjo nuk ka rëndësi. Ju kurrë nuk do të humbni kohë duke kërkuar funksione me sintaksë të barabartë me SQL. Ju kurrë nuk do t'i ktheheni funksionit. (raw-sql "some (‘funky’ :: SYNTAX)")).
  • Mbështetje e shkëlqyer për redaktorin. Redaktori juaj tashmë ka një mbështetje të shkëlqyer për SQL. Duke ruajtur SQL si SQL, thjesht mund ta përdorni atë.
  • Përputhshmëri e komandave. DBA-të tuaj mund të lexojnë dhe shkruajnë SQL që po përdorni në projektin tuaj Clojure.
  • Konfigurim më i thjeshtë i performancës. Duhet të ndërtosh një plan për një pyetje problematike? Kjo nuk është problem, kur pyetja juaj është një SQL i zakonshëm.
  • Ribërja e pyetjeve. Tërhiqni këto të njëjtat skedare SQL në projekte të tjera, sepse ky është thjesht SQL-i i vjetër i mirë — thjesht ndani atë.

Mendimi im është shumë i ftohtë dhe njëkohësisht shumë i thjeshtë, duke bërë që projekti të fitojë shumë ndjekësve në shumë gjuhë të ndryshme. Dhe ne do të përpiqemi të aplikojmë një filozofi të ngjashme të ndarjes së kodit SQL nga e gjithë e tjera, larg nga ORM.

IDE & DB-menaxherë

Le të fillojmë me një detyrë të thjeshtë të përditshme. Shpesh na duhet të kërkojmë ndonjë objekt në DB, për shembull, të gjejmë një tabelë në skemë dhe të studiojmë strukturën e saj (cilat kolona, çelësa, indekse, kufizime dhe të tjera janë përdorur). Çdo IDE grafike ose menaxher DB i pasur duhet të ofrojë këto mundësi. Duhet të jetë e shpejtë dhe të mos presim gjysmë ore derisa të shfaqet dritarja me informacionin e nevojshëm (sidomos me një lidhje të ngadalshëm me DB-në e largët), si dhe informacioni i marrë duhet të jetë i freskët dhe aktual, e jo të vjetëruar dhe të keqcache-zuar. Sa më e komplikuar dhe më e madhe të jetë DB dhe numri i saj, aq më e vështirë e bën këtë.

Por zakonisht unë e heq mouse-in për një anë dhe thjesht shkruaj kod. Supozoni se duhet të di se cilat tabela (dhe me cilat veçori) ndodhen në skemën "HR". Në shumicën e SGBD-ve, rezultatin e dëshiruar mund ta arrihet me këtë kërkesë të thjeshtë nga information_schema:

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

Nga baza në bazë, përmbajtja e tabelave-referencë variaton në varësi të kapacitetit të secilës DBMS. Për shembull, për MySQL, nga kjo referencë mund të merret parametra specifike për këtë DBMS:

select table_name
     , storage_engine -- Motori i përdorur ("MyISAM", "InnoDB" etj)
     , row_format     -- Formati i rreshtit ("Fixed", "Dynamic" etj)
     , ...
  from information_schema.tables
 where schema = 'HR'

Oracle nuk e ka information_schema, por ka metadata Oracle, dhe nuk ka probleme të mëdha:

select table_name
     , pct_free       -- Minimumi i hapësirës së lirë në bllokun e të dhënave (%)
     , pct_used       -- Minimumi i hapësirës së përdorur në bllokun e të dhënave (%)
     , last_analyzed  -- Data e fundit e mbledhjes së statistikave
     , ...
  from all_tables
 where owner = 'HR'

As ClickHouse nuk është përjashtim:

select name
     , engine -- Motori i përdorur ("MergeTree", "Dictionary" etj)
     , ...
  from system.tables
 where database = 'HR'

Një gjë të ngjashme mund të bëhet edhe në Cassandra (ku ka columnfamilies në vend të tabelave dhe keyspace në vend të skemave):

select columnfamily_name
     , compaction_strategy_class  -- Strategjia e mbledhjes së mbeturinave
     , gc_grace_seconds           -- Koha e jetës së mbeturinave
     , ...
  from system.schema_columnfamilies
 where keyspace_name = 'HR'

Për shumicën e DB-ve të tjera mund të merren gjithashtu kërkesa të ngjashme (madje edhe në Mongo ka një koleksion sistemik special, i cili përmban informacion për të gjitha koleksionet në sistem).

Natyrisht, në këtë mënyrë mund të merrni informacion jo vetëm për tabelat, por për çdo objekt. Periudhë pas periudhe, njerëz të mirë ndajnë një kod të tillë për baza të ndryshme të dhënash, siç është seria e artikujve në Habrë "Funksionet për dokumentimin e bazave të të dhënave PostgreSQL" (aib, ben, gim). Natyrisht, mbajtja e gjithë kësaj sasi informacioni në mendje dhe vazhdimisht shtypja e tyre është "e tillë", prandaj në IDE-në/time të preferuar/editorin kam një set të parakohshëm të snippeteve për kërkesat që përdoren shpesh, dhe duhet vetëm të vendos emrat e objekteve në shabllon.

Si rezultat, ky mënyrë navigimi dhe kërkimi të objekteve është shumë më fleksibël, kursen shumë kohë, lejon marrjen e informacionit të saktë dhe në formën që është e nevojshme (siç përshkruhet, për shembull, në postimin "Eksportimi i të dhënave nga BD në çdo format: çfarë dinë IDE-të në platformën IntelliJ").

Operacione me objekte

Pas gjetjes dhe studimit të objekteve të nevojshme, është koha që të bëjmë diçka të dobishme me ta. Natyrisht, pa i ndarë gishti nga tastiera.

Nuk është një sekret se heqja e thjeshtë e një tabele do të duket e ngjashme në pothuajse të gjitha bazat e të dhënave:

hiq tabelën hr.persons

Por krijimi i tabelës është më interesant. Praktikisht çdo DBMS (përfshirë shumë NoSQL) e ka në një formë të caktuar "krijo tabelë", dhe pjesa më e madhe e saj do të jetë pak a shumë e njëjtë (emri, lista e kolonave, tipet e të dhënave), por detajet e tjera mund të dallohen ndjeshëm dhe varen nga struktura dhe mundësitë e brendshme të DBMS përkatëse. Shembulli im i preferuar është që në dokumentacionin Oracle ka vetëm "BNF të thjeshtë" për sintaksën "krijo tabelë" zë 31 faqe. DBMS të tjera kanë mundësi më modeste, por çdo njëra prej tyre gjithashtu ka një sërë veçorish interesante dhe unikale për krijimin e tabelave (postgres, mysql, cockroach, cassandra). Është e pamundur që ndonjë "wizard" grafik nga ndonjë IDE tjetër (sidomos nga ato universale) të mbulojë plotësisht këto aftësi, dhe nëse e bën, do të jetë një shfaqje jo për ata që janë të dobët në nerva. Sidoqoftë, një operator i shkruar saktë dhe në kohë krijo tabelë do të lejojë të shfrytëzoni lehtësisht të gjitha ato, duke bërë ruajtjen dhe qasjen në të dhënat tuaja të sigurta, optimale dhe maksimale të këndshme.

Gjithashtu, në shumë DBMS ka lloje specifike objektesh që mungojnë në DBMS të tjera. Madje, mund të kryejmë operacione jo vetëm mbi objektet e DB, por edhe mbi vetë DBMS, si "të vrasim" një proces, të lirojmë një zonë të caktuar memorjeje, të aktivizojmë gjurmimin, të kalojmë në modalitetin "lexim vetëm" dhe shumë të tjera.

Tani le të pikturojmë pak

Një nga detyrat më të zakonshme është ndërtimi i një diagrami me objektet e DB, për të parë në një imazh të bukur objektet dhe lidhjet midis tyre. Kjo mund të bëhet praktikisht nga çdo IDE grafike, utilitetet e veçanta "command line", veglat grafike të specializuara dhe modeluesit. Të cilat do t'ju pikturojnë diçka "siç dinë", ndërsa pak mund të ndikoni në këtë proces vetëm përmes disa parametrave në skedarin e konfigurimit ose mundësive në ndërfaqe.

Por këtë problem mund të jetë shumë më e thjeshtë, më fleksibile dhe elegante, natyrisht me ndihmën e kodit. Për ndërtimin e diagramëve të çdo kompleksiteti, kemi disa gjuhë të specializuara markup-u (DOT, GraphML etj), dhe për to — një mori aplikacionesh (GraphViz, PlantUML, Mermaid), të cilat janë në gjendje të lexojnë këto udhëzime dhe t'i vizualizojnë në formate të ndryshme. Informacionin rreth objekteve dhe lidhjeve midis tyre tashmë e dimë si ta marrim.

Le të japim një shembull të vogël se si do të dukej kjo, duke përdorur PlantUML dhe një bazë të të dhënave demonstrative për PostgreSQL (majta SQL-queri, i cili do të gjenerojë udhëzimin e nevojshëm për PlantUML, dhe djathtas rezultati):

Eksperienca «Database as Code»

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'

Dhe nëse përpiqemi pak më shumë, mbi ER-shabllonin për PlantUML mund të marrim diçka shumë të ngjashme me një diagram ER të vërtetë:

SQL-queri është pak më kompleks

-- Kreu
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
-- Tabela
select format('Table(%s, "%s n informacion rreth %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
-- Marrëdhëniet midis tabelave
select distinct ccu.table_name || ' "1" --&gt; "0..N" ' || tc.table_name || format(' : "Një %s mund të ketë shumë %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
-- Fundi
select '@enduml'

Eksperienca «Database as Code»

Nëse e shikon me kujdes, shumë mjete vizualizuese në thelb përdorin kërkesa të ngjashme. Megjithatë, këto kërkesa zakonisht janë "të koduara" në vetë aplikacionin dhe janë të vështira për tu kuptuar , pa përmendur ndonjë modifikim të tyre.Metrikat dhe monitorimi

Metritë dhe monitorimi

Le të kalojmë në një temë tradicionalisht të komplikuar — monitorimi i performancës së bazës së të dhënave. Më kujtohet një histori e vogël e vërtetë, e treguar nga "një mik i imi". Në një projekt tjetër kishte një DBA të fuqishëm, dhe pak nga zhvilluesit ishin njohur me të personalisht, madje as e kishin parë ndonjëherë (pavarësisht se, sipas thashethemeve, ai punonte diku në ndërtesën përballë). Në momentin "X", kur sistemi prodhues i një tregtari të madh fillonte për së afërmi "të ndjehej keq", ai në heshtje dërgonte screenshot-e të grafikut nga Oracle Enterprise Manager, ku me kujdes shënonte vendet kritikë me një marker të kuq për "kuptueshmëri" (kjo, për ta thënë butë, ndihmonte pak). Dhe kështu, nga kjo "fotografi", duhej të bëhej riparimi. Në të njëjtën kohë, askush nuk kishte qasje në këtë Enterprise Manager të çmuar (në të dyja kuptimet e fjalës), pasi sistemi është i komplikuar dhe i shtrenjtë, mos ndoshta "zhvilluesit ndodhin në ndonjë gjë dhe gjithçka do shkatërrohet". Prandaj, zhvilluesit gjenin ngjarjen dhe shkakun e ngadalësive në mënyrë "empirike" dhe lëshonin një patch. Nëse një letër e frikshme nga DBA nuk vinte përsëri në një kohë të afërt, të gjithë nxirrnin një frymëmarrje lehtësuese dhe ktheheshin në detyrat e tyre aktuale (deri në letrën e re).

Porcija e monitorimit mund të duket më argëtuese dhe miqësore, dhe mbi të gjitha — e aksesueshme dhe transparente për të gjithë. Të paktën pjesa bazë e saj, si një shtesë për sistemet kryesore të monitorimit (të cilat patjetër që janë të dobishme dhe në shumë raste të pazëvendësueshme). Çdo DBMS është i gatshëm të ndajë informacion mbi gjendjen e tij të tanishme dhe performancën, krejtësisht falas. Në "korrupsionin" Oracle DB, çdo informacion rreth performancës mund të merret nga pamjet sistemike, duke filluar nga proceset dhe seancat e duke përfunduar me gjendjen e caches buffers (p.sh., Skriptet e DBA, seksioni "Monitorimi"). Në PostgreSQL gjithashtu ka një mori pamjesh sistemike për monitorimin e punës së DBMS, veçanërisht ato që janë të pazëvendësueshme në jetën e përditshme të çdo DBA, si pg_stat_activity, pg_stat_database, pg_stat_bgwriter. Në MySQL për këtë është parashikuar madje një skemë e veçantë performance_schema. Ndërsa në Mongo, një profiler grumbullon të dhënat e performancës në një koleksion sistemik system.profile.

Kështu, duke u armatosur me ndonjë mblldhës metrike (Telegraf, Metricbeat, Collectd), i cili di të realizojë kërkesa sql të personalizuara, depoitën e këtyre metrikeve (InfluxDB, Elasticsearch, Timescaledb) dhe vizualizuesin (Grafana, Kibana), mund të krijoni një sistem monitorimi të lehtë dhe fleksibël, i cili do të integrohet ngushtë me metrikat e tjera sistemike (që merren, për shembull, nga serveri i aplikacioneve, nga OS etj.). Siç është bërë në pgwatch2, ku përdoret kombinimi InfluxDB + Grafana dhe një grup kërkesash për pamjet sistemike, të cilave gjithashtu mund të shtoni kërkesa të personalizuara.

Në përfundim

Dhe ky është vetëm një listë e afërt e asaj që mund të bëni me bazën tonë të të dhënave përmes kodit SQL të zakonshëm. Jam i sigurt, mund të gjeni shumë aplikime të tjera, na shkruani në komentet. Dhe për atë se si (dhe më e rëndësishmja, pse) të automatizoni gjithçka këtë dhe ta përfshini në pipeline-in tuaj CI/CD do të flasim herën tjetër.

Burimi: habr.com

Bli një hosting të besueshëm për faqet me mbrojtje DDoS, VPS VDS serverë 🔥 Bli një hosting të besueshëm për faqet me mbrojtje DDoS, VPS VDS serverë | ProHoster