1.1 Какво е шардинг?

Ако упорито търсите в гугъл, се оказва, че има доста размита граница между така нареченото разделяне и така нареченото шардинг. Всеки си вика Howто иска, Howто си иска. Някои хора правят разлика между хоризонтално разделяне и шардинг. Други казват, че шардингът е определен вид хоризонтално разделяне.

Не намерих нито един терминологичен стандарт, който да бъде одобрен от бащите-основатели и сертифициран от ISO. Личното вътрешно убеждение е нещо подобно: разделянето средно е „разрязване на основата на парчета“ по произволно избран начин.

  • Вертикално преграждане - по колони. Например, има гигантска table с няколко мorарда записа в 60 колони. Вместо да поддържаме една такава гигантска table, ние поддържаме 60 най-малко гигантски таблици с 2 мorарда записа всяка - и това не е колонна база, а вертикално разделяне (като пример за терминология).
  • Хоризонтално разделяне - изрязваме ред по ред, може и вътре в сървъра.

Неудобният момент тук е фината разлика между хоризонталното разделяне и шардинга. Мога да бъда нарязан на парчета, но не мога да ви кажа със сигурност Howво е. Има чувството, че шардингът и хоризонталното разделяне са едно и също нещо.

Шардингът е, като цяло, когато голяма table от гледна точка на бази данни or про-колекция от documentи, обекти, ако нямате база данни, а хранorще за documentи, се нарязва точно на обекти. Тоест от 2 мorарда обекта се избират парчета без meaning Howъв размер. Самите обекти във всеки обект не се нарязват на парчета, ние не ги подреждаме в отделни колони, а именно ги подреждаме на партиди на различни места.

Има фини терминологични разлики. Например, относително казано, разработчиците на Postgres могат да кажат, че хоризонталното разделяне е, когато всички таблици, на които е разделена основната table, лежат в една и съща схема, а когато са на различни машини, това вече е шардинг.

В общ смисъл, без да се обвързва с терминологията на конкретна база данни и конкретна система за управление на данни, има усещането, че шардингът е просто нарязване ред по ред / document по document и т.н. - това е всичко.

Подчертавам типично. В смисъл, че правим всичко това не просто за да нарежем 2 мorарда documentа на 20 таблици, всяка от които би била по-управляема, но за да ги разпределим в много ядра, много дискове or много различни физически or виртуални сървъри.

1.2 Разделете неделимото

Разбираемо е, че правим това, така че всеки шард - всяка част от данните - да се репликира много пъти. Но наистина, не.

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

Всъщност, ако направите такова нарязване на данни и от една гигантска SQL table в MySQL на вашия доблестен лаптоп, вие ще генерирате 16 малки таблици, без да надхвърляте нито един лаптоп, нито една схема, нито една база данни и т.н. . и така нататък. - това е, вече имате шардинг.

Това води до следното:

  • Ширината на честотната лента се увеличава.
  • Латентността не се променя, тоест всеки, така да се каже, работник or потребител в този случай получава своето. Различни заявки се обслужват приблизително по едно и също време.
  • Или и двете, и друго, а също и висока наличност (репликация).

Защо честотна лента? Понякога можем да имаме такива обеми от данни, които не се побират - не е ясно къде, но не се побират - на 1 {kernel | диск | сървър | ...}. Просто няма достатъчно ресурси, това е всичко. За да работите с този голям набор от данни, трябва да го изрежете.

Защо забавяне? На едно ядро ​​сканирането на table с 2 мorарда реда е 20 пъти по-бавно от сканирането на 20 таблици на 20 ядра, правейки го паралелно. Данните се обработват твърде бавно на един ресурс.

Защо висока наличност? Или изрязваме данните, за да направим и двете едновременно, и в същото време няколко копия на всеки шард - репликацията гарантира висока наличност.

1.3 Прост пример "How да го направя на ръка"

Условното шардинг може да бъде изрязано с помощта на тестовата table test.documents за 32 documentа и генерирането на 16 тестови таблици от тази table, приблизително 2 documentа всяка 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 

Защо около? Тъй като предварително не знаем How се разпределят id, ако от 1 до 32 включително, тогава ще има точно по 2 documentа, иначе не.

Правим го тук защо. След като сме направor 16 маси, можем да "грабнем" 16 от необходимото. Независимо Howво удряме, можем да паралелизираме тези ресурси. Например, ако няма достатъчно дисково пространство, би имало смисъл тези таблици да се разлагат на отделни дискове.

Всичко това, за съжаление, не е безплатно. Подозирам, че в случая с каноничния SQL стандарт (не съм препрочитал SQL стандарта от дълго време, може би не е актуализиран от дълго време), няма официален стандартизиран синтаксис за казване на който и да е SQL сървър : „Скъпи SQL сървър, направи ми 32 шарда и ги раздели на 4 диска. Но в отделните имплементации често има специфичен синтаксис за извършване на основно едно и също нещо. PostgreSQL има механизми за разделяне, MySQL има MariaDB, Oracle вероятно е направил всичко това преди много време.

Въпреки това, ако го правим на ръка, без поддръжка на база данни и в рамките на стандарта, тогава плащаме условно със сложността на достъпа до данни . Където имаше просто SELECT * FROM documentи WHERE id=123, сега 16 x SELECT * FROM docsXX. И е добре, ако се опитаме да вземем записа по ключ. Много по-интересно, ако се опитвахме да получим ранна гама от записи. Сега (ако ние, подчертавам, сме, така да се каже, глупаци и останем в рамките на стандарта), резултатите от тези 16 SELECT * FROM ще трябва да се комбинират в приложението.

