1.1 Wat is sharden?

Als je aanhoudend googelt, blijkt dat er een nogal vage grens is tussen het zogenaamde partitioneren en het zogenaamde sharding. Iedereen noemt wat ze willen, wat ze maar willen. Sommige mensen maken onderscheid tussen horizontale partitionering en sharding. Anderen zeggen dat sharding een bepaald soort horizontale partitie is.

Ik heb geen enkele terminologische standaard gevonden die zou worden goedgekeurd door de grondleggers en gecertificeerd door ISO. Persoonlijke innerlijke overtuiging is ongeveer zo: Partitioneren is gemiddeld 'de basis in stukken snijden' op een willekeurig gekozen manier.

  • Verticale indeling - per kolom. Er is bijvoorbeeld een gigantische tabel met een paar miljard records in 60 kolommen. In plaats van zo'n gigantische tabel bij te houden, houden we minstens 60 gigantische tabellen bij van elk 2 miljard records - en dit is geen kolombasis, maar verticale partitie (als voorbeeld van terminologie).
  • Horizontale partitionering - we knippen regel voor regel, misschien binnen de server.

Het ongemakkelijke moment hier is het subtiele verschil tussen horizontale partitionering en sharding. Ik kan in stukken worden gesneden, maar ik kan je niet met zekerheid zeggen wat het is. Er is een gevoel dat sharding en horizontale partitionering ongeveer hetzelfde zijn.

Sharding is in het algemeen wanneer een grote tabel in termen van databases of een pro-verzameling van documenten, objecten, als u geen database heeft, maar een documentopslag, precies wordt geknipt door objecten. Dat wil zeggen, uit 2 miljard objecten worden stukken geselecteerd, ongeacht de grootte. De objecten zelf in elk object worden niet in stukken gesneden, we leggen ze niet in afzonderlijke kolommen neer, we leggen ze namelijk in batches op verschillende plaatsen neer.

Er zijn subtiele terminologische verschillen. Relatief gezien kunnen Postgres-ontwikkelaars bijvoorbeeld zeggen dat horizontale partitionering is wanneer alle tabellen waarin de hoofdtabel is verdeeld in hetzelfde schema liggen, en wanneer op verschillende machines, dit al sharding is.

In algemene zin, zonder gebonden te zijn aan de terminologie van een specifieke database en een specifiek gegevensbeheersysteem, bestaat het gevoel dat sharding gewoon regel voor regel / document voor document doorsnijden is, enzovoort - dat is alles.

Ik benadruk typisch. In die zin dat we dit allemaal niet alleen doen om 2 miljard documenten in 20 tabellen te knippen, die elk beter beheersbaar zouden zijn, maar om ze te verdelen over veel kernen, veel schijven of veel verschillende fysieke of virtuele servers .

1.2 Verdeel het ondeelbare

Het is duidelijk dat we dit doen zodat elke scherf - elk stukje data - vele malen wordt gerepliceerd. Maar echt, nee.

INSERT INTO docs00 
SELECT * FROM documents WHERE (id%16)=0 
... 
 
INSERT INTO docs15 
SELECT * FROM documents WHERE (id%16)=15 

In feite, als je zo'n data-slicing doet, en van één gigantische SQL-tabel op MySQL op je dappere laptop, genereer je 16 kleine tabellen, zonder verder te gaan dan een enkele laptop, geen enkel schema, geen enkele database, enz. . enzovoort. - dat is alles, je hebt al sharding.

Dit resulteert in het volgende:

  • De bandbreedte neemt toe.
  • De latentie verandert niet, dat wil zeggen dat elke, om zo te zeggen, werknemer of consument in dit geval zijn eigen krijgt. Verschillende verzoeken worden ongeveer tegelijkertijd afgehandeld.
  • Of beide, en nog een, en ook hoge beschikbaarheid (replicatie).

Waarom bandbreedte? We kunnen soms zulke hoeveelheden gegevens hebben die niet passen - het is niet duidelijk waar, maar ze passen niet - op 1 {kernel | schijf | server | ...}. Er zijn gewoon niet genoeg middelen, dat is alles. Om met deze grote dataset te werken, moet u deze knippen.

Waarom latentie? Op één core is het scannen van een tabel van 2 miljard rijen 20 keer langzamer dan het scannen van 20 tabellen op 20 cores, parallel. Gegevens worden te langzaam verwerkt op een enkele bron.

