KDB+ datumbazo: de financo ĝis Formulo 1

KDB+, firmaoprodukto KX estas vaste konata en mallarĝaj cirkloj, ekstreme rapida, kolumna datumbazo dizajnita por stokado de temposerio kaj analizaj kalkuloj bazitaj sur ili. Komence, ĝi estis (kaj estas) tre populara en la financa industrio - ĉiuj supraj 10 investbankoj kaj multaj konataj heĝfondusoj, interŝanĝoj kaj aliaj organizoj uzas ĝin. Lastatempe, KX decidis pligrandigi sian klientbazon kaj nun proponi solvojn en aliaj areoj kie estas granda kvanto da datumoj, organizitaj laŭ tempo aŭ alie - telekomunikado, bioinformadiko, fabrikado ktp. Ili ankaŭ iĝis partnero de la teamo Aston Martin Red Bull Racing en Formulo 1, kie ili helpas kolekti kaj prilabori datumojn de aŭtosensiloj kaj analizi venttunelajn testojn. En ĉi tiu artikolo, mi volas diri al vi, kiaj trajtoj de KDB+ faras ĝin super-efike, kial kompanioj pretas elspezi multe da mono por ĝi, kaj finfine, kial ĝi ne estas vere datumbazo.
 
KDB+ datumbazo: de financo ĝis Formulo 1
 
En ĉi tiu artikolo mi provos diri al vi ĝenerale, kio estas KDB+, kiajn kapablojn kaj limigojn ĝi havas, kaj kiaj estas ĝiaj avantaĝoj por kompanioj, kiuj volas prilabori grandajn kvantojn da datumoj. Mi ne eniros la detalojn pri la efektivigo de KDB+ aŭ la detalojn de ĝia programlingvo Q. Ambaŭ ĉi tiuj temoj estas tre larĝaj kaj meritas apartajn artikolojn. Multaj informoj pri ĉi tiuj temoj troveblas ĉe code.kx.com, inkluzive de libro pri Q - Q For Mortals (vidu la ligon malsupre).

Kelkaj terminoj

  • En-memora datumbazo. Datumaro, kiu konservas datumojn en RAM por pli rapida aliro. La avantaĝoj de tia datumbazo estas klaraj, sed la malavantaĝoj estas la ebleco de perdo de datumoj kaj la bezono havi multe da memoro en la servilo.
  • Kolumna datumbazo. Datumaro kie datumoj estas stokitaj kolumno post kolumno prefere ol rekordo post rekordo. La ĉefa avantaĝo de tia datumbazo estas, ke datumoj de unu kolumno estas stokitaj kune sur disko kaj en memoro, kio signife akcelas aliron al ĝi. Ne necesas ŝargi kolumnojn kiuj ne estas uzataj en la konsulto. La ĉefa malavantaĝo estas, ke estas malfacile modifi kaj forigi rekordojn.
  • Temposerio. Datumoj kun dato aŭ horo kolumno. Tipe, tempa ordigo estas grava por tiaj datumoj, tiel ke vi povas facile determini kiu rekordo antaŭas aŭ sekvas la nunan, aŭ apliki funkciojn kies rezultoj dependas de la ordo de la rekordoj. Klasikaj datumbazoj estas konstruitaj laŭ tute malsama principo - reprezentante kolekton de rekordoj kiel aro, kie la ordo de la rekordoj estas, principe, ne difinita.
  • Vektoro. En la kunteksto de KDB+, ĉi tio estas listo de elementoj de la sama atomtipo, ekzemple, nombroj. Alivorte, tabelo de elementoj. Tabeloj, male al listoj, povas esti stokitaj kompakte kaj prilaboritaj uzante vektorajn procesorinstrukciojn.

 

Historia Fono

KX estis fondita en 1993 de Arthur Whitney, kiu antaŭe laboris ĉe Morgan Stanley Bank pri la lingvo A+, la posteulo de APL - tre originala kaj siatempe populara lingvo en la financa mondo. Kompreneble, en KX, Arthur daŭrigis en la sama spirito kaj kreis la vektor-funkcian lingvon K, gvidatan de la ideoj de radikala minimumismo. K-programoj aspektas kiel miksaĵo de interpunkcioj kaj specialaj signoj, la signifo de signoj kaj funkcioj dependas de la kunteksto, kaj ĉiu operacio portas multe pli da signifo ol ĝi havas en konvenciaj programlingvoj. Pro tio, K-programo okupas minimuman spacon—kelkaj linioj povas anstataŭigi paĝojn de teksto en multlingva lingvo kiel Java—kaj estas superkoncentrita efektivigo de la algoritmo.
 
