HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

Die volgende HighLoad++-konferensie sal op 6 en 7 April 2020 in St. Petersburg plaasvind.
Besonderhede en kaartjies по ссылке. HighLoad++ Siberië 2019. Krasnoyarsk Hall. 25 Junie, 12:00. Opsommings en aanbieding.

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

Dit gebeur dat praktiese vereistes bots met teorie, waar aspekte belangrik vir 'n kommersiële produk nie in ag geneem word nie. Hierdie vraestel bied die proses aan om verskillende benaderings te kies en te kombineer om Oorsaaklike konsekwentheidskomponente te skep gebaseer op akademiese navorsing gebaseer op die vereistes van 'n kommersiële produk. Studente sal leer oor bestaande teoretiese benaderings tot logiese horlosies, afhanklikheidsnasporing, stelselsekuriteit, kloksinchronisasie, en hoekom MongoDB op sekere oplossings besluit het.

Mikhail Tyulenev (hierna - MT): - Ek sal praat oor Oorsaaklike konsekwentheid - dit is 'n kenmerk waaraan ons in MongoDB gewerk het. Ek werk in die verspreide stelsels-groep, ons het dit so twee jaar gelede gedoen.

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

In die proses moes ek met 'n groot hoeveelheid akademiese navorsing kennis maak, want hierdie kenmerk is goed bestudeer. Dit het geblyk dat nie 'n enkele artikel inpas by wat in produksie vereis word nie, die databasis in die lig van die baie spesifieke vereistes wat waarskynlik in enige produksietoepassings is.

Ek gaan gesels oor hoe ons as verbruiker van akademiese navorsing iets daaruit voorberei wat ons dan aan ons gebruikers kan voorsit as 'n klaargemaakte gereg wat gerieflik en veilig is om te gebruik.

Oorsaaklike konsekwentheid. Kom ons definieer konsepte

Om mee te begin, wil ek in algemene terme sê wat Oorsaaklike konsekwentheid is. Daar is twee karakters - Leonard en Penny (TV-reeks "The Big Bang Theory"):

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

Kom ons sê Penny is in Europa en Leonard wil 'n verrassingspartytjie vir haar hou. En hy dink aan niks beter as om haar van die vriendelys af te gooi en 'n opdatering aan al sy vriende op die stroom te stuur nie: "Kom ons maak Penny gelukkig!" (sy is in Europa, terwyl sy slaap, sien dit alles nie en kan dit nie sien nie, want sy is nie daar nie). Op die laaste oomblik vee sy hierdie plasing uit, vee dit uit die "Feed" uit en herstel toegang sodat sy niks agterkom nie en daar geen skandaal is nie.
Dit is alles goed, maar kom ons neem aan dat die stelsel versprei is, en dinge het nie 'n bietjie verkeerd gegaan nie. Dit kan byvoorbeeld gebeur dat Penny se toegangsbeperking plaasgevind het nadat hierdie pos verskyn het, as die gebeure nie deur kousaliteit verbind word nie. Eintlik is dit 'n voorbeeld van wanneer Oorsaaklike konsekwentheid vereis word om 'n besigheidsfunksie uit te voer (in hierdie geval).

Trouens, dit is nogal nie-triviale eienskappe van die databasis - baie min mense ondersteun dit. Kom ons gaan aan na modelle.

Konsekwentheidsmodelle

Wat is 'n konsekwentheidsmodel in databasisse? Dit is 'n paar van die waarborge wat 'n verspreide stelsel maak oor watter data en in watter volgorde die kliënt kan ontvang.

In beginsel kom alle konsekwentheidsmodelle daarop neer hoe soortgelyk 'n verspreide stelsel is aan 'n stelsel wat byvoorbeeld op een nodus op 'n skootrekenaar werk. En dit is hoe 'n stelsel wat op duisende geo-verspreide "Nodes" loop, soortgelyk is aan 'n skootrekenaar waarin al hierdie eienskappe in beginsel outomaties uitgevoer word.

Daarom is konsekwentheidsmodelle slegs van toepassing op verspreide stelsels. Alle stelsels wat voorheen bestaan ​​en op dieselfde vertikale skaal gewerk het, het nie sulke probleme ondervind nie. Daar was een Buffer Cache, en alles is altyd daarvan afgetrek.

Sterk model

Eintlik is die heel eerste model Strong (of die stygvermoë-lyn, soos dit dikwels genoem word). Dit is 'n konsekwentheidsmodel wat verseker dat elke verandering, sodra dit bevestig is dat dit gebeur het, sigbaar is vir alle gebruikers van die stelsel.

Dit skep 'n globale volgorde vir alle gebeurtenisse in die databasis. Dit is 'n baie sterk eienskap van konsekwentheid, en dit is oor die algemeen baie duur. Dit word egter baie goed ondersteun. Dit is net baie duur en stadig – dit word net min gebruik. Dit word stygvermoë genoem.

Daar is 'n ander, kragtiger eienskap wat in Spanner ondersteun word - dit word Eksterne Konsekwentheid genoem. Ons sal 'n bietjie later daaroor praat.

oorsaaklike

Die volgende een is Oorsaaklik, dit is presies waarvan ek gepraat het. Daar is verskeie ander subvlakke tussen Strong en Causal waaroor ek nie sal praat nie, maar hulle kom almal neer op Causal. Dit is 'n belangrike model, want dit is die sterkste van alle modelle, die sterkste konsekwentheid in die teenwoordigheid van 'n netwerk of partisies.

Kousale is eintlik 'n situasie waarin gebeure deur 'n oorsaaklike verband verbind word. Baie dikwels word hulle beskou as Lees jou op regte vanuit die oogpunt van die kliënt. As die kliënt sekere waardes waargeneem het, kan hy nie die waardes sien wat in die verlede was nie. Dit begin reeds voorvoegsels lees. Dit kom alles neer op dieselfde ding.
Oorsake as 'n konsekwentheidsmodel is 'n gedeeltelike ordening van gebeure op die bediener, waarin gebeure van alle kliënte in dieselfde volgorde waargeneem word. In hierdie geval, Leonard en Penny.

