Add week 9 materials

This commit is contained in:
osukhoroslov committed 2026-11-12 11:31:33 +03:00
1 parent 8963ac82d5
commit a4dc591ca0
12 files changed
+383

No files matched your search

Binary file not shown.

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="&lt;mxfile host=&quot;app.diagrams.net&quot; modified=&quot;2021-04-20T21:20:52.582Z&quot; agent=&quot;5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36&quot; etag=&quot;zU370sZN0VRoGCvcs8OI&quot; version=&quot;14.6.1&quot; type=&quot;google&quot;&gt;&lt;diagram id=&quot;k1PHZbrOtdCraHrjyHFp&quot; name=&quot;Page-1&quot;&gt;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&lt;/diagram&gt;&lt;/mxfile&gt;" 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

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: Репликация
В рамках данного семинара предлагается разобраться с концепцией репликации данных.
Идея простая &mdash; иногда вместо одного сервера необходимо завести его "реплики" (или копии),
где будет лежать одна и та же информация.
Это позволяет добиться:
- Отказоустойчивость и сохранность данных &mdash; при отказе одного из серверов, данные можно получить с другого сервера.
- Увеличение производительности &mdash; если один сервер не справляется с обработкой всех запросов на чтение.
- Уменьшение latency &mdash; расположить сервера-реплики ближе к клиентам (пример &mdash; CDN).
## Репликация в СУБД
Как правило в существующих хранилищах (СУБД) репликация уже реализована и используется повсеместно.
Как правило выделяется узел, который принимает запросы от пользователя на запись. Далее этот узел должен
отправить запрос на запись в остальные узлы (реплики).
### Sync and async replication
Подход, когда есть один узел, принимающий запросы на запись и реплицирующий данные на другие реплики,
называется leader-follower (ранее известный как master-slave).
![sync replication](media/sync-replication.png)
*[Источник](https://arpitbhayani.me/blogs/replication-strategies/)*
Выделяют два вида репликации по принципу "когда клиент получает ответ на запрос записи":
- **Синхронная репликация** &mdash; данные сразу дублируются на все реплики, и только после
подтверждения записи на других репликах, клиент получает ответ об успешной записи.
Это позволяет избежать потери данных при отказе узла, который производит запись, но сложнее
в реализации и увеличивает latency ответа клиенту.
- **Асинхронная репликация** &mdash; клиент получает ответ сразу после успешной записи на узел, который
изначально принял запрос пользователя. Данные на реплики дублируются в фоновой операции.
Возможна комбинация данных режимов: например, одна sync-реплика и несколько async-реплик:
![sync and async replica](media/sync-async.png)
*Источник: Martin Kleppmann, Designing Data-Intensive Applications*
Асинхронный режим репликации проще в реализации, уменьшает latency ответа клиенту,
но может привести к потере данных (лидер не успел отправить всю информацию на реплики).
Также в async-репликации возможны так называемые stale чтения (чтение возвращает не самую актуальную информацию):
![stale read from async replica](media/async-lost.png)
*Источник: Martin Kleppmann, Designing Data-Intensive Applications*
### Что реплицировать
Возникает вопрос, а что именно необходимо реплицировать.
**Репликация запросов**. Лидер, приняв запрос от клиента, выполняет аналогичный запрос на всех репликах,
а затем возвращает ответ клиенту. Вариант прост для реализации и довольно экономный, однако возможны трудности
с недетерминированными запросами. Если запрос полагается на вызов rand(), это значение на каждом из
узлов может быть разным, что приведет к расхождению данных.
**Row-based репликация**. Если представить, что наша модель данных может быть представлена в виде таблицы
(например, таблицы из пар ключ-значение), то можно реплицировать все изменения строк в данной таблице.
Таким образом мы не будем страдать от недетерминированных запросов, так как предварительно лидер применяет
все изменения к своему состоянию и только потом отправляет измененные значения строк. Однако в случае
массовых операций обновления (PUT *=NULL) потребуется передавать довольно много информации.
**Репликация файловой системы**. Можно реплицировать файловую систему. При вызове системного вызова
write(offset, "data") этот запрос проксируется на все реплики, и каждая из них выполняет запись в
свою локальную файловую систему. Реализация возможна на уровне операционной системы, однако в таком
подходе необходимо следить за совместимостью версий операционной системы и файловой системы, что не всегда
возможно.
Идеального подхода к репликации не существует; необходимо выбирать его из решаемой задачи и имеющихся
ограничений.
В современных СУБД для восстановления и репликации часто используют WAL (write-ahead log) — журнал
предзаписи. При изменении данных СУБД формирует запись WAL и обновляет страницу в оперативной памяти;
страница может измениться в памяти раньше, чем журнал попадёт на диск. Правило write-ahead требует сохранить WAL
на диске до того, как туда будет записана изменённая страница. Если после сбоя страница на диске оказалась
устаревшей, СУБД может воспроизвести журнал и восстановить недостающие изменения.
Для репликации лидер передаёт записи WAL реплике, а та применяет их у себя в том же порядке. При физической
репликации узлам нужны совместимые форматы страниц и WAL.
![write ahead log](media/wal.svg)
*[Источник](https://martinfowler.com/articles/patterns-of-distributed-systems/write-ahead-log.html)*
### Кворумная репликация
Дожидаться подтверждения операции от всех реплик может быть накладно, однако хочется недопустить потери значения.
В таких условиях возникла идмея кворумной репликации.
[Кворум](https://en.wikipedia.org/wiki/Quorum_(distributed_computing)) &mdash; это минимальное
количество подтверждений, которое необходимо получить для того, чтобы считать операцию выполненной.
Допустим, мы хотим реплицировать данные на три реплики. При получении запроса будем отправлять запросы
на запись на все три реплики, но дожидаться ответа будем только от двух реплик (от тех, что выполнили
операцию быстрее). Если ответ получен от двух реплик, то считаем операцию выполненной.
Внимательный слушатель заметит, что при такой реализации запрос на чтение может вернуть неактуальные данные,
если одна из реплик еще не успела применить обновления. Чтобы решить эту проблему, предлагается
отправлять запросы на чтение на все реплики, дожидаться ответа от двух самых быстрых реплик и выбирать
наиболее актуальную версию значения из полученных (тему версионирования затронем далее в курсе).
В таком случае нам неважно, какие реплики подтвердили запись и ответили на операцию чтения.
Так как на операцию записи и чтения мы обращались на две реплики, как бы мы не выбрали эти множества
реплик, они обязательно пересекутся как минимум по одной реплике. Эта реплика будет содержать
наиболее актуальные данные.
Если обобщить задачу на произвольное число реплик N, то для кворума необходимо выбрать числа R и W.
R &mdash; число реплик, от которых мы ждем ответа на операцию чтения, W &mdash; число реплик,
которые должны подтвердить операцию записи. Чтобы система возвращала актуальные значения, должно
выполняться условие `R + W > N`. В зависимости от отношения и сложности операций чтения и записи
можно выбирать наиболее подходящие R и W для вашей системы.
Реализацию этой идеи можно увидеть в СУБД Cassandra
(и почитать в paper'e о Dynamo, секция 4, sloppy quorum & hinted handoff).
![Dynamo replication](media/dynamo-replication.svg)
*[Источник](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)
&mdash; переносит идею репликации данных из распределенной системы на уровень одного компьютера.
Оказывается, к одному серверу можно подключить сразу несколько дисков с целью увеличения скорости доступа
и надежности сохранности (пережить отказ диска).
Первая простая идея &mdash; поставить два диска рядом, писать половину данных на первый диск,
вторую половину &mdash; на второй. Надежность не увеличивается, но зато увеличивается вместительность
и скорость доступа. Такой подход называется RAID 0:
<p align="center">
<img alt="https://en.wikipedia.org/wiki/File:RAID_0.svg" src="media/raid-0.png" width=200px/>
</p>
Вторая простая идея &mdash; это использовать несколько дисков для хранения одного и того же файла
(или блока данных). Это называют 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)