Waarom hoge beschikbaarheid? Of we knippen de gegevens om beide tegelijkertijd te doen, en tegelijkertijd verschillende kopieën van elke shard - replicatie zorgt voor een hoge beschikbaarheid.

1.3 Een simpel voorbeeld "hoe doe je het met de hand"

Voorwaardelijke sharding kan worden verwijderd met behulp van de testtabel test.documents voor 32 documenten en het genereren van 16 testtabellen uit deze tabel, ongeveer 2 documenten per test.docs00, 01, 02, ..., 15.

INSERT INTO docs00 
SELECT * FROM documents WHERE (id%16)=0 
... 
 
INSERT INTO docs15 
SELECT * FROM documents WHERE (id%16)=15 

Waarom ongeveer? Omdat we a priori niet weten hoe id wordt verdeeld, als van 1 tot en met 32, dan zijn er elk precies 2 documenten, anders niet.

We doen het hier waarom. Nadat we 16 tafels hebben gemaakt, kunnen we er 16 "pakken" van wat we nodig hebben. Ongeacht wat we tegenkomen, we kunnen deze bronnen parallelliseren. Als er bijvoorbeeld onvoldoende schijfruimte is, is het zinvol om deze tabellen op afzonderlijke schijven op te splitsen.

Dit alles is helaas niet gratis. Ik vermoed dat in het geval van de canonieke SQL-standaard (ik heb de SQL-standaard lange tijd niet herlezen, misschien is deze lange tijd niet bijgewerkt), er geen officiële gestandaardiseerde syntaxis is om tegen een SQL-server te zeggen : "Beste SQL-server, maak 32 shards voor me en verdeel ze in 4 schijven. Maar in individuele implementaties is er vaak een specifieke syntaxis om in wezen hetzelfde te doen. PostgreSQL heeft mechanismen voor partitionering, MySQL heeft MariaDB, Oracle heeft dit waarschijnlijk allemaal lang geleden gedaan.

Desalniettemin, als we het met de hand doen, zonder database-ondersteuning en binnen het kader van de standaard, dan betalen we voorwaardelijk met de complexiteit van gegevenstoegang . Waar eerst een simpele SELECT * FROM documenten WHERE id=123 was, nu 16 x SELECT * FROM docsXX. En het is goed als we proberen de plaat op sleutel te krijgen. Veel interessanter als we probeerden een vroege reeks platen te krijgen. Nu (als we, ik benadruk, als het ware dwazen zijn en binnen de kaders van de norm blijven), zullen de resultaten van deze 16 SELECT * FROM in de applicatie gecombineerd moeten worden.

Welke prestatieverandering kunt u verwachten?

  • Intuïtief - lineair.
  • Theoretisch - sublineair, omdat de wet van Amdahl.
  • Praktisch, misschien bijna lineair, misschien niet.

In feite is het juiste antwoord onbekend. Met een slimme toepassing van de sharding-techniek kunt u een aanzienlijke superlineaire degradatie in de prestaties van uw toepassing bereiken, en zelfs de DBA komt aanrennen met een gloeiend hete pook.

Laten we kijken hoe dit kan worden bereikt. Het is duidelijk dat het niet interessant is om alleen de instelling op PostgreSQL shards=16 in te stellen, en dan gaat het vanzelf van start. Laten we eens kijken hoe we ervoor kunnen zorgen dat we 16 keer 32 keer vertragen van sharding - dit is interessant vanuit het oogpunt van hoe je dit niet moet doen.

Onze pogingen om te versnellen of te vertragen zullen altijd de klassiekers tegenkomen - de goede oude Amdahl-wet, die zegt dat er geen perfecte parallellisatie is van een verzoek, er is altijd een consistent onderdeel.

1.4 Amdahl-wet

Er is altijd een serienummer.

Er is altijd een deel van de query-uitvoering dat parallel loopt en er is altijd een deel dat niet parallel loopt. Zelfs als het u lijkt dat een perfect parallelle query is, is er in ieder geval altijd de verzameling van de resultatenrij die u naar de client gaat sturen vanuit de rijen die van elke shard zijn ontvangen, en deze is altijd opeenvolgend.

Er is altijd een consistent onderdeel. Het kan klein zijn, volledig onzichtbaar tegen de algemene achtergrond, het kan gigantisch zijn en daardoor de parallellisatie sterk beïnvloeden, maar het bestaat altijd.