Funkcio en K kiu efektivigas la plej grandan parton de la LL1-analizgeneratoro laŭ antaŭfiksita gramatiko:

1. pp:{q:{(x;p3(),y)};r:$[-11=@x;$x;11=@x;q[`N;$*x];10=abs@@x;q[`N;x]  
2.   ($)~*x;(`P;p3 x 1);(1=#x)&11=@*x;pp[{(1#x;$[2=#x;;,:]1_x)}@*x]  
3.      (?)~*x;(`Q;pp[x 1]);(*)~*x;(`M;pp[x 1]);(+)~*x;(`MP;pp[x 1]);(!)~*x;(`Y;p3 x 1)  
4.      (2=#x)&(@x 1)in 100 101 107 7 -7h;($[(@x 1)in 100 101 107h;`Ff;`Fi];p3 x 1;pp[*x])  
5.      (|)~*x;`S,(pp'1_x);2=#x;`C,{@[@[x;-1+#x;{x,")"}];0;"(",]}({$[".s.C"~4#x;6_-2_x;x]}'pp'x);'`pp];  
6.   $[@r;r;($[1<#r;".s.";""],$*r),$[1<#r;"[",(";"/:1_r),"]";""]]}  

 Arthur enkorpigis ĉi tiun filozofion de ekstrema efikeco kun minimumo de korpaj movoj en KDB+, kiu aperis en 2003 (mi pensas, ke nun estas klare de kie venas la litero K en la nomo) kaj estas nenio alia ol interpretisto de la kvara versio de la K. Pli uzebla versio estis aldonita aldone al K K nomata Q. Q ankaŭ aldonis subtenon por specifa dialekto de SQL - QSQL, kaj la interpretisto - subteno por tabeloj kiel sistema datumtipo, iloj por labori kun tabeloj. en memoro kaj sur disko, ktp.
 
Do el la perspektivo de uzanto, KDB+ estas simple Q-lingva interpretisto kun subteno por tabeloj kaj SQL-similaj LINQ-stilaj esprimoj de C#. Ĉi tiu estas la plej grava diferenco inter KDB+ kaj aliaj datumbazoj kaj ĝia ĉefa konkurenciva avantaĝo, kiu ofte estas preteratentita. Ĉi tio ne estas datumbazo + malfunkciigita helplingvo, sed plentaŭga potenca programlingvo + enkonstruita subteno por datumbazaj funkcioj. Ĉi tiu distingo ludos decidan rolon en listigo de ĉiuj avantaĝoj de KDB+. Ekzemple…
 

grandeco

Laŭ modernaj normoj, KDB+ estas simple mikroskopa en grandeco. Ĝi estas laŭvorte unu sub-megabajta rulebla dosiero kaj unu malgranda tekstdosiero kun iuj sistemaj funkcioj. En realeco - malpli ol unu megabajto, kaj por ĉi tiu programo kompanioj pagas dekojn da miloj da dolaroj jare por unu procesoro sur la servilo.

  • Ĉi tiu grandeco permesas al KDB+ sentiĝi bonege en ajna aparataro - de mikrokomputilo Pi ĝis serviloj kun terabajtoj da memoro. Ĉi tio neniel influas la funkciecon; krome, Q komenciĝas tuj, kio permesas ĝin esti uzata interalie kiel skriptlingvo.
  • Je ĉi tiu grandeco, la Q-interpretisto konvenas tute en la procesorkaŝmemoron, kiu akcelas programekzekuton.
  • Kun ĉi tiu grandeco de la plenumebla dosiero, la Q-procezo okupas nekonsiderindan spacon en memoro; vi povas ruli centojn da ili. Krome, se necese, Q povas funkcii kun dekoj aŭ centoj da gigabajtoj da memoro ene de ununura procezo.

Versatilidad

Q estas bonega por larĝa gamo de aplikoj. Procezo Q povas funkcii kiel historia datumbazo kaj disponigi rapidan aliron al terabajtoj da informoj. Ekzemple, ni havas dekojn da historiaj datumbazoj, en iuj el kiuj unu nekunpremita tago da datumoj okupas pli ol 100 gigabajtojn. Tamen, sub akcepteblaj limigoj, demando al la datumbazo estos kompletigita en dekoj ĝis centoj da milisekundoj. Ĝenerale, ni havas universalan tempon por uzantpetoj - 30 sekundoj - kaj ĝi funkcias tre malofte.
 
Q same facile povus esti en-memora datumbazo. Novaj datumoj estas aldonitaj al en-memoraj tabeloj tiel rapide ke uzantpetoj estas la limiga faktoro. Datumoj en tabeloj estas stokitaj en kolumnoj, kio signifas, ke ĉiu operacio sur kolumno uzos la procesoran kaŝmemoron plenkapacite. Aldone al tio, KX provis efektivigi ĉiujn bazajn operaciojn kiel ekzemple aritmetiko per vektoraj instrukcioj de la procesoro, maksimumigante ilian rapidecon. Q ankaŭ povas plenumi taskojn, kiuj ne estas tipaj por datumbazoj - ekzemple, prilabori fluajn datumojn kaj kalkuli en "reala tempo" (kun malfruo de dekoj da milisekundoj ĝis pluraj sekundoj depende de la tasko) diversajn agregajn funkciojn por financaj instrumentoj por malsama tempo. intervaloj aŭ konstrui modelon de la influo de perfektaj transakcioj al la merkato kaj efektivigi ĝian profiladon preskaŭ tuj post ĝia kompletigo. En tiaj taskoj, plej ofte la ĉefa tempoprokrasto ne estas Q, sed la bezono sinkronigi datumojn de malsamaj fontoj. Alta rapido estas atingita pro la fakto, ke la datumoj kaj la funkcioj, kiuj prilaboras ilin, estas en unu procezo, kaj prilaborado estas reduktita al ekzekuto de pluraj QSQL-esprimoj kaj kuniĝoj, kiuj ne estas interpretataj, sed ekzekutitaj per binara kodo.
 
Fine, vi povas skribi iujn ajn servajn procezojn en Q. Ekzemple, Gateway procezoj kiuj aŭtomate distribuas uzantpetojn al la necesaj datumbazoj kaj serviloj. La programisto havas plenan liberecon efektivigi ajnan algoritmon por ekvilibro, prioritato, misfunkciadoleremo, alirrajtoj, kvotoj kaj esence io ajn alia lia koro deziras. La ĉefa problemo ĉi tie estas, ke vi mem devos efektivigi ĉion ĉi.
 
Kiel ekzemplo, mi listigos kiajn tipojn de procezoj ni havas. Ĉiuj ili estas aktive uzataj kaj funkcias kune, kombinante dekduojn da malsamaj datumbazoj en unu, prilaborante datumojn de multoblaj fontoj kaj servante centojn da uzantoj kaj aplikoj.

  • Konektiloj (feedhandler) al datumfontoj. Ĉi tiuj procezoj kutime uzas eksterajn bibliotekojn, kiuj estas ŝarĝitaj en Q. La C-interfaco en Q estas ekstreme simpla kaj permesas al vi facile krei prokurajn funkciojn por iu ajn C/C++-biblioteko. Q estas sufiĉe rapida por trakti, ekzemple, prilabori inundon da FIX-mesaĝoj de ĉiuj eŭropaj borsoj samtempe.
  • Distribuistoj de datumoj (tickerplanto), kiuj funkcias kiel meza ligo inter konektiloj kaj konsumantoj. Samtempe, ili skribas envenantajn datumojn al speciala binara protokolo, provizante fortikecon por konsumantoj kontraŭ konektperdoj aŭ rekomencoj.
  • En-memora datumbazo (rdb). Ĉi tiuj datumbazoj disponigas la plej rapidan eblan aliron al krudaj, freŝaj datumoj stokante ĝin en memoro. Tipe, ili amasigas datumojn en tabeloj dum la tago kaj restarigas ilin nokte.
  • Persisti datumbazo (pdb). Ĉi tiuj datumbazoj certigas, ke datumoj por hodiaŭ estas konservitaj en historia datumbazo. Kiel regulo, male al rdb, ili ne konservas datumojn en memoro, sed uzas specialan kaŝmemoron sur disko dum la tago kaj kopias la datumojn noktomeze al la historia datumbazo.
  • Historiaj datumbazoj (hdb). Ĉi tiuj datumbazoj disponigas aliron al datumoj de antaŭaj tagoj, monatoj kaj jaroj. Ilia grandeco (en tagoj) estas limigita nur de la grandeco de la malmolaj diskoj. Datumoj povas troviĝi ie ajn, precipe sur malsamaj diskoj por akceli aliron. Eblas kunpremi datumojn uzante plurajn algoritmojn por elekti. La strukturo de la datumbazo estas bone dokumentita kaj simpla, la datumoj estas konservitaj kolumno post kolumno en regulaj dosieroj, do ili povas esti procesitaj, inkluzive per la operaciumo.
  • Datumbazoj kun aldonitaj informoj. Ili stokas diversajn agregaĵojn, kutime kun, grupigitaj per instrumentnomo kaj tempointervalo. En-memoraj datumbazoj ĝisdatigas sian staton kun ĉiu envenanta mesaĝo, kaj historiaj datumbazoj stokas antaŭ-komputitajn datenojn por akceli aliron al historiaj datenoj.
  • Fine enirejprocezojpriservado de aplikoj kaj uzantoj. Q permesas efektivigi tute nesinkronan prilaboradon de envenantaj mesaĝoj, distribuante ilin tra datumbazoj, kontrolante alirrajtojn ktp. Notu, ke mesaĝoj ne estas limigitaj kaj plej ofte ne estas SQL-esprimoj, kiel okazas en aliaj datumbazoj. Plej ofte, la SQL-esprimo estas kaŝita en speciala funkcio kaj estas konstruita surbaze de la parametroj petitaj de la uzanto - tempo estas konvertita, filtrita, datumoj estas normaligitaj (ekzemple, la akcia prezo estas egaligita se dividendoj estis pagitaj), ktp.

Tipa arkitekturo por unu datumtipo:

KDB+ datumbazo: de financo ĝis Formulo 1

Rapido

Kvankam Q estas interpretita lingvo, ĝi ankaŭ estas vektora lingvo. Tio signifas ke multaj enkonstruitaj funkcioj, precipe aritmetikaj, prenas argumentojn de ajna formo - nombroj, vektoroj, matricoj, listoj - kaj la programisto estas atendita efektivigi la programon kiel tabeloperacioj. En tia lingvo, se vi aldonas du vektorojn de miliono da elementoj, ne plu gravas, ke la lingvo estas interpretita; la aldono estos farita per superoptimumigita binara funkcio. Ĉar la plej granda parto de la tempo en Q-programoj estas elspezita por operacioj kun tabloj kiuj uzas ĉi tiujn bazajn vektorigitajn funkciojn, la eligo estas tre deca operacia rapideco, permesante al ni prilabori grandegan kvanton da datumoj eĉ en unu procezo. Ĉi tio similas al matematikaj bibliotekoj en Python - kvankam Python mem estas tre malrapida lingvo, ĝi havas multajn bonegajn bibliotekojn kiel numpy, kiuj permesas vin prilabori nombrajn datumojn kun la rapideco de kompilita lingvo (cetere, numpy estas ideologie proksima al Q. ).
 
Krome, KX prenis tre zorgeman aliron al dizajni tabelojn kaj optimumigi laboron kun ili. Unue, pluraj specoj de indeksoj estas subtenataj, kiuj estas subtenataj de enkonstruitaj funkcioj kaj povas esti aplikataj ne nur al tabelkolumnoj, sed ankaŭ al iuj vektoroj - grupigo, ordigo, unikeco-atributo kaj speciala grupiĝo por historiaj datumbazoj. La indekso estas aplikata simple kaj estas aŭtomate ĝustigita kiam oni aldonas elementojn al la kolumno/vektoro. Indeksoj povas same sukcese esti aplikataj al tabelkolumnoj kaj en memoro kaj sur disko. Dum plenumado de QSQL-demando, indeksoj estas uzataj aŭtomate se eble. Due, laboro kun historiaj datumoj estas farita per la mekanismo por montri OS-dosierojn (memormapo). Grandaj tabeloj neniam estas ŝarĝitaj en memoron; anstataŭe, la necesaj kolumnoj estas mapitaj rekte en memoron kaj nur tiu parto de ili estas fakte ŝarĝita (indeksoj ankaŭ helpas ĉi tie) kiu estas bezonata. Ne faras diferencon al la programisto ĉu la datenoj estas en memoro aŭ ne; la mekanismo por labori kun mmap estas tute kaŝita en la profundoj de Q.
 
KDB+ ne estas rilata datumbazo; tabeloj povas enhavi arbitrajn datumojn, dum la ordo de vicoj en la tabelo ne ŝanĝas kiam novaj elementoj estas aldonitaj kaj povas kaj devas esti uzataj dum skribado de demandoj. Ĉi tiu funkcio estas urĝe necesa por labori kun temposerio (datenoj de interŝanĝoj, telemetrio, evento-protokoloj), ĉar se la datumoj estas ordigitaj laŭ tempo, tiam la uzanto ne bezonas uzi iujn ajn SQL-trukojn por trovi la unuan aŭ lastan vicon aŭ N. vicoj en la tabelo, determini kiu linio sekvas la N-an linion, ktp. Tablokunigoj estas simpligitaj eĉ pli, ekzemple, trovi la lastan citaĵon por 16000 VOD.L (Vodafone) transakcioj en tabelo de 500 milionoj da elementoj daŭras ĉirkaŭ sekundo sur disko kaj dekoj da milisekundoj en memoro.
 
Ekzemplo de tempokunigo - la cittabelo estas mapita al memoro, do ne necesas specifi VOD.L en kie, la indekso sur la sym-kolumno kaj la fakto ke la datumoj estas ordigitaj laŭ tempo estas implicite uzataj. Preskaŭ ĉiuj kuniĝoj en Q estas regulaj funkcioj, ne parto de elektita esprimo:

1. aj[`sym`time;select from trade where date=2019.03.26, sym=`VOD.L;select from quote where date=2019.03.26]  

Fine, indas rimarki, ke la inĝenieroj ĉe KX, komencante de Arthur Whitney mem, estas vere obseditaj pri efikeco kaj multe klopodas por eltiri la plej multajn el la normaj funkcioj de la Q kaj optimumigi la plej oftajn uzpadronojn.
 

La rezulto

KDB+ estas populara inter entreprenoj ĉefe pro sia escepta ĉiuflankeco - ĝi servas same bone kiel enmemora datumbazo, kiel datumbazo por stoki terabajtojn da historiaj datumoj, kaj kiel platformo por datumanalizo. Pro la fakto, ke datumtraktado okazas rekte en la datumbazo, alta rapideco de laboro kaj resursa ŝparado estas atingita. Plenrajta programlingvo integrita kun datumbazaj funkcioj ebligas al vi efektivigi la tutan stakon de necesaj procezoj sur unu platformo - de ricevado de datumoj ĝis prilaborado de uzantpetoj.
 

Por pliaj informoj,

mankoj

Grava malavantaĝo de KDB+/Q estas la alta enira sojlo. La lingvo havas strangan sintakson, iuj funkcioj estas tre troŝarĝitaj (valoro, ekzemple, havas ĉirkaŭ 11 uzkazojn). Plej grave, ĝi postulas radikale malsaman aliron al verkado de programoj. En vektora lingvo, vi devas ĉiam pensi laŭ tabelaj transformoj, efektivigi ĉiujn maŝojn per pluraj variantoj de la funkcioj map/redukti (kiuj estas nomitaj adverboj en Q), kaj neniam provi ŝpari monon anstataŭigante vektorajn operaciojn per atomaj. Ekzemple, por trovi la indekson de la N-a okazo de elemento en tabelo, vi devus skribi:

1. (where element=vector)[N]  

kvankam tio ŝajnas terure malefika laŭ C/Java normoj (= kreas bulean vektoron, kie liveras la verajn indeksojn de la elementoj en ĝi). Sed ĉi tiu notacio pliklarigas la signifon de la esprimo kaj vi uzas rapidajn vektorajn operaciojn anstataŭ malrapidajn atomajn. La koncipa diferenco inter vektora lingvo kaj aliaj estas komparebla al la diferenco inter imperativo kaj funkcia aliro al programado, kaj vi devas esti preta por ĉi tio.
 
Iuj uzantoj ankaŭ estas malfeliĉaj pri QSQL. La punkto estas, ke ĝi aspektas nur kiel vera SQL. En realeco, ĝi estas nur interpretisto de SQL-similaj esprimoj, kiu ne subtenas demand-optimumigon. La uzanto devas skribi optimumajn demandojn mem, kaj en Q, por kiuj multaj ne pretas. Aliflanke, kompreneble, vi ĉiam povas skribi la optimuman demandon mem, anstataŭ fidi al nigra-skatola optimumigo.
 
Kiel pluso, libro pri Q - Q For Mortals disponeblas senpage ĉe retejo de la kompanio, estas ankaŭ multaj aliaj utilaj materialoj kolektitaj tie.
 
Alia granda malavantaĝo estas la kosto de la permesilo. Tio estas dekoj da miloj da dolaroj jare per CPU. Nur grandaj kompanioj povas pagi tiajn elspezojn. Lastatempe, KX igis sian licencadpolitikon pli fleksebla kaj donas la ŝancon pagi nur por la tempo de uzo aŭ lui KDB+ en la Google kaj Amazon-nuboj. KX ankaŭ proponas por elŝuto senpaga versio por nekomercaj celoj (32-bita versio aŭ 64-bita laŭpeto).
 

Konkurantoj

Estas sufiĉe multaj fakaj datumbazoj konstruitaj laŭ similaj principoj - kolumnaj, en-memoraj, koncentritaj al tre grandaj kvantoj da datumoj. La problemo estas, ke ĉi tiuj estas fakaj datumbazoj. Frapa ekzemplo estas Clickhouse. Ĉi tiu datumbazo havas tre similan principon al KDB+ por stoki datumojn sur disko kaj konstrui indekson; ĝi faras kelkajn demandojn pli rapide ol KDB+, kvankam ne signife. Sed eĉ kiel datumbazo, Clickhouse estas pli specialigita ol KDB+ - TTT-analitiko vs arbitra temposerio (tiu diferenco estas tre grava - pro tio, ekzemple, en Clickhouse ne eblas uzi la ordigon de rekordoj). Sed, plej grave, Clickhouse ne havas la ĉiuflankecon de KDB+, lingvo kiu permesus prilabori datumojn rekte en la datumbazo, prefere ol ŝarĝi ĝin unue en apartan aplikaĵon, konstruante arbitrajn SQL-esprimojn, aplikante arbitrajn funkciojn en demando, kreante procezojn. ne rilata al la ekzekuto de historiaj datumbazaj funkcioj. Tial estas malfacile kompari KDB+ kun aliaj datumbazoj; ili povas esti pli bonaj en certaj uzkazoj aŭ simple pli bonaj se temas pri klasikaj datumbazaj taskoj, sed mi ne konas alian same efikan kaj multflankan ilon por prilabori provizorajn datumojn.
 

Python integriĝo

Por igi KDB+ pli facile uzebla por homoj nekonantaj la teknologion, KX kreis bibliotekojn por integri firme kun Python ene de ununura procezo. Vi povas aŭ voki ajnan Python-funkcion de Q, aŭ inverse - voki ajnan Q-funkcion de Python (precipe, QSQL-esprimojn). Bibliotekoj konvertas, se necese (ne ĉiam pro efikeco), datumojn de la formato de unu lingvo al la formato de alia. Kiel rezulto, Q kaj Python vivas en tia proksima simbiozo ke la limoj inter ili estas malklarigitaj. Kiel rezulto, la programisto, unuflanke, havas plenan aliron al multaj utilaj Python-bibliotekoj, aliflanke, li ricevas rapidan bazon por labori kun grandaj datumoj integritaj en Python, kiu estas speciale utila por tiuj, kiuj okupiĝas pri maŝinlernado. aŭ modelado.
 
Laborante kun Q en Python:

1. >>> q()  
2.q)trade:([]date:();sym:();qty:())  
3. q)  
4. >>> q.insert('trade', (date(2006,10,6), 'IBM', 200))  
5. k(',0')  
6. >>> q.insert('trade', (date(2006,10,6), 'MSFT', 100))  
7. k(',1')  

referencoj

La retejo de la kompanio - https://kx.com/
Retejo por programistoj - https://code.kx.com/v2/
Libro Q Por Mortuloj (angla) - https://code.kx.com/q4m3/
Artikoloj pri KDB+/Q-aplikoj de kx-dungitoj - https://code.kx.com/v2/wp/

fonto: www.habr.com

Aldoni komenton