Add week 9 materials
No files matched your search
@@ -0,0 +1,10 @@
|
||||
## 9. Репликация данных.
|
||||
|
||||
### Лекция
|
||||
|
||||
- [Презентация](09-data-replication.pdf)
|
||||
- См. также материалы в конце
|
||||
|
||||
### Семинар
|
||||
|
||||
- [Материалы семинара](seminar/readme.md)
|
||||
|
After Width: | Height: | Size: 179 KiB |
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="730px" height="513px" viewBox="-0.5 -0.5 730 513" content="<mxfile host="app.diagrams.net" modified="2021-04-20T21:20:52.582Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36" etag="zU370sZN0VRoGCvcs8OI" version="14.6.1" type="google"><diagram id="k1PHZbrOtdCraHrjyHFp" name="Page-1">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</diagram></mxfile>" style="background-color: rgb(241, 250, 238);"><defs/><g><ellipse cx="266" cy="252" rx="170" ry="170" fill="#000000" stroke="#000000" stroke-dasharray="3 3" pointer-events="all" transform="translate(2,3)" opacity="0.25"/><ellipse cx="266" cy="252" rx="170" ry="170" fill="#fff2cc" stroke="#d6b656" stroke-dasharray="3 3" pointer-events="all"/><ellipse cx="122.25" cy="335" rx="36.25" ry="35" fill="#000000" stroke="#000000" stroke-dasharray="3 3" pointer-events="all" transform="translate(2,3)" opacity="0.25"/><ellipse cx="122.25" cy="335" rx="36.25" ry="35" fill="#f0e3c0" stroke="#1a1a1a" stroke-dasharray="3 3" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 71px; height: 1px; padding-top: 335px; margin-left: 87px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><span style="font-size: 14px">Server 4</span></div></div></div></foreignObject><text x="122" y="339" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">Server 4</text></switch></g><ellipse cx="422.25" cy="165" rx="36.25" ry="35" fill="#000000" stroke="#000000" stroke-dasharray="3 3" pointer-events="all" transform="translate(2,3)" opacity="0.25"/><ellipse cx="422.25" cy="165" rx="36.25" ry="35" fill="#f0e3c0" stroke="#1a1a1a" stroke-dasharray="3 3" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-aliLine truncated
|
||||
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 301 KiB |
|
After Width: | Height: | Size: 143 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 276 KiB |
|
After Width: | Height: | Size: 68 KiB |
@@ -0,0 +1,131 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" height="249px" preserveAspectRatio="none" style="width:699px;height:249px;background:#FFFFFF;" version="1.1" viewBox="0 0 699 249" width="699px" zoomAndPan="magnify"><defs/><g><g id="elem_Client"><rect fill="#F1F1F1" height="21.6094" style="stroke:#181818;stroke-width:0.5;" width="50" x="7" y="49"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="36" x="14" y="65.5332">Client</text></g><g id="elem_KVStore"><rect fill="#F1F1F1" height="21.6094" style="stroke:#181818;stroke-width:0.5;" width="66" x="271.5" y="49"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="52" x="278.5" y="65.5332">KVStore</text></g><g id="elem_Log"><rect fill="#F1F1F1" height="108.0469" style="stroke:#181818;stroke-width:0.5;" width="139" x="235" y="134"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="24" x="292.5" y="150.5332">Log</text><line style="stroke:#181818;stroke-width:1.0;" x1="235" x2="374" y1="155.6094" y2="155.6094"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="8" x="240" y="172.1426">1</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="111" x="258" y="172.1426">Set(key1, value1)</text><line style="stroke:#181818;stroke-width:1.0;" x1="253" x2="253" y1="155.6094" y2="177.2188"/><line style="stroke:#181818;stroke-width:1.0;" x1="235" x2="374" y1="177.2188" y2="177.2188"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="8" x="240" y="193.752">2</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="111" x="258" y="193.752">Set(key2, value4)</text><line style="stroke:#181818;stroke-width:1.0;" x1="253" x2="253" y1="177.2188" y2="198.8281"/><line style="stroke:#181818;stroke-width:1.0;" x1="235" x2="374" y1="198.8281" y2="198.8281"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="8" x="240" y="215.3613">3</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="111" x="258" y="215.3613">Set(key3, value3)</text><line style="stroke:#181818;stroke-width:1.0;" x1="253" x2="253" y1="198.8281" y2="220.4375"/><line style="stroke:#181818;stroke-width:1.0;" x1="235" x2="374" y1="220.4375" y2="220.4375"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="8" x="240" y="236.9707">4</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="111" x="258" y="236.9707">Set(key4, value4)</text><line style="stroke:#181818;stroke-width:1.0;" x1="253" x2="253" y1="220.4375" y2="242.0469"/></g><g id="elem_GMN149"><path d="M565,165 L565,182.5 L382.22,186.5 L565,190.5 L565,207.7031 A0,0 0 0 0 565,207.7031 L692,207.7031 A0,0 0 0 0 692,207.7031 L692,175 L682,165 L565,165 A0,0 0 0 0 565,165 " fill="#F7F8FE" style="stroke:#181818;stroke-width:0.5;"/><path d="M682,165 L682,175 L692,175 L682,165 " fill="#F7F8FE" style="stroke:#181818;stroke-width:0.5;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="97" x="571" y="183.4951">Write Ahead Log</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="106" x="571" y="199.8467">/var/log/wal000.log</text></g><g id="elem_Storage"><rect fill="#F1F1F1" height="108.0469" style="stroke:#181818;stroke-width:0.5;" width="92" x="582.5" y="7"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="50" x="603.5" y="23.5332">Storage</text><line style="stroke:#181818;stroke-width:1.0;" x1="582.5" x2="674.5" y1="28.6094" y2="28.6094"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="30" x="587.5" y="45.1426">key1</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="42" x="627.5" y="45.1426">value1</text><line style="stroke:#181818;stroke-width:1.0;" x1="622.5" x2="622.5" y1="28.6094" y2="50.2188"/><line style="stroke:#181818;stroke-width:1.0;" x1="582.5" x2="674.5" y1="50.2188" y2="50.2188"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="30" x="587.5" y="66.752">key2</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="42" x="627.5" y="66.752">value2</text><line style="stroke:#181818;stroke-width:1.0;" x1="622.5" x2="622.5" y1="50.2188" y2="71.8281"/><line style="stroke:#181818;stroke-width:1.0;" x1="582.5" x2="674.5" y1="71.8281" y2="71.8281"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" texLine truncated
|
||||
link Client to KVStore--><g id="link_Client_KVStore"><path codeLine="36" d="M65.09,59.5 C112.95,59.5 203.23,59.5 257.92,59.5 " fill="none" id="Client-to-KVStore" style="stroke:#070D4A;stroke-width:1.0;"/><polygon fill="#070D4A" points="263.1,59.5,254.1,55.5,258.1,59.5,254.1,63.5,263.1,59.5" style="stroke:#070D4A;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="100" x="96" y="55.9951">Set(key4, value4)</text></g><!--MD5=[d15b131dd1a0d42a2b4e7ff26cddba69]
|
||||
link KVStore to Log--><g id="link_KVStore_Log"><path codeLine="37" d="M304.5,77.7 C304.5,90.1 304.5,107.32 304.5,124.4 " fill="none" id="KVStore-to-Log" style="stroke:#070D4A;stroke-width:1.0;"/><polygon fill="#070D4A" points="304.5,129.69,308.5,120.69,304.5,124.69,300.5,120.69,304.5,129.69" style="stroke:#070D4A;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="59" x="263" y="109.2451">1. Append</text></g><!--MD5=[937f200f8032e7ed6e8781237a59e151]
|
||||
link KVStore to Storage--><g id="link_KVStore_Storage"><path codeLine="38" d="M345.63,59.5 C401.79,59.5 504.2,59.5 568.9,59.5 " fill="none" id="KVStore-to-Storage" style="stroke:#070D4A;stroke-width:1.0;"/><polygon fill="#070D4A" points="574.26,59.5,565.26,55.5,569.26,59.5,565.26,63.5,574.26,59.5" style="stroke:#070D4A;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="121" x="413" y="55.9951">2. Set(Key4, Value4)</text></g><!--MD5=[5ce81b978b9f5bd89690ef5bf57354bc]
|
||||
@startuml
|
||||
|
||||
|
||||
left to right direction
|
||||
|
||||
|
||||
!include ../style.puml
|
||||
|
||||
map Client {
|
||||
}
|
||||
|
||||
map KVStore {
|
||||
}
|
||||
|
||||
map Log {
|
||||
1=>Set(key1, value1)
|
||||
2=>Set(key2, value4)
|
||||
3=>Set(key3, value3)
|
||||
4=>Set(key4, value4)
|
||||
}
|
||||
|
||||
note right of Log
|
||||
Write Ahead Log
|
||||
/var/log/wal000.log
|
||||
end note
|
||||
|
||||
map Storage {
|
||||
key1=>value1
|
||||
key2=>value2
|
||||
key3=>value3
|
||||
key4=>value4
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
Client - -> KVStore: Set(key4, value4)
|
||||
KVStore -r-> Log: 1. Append
|
||||
KVStore -d-> Storage: 2. Set(Key4, Value4)
|
||||
|
||||
@enduml
|
||||
|
||||
@startuml
|
||||
|
||||
|
||||
left to right direction
|
||||
|
||||
|
||||
|
||||
skinparam shadowing false
|
||||
skinparam sequenceParticipant{
|
||||
BackgroundColor #F7F8FE
|
||||
BorderColor #070D4A
|
||||
}
|
||||
skinparam sequenceLifeLine {
|
||||
BackgroundColor #F7F8FE
|
||||
BorderColor #070D4A
|
||||
}
|
||||
skinparam sequence {
|
||||
ArrowColor #070D4A
|
||||
}
|
||||
|
||||
skinparam Note {
|
||||
BackgroundColor #F7F8FE
|
||||
BorderColor #070D4A
|
||||
}
|
||||
skinparam Node {
|
||||
BackgroundColor #F7F8FE
|
||||
BorderColor #070D4A
|
||||
}
|
||||
|
||||
skinparam object {
|
||||
BackgroundColor #F7F8FE
|
||||
BorderColor #070D4A
|
||||
}
|
||||
|
||||
skinparam actor {
|
||||
BackgroundColor #F7F8FE
|
||||
BorderColor #070D4A
|
||||
}
|
||||
|
||||
|
||||
|
||||
hide footbox
|
||||
|
||||
map Client {
|
||||
}
|
||||
|
||||
map KVStore {
|
||||
}
|
||||
|
||||
map Log {
|
||||
1=>Set(key1, value1)
|
||||
2=>Set(key2, value4)
|
||||
3=>Set(key3, value3)
|
||||
4=>Set(key4, value4)
|
||||
}
|
||||
|
||||
note right of Log
|
||||
Write Ahead Log
|
||||
/var/log/wal000.log
|
||||
end note
|
||||
|
||||
map Storage {
|
||||
key1=>value1
|
||||
key2=>value2
|
||||
key3=>value3
|
||||
key4=>value4
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
Client - -> KVStore: Set(key4, value4)
|
||||
KVStore -r-> Log: 1. Append
|
||||
KVStore -d-> Storage: 2. Set(Key4, Value4)
|
||||
|
||||
@enduml
|
||||
|
||||
PlantUML version 1.2022.6(Tue Jun 21 14:34:49 ADT 2022)
|
||||
(GPL source distribution)
|
||||
Java Runtime: Java(TM) SE Runtime Environment
|
||||
JVM: Java HotSpot(TM) 64-Bit Server VM
|
||||
Default Encoding: UTF-8
|
||||
Language: en
|
||||
Country: CA
|
||||
--></g></svg>
|
||||
|
After Width: | Height: | Size: 8.8 KiB |
@@ -0,0 +1,239 @@
|
||||
# Семинар 9: Репликация
|
||||
|
||||
В рамках данного семинара предлагается разобраться с концепцией репликации данных.
|
||||
Идея простая — иногда вместо одного сервера необходимо завести его "реплики" (или копии),
|
||||
где будет лежать одна и та же информация.
|
||||
|
||||
Это позволяет добиться:
|
||||
- Отказоустойчивость и сохранность данных — при отказе одного из серверов, данные можно получить с другого сервера.
|
||||
- Увеличение производительности — если один сервер не справляется с обработкой всех запросов на чтение.
|
||||
- Уменьшение latency — расположить сервера-реплики ближе к клиентам (пример — CDN).
|
||||
|
||||
## Репликация в СУБД
|
||||
|
||||
Как правило в существующих хранилищах (СУБД) репликация уже реализована и используется повсеместно.
|
||||
|
||||
Как правило выделяется узел, который принимает запросы от пользователя на запись. Далее этот узел должен
|
||||
отправить запрос на запись в остальные узлы (реплики).
|
||||
|
||||
### Sync and async replication
|
||||
|
||||
Подход, когда есть один узел, принимающий запросы на запись и реплицирующий данные на другие реплики,
|
||||
называется leader-follower (ранее известный как master-slave).
|
||||
|
||||

|
||||
|
||||
*[Источник](https://arpitbhayani.me/blogs/replication-strategies/)*
|
||||
|
||||
Выделяют два вида репликации по принципу "когда клиент получает ответ на запрос записи":
|
||||
- **Синхронная репликация** — данные сразу дублируются на все реплики, и только после
|
||||
подтверждения записи на других репликах, клиент получает ответ об успешной записи.
|
||||
Это позволяет избежать потери данных при отказе узла, который производит запись, но сложнее
|
||||
в реализации и увеличивает latency ответа клиенту.
|
||||
- **Асинхронная репликация** — клиент получает ответ сразу после успешной записи на узел, который
|
||||
изначально принял запрос пользователя. Данные на реплики дублируются в фоновой операции.
|
||||
|
||||
Возможна комбинация данных режимов: например, одна sync-реплика и несколько async-реплик:
|
||||

|
||||
|
||||
*Источник: Martin Kleppmann, Designing Data-Intensive Applications*
|
||||
|
||||
Асинхронный режим репликации проще в реализации, уменьшает latency ответа клиенту,
|
||||
но может привести к потере данных (лидер не успел отправить всю информацию на реплики).
|
||||
|
||||
Также в async-репликации возможны так называемые stale чтения (чтение возвращает не самую актуальную информацию):
|
||||

|
||||
|
||||
*Источник: Martin Kleppmann, Designing Data-Intensive Applications*
|
||||
|
||||
### Что реплицировать
|
||||
|
||||
Возникает вопрос, а что именно необходимо реплицировать.
|
||||
|
||||
**Репликация запросов**. Лидер, приняв запрос от клиента, выполняет аналогичный запрос на всех репликах,
|
||||
а затем возвращает ответ клиенту. Вариант прост для реализации и довольно экономный, однако возможны трудности
|
||||
с недетерминированными запросами. Если запрос полагается на вызов rand(), это значение на каждом из
|
||||
узлов может быть разным, что приведет к расхождению данных.
|
||||
|
||||
**Row-based репликация**. Если представить, что наша модель данных может быть представлена в виде таблицы
|
||||
(например, таблицы из пар ключ-значение), то можно реплицировать все изменения строк в данной таблице.
|
||||
Таким образом мы не будем страдать от недетерминированных запросов, так как предварительно лидер применяет
|
||||
все изменения к своему состоянию и только потом отправляет измененные значения строк. Однако в случае
|
||||
массовых операций обновления (PUT *=NULL) потребуется передавать довольно много информации.
|
||||
|
||||
**Репликация файловой системы**. Можно реплицировать файловую систему. При вызове системного вызова
|
||||
write(offset, "data") этот запрос проксируется на все реплики, и каждая из них выполняет запись в
|
||||
свою локальную файловую систему. Реализация возможна на уровне операционной системы, однако в таком
|
||||
подходе необходимо следить за совместимостью версий операционной системы и файловой системы, что не всегда
|
||||
возможно.
|
||||
|
||||
Идеального подхода к репликации не существует; необходимо выбирать его из решаемой задачи и имеющихся
|
||||
ограничений.
|
||||
|
||||
В современных СУБД для восстановления и репликации часто используют WAL (write-ahead log) — журнал
|
||||
предзаписи. При изменении данных СУБД формирует запись WAL и обновляет страницу в оперативной памяти;
|
||||
страница может измениться в памяти раньше, чем журнал попадёт на диск. Правило write-ahead требует сохранить WAL
|
||||
на диске до того, как туда будет записана изменённая страница. Если после сбоя страница на диске оказалась
|
||||
устаревшей, СУБД может воспроизвести журнал и восстановить недостающие изменения.
|
||||
|
||||
Для репликации лидер передаёт записи WAL реплике, а та применяет их у себя в том же порядке. При физической
|
||||
репликации узлам нужны совместимые форматы страниц и WAL.
|
||||
|
||||

|
||||
|
||||
*[Источник](https://martinfowler.com/articles/patterns-of-distributed-systems/write-ahead-log.html)*
|
||||
|
||||
### Кворумная репликация
|
||||
|
||||
Дожидаться подтверждения операции от всех реплик может быть накладно, однако хочется недопустить потери значения.
|
||||
В таких условиях возникла идмея кворумной репликации.
|
||||
|
||||
[Кворум](https://en.wikipedia.org/wiki/Quorum_(distributed_computing)) — это минимальное
|
||||
количество подтверждений, которое необходимо получить для того, чтобы считать операцию выполненной.
|
||||
|
||||
Допустим, мы хотим реплицировать данные на три реплики. При получении запроса будем отправлять запросы
|
||||
на запись на все три реплики, но дожидаться ответа будем только от двух реплик (от тех, что выполнили
|
||||
операцию быстрее). Если ответ получен от двух реплик, то считаем операцию выполненной.
|
||||
|
||||
Внимательный слушатель заметит, что при такой реализации запрос на чтение может вернуть неактуальные данные,
|
||||
если одна из реплик еще не успела применить обновления. Чтобы решить эту проблему, предлагается
|
||||
отправлять запросы на чтение на все реплики, дожидаться ответа от двух самых быстрых реплик и выбирать
|
||||
наиболее актуальную версию значения из полученных (тему версионирования затронем далее в курсе).
|
||||
|
||||
В таком случае нам неважно, какие реплики подтвердили запись и ответили на операцию чтения.
|
||||
Так как на операцию записи и чтения мы обращались на две реплики, как бы мы не выбрали эти множества
|
||||
реплик, они обязательно пересекутся как минимум по одной реплике. Эта реплика будет содержать
|
||||
наиболее актуальные данные.
|
||||
|
||||
Если обобщить задачу на произвольное число реплик N, то для кворума необходимо выбрать числа R и W.
|
||||
R — число реплик, от которых мы ждем ответа на операцию чтения, W — число реплик,
|
||||
которые должны подтвердить операцию записи. Чтобы система возвращала актуальные значения, должно
|
||||
выполняться условие `R + W > N`. В зависимости от отношения и сложности операций чтения и записи
|
||||
можно выбирать наиболее подходящие R и W для вашей системы.
|
||||
|
||||
Реализацию этой идеи можно увидеть в СУБД Cassandra
|
||||
(и почитать в paper'e о Dynamo, секция 4, sloppy quorum & hinted handoff).
|
||||
|
||||

|
||||
|
||||
*[Источник](https://www.designgurus.io/course-play/grokking-the-advanced-system-design-interview/doc/replication)*
|
||||
|
||||
В [учебном примере KV](../../08-scaling/seminar/kv/) есть режим `replication-quorum`.
|
||||
Прокси последовательно обращается ко всем узлам. Запись успешна, если её подтвердило большинство узлов.
|
||||
При чтении прокси возвращает значение, которое совпало в ответах большинства. Например, для трёх узлов
|
||||
ответы `v₂, v₂, v₁` дают `v₂`, а три разных значения приводят к ошибке.
|
||||
|
||||
Этот пример показывает голосование по значениям. Для выбора самой свежей версии, описанного выше,
|
||||
понадобятся метки версий и другое правило обработки ответов.
|
||||
|
||||
## RAID
|
||||
|
||||
[RAID](https://en.wikipedia.org/wiki/Standard_RAID_levels) (redundant array of inexpensive disks)
|
||||
— переносит идею репликации данных из распределенной системы на уровень одного компьютера.
|
||||
|
||||
Оказывается, к одному серверу можно подключить сразу несколько дисков с целью увеличения скорости доступа
|
||||
и надежности сохранности (пережить отказ диска).
|
||||
|
||||
Первая простая идея — поставить два диска рядом, писать половину данных на первый диск,
|
||||
вторую половину — на второй. Надежность не увеличивается, но зато увеличивается вместительность
|
||||
и скорость доступа. Такой подход называется RAID 0:
|
||||
|
||||
<p align="center">
|
||||
<img alt="https://en.wikipedia.org/wiki/File:RAID_0.svg" src="media/raid-0.png" width=200px/>
|
||||
</p>
|
||||
|
||||
Вторая простая идея — это использовать несколько дисков для хранения одного и того же файла
|
||||
(или блока данных). Это называют RAID 1:
|
||||
|
||||
<p align="center">
|
||||
<img alt="https://en.wikipedia.org/wiki/File:RAID_1.svg" src="media/raid-1.png" width=200px/>
|
||||
</p>
|
||||
|
||||
Можно также обратиться к теории алгебры и вспомнить коды Хэмминга и коды Рида-Соломона, которые
|
||||
позволяют детектировать ошибки в записи и хранении данных. На N полезных байт можно завести дополнительные
|
||||
K байт для исправления ошибок. Такой подход называется RAID 6 (используются коды Рида-Соломона):
|
||||
|
||||
<p align="center">
|
||||
<img alt="https://en.wikipedia.org/wiki/File:RAID_6.svg" src="media/raid-6.png" width=400px/>
|
||||
</p>
|
||||
|
||||
Не погружаясь в детали алгебру, идею восстанавливающих кодов можно понять на примере операции XOR.
|
||||
Допустим есть две половины данных: A и B. Давайте посчитаем `C = A ^ B`. Распределим эти три блока по
|
||||
трём дискам. Тогда при потере одного из дисков мы всегда можем обратиться к оставшимся двум и восстановить
|
||||
данные с помощью великолепного свойства операции XOR: `A = C ^ B`.
|
||||
|
||||
## Erasure coding
|
||||
|
||||
Подход с RAID-массивами можно обратно перенести на уровень приложений в распределенных системах.
|
||||
|
||||
Стандартная репликация данных как правило влечет большой расход дискового пространства: приходится
|
||||
хранить на 200% больше данных, чем нужно.
|
||||
|
||||
Подход Erasure coding предлагает использовать коды коррекции ошибок на уровне репликации хранилищ.
|
||||
При записи очередного блока данных необходимо распределить этот блок и его parity-блоки (чексуммы)
|
||||
на несколько других реплик. Тем самым мы сможем добиться меньшего дублирования данных и по-прежнему
|
||||
уметь переживать отказы небольшого числа узлов.
|
||||
|
||||
Этим свойством пользуются при реализации хранилищ, которые должны работать в рамках большого кластера
|
||||
(сотни и тысячи узлов).
|
||||
|
||||
При реализации данного подхода часто обращаются к алгоритму
|
||||
[Erasure Coding in Windows Azure Storage](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/LRC12-cheng20webpage.pdf).
|
||||
|
||||
<p align="center">
|
||||
<img alt="Microsoft LRC" src="media/lrc.png" width=600px/>
|
||||
</p>
|
||||
|
||||
## Линеаризуемость. Примеры для самостоятельного разбора
|
||||
|
||||
В каждой истории исходно `x = 0`, а все операции указаны в таблице. Числа обозначают моменты на общей
|
||||
условной временной оси. `write(x, 1)` записывает единицу, `read(x)` возвращает значение `x`.
|
||||
|
||||
История линеаризуема, если каждой операции можно назначить точку между её вызовом и ответом так, чтобы
|
||||
в полученном порядке каждое чтение возвращало значение последней записи или исходное значение, если записей
|
||||
ещё не было. Если одна операция завершилась до вызова другой, их порядок нужно сохранить.
|
||||
|
||||
Для каждой истории попробуйте расставить точки линеаризации или объяснить, почему это невозможно.
|
||||
|
||||
### История 1
|
||||
|
||||
| Клиент | Операция | Вызов | Ответ | Результат |
|
||||
| --- | --- | ---: | ---: | --- |
|
||||
| A | `write(x, 1)` | 1 | 6 | `OK` |
|
||||
| B | Первое `read(x)` | 2 | 3 | `0` |
|
||||
| B | Второе `read(x)` | 4 | 5 | `1` |
|
||||
|
||||
Эту историю можно линеаризовать. Выберем точку первого чтения в момент 2,5, записи — в момент 3,5,
|
||||
второго чтения — в момент 4,5. Получим порядок «чтение 0, запись 1, чтение 1».
|
||||
Все точки лежат внутри интервалов своих операций. Второе чтение может увидеть единицу ещё до ответа
|
||||
на запись, поскольку эти операции перекрываются во времени.
|
||||
|
||||
### История 2
|
||||
|
||||
| Клиент | Операция | Вызов | Ответ | Результат |
|
||||
| --- | --- | ---: | ---: | --- |
|
||||
| A | `write(x, 1)` | 1 | 6 | `OK` |
|
||||
| B | Первое `read(x)` | 2 | 3 | `1` |
|
||||
| B | Второе `read(x)` | 4 | 5 | `0` |
|
||||
|
||||
Эту историю линеаризовать нельзя. Чтобы первое чтение вернуло единицу, запись должна стоять перед ним.
|
||||
Первое чтение завершилось до вызова второго, поэтому второе обязано стоять ещё позже.
|
||||
Оно тоже должно вернуть единицу, поскольку других записей нет. Перекрытие записи с обоими чтениями
|
||||
не позволяет объяснить возврат к нулю.
|
||||
|
||||
Такой сценарий возможен при чтении с разных реплик, если первое чтение попало на реплику с новым
|
||||
значением, а второе — на отстающую. Первая история показывает, что чтение во время записи может
|
||||
возвращать разные значения, а вторая — почему порядок этих ответов имеет значение.
|
||||
|
||||
## Полезные материалы
|
||||
|
||||
- [Видео](https://youtu.be/gkg-FAEXIkY) и [вики-страница](https://en.wikipedia.org/wiki/CAP_theorem) о CAP-теореме
|
||||
- [Критика CAP-теоремы](https://jvns.ca/blog/2016/11/19/a-critique-of-the-cap-theorem/)
|
||||
- [Описание режимов репликации в Postgres](https://www.postgresql.org/docs/current/different-replication-solutions.html)
|
||||
- [Кворумная репликация в Dynamo](https://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf) (секция 4)
|
||||
- Коды коррекции: [код Хэмминга](https://en.wikipedia.org/wiki/Hamming_code) и [коды Рида-Соломона](https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction)
|
||||
- [Понятное описание различных RAID-стандартов](https://en.wikipedia.org/wiki/Standard_RAID_levels)
|
||||
- [Особенности устройства и обращения с HDD и как выбирать длину блока](https://ioflood.com/blog/optimal-raid-stripe-size-and-filesystem-readahead-for-raid-10/#:~:text=In%20modern%20operating%20systems%2C%20the,raid%20cards%20default%20stripe%20size)
|
||||
- [How RAID-6 dual parity calculation works](https://igoro.com/archive/how-raid-6-dual-parity-calculation-works/)
|
||||
- [Описание Erasure coding в версии семинара 2018](https://github.com/osukhoroslov/distsys-course-hse/tree/2020/seminars/08-scalability#erasure-coding)
|
||||
- [Алгоритм Erasure Coding в Windows Azure Storage](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/LRC12-cheng20webpage.pdf)
|
||||