Bovendien verandert de invloed ervan en kan deze aanzienlijk groeien, bijvoorbeeld als we onze tabel - laten we de inzet verhogen - van 64 records naar 16 tabellen van 4 records snijden, zal dit onderdeel veranderen. Natuurlijk, te oordelen naar zulke gigantische hoeveelheden data, werken we aan een mobiele telefoon en een 2 MHz 86-processor, en hebben we niet genoeg bestanden die tegelijkertijd open kunnen worden gehouden. Blijkbaar openen we met dergelijke invoer één bestand tegelijk.

  • Het was Totaal = Serieel + Parallel . Waar bijvoorbeeld parallel al het werk in de database is en serieel het resultaat naar de klant stuurt.
  • Werd Total2 = Serial + Parallel/N + Xserial . Wanneer bijvoorbeeld de algehele ORDER BY, Xserial>0.

Met dit eenvoudige voorbeeld probeer ik aan te tonen dat er een Xserial verschijnt. Naast het feit dat er altijd een geserialiseerd deel is, en het feit dat we parallel met data proberen te werken, is er een extra deel om deze data-slicing te bieden. Grofweg hebben we mogelijk het volgende nodig:

  • vind deze 16 tabellen in het interne woordenboek van de database;
  • open bestanden;
  • geheugen toewijzen;
  • geheugen ongedaan maken;
  • resultaten samenvoegen;
  • synchroniseren tussen kernen.

Sommige niet-synchrone effecten verschijnen nog steeds. Ze kunnen onbeduidend zijn en een miljardste van de totale tijd in beslag nemen, maar ze zijn altijd niet nul en altijd aanwezig. Met hun hulp kunnen we de prestaties drastisch verliezen na sharding.

Dit is een standaardbeeld over de wet van Amdahl. Het belangrijkste hierbij is dat de lijnen, die idealiter recht moeten zijn en lineair groeien, in een asymptoot uitlopen. Maar aangezien de grafiek van internet onleesbaar is, heb ik naar mijn mening meer visuele tabellen met getallen gemaakt.

Laten we zeggen dat we een geserialiseerd deel van de aanvraagverwerking hebben dat slechts 5% in beslag neemt: serial = 0.05 = 1/20 .

Intuïtief lijkt het erop dat met een geserialiseerd onderdeel dat slechts 1/20 van de verzoekverwerking in beslag neemt, als we de verzoekverwerking voor 20 kernen parallelliseren, het ongeveer 20, in het slechtste geval 18, keer sneller zal worden.

In feite is wiskunde iets harteloos :

muur = 0.05 + 0.95/num_cores, versnelling = 1 / (0.05 + 0.95/num_cores)

Het blijkt dat als je zorgvuldig rekent, met een geserialiseerd deel van 5%, de versnelling 10 keer (10,3) zal zijn, wat 51% is in vergelijking met het theoretische ideaal.

8 kernen = 5,9 = 74%
10 kernen = 6,9 = 69%
20 kernen = 10.3 = 51%
40 kernen = 13,6 = 34%
128 kernen = 17,4 = 14%

Als we 20 kernen hebben gebruikt (20 schijven, als je wilt) voor de taak waar je vroeger aan werkte, zullen we in theorie zelfs nooit een versnelling van meer dan 20 keer krijgen, maar in de praktijk - veel minder. Bovendien, met een toename van het aantal parallellen, neemt de inefficiëntie enorm toe.

Wanneer slechts 1% van het geserialiseerde werk overblijft en 99% parallel is, verbeteren de versnellingswaarden enigszins:

8 kernen = 7,5 = 93%
16 kernen = 13,9 = 87%
32 kernen = 24,4 = 76%
64 kernen = 39,3 = 61%

Voor een perfect thermonucleaire query, die natuurlijk uren in beslag neemt, en het voorbereidende werk en de montage van het resultaat heel weinig tijd kosten (serieel = 0,001), zullen we al een goede efficiëntie zien:

8 kernen = 7,94 = 99%
16 kernen = 15,76 = 99%
32 kernen = 31.04 = 97%
64 kernen = 60,20 = 94%

Houd er rekening mee dat we nooit 100% zullen zien . In bijzonder goede gevallen kunt u bijvoorbeeld 99,999% zien, maar niet precies 100%.