uiteindelike

Die derde model is uiteindelike konsekwentheid. Dit is wat absoluut alle verspreide stelsels ondersteun, die minimale model wat hoegenaamd sin maak. Dit beteken die volgende: wanneer ons 'n paar veranderinge in die data het, word hulle een of ander tyd konsekwent.

Op so 'n oomblik sê sy niks nie, anders sou sy in Eksterne Konsekwentheid verander - dit sou 'n heeltemal ander storie wees. Nietemin, dit is 'n baie gewilde model, die algemeenste. By verstek gebruik alle gebruikers van verspreide stelsels Eventuele Konsekwentheid.

Ek wil 'n paar vergelykende voorbeelde gee:

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

Wat beteken hierdie pyle?

  • latensie. Soos die sterkte van die konsekwentheid toeneem, word dit om ooglopende redes groter: jy moet meer inskrywings maak, bevestiging kry van al die gashere en nodusse wat aan die groep deelneem dat die data reeds daar is. Gevolglik is die uiteindelike konsekwentheid die vinnigste antwoord, want daar, as 'n reël, kan jy selfs in die geheue verbind en dit sal in beginsel genoeg wees.
  • Beskikbaarheid. As dit verstaan ​​word as die vermoë van die stelsel om te reageer in die teenwoordigheid van netwerkonderbrekings, partisies of 'n soort mislukking, neem fouttoleransie toe met 'n afname in die konsekwentheidsmodel, aangesien dit vir ons genoeg is dat een gasheer woon en by gee terselfdertyd sommige data uit. Eventuele Konsekwentheid waarborg glad nie enigiets oor die data nie - dit kan enigiets wees.
  • anomalieë. Dit verhoog natuurlik die aantal anomalieë. In Sterk Konsekwentheid behoort hulle byna glad nie te bestaan ​​nie, en in Uiteindelike Konsekwentheid kan hulle enigiets wees. Die vraag ontstaan: hoekom kies mense uiteindelike konsekwentheid as dit anomalieë bevat? Die antwoord is dat uiteindelike konsekwentheid-modelle van toepassing is en afwykings bestaan, byvoorbeeld, in 'n kort tydperk; dit is moontlik om die meester te gebruik vir lees en min of meer konsekwente data te lees; dit is dikwels moontlik om sterk konsekwentheidsmodelle te gebruik. In die praktyk werk dit, en dikwels is die aantal anomalieë in tyd beperk.

C.A.P.-stelling

As jy die woorde konsekwentheid, beskikbaarheid sien - wat kom by jou op? Dis reg - CAP-stelling! Nou wil ek die mite uit die weg ruim ... Dis nie ek nie - daar is Martin Kleppman, wat 'n wonderlike artikel geskryf het, 'n wonderlike boek.

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

Die CAP-stelling is 'n beginsel wat in die 2000's geformuleer is dat Konsekwentheid, Beskikbaarheid, Partisies: neem enige twee, en drie kan nie gekies word nie. Dit was 'n beginsel. Dit is 'n paar jaar later deur Gilbert en Lynch as 'n stelling bewys. Toe begin dit as 'n mantra gebruik word - stelsels begin verdeel word in CA, CP, AP ensovoorts.

Hierdie stelling is eintlik bewys vir die volgende gevalle ... Eerstens, Beskikbaarheid is nie as 'n kontinue waarde van nul tot honderde beskou nie (0 - die stelsel is "dood", 100 - reageer vinnig; ons is so gewoond daaraan om dit te oorweeg), maar as 'n eienskap van die algoritme, wat waarborg dat dit vir al sy uitvoerings data terugstuur.

Daar is glad nie 'n woord oor die reaksietyd nie! Daar is 'n algoritme wat data na 100 jaar terugstuur - 'n baie goeie beskikbare algoritme, wat deel is van die CAP-stelling.
Tweedens: 'n stelling is bewys vir veranderinge in die waardes van dieselfde sleutel, ten spyte van die feit dat hierdie veranderinge 'n lyn wat verander kan word. Dit beteken dat hulle in werklikheid feitlik nie gebruik word nie, want die modelle is anders Uiteindelik Konsekwentheid, Sterk Konsekwentheid (miskien).

Hoekom is dit alles? Op die feit dat die CAP-stelling in die vorm waarin dit bewys word feitlik nie van toepassing is nie, word dit selde gebruik. In teoretiese vorm beperk dit alles op een of ander manier. Dit blyk 'n sekere beginsel wat intuïtief waar is, maar in die algemeen op geen manier bewys is nie.

Oorsaaklike konsekwentheid is die sterkste model

Wat nou gebeur, is dat jy al drie dinge kan kry: Konsekwentheid, Beskikbaarheid kry met behulp van Partitions. Veral oorsaaklike konsekwentheid is die sterkste konsekwentheidsmodel, wat, in die teenwoordigheid van partisies (breuke in die netwerk), steeds werk. Daarom is dit van so groot belang, daarom het ons dit opgeneem.

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

Eerstens vergemaklik dit die werk van toepassingsontwikkelaars. In die besonder, die teenwoordigheid van groot ondersteuning van die bediener: wanneer al die rekords wat binne een kliënt voorkom, gewaarborg is om in so 'n volgorde op 'n ander kliënt te kom. Tweedens hou dit partisies in stand.

Die binnekombuis van MongoDB

As ons daardie middagete onthou, beweeg ons kombuis toe. Ek sal praat oor die stelselmodel, naamlik wat MongoDB is vir diegene wat vir die eerste keer van so 'n databasis hoor.

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

MongoDB (hierna na verwys as "MongoDB") is 'n verspreide stelsel wat horisontale skalering ondersteun, dit wil sê sharding; en binne elke skerf ondersteun dit ook data-oortolligheid, dws replikasie.