Каква промяна в производителността можете да очаквате?

  • Интуитивно - линейно.
  • Теоретично - сублинейно, защото закона на Амдал.
  • Практически, може би почти линейно, може би не.

Всъщност верният отговор е неизвестен. С интелигентно прилагане на техниката за шардинг можете да постигнете значително суперлинейно влошаване на производителността на вашето приложение и дори DBA ще се задейства с нажежен покер.

Нека да видим How може да се постигне това. Ясно е, че просто да зададете настройката на PostgreSQL shards=16 и след това тя да тръгне сама, не е интересно. Нека помислим How можем да сме сигурни, че забавяме от шардинга 16 пъти по 32 - това е интересно от гледна точка How да не правим това.

Опитите ни да ускорим or забавим винаги ще се натъкваме на класиката – добрия стар закон на Амдал, който казва, че няма перфектно паралелизиране на всяка заявка, винаги има няHowва последователна част.

1.4 Законът на Амдал

Винаги има сериализирана част.

Винаги има част от изпълнението на заявката, която е паралелизирана, и винаги има част, която не е паралелизирана. Дори и да ви се струва, че е идеално паралелна заявка, поне колекцията от реда с резултати, която ще изпратите на клиента от редовете, получени от всеки шард, винаги е там и винаги е последователна.

Винаги има няHowва последователна част. Тя може да бъде малка, напълно невидима на общия фон, може да бъде гигантска и съответно да повлияе силно на паралелизацията, но винаги съществува.

Освен това влиянието му се променя и може да нарасне значително, например, ако изрежем нашата table - нека вдигнем залозите - от 64 записа в 16 таблици с 4 записа, тази част ще се промени. Разбира се, съдейки по такива гигантски количества данни, ние работим върху мобилен телефон и 2 MHz 86 процесор и нямаме достатъчно файлове, които могат да бъдат отворени едновременно. Очевидно с такива входове отваряме файл по файл.

  • Беше Общо = Сериен + Паралелен . Където, например, parallel е цялата работа вътре в DB, ​​а serial изпраща резултата на клиента.
  • Стана Total2 = Serial + Parallel/N + Xserial . Например, когато цялостният ORDER BY, Xserial>0.

С този прост пример се опитвам да покажа, че се появява няHowъв Xserial. В допълнение към факта, че винаги има сериализирана част и факта, че се опитваме да работим с данни паралелно, има допълнителна част, която да осигури това нарязване на данни. Грубо казано, може да се нуждаем от:

  • намерете тези 16 таблици във вътрешния речник на базата данни;
  • отворени файлове;
  • разпределете памет;
  • неразпределена памет;
  • сливане на резултати;
  • синхронизиране между ядрата.

Някои несинхронизирани ефекти все още се появяват. Те могат да бъдат незначителни и да заемат една мorардна от общото време, но винаги са различни от нула и винаги са налице. С тяхна помощ можем драстично да загубим производителност след шардинг.

Това е стандартна картина за закона на Амдал. Важното тук е, че линиите, които в идеалния случай трябва да са прави и да растат линейно, попадат в асимптота. Но тъй като графиката от интернет е нечетлива, направих според мен по-нагледни таблици с числа.

Да кажем, че имаме няHowва сериализирана част от обработката на заявката, която отнема само 5%: serial = 0.05 = 1/20 .

Интуитивно изглежда, че със сериализирана част, която отнема само 1/20 от обработката на заявката, ако паралелизираме обработката на заявката за 20 ядра, тя ще стане около 20, в най-лошия случай 18, пъти по-бърза.

Всъщност математиката е безсърдечно нещо :

стена = 0,05 + 0,95/брой_ядра, ускоряване = 1 / (0,05 + 0,95/брой_ядра)

Оказва се, че ако внимателно изчислите, със сериализирана част от 5%, ускорението ще бъде 10 пъти (10.3), което е 51% в сравнение с теоретичния идеал.

8 ядра = 5,9 = 74%
10 ядра = 6,9 = 69%
20 ядра = 10,3 = 51%
40 ядра = 13,6 = 34%
128 ядра = 17,4 = 14%

Използвайки 20 ядра (20 диска, ако щете) за задачата, върху която се е работило, никога дори теоретично няма да получим ускорение повече от 20 пъти, но на практика - много по-малко. Освен това, с увеличаване на броя на паралелите, неефективността се увеличава значително.

Когато остава само 1% от сериализираната работа и 99% са паралелизирани, стойностите на ускорението се подобряват донякъде:

8 ядра = 7,5 = 93%
16 ядра = 13,9 = 87%
32 ядра = 24,4 = 76%
64 ядра = 39,3 = 61%

За перфектно термоядрено запитване, което естествено отнема часове, за да завърши, а подготвителната работа и сглобяването на резултата отнема много малко време (serial = 0.001), вече ще видим добра ефективност:

8 ядра = 7,94 = 99%
16 ядра = 15,76 = 99%
32 ядра = 31.04 = 97%
64 ядра = 60,20 = 94%

Моля, имайте предвид, че никога няма да видим 100% . В особено добри случаи можете да видите например 99,999%, но не точно 100%.