OS·V Операционная система Глава 40 из 65
Спасательная операция
В 1998 году одна команда на сервере Pixar стёрла почти весь мультфильм «История игрушек 2», а резервные копии оказались негодными. Фильм спас компьютер, стоявший у сотрудницы дома. Впереди учения спасателей: пять аварий — погасший свет, сгоревший диск, тихая порча битов, ошибка человека и потерянная версия — и для каждой свой способ выжить, вплоть до git, который вы соберёте сами.
Операционная система
- 36 ОС
- 37 Планировщик
- 38 Виртуальная память
- 39 Конкурентность
- 40 Хранение вы здесь
Опирается на: 36 · Экскурсия по живой системе 17 · Сад деревьев поиска 16 · Хеш-таблица: атака и защита
Что вы унесёте из главы
- сохранять файл так, чтобы сбой посередине не оставил половину: временный файл, fsync и атомарная замена
- восстанавливать потерянный диск по чётности XOR и проверять данные контрольными суммами
- понимать, как git хранит версии: объекты по адресу-хешу, деревья и коммиты, — и собрать свой маленький git
Прошлая глава закончилась тем, что все наши аккуратно сосчитанные просмотры живут в памяти процесса и исчезнут, стоит выключить свет. Память из триггеров, которую мы собирали в главе 31, помнит, только пока течёт ток. Чтобы данные пережили выключение, их кладут туда, где они держатся без электричества: на магнитный диск или во флеш-память. Но положить мало. Свет гаснет посреди записи, диски ломаются, биты портятся сами собой, а люди ошибаются чаще, чем железо. Эта глава — учения: пять аварий, и после каждой мы смотрим, что уцелело и что надо было сделать, чтобы уцелело всё.
Pixar, 1998. Фильм исчезает
Мультфильм, кстати, потом всё равно в основном переделали, но уже по решению самой студии. Из этой истории нам понадобятся в четвёртом учении два урока. Копия, которую никто не проверяет, может давно не работать. А спасает та копия, что лежит далеко от места аварии. Но сначала — о том, где и как вообще лежат данные.
Где лежат данные
Для программы диск выглядит как огромный массив пронумерованных блоков — как память из главы 14, только читать и писать его приходится целыми блоками, обычно по 4096 байт. Как диск устроен внутри, программе неважно. В жёстком диске это намагниченные участки на вращающихся пластинах: чтобы прочесть блок, головка должна доехать до нужной дорожки и дождаться, пока нужное место подплывёт под неё, — это миллисекунды, по меркам процессора вечность, как мы считали в главе 34. В твердотельном накопителе блоки лежат во флеш-памяти, механики нет, и блок читается примерно в сто раз быстрее. Зато у флеш-памяти свой характер: стирать её можно только большими кусками, а каждая ячейка выдерживает ограниченное число перезаписей. Поэтому контроллер SSD держит собственную таблицу «номер блока для программы → место в микросхеме» и раскладывает записи равномерно по всем ячейкам — та же идея, что таблица страниц из главы 38.
Файл всегда занимает целое число блоков, даже если в нём один байт.
Один байт занимает 4096 байт, а 4097 байт — уже 8192: второй блок начат, и он весь наш. Последняя строка — признание. Когда писалась глава, каталог /tmp песочницы был устроен как tmpfs — файловая система в оперативной памяти. Всё, что ваши ячейки сюда пишут, исчезает вместе с песочницей. Для опытов этой главы так даже удобнее: аварии мы будем устраивать понарошку, а законы, по которым живут блоки и файлы, от этого не меняются.
Оглавление: файловая система
Массив блоков — ещё не файлы. Надо где-то записать, какие блоки принадлежат какому файлу, как файл называется, кто его владелец и когда его меняли. Этим занимается файловая система. В Unix у каждого файла есть индексный узел, по-английски inode: запись с размером, владельцем, правами, временем изменения и списком блоков, где лежит содержимое. Узлы пронумерованы. Деннис Ричи потом говорил, что и сам не уверен, откуда буква i, и лучшая его догадка — index: номер узла служил индексом в массиве узлов на диске.
Имени в узле нет. Имена живут в каталогах, а каталог — это особый файл, содержимое которого — таблица «имя → номер узла». Каталоги лежат в каталогах, и получается дерево из главы 17: путь /tmp/film/woody.txt — это спуск от корня по веткам с именами tmp, film, woody.txt. Раз имя — всего лишь строка в таблице, у одного узла может быть несколько имён.
Оба имени ведут к одному узлу, и у узла счётчик имён — 2. Команда rm стирает не файл, а строку в каталоге и уменьшает счётчик. Пока у узла остались имена, содержимое живо; когда счётчик доходит до нуля, файловая система отмечает узел и его блоки свободными. Сами байты при этом обычно никто не затирает — они лежат, пока на их место не запишут что-то новое. Поэтому удалённое иногда удаётся достать специальными программами, и поэтому первое, что сделали в Pixar, — остановили серверы: каждая новая запись могла лечь на место стёртых сцен. Это знание полезно и вам: заметили, что стёрли нужное с флешки, — ничего на неё не пишите.
Учение 1. Погас свет
Сценарий первый: программа сохраняет документ, и посреди записи пропадает питание. Что окажется на диске?
Запись файла складывается из нескольких действий. open(path, "w") сразу обрезает файл до нуля: старой версии больше нет, а новой ещё нет. Потом новое содержимое уходит кусками. И даже «записанное» не обязательно на диске: write возвращается, как только ядро положило байты в свой кэш в памяти, а на диск они отправятся позже, когда ядру будет удобно, — это кэш из главы 34, только между памятью и диском. Выключить свет в песочнице мы не можем, но можем сделать почти то же: оборвать процесс посреди записи. Процесс-ребёнок сохраняет новую версию сцены, а родитель через 0,05 секунды убивает его сигналом, который нельзя перехватить.
Наивное сохранение оставило на диске обрубок: старых строк ноль, новых — сколько успело записаться (когда писалась глава, 454 из тысячи, а иногда 363: на один кусок меньше). Пропали обе версии. Безопасное сохранение оставило старую версию нетронутой: обрыв пришёлся на временный файл, а его потерять не жалко.
Рецепт безопасного сохранения стоит запомнить, им пользуются текстовые редакторы, базы данных и программы установки. Пишем новую версию во временный файл в том же каталоге. Вызываем os.fsync — он не возвращается, пока ядро не отправит байты файла на диск. И только потом os.replace переименовывает временный файл, и тот занимает место старого. Переименование внутри одной файловой системы атомарно: это одна правка строки в каталоге, и любой, кто откроет файл, увидит либо старую версию целиком, либо новую. После аварии на диске будет старый файл и, может быть, недописанный временный — но не обрубок. Для полной уверенности осторожные программы вызывают fsync ещё и для самого каталога, чтобы на диск попало и переименование.
Обрыв процесса и выключение света — не одно и то же. Когда умирает процесс, ядро живо, и всё, что оно взяло в свой кэш, рано или поздно дойдёт до диска. Когда гаснет свет, умирает и кэш ядра. Поэтому при обрыве процесса наивное сохранение теряет то, что не успело дописаться, а при выключении света может потерять даже то, что «записалось», — если не было fsync.
Журнал
Та же беда подстерегает и саму файловую систему. Чтобы дописать в файл один блок, ей нужно изменить несколько мест на диске: сам блок с данными, карту свободных блоков (теперь этот блок занят), индексный узел (теперь блок в его списке, и размер другой). Свет может погаснуть между любыми двумя записями. Блок отмечен занятым, но ни одному файлу не принадлежит — место утекло. Узел ссылается на блок, в который так и не записали данные, — в файле чужой мусор. Раньше после каждого сбоя запускали программу проверки, которая обходила весь диск и сводила концы с концами; на больших дисках она работала часами.
Современные файловые системы ведут журнал. Прежде чем что-то менять на местах, они записывают в отдельную область все задуманные изменения одной записью и ставят в конце отметку «готово». Потом вносят изменения на места и стирают запись. Если свет погас до отметки, запись в журнале неполная: её выбрасывают, и диск остаётся в состоянии «до». Если после — при включении записи журнала повторяют, и диск приходит в состояние «после». Середины не бывает. Ту же идею, «сначала запиши, что собираешься сделать», мы встретим в базах данных в главе 46.
Учение 2. Сгорел диск
Сценарий второй: диск умер насовсем. У жёсткого изнашивается механика, у твердотельного — ячейки, у обоих бывают сгоревшие контроллеры. Для одного диска это редкость, но в больших хранилищах, где дисков десятки тысяч, замена сгоревших — обычная ежедневная работа. Диск сломается обязательно, вопрос только в том, что будет с данными, когда это случится.
Первое, что приходит в голову, — зеркало: два диска с одинаковым содержимым. Умер один — работаем на втором, ставим новый и копируем на него. Надёжно, но половина места уходит на копию. В 1988 году Дэвид Паттерсон, Гарт Гибсон и Рэнди Кац из Беркли опубликовали статью «Доводы в пользу избыточных массивов недорогих дисков» — так появилось слово RAID. Мысль была такая: вместо одного дорогого большого диска поставить много дешёвых маленьких. Но чем больше дисков, тем чаще какой-нибудь из них ломается, поэтому массиву нужна избыточность, и не обязательно вдвое. Хватает одного лишнего диска на весь массив, если хранить на нём чётность.
Чётность — это исключающее «или» из главы 29, применённое к целым блокам, бит за битом. У XOR два свойства, на которых держится всё остальное: $a \oplus a = 0$ и $a \oplus 0 = a$. Пусть на трёх дисках лежат блоки $d_1, d_2, d_3$, а на четвёртом — $p = d_1 \oplus d_2 \oplus d_3$. Если пропал $d_2$, сложим по XOR всё, что осталось: $d_1 \oplus d_3 \oplus p = d_1 \oplus d_3 \oplus d_1 \oplus d_2 \oplus d_3 = d_2$, потому что каждый из уцелевших встречается дважды и гасит сам себя.
Байты чётности сами по себе бессмысленны, но из них и двух уцелевших дисков BUZZ собирается обратно. Так устроен RAID-5. Данные режут на полосы: в каждой полосе на всех дисках, кроме одного, лежат блоки данных, а на оставшемся — их чётность. Диск с чётностью меняется от полосы к полосе, чтобы ни один диск не стал узким местом: чётность пересчитывается при каждой записи, и если бы она жила на одном диске, он работал бы за всех.
У RAID-5 есть слабое место — время восстановления. Чтобы пересчитать умерший диск, надо прочесть все остальные от начала до конца, а на больших дисках это часы и даже сутки. Если за это время умрёт второй диск, данные пропали: одно уравнение с двумя неизвестными не решить. Поэтому большие массивы держат две независимые чётности (RAID-6) и переживают смерть любых двух дисков.
RAID защищает от поломки железа, но не от ошибок. Команду rm -r -f * массив выполнит на всех дисках сразу и так же быстро, как один диск, — и чётность честно пересчитает пустоту. RAID — не резервная копия.
Учение 3. Тихая порча
Сценарий третий — самый коварный: диск жив, на вопросы отвечает, но в одном из блоков перевернулся бит. Виноваты могут быть частица космических лучей, стареющая ячейка флеш-памяти, ошибка в прошивке контроллера. Диск отдаёт блок как ни в чём не бывало, и испорченная фотография или таблица живёт годами, пока её не откроют. Она попадает в резервные копии, и порча расходится по всем.
Защита — контрольная сумма: короткое число, которое вычисляют по блоку при записи и хранят рядом, а при чтении вычисляют заново и сравнивают. Простейшая сумма нам знакома — бит чётности из главы 31, которым проверяли верёвочную память «Аполлона». Посмотрим, что она ловит, а что нет, рядом с двумя суммами посильнее.
Один перевёрнутый бит меняет чётность, а два возвращают её на место: бит чётности слеп к любому чётному числу ошибок. CRC-32 — циклический код, который считают в Ethernet, архивах ZIP и картинках PNG, — меняется в обоих случаях. Он придуман так, чтобы гарантированно ловить все короткие пачки испорченных битов подряд, а случайную порчу пропускает примерно в одном случае из четырёх миллиардов. SHA-256 — криптографическая хеш-функция, родственница хешей из главы 16, но построенная так, что подобрать два разных блока с одинаковым хешем никто не умеет даже нарочно. Она длиннее и медленнее, зато годится не только против случайной порчи, но и против подделки.
Файловые системы ZFS и Btrfs хранят контрольную сумму каждого блока и проверяют её при каждом чтении. Если диск отдал испорченный блок, а у массива есть зеркало или чётность, файловая система возьмёт исправный экземпляр и перепишет плохой — порча лечится сама, прежде чем её заметит человек. А контрольные суммы скачанных файлов, которые публикуют рядом со ссылками, отвечают на тот же вопрос в сети: дошёл ли файл таким, каким его отправили. Свою контрольную сумму — ту, что работает внутри каждого потока zlib, — вы посчитаете в задаче «Сумма с памятью».
Учение 4. Ошибся человек
Сценарий четвёртый — тот, что случился в Pixar. Железо исправно, биты целы, журнал ведётся. Человек отдал команду, система её выполнила. От этого не спасают ни журнал, ни RAID, ни контрольные суммы: все они берегут данные в том виде, в каком им велели их хранить. Спасает только копия, до которой команда не дотянулась. Сюда же относятся программа-вымогатель, которая шифрует файлы, кража ноутбука и пожар: беда приходит снаружи и задевает всё, что рядом.
Здесь и пригодятся оба урока Pixar. Копия у студии была, но месяц как не работала, и никто этого не знал: из неё ни разу не пробовали восстановить. Резервная копия, из которой не восстанавливали, — не копия, а надежда. Проверка простая и скучная: время от времени достать из копии несколько файлов и открыть их. Второй урок: спасла копия, которая лежала далеко от сервера, на другом компьютере, в другом доме, и до которой команда rm добраться не могла.
Обе мысли собраны в правиле, которое часто называют «3-2-1»: держите не меньше трёх копий данных, на носителях двух разных видов, и одну из них — в другом месте. Три копии нужны потому, что две могут умереть одновременно, как диски в RAID-5 во время восстановления. Носители разных видов — потому что у одинаковых дисков из одной партии одинаковые болезни. А копия вдали спасает от пожара и вора: они не выбирают, какой из дисков в комнате забрать. Проверьте на учениях, какие копии что переживают.
Учения быстро показывают две ловушки. Синхронизация — папка, которая сама повторяет себя на другом компьютере или в облаке, — не резервная копия: она старательно повторяет и удаление, и шифрование. Зеркало и постоянно подключённый диск тоже повторяют всё, что случилось с оригиналом, или погибают вместе с ним. Спасают копии с историей версий — где можно достать вчерашнее состояние, даже если сегодняшнее испорчено, — и копии, которые большую часть времени отключены. Многие системы делают такие снимки сами: файловые системы ZFS и Btrfs умеют за долю секунды сохранить состояние всего диска, а программы резервного копирования хранят версии за часы, дни и месяцы назад.
Учение 5. Вернуть вчерашний день
Сценарий пятый: ничего не сгорело и никто ничего не стёр. Вчера программа работала, а после сегодняшних правок перестала, и непонятно, какая из сорока правок виновата. Или двое правили один файл, и надо свести их работу. Тут нужна история: все версии, с подписью, кто и зачем менял. Сегодня почти все программисты мира хранят её в git, а сам git родился как спасательная операция.
Git успели написать за несколько дней, потому что в основе у него одна простая мысль. Файл хранится под адресом, который вычисляется из его содержимого: SHA-1 от короткого заголовка и самих байтов. Это адресация по содержимому. Самого git в песочнице нет, но его адреса можно посчитать вручную, и они совпадут с теми, что выдаёт git hash-object на любом компьютере мира.
Строка hello world с переводом строки живёт в любом репозитории по адресу 3b18e51…, пустой файл — по адресу e69de29…. Из этой мысли сразу следуют три свойства. Одинаковые файлы хранятся один раз, сколько бы версий и копий их ни упоминали: адрес у них один. Изменённый файл получает новый адрес, а старый остаётся на месте — ничего не перезаписывается. А если байт в хранилище испортится, хеш перестанет совпадать с адресом, и порча выдаст себя: контрольная сумма из третьего учения встроена в само хранилище.
Остальное git строит из тех же кирпичей. Каталог — это объект-дерево: список строк «адрес файла и имя», и у дерева тоже есть адрес-хеш. Коммит — объект с адресом дерева, адресом родительского коммита, автором и сообщением. Вот игрушечная версия: хранилище — словарь, а коммит — текст.
Два коммита, а объектов всего семь: файлы Вуди и Базза во второй версии не изменились, и второе дерево ссылается на те же адреса. В коммите видна вся конструкция: адрес дерева, адрес родителя и сообщение. А последние строки показывают, что будет, если испортить один байт в старом файле: проверка находит объект, чьё содержимое больше не совпадает с адресом.
От адреса коммита зависит гораздо больше, чем один снимок. В него входит адрес дерева, то есть адреса всех файлов, и адрес родителя, а в того — адрес его родителя, и так до самого первого коммита. Поменяйте хоть байт в любой версии любого файла в прошлом, и изменятся адреса всех коммитов после неё. Сорок шестнадцатеричных цифр заверяют всю историю проекта. Коммиты со ссылками на родителей образуют ориентированный граф без циклов из главы 19: у обычного коммита один родитель, у коммита слияния — два, а ветка — всего лишь имя, которое указывает на какой-то коммит и переезжает на новый при каждом коммите в неё.
Git хранит не разницы между версиями, а полные снимки; разницу он вычисляет, когда вы её просите, — тем же поиском наибольшей общей подпоследовательности строк, что и программа diff в главе 22, только более быстрым её вариантом. Снимки не раздувают хранилище, потому что неизменённые файлы — общие, всё сжато, а при упаковке git всё-таки хранит похожие объекты как разницу друг от друга. И ещё одно: каждый клон репозитория — полная копия всей истории. Сервер с вашим проектом на другом конце света и есть копия «одна далеко» из правила 3-2-1. Свой git, который умеет не только сохранять, но и доставать версии и читать историю, вы соберёте в задаче «Свой git».
Задачи
Четыре задачи — четыре инструмента спасателя: чётность, восстановление массива, контрольная сумма и хранилище версий.
Напишите parity(blocks): блоки — непустой список строк байтов (bytes) одинаковой длины, а ответ — их XOR, байт за байтом, тоже bytes той же длины. Если список пуст или блоки разной длины, бросьте ValueError. Тесты проверяют и свойство, ради которого чётность заводят: XOR всех блоков, кроме одного, вместе с чётностью даёт пропавший блок. На шесть блоков по 200 КБ дано три секунды.
bytearray можно менять по индексу: result[i] ^= block[i]. Пройдите так по всем блокам и всем позициям. Проверки на пустой список и разную длину поставьте в самое начало: blocks[0] у пустого списка упадёт с IndexError.
Быстрый способ без цикла по байтам: int.from_bytes(block, "big") превращает весь блок в одно большое целое число, XOR таких чисел — то же, что XOR байтов, а to_bytes(len, "big") возвращает число обратно в байты.
У XOR нет переносов, он не смотрит на соседние разряды, поэтому XOR больших чисел совпадает с XOR их байтов по отдельности. Длину ответа нужно указать явно: если в старших байтах нули, число «забудет» о них, и to_bytes(size, ...) вернёт их на место.
Массив RAID-5 из $n \ge 3$ дисков устроен так. Данные режут на блоки по block байт. Полоса номер $s$ (с нуля) — это кусок длины block на каждом диске, начиная с байта $s \cdot \text{block}$. В полосе $s$ чётность лежит на диске номер $(n - 1 - s) \bmod n$, а $n - 1$ блоков данных — на остальных дисках по возрастанию их номеров. Напишите read_array(disks, block): disks — список содержимого дисков (bytes), мёртвый диск — None. Верните все данные по порядку: полосу за полосой, в каждой — блоки данных слева направо, без чётности. Если мёртвых дисков больше одного, бросьте ValueError. Массив из шести дисков по 120 КБ нужно прочесть быстрее четырёх секунд. Пример: три диска, block = 2, данные b"ABCDEFGH". В полосе 0 чётность на диске 2, в полосе 1 — на диске 1:
Восстанавливать удобно весь диск сразу, а не по блоку: XOR всех уцелевших дисков даёт содержимое мёртвого, потому что в каждой полосе блоки данных и чётность вместе дают ноль. Это ваша parity из прошлой задачи, применённая к целым дискам.
Число полос — len(disk) // block для любого живого диска disk. В полосе s блок диска d — это срез disk[s * block:(s + 1) * block]; добавляйте его в ответ, если d не диск с чётностью.
Восстановлению всё равно, на каком диске в данной полосе чётность: мёртвый блок — это XOR всех остальных блоков полосы, будь он данными или чётностью. Где лежит чётность, важно только при чтении: её нельзя выдать за данные. Контроллеры RAID восстанавливают так же, но по полосе за раз, чтобы не держать в памяти диски целиком.
Контрольная сумма Adler-32 стоит в конце каждого потока zlib — формата сжатия, которым пользуются, например, картинки PNG. Её придумал Марк Адлер. Считается она так: $a$ начинается с 1, $b$ — с 0; для каждого байта по порядку $a$ увеличивается на значение байта, а $b$ — на новое значение $a$; обе суммы берутся по модулю 65521. Ответ — число $b \cdot 65536 + a$. Напишите adler32(data) для строки байтов. Тесты сверяют ответ со стандартной библиотекой, но пользоваться ею нельзя: модули zlib и binascii в решении запрещены. Мегабайт данных должен считаться быстрее трёх секунд.
В заготовке есть только $a$ — обычная сумма байтов. Она не замечает перестановок: у b"ab" и b"ba" она одна и та же. Добавьте вторую сумму $b$: после каждого байта прибавляйте к ней текущее $a$.
Не забудьте модуль и для $b$: без него на длинных данных $b$ вырастет далеко за 16 бит. Склеить две половины можно сдвигом: (b << 16) | a.
Почему $b$ замечает перестановки: байт, стоящий на месте $i$ из $n$, входит в $b$ столько раз, сколько после него осталось шагов, — с весом $n - i$. Поменяйте два разных байта местами, и их веса поменяются, а с ними и $b$. Модуль 65521 — самое большое простое число меньше $2^{16}$: с простым модулем суммы лучше перемешиваются. Adler-32 считается быстрее, чем CRC-32, но слабее на коротких данных, в несколько сотен байт. В файле PNG работают обе: Adler-32 в конце потока zlib проверяет все распакованные данные, а каждый отдельный кусок файла снабжён ещё и своей CRC-32.
Соберите хранилище версий. Класс Repo держит все объекты в словаре objects: адрес — 40 шестнадцатеричных цифр SHA-1 — указывает на байты объекта, и каждый объект лежит под SHA-1 своих собственных байтов. Методы:
put(data)— сохранить файл как в git: объектb"blob " + длина + b"\0" + data; вернуть адрес. Он должен совпасть с тем, что выдаёт сам git.commit(files, parent, message)—files— словарь «имя → байты»,parent— адрес прошлого коммита илиNone. Сохранить файлы, дерево и коммит, вернуть адрес коммита. Формат дерева и коммита — ваш, но адрес коммита должен меняться от любого изменения в файлах, их именах, родителе или сообщении и не должен зависеть от порядка файлов в словаре и от времени.checkout(address)— словарь «имя → байты» из этого коммита.log(address)— список сообщений от этого коммита до самого первого.
В последнем тесте цепочка из 2000 коммитов: на сами коммиты и log от последнего дано четыре секунды.
Начните с ячейки «игрушечный-git.py»: там уже есть store и commit. Чтобы адрес не зависел от порядка файлов, перебирайте имена в sorted(files). Чтобы прочесть объект обратно, отрежьте заголовок до первого нулевого байта: data[data.index(b"\0") + 1:].
Для checkout разберите коммит: строку tree … — это адрес дерева, а в дереве каждая строка — «адрес имя». Для log идите по строке parent …, пока родителя нет. Если первый коммит записывает родителя как None, при разборе получится строка "None" — договоритесь о понятном знаке, например -.
Хранилище не знает слова «версия»: в нём только объекты под своими адресами, а история возникает из ссылок коммитов на родителей. Настоящий git устроен так же, только дерево хранит ещё права файлов и вложенные каталоги как вложенные деревья, коммит — автора и время, а объекты сжаты. Время в коммите git есть, поэтому два одинаковых коммита, сделанных в разные секунды, получат разные адреса; здесь мы его нарочно не храним, чтобы результат можно было проверить.
Куда дальше
Все пять учений пройдены, и у каждой аварии нашлось своё средство: от fsync и журнала до git, где каждая версия заверена хешем.
Теперь вернитесь к спасению «Истории игрушек 2». Копия на домашнем компьютере Гэлин Сусман появилась не сама: студия раз за разом пересылала ей изменения по телефонной линии. Резервная копия «одна далеко» из правила 3-2-1 тоже должна как-то туда доехать, а git push отправляет коммиты на сервер на другом конце света. Везде одно и то же: компьютер не один. У двух машин нет общей памяти и общего диска, между ними только провод или радиоволна, по которой бегут сигналы, — а то и десяток посредников по дороге. Как две машины обмениваются данными, кто прокладывает путь через полмира и почему байты вообще доходят до адресата, расскажет следующая глава.