Sharing in "MongoDB" (nie 'n relasionele databasis nie) voer outomatiese balansering uit, dit wil sê, elke versameling dokumente (of "tabel" in terme van relasionele data) in stukke, en die bediener skuif hulle outomaties tussen skerwe.

Die Query Router wat navrae vir 'n kliënt versprei, is een of ander kliënt waardeur dit werk. Dit weet reeds waar en watter data geleë is, rig alle versoeke na die regte skerf.

Nog 'n belangrike punt: MongoDB is 'n enkele meester. Daar is een Primêr - dit kan rekords neem wat die sleutels ondersteun wat dit bevat. Jy kan nie Multi-master skryf nie.

Ons het vrystelling 4.2 gemaak - nuwe interessante dinge het daar verskyn. Hulle het veral Lucene -search - presies uitvoerbare java direk in Mongo ingevoeg, en daar het dit moontlik geword om deur Lucene te soek, dieselfde as in Elastica.

En hulle het 'n nuwe produk gemaak - Charts, dit is ook beskikbaar op Atlas (Mongo se eie Wolk). Hulle het Free Tier - jy kan daarmee rondspeel. Ek het baie van Charts gehou - datavisualisering, baie intuïtief.

Oorsaaklike konsekwentheid bestanddele

Ek het ongeveer 230 artikels getel wat oor hierdie onderwerp gepubliseer is – van Leslie Lampert. Nou, uit my geheue, sal ek dit vir jou 'n paar dele van hierdie materiaal bring.

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

Dit het alles begin met 'n artikel deur Leslie Lampert wat in die 1970's geskryf is. Soos u kan sien, is sommige navorsing oor hierdie onderwerp nog aan die gang. Nou ervaar Oorsaaklike konsekwentheid belangstelling in verband met die ontwikkeling van verspreide stelsels.

Beperkings

Wat is die beperkings? Dit is eintlik een van die hoofpunte, want die beperkings wat 'n produksiestelsel oplê, verskil baie van die beperkings wat in akademiese artikels bestaan. Dikwels is hulle redelik kunsmatig.

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

  • Eerstens, "MongoDB" is 'n enkele meester, soos ek voorheen gesê het (dit vereenvoudig aansienlik).
  • Ons glo dat die stelsel ongeveer 10 duisend skerwe moet ondersteun. Ons kan geen argitektoniese besluite neem wat hierdie waarde uitdruklik sal beperk nie.
  • Ons het 'n wolk, maar ons neem aan dat 'n persoon steeds die geleentheid moet hê wanneer hy die binêre aflaai, dit op sy skootrekenaar laat loop, en alles werk goed.
  • Ons neem aan iets wat selde in Navorsing gebruik word: eksterne kliënte kan enigiets doen. MongoDB is oopbron. Gevolglik kan kliënte so slim, kwaad wees - hulle wil dalk alles breek. Ons veronderstel dat die Bisantynse Feilors kan voorkom.
  • Vir eksterne kliënte wat buite die omtrek is, is dit 'n belangrike beperking: as hierdie kenmerk gedeaktiveer is, moet geen prestasie-agteruitgang waargeneem word nie.
  • Nog 'n punt is oor die algemeen anti-akademies: die verenigbaarheid van vorige weergawes en toekomstige weergawes. Ou drywers moet nuwe opdaterings ondersteun, en die databasis moet ou drywers ondersteun.

Oor die algemeen stel dit alles beperkings.

Oorsaaklike konsekwentheidskomponente

Ek sal nou oor sommige van die komponente praat. As ons oorsaaklike konsekwentheid in die algemeen oorweeg, kan ons blokke kies. Ons het gekies uit werke wat aan een of ander blok behoort: Afhanklikheidsopsporing, die keuse van horlosies, hoe hierdie horlosies met mekaar gesinchroniseer kan word, en hoe ons sekuriteit verskaf - dit is 'n benaderde plan van waaroor ek sal praat:

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

Volledige afhanklikheid dop

Hoekom is dit nodig? Sodat wanneer die data gerepliseer word, elke rekord, elke dataverandering inligting bevat oor watter veranderinge dit afhang. Die heel eerste en naïewe verandering is wanneer elke boodskap wat 'n inskrywing bevat inligting oor vorige boodskappe bevat:

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

In hierdie voorbeeld is die nommer tussen krullerige hakies die rekordnommers. Soms word hierdie rekords met waardes selfs in hul geheel oorgedra, soms word sommige weergawes oorgedra. Die slotsom is dat elke verandering inligting oor die vorige een bevat (dra dit natuurlik alles op sigself).

Hoekom het ons besluit om nie hierdie benadering (volle dop) te gebruik nie? Natuurlik, want hierdie benadering is onprakties: enige verandering in 'n sosiale netwerk hang af van alle vorige veranderinge in hierdie sosiale netwerk, deur byvoorbeeld "Facebook" of "Vkontakte" in elke opdatering deur te gee. Nietemin is daar baie studies oor Volle Afhanklikheidsopsporing - dit is pre-sosiale netwerke, vir sommige situasies werk dit regtig.

Eksplisiete afhanklikheid dop

Die volgende een is meer beperk. Ook hier word die oordrag van inligting in ag geneem, maar slegs dit wat duidelik afhanklik is. Wat afhang van wat, word as 'n reël reeds deur Aansoek bepaal. Wanneer data gerepliseer word, sal 'n navraag slegs antwoorde gee wanneer vorige afhanklikhede bevredig is, d.w.s. gewys. Dit is die kern van hoe Oorsaaklike konsekwentheid werk.

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

Sy sien dat rekord 5 afhang van rekords 1, 2, 3, 4 - gevolglik wag sy voordat die kliënt toegang verkry tot die veranderinge wat deur Penny se toegangsreël gemaak is, wanneer alle vorige veranderinge reeds in die databasis geslaag het.

Dit pas ons ook nie, want daar is nog te veel inligting, en dit sal stadiger word. Daar is 'n ander benadering ...

Lamport horlosie

Hulle is baie oud. Lamport-klok beteken dat hierdie afhanklikhede in 'n skalêre funksie gevou word, wat Lamport-klok genoem word.

'n Skalêre funksie is 'n abstrakte getal. Daar word dikwels na verwys as logiese tyd. Met elke gebeurtenis word hierdie teller verhoog. Die teller, wat tans aan die proses bekend is, stuur elke boodskap. Dit is duidelik dat prosesse nie gesinchroniseer kan word nie, dit kan heeltemal verskillende tye hê. Nietemin, deur so 'n uitruil van boodskappe, balanseer die stelsel op een of ander manier die klok. Wat gebeur in hierdie geval?

Ek het daardie groot skerf in twee verdeel om dit duidelik te maak: Vriende kan in een nodus woon wat 'n stukkie van 'n versameling bevat, en Feed kan in 'n ander nodus woon wat 'n stuk van hierdie versameling bevat. Ek sien hoe hulle uit die lyn kan kom? Stroom sal eers "Repliseer" sê, en dan Vriende. As die stelsel nie 'n waarborg bied dat die stroom nie gewys sal word voordat die Vriende-afhanklikhede in die Vriende-versameling ook afgelewer is nie, dan sal ons presies die situasie hê wat ek genoem het.

Jy kan sien hoe die tellertyd op die stroom logies toeneem:

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

Die hoofeienskap van hierdie Lamport-klok en oorsaaklike konsekwentheid (verduidelik via Lamport-klok) is dus dit: as ons gebeurtenisse A en B het, en gebeurtenis B hang af van gebeurtenis A*, dan volg dit dat die logiese tyd van gebeurtenis A minder is as as LogicalTime vanaf Gebeurtenis B.

* Soms sê hulle ook dat A voor B gebeur het, dit wil sê A het voor B gebeur - dit is 'n soort verhouding wat die hele stel gebeure wat hoegenaamd gebeur het, gedeeltelik orden.

Andersom is verkeerd. Dit is eintlik een van die belangrikste nadele van Lamport Clock - gedeeltelike volgorde. Daar is 'n konsep van gelyktydige gebeure, dit wil sê gebeure waarin nóg (A voor B gebeur het) nóg (A voor B gebeur het). 'n Voorbeeld is die parallelle toevoeging van Leonard as 'n vriend van iemand anders (nie eers Leonard nie, maar byvoorbeeld Sheldon).
Dit is die eienskap wat dikwels gebruik word wanneer daar met Lamport-horlosies gewerk word: hulle kyk na die funksie en maak 'n gevolgtrekking hieruit - miskien is hierdie gebeurtenisse afhanklik. Want op een manier is dit waar: as Logiese Tyd A minder is as Logiese Tyd B, dan kan B nie voor A gebeur nie; en as meer, dan miskien.

Vektor Klok

Die logiese ontwikkeling van Lamport se horlosie is die Vector-horlosie. Hulle verskil deurdat elke nodus wat hier is sy eie, aparte horlosie bevat, en hulle word as 'n vektor oorgedra.
In hierdie geval kan jy sien dat die nulindeks van die vektor verantwoordelik is vir Voer, en die eerste indeks van die vektor is vir Vriende (elkeen van hierdie nodusse). En nou sal hulle toeneem: die nul-indeks "Fida" neem toe wanneer jy skryf - 1, 2, 3:

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

Hoekom is Vector Clock beter? Diegene wat jou toelaat om uit te vind watter gebeure gelyktydig is en wanneer dit op verskillende nodusse plaasvind. Dit is baie belangrik vir 'n sharding-stelsel soos MongoDB. Ons het dit egter nie gekies nie, alhoewel dit 'n wonderlike ding is, en dit werk uitstekend, en dit sal waarskynlik ons ​​pas ...

As ons 10 duisend skerwe het, kan ons nie 10 duisend komponente oordra nie, selfs as ons saampers, kom ons met iets anders vorendag - eweneens, die loonvrag sal baie keer minder wees as die volume van hierdie hele vektor. Daarom, terwyl ons op ons harte en tande kners, het ons hierdie benadering laat vaar en na 'n ander aanbeweeg.

Spanner TrueTime. atoomhorlosie

Ek het gesê daar sal 'n storie oor Spanner wees. Dit is 'n gawe ding, reg in die XNUMXste eeu: atoomhorlosies, GPS-sinchronisasie.

Wat is die idee? "Spanner" is 'n Google-stelsel wat selfs onlangs vir mense beskikbaar geword het (hulle het SQL daaraan gekoppel). Elke transaksie daar het 'n tydstempel. Aangesien die tyd gesinchroniseer is *, kan elke gebeurtenis 'n spesifieke tye toegeken word - atoomhorlosies het 'n wagtyd, waarna 'n ander tyd gewaarborg word om te "gebeur".

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

Dus, deur eenvoudig na die databasis te skryf en vir 'n tydperk te wag, word die Serialiseerbaarheid van die gebeurtenis outomaties gewaarborg. Hulle het die sterkste Konsekwentheidsmodel wat in beginsel voorgestel kan word – dit is Eksterne Konsekwentheid.

* Dit is die hoofprobleem met Lampart-horlosies - hulle is nooit sinchronies op verspreide stelsels nie. Hulle kan afwyk, selfs met NTP werk hulle steeds nie baie goed nie. "Spanner" het 'n atoomhorlosie en die tydsberekening blyk mikrosekondes te wees.

Hoekom het ons nie gekies nie? Ons neem nie aan dat ons gebruikers ingeboude atoomhorlosies het nie. Wanneer hulle uitkom, ingebou in elke skootrekenaar, sal daar 'n paar super-cool GPS-sinchronisasie wees - dan ja ... Intussen is die beste ding moontlik Amazon, basisstasies - vir fanatici ... So ons het ander horlosies gebruik.

Hibriede horlosie

Dit is eintlik wat in "MongoDB" merk terwyl dit Oorsaaklike konsekwentheid verseker. Wat is hulle baster? ’n Baster is ’n skalêre waarde, maar dit het twee komponente:

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

  • Die eerste is die unix-era (hoeveel sekondes het verloop sedert die "begin van die rekenaarwêreld").
  • Die tweede is 'n mate van inkrement, ook 'n 32-bis ongetekende int.

Dit is in werklikheid al. Daar is so 'n benadering: die deel wat vir tyd verantwoordelik is, word heeltyd met die horlosie gesinchroniseer; elke keer as 'n opdatering plaasvind, word hierdie deel met die horlosie gesinchroniseer en dit blyk dat die tyd altyd min of meer korrek is, en inkrement laat jou toe om te onderskei tussen gebeure wat op dieselfde tyd gebeur het.

Waarom is dit belangrik vir MongoDB? Omdat dit jou in staat stel om 'n soort rugsteunherstel op 'n sekere tydstip te maak, dit wil sê, die gebeurtenis word volgens tyd geïndekseer. Dit is belangrik wanneer sommige geleenthede nodig is; vir die databasis is gebeurtenisse veranderinge in die databasis wat met sekere intervalle op 'n tydstip plaasgevind het.

Die grootste rede waarom ek net vir jou sal vertel (moet asseblief vir niemand vertel nie)! Ons het dit gedoen omdat dit is hoe geordende, geïndekseerde data in MongoDB OpLog lyk. OpLog is 'n datastruktuur wat absoluut alle veranderinge in die databasis bevat: hulle gaan eers na OpLog, en dan word dit reeds op Storage self toegepas in die geval wanneer dit 'n gerepliseerde datum of skerf is.

Dit was die hoofrede. Tog is daar ook praktiese vereistes vir die ontwikkeling van die basis, wat beteken dat dit eenvoudig moet wees – min kode, so min as moontlik stukkende goed wat oorgeskryf en getoets moet word. Die feit dat ons oplogs deur hibriede horlosies geïndekseer is, het baie gehelp en ons toegelaat om die regte keuse te maak. Dit het homself regtig geregverdig en op een of ander manier magies verdien, op die heel eerste prototipe. Dit was baie cool!

Kloksinchronisasie

Daar is verskeie sinchronisasiemetodes wat in die wetenskaplike literatuur beskryf word. Ek praat van sinchronisasie wanneer ons twee verskillende skerwe het. As een replika 'n stel is, is geen sinchronisasie daar nodig nie: dit is 'n "enkelmeester"; ons het 'n OpLog, waarin alle veranderinge val - in hierdie geval is alles reeds opeenvolgend in die Oplog self georden. Maar as ons twee verskillende skerwe het, is tydsinchronisasie hier belangrik. Dit is waar die vektorklok meer gehelp het! Maar ons het hulle nie.

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

Die tweede een is die Heartbeats. Dit is moontlik om sekere seine uit te ruil wat elke eenheid van tyd voorkom. Maar "Hartklop" is te stadig, ons kan nie latency aan ons kliënt verskaf nie.

Ware tyd is natuurlik 'n wonderlike ding. Maar, weereens, dit is waarskynlik die toekoms ... Alhoewel dit reeds moontlik is om dit in Atlas te doen, is daar reeds vinnige "Amazon" tydsinchroniseerders. Maar dit sal nie vir almal beskikbaar wees nie.

Skinder is wanneer alle boodskappe tyd insluit. Dit is ongeveer wat ons gebruik. Elke boodskap tussen nodusse, 'n drywer, 'n data node router, absoluut alles vir MongoDB is 'n soort elemente, databasiskomponente wat horlosies bevat wat vloei. Hulle het oral 'n hibriede tydwaarde, dit word oorgedra. 64 bisse? Dit laat toe, dit is moontlik.

Hoe werk dit alles saam?

Hier oorweeg ek een cue-set om dit 'n bietjie makliker te maak. Daar is Primêr en Sekondêr. Sekondêr doen replikasie en is nie altyd ten volle gesinchroniseer met Primêr nie.

Daar is 'n insetsel (inset) in "Primêr" met 'n mate van tyd. Hierdie insetsel verhoog die interne teller met 11 as dit die maksimum is. Of dit sal die klokwaardes nagaan en per klok sinkroniseer as die klokwaardes groter is. Dit laat jou toe om volgens tyd te sorteer.

Nadat hy opgeneem het, vind 'n belangrike oomblik plaas. Die horlosie is in "MongoDB" en word slegs verhoog in die geval van skryf aan die "Oplog". Dit is die gebeurtenis wat die toestand van die stelsel verander. Absoluut in alle klassieke artikels word 'n gebeurtenis beskou as 'n boodskap wat 'n nodus tref: 'n boodskap het aangekom, wat beteken dat die stelsel sy toestand verander het.

Dit is te wyte aan die feit dat dit tydens die studie nie heeltemal moontlik is om te verstaan ​​hoe hierdie boodskap geïnterpreteer gaan word nie. Ons weet verseker dat as dit nie in die "Oplog" weerspieël word nie, dit op geen manier geïnterpreteer sal word nie, en die verandering in die toestand van die stelsel is slegs 'n inskrywing in die "Oplog". Dit vergemaklik alles vir ons: beide die model vereenvoudig en laat bestellings binne 'n enkele replika-stel toe, en baie ander nuttige dinge.

Die waarde wat reeds na die "Oplog" geskryf is, word teruggestuur - ons weet dat hierdie waarde reeds in die "Oplog" is, en sy tyd is 12. Nou, kom ons sê, lees begin vanaf 'n ander nodus (Sekondêr), en dit is reeds slaag afterClusterTime op sigself boodskap. Hy sê: “Ek wil alles hê wat ten minste ná 12 of gedurende twaalf gebeur het” (sien die prentjie hierbo).

Dit is wat genoem word Causal a consistent (CAT). Daar is so 'n konsep in teorie dat dit 'n stukkie tyd is, wat op sigself konsekwent is. In hierdie geval kan ons sê dat dit die toestand van die stelsel is wat op tyd 12 waargeneem is.

Op die oomblik is daar nog niks hier nie, want hierdie soort boots die situasie na wanneer jy wil hê dat Sekondêr data vanaf Primêr moet repliseer. Dit wag ... En nou het die data aangekom - dit gee hierdie waardes terug.

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

Dit is omtrent hoe dit alles werk. Amper.

Wat beteken "amper"? Kom ons veronderstel dat daar iemand is wat gelees het en verstaan ​​het hoe dit alles werk. Ek het besef dat elke keer as ClusterTime gebeur, dit die interne logiese klok opdateer, en dan verhoog die volgende rekord met een. Hierdie funksie neem 20 reëls. Kom ons sê hierdie persoon stuur die grootste moontlike 64-bis-nommer, minus een.

Hoekom "minus een"? Omdat die interne klok in hierdie waarde vervang sal word (natuurlik is dit die grootste moontlike en meer as die huidige tyd), dan sal 'n inskrywing in die "Oplog" plaasvind, en die klok sal met een meer verhoog word - en daar sal reeds 'n maksimum waarde in die algemeen (daar is net alle eenhede, daar is nêrens verder , unsaint ints).

Dit is duidelik dat die stelsel daarna absoluut ontoeganklik word vir enigiets. Dit kan net afgelaai, skoongemaak word – baie handewerk. Volle beskikbaarheid:

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

Verder, as dit iewers anders gerepliseer word, gaan die hele groep eenvoudig af. Absoluut onaanvaarbare situasie wat enigiemand baie vinnig en maklik kan organiseer! Daarom het ons hierdie oomblik as een van die belangrikste beskou. Hoe om dit te voorkom?

Ons manier is om clusterTime te teken

Dit is hoe dit in die boodskap (voor die blou teks) oorgedra word. Maar ons het ook 'n handtekening (blou teks) begin genereer:

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

Die handtekening word gegenereer deur 'n sleutel wat binne die databasis gestoor word, binne die veilige omtrek; self word gegenereer, opgedateer (gebruikers sien dit nie). 'n Hash word gegenereer, en elke boodskap word onderteken wanneer dit geskep word en bekragtig wanneer dit ontvang word.
Mense het waarskynlik 'n vraag: "Hoeveel vertraag dit dinge?" Ek het vir jou gesê dat dit vinnig moet werk, veral in die afwesigheid van hierdie kenmerk.

Wat beteken dit om Oorsaaklike konsekwentheid in hierdie geval te gebruik? Dit is om die afterClusterTime-parameter te wys. En daarsonder sal dit in elk geval net die waardes slaag. Skinder, sedert weergawe 3.6, werk altyd.

As ons die konstante generering van handtekeninge verlaat, sal dit die stelsel vertraag, selfs in die afwesigheid van 'n kenmerk, wat nie ooreenstem met ons benaderings en vereistes nie. En wat het ons gedoen?

Doen dit vinnig!

'N Redelik eenvoudige ding, maar 'n interessante truuk - ek sal deel, miskien sal iemand belangstel.
Ons het 'n hash wat die ondertekende data stoor. Alle data gaan deur die kas. Die kas-tekens nie spesifiek tyd nie, maar Range. Wanneer een of ander waarde inkom, genereer ons 'n reeks, masker die laaste 16 bisse, en ons teken hierdie waarde:

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

Deur so 'n handtekening te bekom, versnel ons die stelsel (voorwaardelik) met 65 10 keer. Dit werk uitstekend: toe eksperimente opgestel is, is die tyd eintlik met XNUMX duisend keer verminder wanneer ons 'n opeenvolgende opdatering het. Dit is duidelik dat wanneer hulle in onenigheid is, dit nie werk nie. Maar in die meeste praktiese gevalle werk dit. Die kombinasie van die Range-handtekening saam met die handtekening het die sekuriteitsprobleem opgelos.

Wat het ons geleer?

Lesse wat ons hieruit geleer het:

  • Ons moet materiaal, stories, artikels lees, want ons het baie interessante dinge. Wanneer ons aan een of ander kenmerk werk (veral nou, toe ons transaksies gemaak het, ens.), moet ons lees, verstaan. Dit neem tyd, maar dit is eintlik baie nuttig, want dit maak dit duidelik waar ons is. Ons het niks nuuts uitgedink nie, ons het net die bestanddele geneem.

    Oor die algemeen is daar 'n sekere verskil in denke wanneer daar 'n akademiese konferensie is (Sigmon, byvoorbeeld) - almal is gefokus op nuwe idees. Wat is die nuutheid van ons algoritme? Hier is niks besonders nie. Die nuwigheid lê eerder in hoe ons bestaande benaderings saamgevoeg het. Daarom is die eerste ding om die klassieke te lees, begin met Lampart.

  • In produksie is die vereistes heeltemal anders. Ek is seker dat baie van julle nie te doen het met "sferiese" databasisse in 'n abstrakte vakuum nie, maar met normale, werklike dinge wat beskikbaarheid, latensie en fouttoleransie probleme het.
  • Die laaste ding is dat ons verskillende idees moes oorweeg en verskeie heeltemal verskillende artikels in een benadering saam moes kombineer. Die idee oor ondertekening, byvoorbeeld, kom gewoonlik uit 'n artikel wat die Paxos-protokol oorweeg het, wat vir nie-Bisantynse Feilors binne die magtigingsprotokol is, vir Bisantynse mense - buite die magtigingsprotokol ... Oor die algemeen is dit presies wat ons uiteindelik doen.

    Hier is absoluut niks nuuts nie! Maar sodra ons dit alles saam gemeng het ... Dit is soos om te sê die Olivier-slaairesep is nonsens, want eiers, mayonnaise en komkommers is reeds uitgevind ... Dit is omtrent dieselfde storie.

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

Ek sal hiermee klaarmaak. Dankie!

vrae

Vraag van die gehoor (hierna – B): Dankie Michael vir die verslag! Die onderwerp van tyd is interessant. Jy gebruik Skinder. Hulle het gesê dat almal hul eie tyd het, almal ken hul plaaslike tyd. Ek verstaan ​​dat ons 'n drywer het - daar kan baie kliënte met drywers wees, navraagbeplanners ook, baie skerwe ook ... En wat gly die stelsel in as ons skielik 'n teenstrydigheid het: iemand besluit dat hy vir 'n minuut voor, iemand - 'n minuut agter? Waar sal ons wees?

MT: - Groot vraag inderdaad! Ek wou net sê oor skerwe. As ek die vraag reg verstaan, het ons die volgende situasie: daar is skerf 1 en skerf 2, lees vind plaas uit hierdie twee skerwe - hulle het 'n verskil, hulle het nie interaksie met mekaar nie, want die tyd wat hulle ken is anders, veral die tyd wat hulle het bestaan ​​hulle in oplogs.
Kom ons sê skerf 1 het 'n miljoen rekords gemaak, skerf 2 het glad niks gedoen nie, en die versoek het op twee skerwe gekom. En die eerste een het meer as 'n miljoen AfterClusterTime. In so 'n situasie, soos ek verduidelik het, sal skerf 2 glad nie reageer nie.

IN: - Ek wou weet hoe hulle sinchroniseer en een logiese tyd kies?

MT: - Baie maklik om te sinkroniseer. 'n Skerf, wanneer afterClusterTime daarby kom, en dit kry nie tyd in die Oplog nie, begin dit geen goedgekeurde nie. Dit wil sê, hy verhoog sy tyd tot hierdie waarde met sy hande. Dit beteken dat dit geen gebeure het wat ooreenstem met hierdie versoek nie. Hy skep hierdie gebeurtenis kunsmatig en word dus 'n Kousale Konsekwent.

IN: - En as daar daarna 'n paar gebeurtenisse na hom toe kom wat iewers in die netwerk verlore is?

MT: - Die Skerf is so gerangskik dat hulle nie sal kom nie, aangesien dit 'n enkele meester is. As hy reeds neergeskryf het, dan sal hulle nie meer aankom nie, maar agterna wees. Dit kan nie gebeur dat iets iewers vassteek nie, dan sal dit nie skryf nie, en dan het hierdie gebeure aangebreek - en Oorsaaklike konsekwentheid is geskend. Wanneer hy nie skryf nie, moet hulle almal volgende kom (hy sal vir hulle wag).

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

IN: – Ek het 'n paar vrae oor toue. Oorsaaklike konsekwentheid veronderstel dat daar 'n sekere tou van aksies is wat uitgevoer moet word. Wat gebeur as ons een pakkie verloor? Hier kom die 10de, 11de... Die 12de is weg, en almal wag dat dit vervul word. En skielik is ons kar dood, ons kan niks doen nie. Is daar 'n maksimum tou-lengte wat opgehoop word voordat dit uitgevoer word? Watter noodlottige mislukking vind plaas wanneer enige staat verlore gaan? Verder, as ons neerskryf dat daar 'n soort vorige toestand is, moet ons op een of ander manier daarvan begin? En hulle het hom nie weggestoot nie!

MT: - Dit is ook 'n goeie vraag! Wat doen ons? MongoDB het die konsep van kworum skryf, kworum lees. In watter gevalle kan 'n boodskap verdwyn? Wanneer die skryf nie kworum is nie of wanneer die lees nie kworum is nie (een soort vullis kan ook vassit).
Met betrekking tot Oorsaaklike konsekwentheid is 'n groot eksperimentele kontrole uitgevoer, waarvan die resultaat was dat in die geval waar skryf en lees nie kworum is nie, Oorsaaklike konsekwentheidsoortredings voorkom. Presies wat jy sê!

Ons raad is om ten minste 'n kworum gelees te gebruik wanneer Oorsaaklike konsekwentheid gebruik word. In hierdie geval sal niks verlore gaan nie, selfs al is die kworumrekord verlore ... Dit is 'n ortogonale situasie: as die gebruiker nie wil hê dat data verlore gaan nie, moet 'n kworumrekord gebruik word. Oorsaaklike konsekwentheid waarborg nie duursaamheid nie. Duursaamheid word gewaarborg deur replikasie en die masjinerie wat met replikasie geassosieer word.

IN: - Wanneer ons 'n instansie skep wat sharding vir ons doen (nie meester nie, maar slaaf, onderskeidelik), maak dit staat op die unix-tyd van sy eie masjien of op die tyd van die "meester"; Gesinchroniseer vir die eerste keer of periodiek?

MT: - Nou sal ek verduidelik. Skerf (d.w.s. horisontale partisie) - daar is altyd 'n Primêr daar. En in 'n skerf kan daar 'n "meester" wees en daar kan replikas wees. Maar 'n skerf is altyd skryfbaar omdat dit een of ander domein moet ondersteun (die skerf het 'n Primêr).

IN: - So dit hang alles suiwer af van die "meester"? Word "meester"-tyd altyd gebruik?

MT: - Ja. Dit kan figuurlik gesê word: die horlosie tik wanneer 'n rekord gemaak word in die "meester", in die "Oplog".

IN: - Ons het 'n kliënt wat verbind, en hy hoef niks van die tyd te weet nie?

MT: “Jy hoef regtig niks te weet nie! As ons praat oor hoe dit op die kliënt werk: wanneer die kliënt Oorsaaklike konsekwentheid wil gebruik, moet hy 'n sessie oopmaak. Nou is alles daar: beide transaksies in die sessie, en haal 'n regte ... 'n Sessie is 'n ordening van logiese gebeure wat plaasvind met 'n kliënt.

As hy hierdie sessie oopmaak en daar sê dat hy oorsaaklike konsekwentheid wil hê (as die sessie by verstek Oorsaaklike konsekwentheid ondersteun), werk alles outomaties. Die bestuurder onthou hierdie tyd en verhoog dit wanneer hy 'n nuwe boodskap ontvang. Dit onthou watter antwoord die vorige een teruggestuur het vanaf die bediener wat die data teruggestuur het. Die volgende navraag sal afterCluster bevat ("tyd is groter as dit").

Die kliënt hoef absoluut niks te weet nie! Dit is vir hom heeltemal ondeursigtig. As mense hierdie kenmerke gebruik, wat laat dit hulle toe om te doen? Eerstens kan jy veilig sekondêre lees: jy kan aan Primêr skryf en van geografies gerepliseerde sekondêre lees en seker wees dat dit werk. Terselfdertyd kan sessies wat op Primêr opgeneem is, selfs na Sekondêr oorgedra word, dit wil sê, jy kan nie een sessie gebruik nie, maar verskeie.

IN: - 'n Nuwe laag van Rekenkunde is sterk verbind met die onderwerp van uiteindelike konsekwentheid - CRDT (Conflict-free Replicated Data Types) datatipes. Het jy dit oorweeg om hierdie datatipes in die databasis te integreer en wat kan jy daaroor sê?

MT: - Goeie vraag! CRDT maak sin vir skryfkonflikte: MongoDB het 'n enkele meester.

IN: – Ek het 'n vraag van devops. In die regte wêreld is daar sulke Jesuïete-situasies wanneer Bisantynse mislukking plaasvind, en bose mense binne die beskermde omtrek begin om by die protokol vas te hou, handwerkpakkette op 'n spesiale manier te stuur?

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

MT: "Bose mense binne die omtrek is soos 'n Trojaanse perd!" Bose mense binne die omtrek kan baie slegte dinge doen.

IN: – Dit is duidelik dat om 'n gat in die bediener te laat, rofweg gesproke, waardeur jy die dieretuin van olifante kan steek en die hele groep vir altyd inmekaar kan stort ... Dit sal tyd neem vir handmatige herstel ... Dit, om dit sagkens te stel, is verkeerde. Aan die ander kant is die volgende eienaardig: in die werklike lewe, in die praktyk, is daar situasies wanneer natuurlik soortgelyke interne aanvalle plaasvind?

MT: – Aangesien ek selde sekuriteitsoortredings in die werklike lewe teëkom, kan ek nie sê of dit wel gebeur nie. Maar as ons oor die ontwikkelingsfilosofie praat, dan dink ons ​​so: ons het 'n omtrek wat die ouens verskaf wat sekuriteit maak - dit is 'n kasteel, 'n muur; en binne die omtrek kan jy doen wat jy wil. Dit is duidelik dat daar gebruikers is met die vermoë om slegs te kyk, en daar is gebruikers met die vermoë om die gids uit te vee.

Afhangende van die regte, kan die skade wat gebruikers kan aanrig 'n muis wees, of dit kan 'n olifant wees. Dit is duidelik dat 'n gebruiker met volle regte hoegenaamd enigiets kan doen. 'n Gebruiker met beperkte regte kan baie minder skade berokken. Dit kan veral nie die stelsel breek nie.

IN: - In die beskermde omtrek het iemand ingeklim om onverwagte protokolle vir die bediener te skep om die bediener in kanker te plaas, en as jy gelukkig is, dan is die hele groep ... Kan dit so "goed" wees?

MT: “Ek het nog nooit van sulke goed gehoor nie. Die feit dat u op hierdie manier die bediener kan vul, is nie 'n geheim nie. Om binne te vul, van die protokol te wees, 'n gemagtigde gebruiker te wees wat so iets in die boodskap kan skryf ... In werklikheid is dit onmoontlik, want dit sal steeds geverifieer word. Dit is moontlik om hierdie verifikasie te deaktiveer vir gebruikers wat nie wil nie - dit is dan hul probleem; Rofweg gesproke het hulle self die mure vernietig en jy kan 'n olifant daar instoot, wat hom sal vermorsel ... Oor die algemeen kan jy jou aantrek as 'n hersteller, kom trek dit uit!

IN: - Dankie vir die verslag. Sergey (Yandex). Monga het 'n konstante wat die aantal stemgeregtigde lede in die Replika-stel beperk, en hierdie konstante is 7 (sewe). Hoekom is dit 'n konstante? Hoekom is dit nie 'n parameter van een of ander aard nie?

MT: - Ons het ook replika-stelle met 40 nodusse. Daar is altyd 'n meerderheid. Ek weet nie watter weergawe nie...

IN: - In die Replica Set, kan jy nie stemgeregtigde lede, maar stem lede - 'n maksimum van 7. Hoe in hierdie geval om 'n afsluiting te oorleef as ons 'n Replica Stel ingetrek in 3 datasentrums? Een datasentrum kan maklik afskakel en 'n ander masjien val uit.

MT: – Dit is reeds 'n bietjie buite die bestek van die verslag. Dit is 'n algemene vraag. Miskien kan ek jou later vertel.

HighLoad++, Mikhail Tyulenev (MongoDB): Oorsaaklike konsekwentheid: van teorie tot praktyk

Sommige advertensies 🙂

Dankie dat jy by ons gebly het. Hou jy van ons artikels? Wil jy meer interessante inhoud sien? Ondersteun ons deur 'n bestelling te plaas of by vriende aan te beveel, wolk VPS vir ontwikkelaars vanaf $4.99, 'n unieke analoog van intreevlakbedieners, wat deur ons vir jou uitgevind is: Die hele waarheid oor VPS (KVM) E5-2697 v3 (6 Cores) 10GB DDR4 480GB SSD 1Gbps vanaf $19 of hoe om 'n bediener te deel? (beskikbaar met RAID1 en RAID10, tot 24 kerne en tot 40 GB DDR4).

Dell R730xd 2x goedkoper in Equinix Tier IV-datasentrum in Amsterdam? Net hier 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TV vanaf $199 in Nederland! Dell R420 - 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB - vanaf $99! Lees van Hoe om infrastruktuur korp. klas met die gebruik van Dell R730xd E5-2650 v4-bedieners ter waarde van 9000 XNUMX euro vir 'n sent?

Bron: will.com

Voeg 'n opmerking