mirror of https://github.com/voidlizard/hbs2
hbs2-git-problem document added
This commit is contained in:
parent
e1cea76d27
commit
1428324f17
|
@ -0,0 +1,8 @@
|
||||||
|
.PHONY: all clean
|
||||||
|
|
||||||
|
all:
|
||||||
|
xelatex hbs2-git-problem.tex < /dev/null
|
||||||
|
xelatex hbs2-git-problem.tex < /dev/null
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f *.aux *.log *.nav *.out *.snm *.vrb *.toc
|
|
@ -0,0 +1,627 @@
|
||||||
|
\documentclass[11pt,a4paper]{article}
|
||||||
|
|
||||||
|
\usepackage{polyglossia}
|
||||||
|
\usepackage{xltxtra}
|
||||||
|
\usepackage[margin=2cm,a4paper]{geometry}% http://ctan.org/pkg/geometry
|
||||||
|
\usepackage{pdfpages}
|
||||||
|
\usepackage{graphicx}
|
||||||
|
\usepackage[ddmmyyyy]{datetime}
|
||||||
|
\usepackage{booktabs}
|
||||||
|
\usepackage{enumitem}
|
||||||
|
\usepackage{amssymb}
|
||||||
|
\usepackage{amsmath}
|
||||||
|
\usepackage{bm}
|
||||||
|
\usepackage[nomessages]{fp}
|
||||||
|
\usepackage{caption}
|
||||||
|
\usepackage{url}
|
||||||
|
\usepackage{indentfirst}
|
||||||
|
\usepackage[parfill]{parskip}
|
||||||
|
\usepackage[ colorlinks=true
|
||||||
|
, linkcolor=black
|
||||||
|
, anchorcolor=black
|
||||||
|
, citecolor=black
|
||||||
|
, filecolor=black
|
||||||
|
, menucolor=black
|
||||||
|
, runcolor=black
|
||||||
|
, urlcolor=blue]{hyperref}
|
||||||
|
\usepackage{tikz}
|
||||||
|
\usetikzlibrary{arrows,snakes,shapes,backgrounds,positioning,calc}
|
||||||
|
\usepackage{marvosym}
|
||||||
|
\usepackage{pifont}
|
||||||
|
\usepackage{fontspec}
|
||||||
|
\usepackage{fontawesome5}
|
||||||
|
|
||||||
|
\setmainlanguage{russian}
|
||||||
|
\defaultfontfeatures{Ligatures=TeX,Mapping=tex-text}
|
||||||
|
\setmainfont{Liberation Serif}
|
||||||
|
\newfontfamily\cyrillicfont{Liberation Serif}[Script=Cyrillic]
|
||||||
|
|
||||||
|
\setlist{noitemsep}
|
||||||
|
\setlength{\intextsep}{2cm}
|
||||||
|
|
||||||
|
\renewcommand{\dateseparator}{.}
|
||||||
|
\renewcommand*\contentsname{Содержание}
|
||||||
|
|
||||||
|
\graphicspath{ {img/}}
|
||||||
|
|
||||||
|
\title{Проблема репозитория HBS2 GIT}
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
|
||||||
|
\maketitle
|
||||||
|
|
||||||
|
\section{Определения}
|
||||||
|
|
||||||
|
\begin{description}[itemsep=10pt]
|
||||||
|
|
||||||
|
\item[Объект] Любой объект, сохраняемый и передаваемый в системе. Файл,
|
||||||
|
структура данных и т.п.
|
||||||
|
|
||||||
|
\item[Блок] Адресуемые (имеющие криптографический хэш) данные. Данные это последовательность байт.
|
||||||
|
Может быть любого размера, типичный размер: не более 256K. Зашифрованный блок может
|
||||||
|
быть больше, так как при шифровании в него добавятся данные алгоритма шифрования.
|
||||||
|
Блок может быть и меньше 256K, допустим блок это маленький объект. Тогда размер блока
|
||||||
|
будет равен размеру объекта.
|
||||||
|
\par \textbf{Note:} при шифровании данные можно предварительно сжимать.
|
||||||
|
|
||||||
|
|
||||||
|
\item[Чанк] Нумерованая часть \textit{блока}. Для каждого блока известного размера можно построить
|
||||||
|
единственное разбиение на чанки заданного размера. Размер чанка выбирается таким
|
||||||
|
образом, что бы его можно было поместить в UDP пакет безопасного размера.
|
||||||
|
Безопасного это такого, что можно не заботиться о фрагментации. Считается безопасным
|
||||||
|
размер UDP пакета 508 байт, на практике MTU всегда 1500 и либо фрагментацией
|
||||||
|
занимается сетевой стек и делает это хорошо, либо MTU других размеров уже не
|
||||||
|
встречаются, в любом случае практических проблем с размером чанка = 1400 не
|
||||||
|
встретилось. В системе размер чанка принят за 1200 --- 1400 байт. Чанк используется
|
||||||
|
в протоколе передачи блоков: так как по UDP мы не можем запросить блок целиком, мы
|
||||||
|
запрашиваем его по частям, каждая такая часть и есть чанк. В протоколе существует
|
||||||
|
возможность запрашивать не каждый чанк одельно, а последовательностями, именуемыми
|
||||||
|
burst.
|
||||||
|
|
||||||
|
\item[BlockSize] Размер блока. Может быть любой, не должен быть слишком
|
||||||
|
маленьким, но и слишком большим. Типично в районе 256K.
|
||||||
|
|
||||||
|
\item[ChunkSize] Максимальный рамер чанка. Должен иметь размер, не превышающий
|
||||||
|
максимального безопасного размера для UDP (1200 --- 1400
|
||||||
|
байт) (с учетом заголовка пакета).
|
||||||
|
|
||||||
|
\item[Burst] Последовательность чанков в протоколе. Клиент запрашивает не по каждому чанку
|
||||||
|
отдельно, а сериями по N чанков, начиная с k-го. Каждая такая последовательность
|
||||||
|
и есть burst.
|
||||||
|
|
||||||
|
\item[Merkle~Tree] Любой крупный объект, данные или последовательность объектов
|
||||||
|
разбивается на блоки при помощи построения его Merkle дерева
|
||||||
|
(в нашей интерпретации). Дерево имеет два параметра:
|
||||||
|
количество элементов в каждом узле, и размер (в каком-то виде) каждого
|
||||||
|
элемента. Построение дерева происходит в два этапа: сначала мы производим
|
||||||
|
разбиение, учитывая эти два параметра, затем --- обходим получившееся дерево,
|
||||||
|
сериализуем каждый узел (пишем блоки в \textit{хранилище}), и из полученных
|
||||||
|
при сериализации хэшей строим уже дерево Меркля (Merkle~Tree).
|
||||||
|
Дерево может состоять из узлов типа (Merkle~Tree) либо Leaf
|
||||||
|
- указание на блок (данных).
|
||||||
|
|
||||||
|
\item[Annotated~Merkle~Tree] Merkle~Tree с аннотацией в виде либо короткой
|
||||||
|
строки, либо ссылкой на блок с метаинформацией.
|
||||||
|
|
||||||
|
\item[Peer] Пир. Участник сети, авторизованный своим криптографическим ключом
|
||||||
|
подписи, и поддерживающий протоколы системы.
|
||||||
|
|
||||||
|
\item[RTT] Round trip time, время на посылку запроса и получение
|
||||||
|
ответа (в сети). Можно рассматривать на уровне прикладного протокола HBS2,
|
||||||
|
можно на уровне транспортного (TCP). Обычно имеется ввиду уровень протокола
|
||||||
|
HBS2.
|
||||||
|
|
||||||
|
\item[Reflog] Механизм реализации изменяемой ссылки. Каждая ссылка
|
||||||
|
определяется неким публичным ключом. Для каждой ссылки можно опубликовать
|
||||||
|
транзакцию, короткий (ChunkSize) блок данных, который может либо содержать
|
||||||
|
произвольные данные, либо \textit{ссылку} на блок или Merkle~Tree. В случае,
|
||||||
|
если внутри ссылка -- то данные ссылки будут скачаны пиром автоматически
|
||||||
|
рекурсивно. Все транзакции Reflog упорядочиваются по их хэшам в виде списка,
|
||||||
|
который сохраняется в виде Merkle~Tree. Таким образом, значение ссылки типа
|
||||||
|
Reflog это хэш Merkle~Tree упорядоченного по хэшам списка транзакций.
|
||||||
|
|
||||||
|
\end{description}
|
||||||
|
|
||||||
|
\section{Проблема}
|
||||||
|
|
||||||
|
Медленная передача большого количества маленьких объектов (блоков).
|
||||||
|
Приводит к очень медленному первоначальному скачиванию репозитория.
|
||||||
|
Обновления затем происходят с приемлемой скоростью.
|
||||||
|
|
||||||
|
Связано с тем, что в текущем протоколе для каждого передаваемого адресуемого
|
||||||
|
блока нам нужно:
|
||||||
|
|
||||||
|
\begin{enumerate}
|
||||||
|
\item Запросить размер (выяснить его наличие)
|
||||||
|
\item Запросить его чанки
|
||||||
|
\end{enumerate}
|
||||||
|
|
||||||
|
Таким образом, скачивание одного маленького объекта занимает минимум:
|
||||||
|
\mbox{$ 2 \times RTT + C + T$}, где C - время обработки запроса на обеих
|
||||||
|
сторонах, T - время передачи самих данных.
|
||||||
|
|
||||||
|
На практике это означает, что например при типичном $ RTT = 50ms $ мы доcтигаем
|
||||||
|
скорости 6 блоков (чанков) в секунду, что даём нам не более 9Kb/s, реально в
|
||||||
|
районе 6 --- 8. Каковая скорость и наблюдается экспериментально.
|
||||||
|
|
||||||
|
|
||||||
|
Текущее устройство репозитория hbs2-git:
|
||||||
|
|
||||||
|
\begin{figure}[h!]
|
||||||
|
\centering
|
||||||
|
\begin{tikzpicture}[every node/.append style={font=\scriptsize}]
|
||||||
|
|
||||||
|
\node[ rectangle split
|
||||||
|
, rectangle split parts=3
|
||||||
|
, draw
|
||||||
|
, font=\scriptsize
|
||||||
|
, text width=3cm
|
||||||
|
, label={above:Reflog}
|
||||||
|
] (reflog)
|
||||||
|
{
|
||||||
|
Transaction~N
|
||||||
|
\nodepart{two}...
|
||||||
|
\nodepart{three}Transaction~1
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
\node[ rectangle split
|
||||||
|
, rectangle split parts=4
|
||||||
|
, draw
|
||||||
|
, font=\scriptsize
|
||||||
|
, text width=3cm
|
||||||
|
, right=2cm of reflog.north east, anchor=north west
|
||||||
|
, label={above:Object~list}
|
||||||
|
] (objlist)
|
||||||
|
{
|
||||||
|
\nodepart{one}HEAD~Object
|
||||||
|
\nodepart{two}Git Object~1
|
||||||
|
\nodepart{three}...
|
||||||
|
\nodepart{four}Git object~N
|
||||||
|
};
|
||||||
|
|
||||||
|
\node[draw,rectangle,below=2cm of objlist,xshift=-3cm,minimum
|
||||||
|
height=1cm,label={below:Git Object}]
|
||||||
|
(merkle) {Annotated~Merkle~Tree};
|
||||||
|
|
||||||
|
\draw[-latex] (reflog.15) -- (objlist.155);
|
||||||
|
\draw[-latex] (objlist.205) to [bend right,looseness=1] (merkle.north);
|
||||||
|
|
||||||
|
|
||||||
|
\end{tikzpicture}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
|
||||||
|
\begin{description}[itemsep=10pt]
|
||||||
|
\item[Reflog] Это Merkle~Tree списка \textit{транзакций}.
|
||||||
|
\item[Транзакция] Это небольшой объект (размера не больше ChunkSize),
|
||||||
|
произвольного содержания, в случае HBS2 Git -- со ссылкой на Merkle~Tree
|
||||||
|
списка объектов.
|
||||||
|
\item[Object list] Список всех достижимых объектов репозитория (транзитивное
|
||||||
|
замыкание всех объектов для коммита) + HEAD (список ссылок в смысле git и их значений)
|
||||||
|
\item[Git Object] Сохраненный в виде Annotated~Merkle~Tree объект git. В
|
||||||
|
аннотации указан тип объекта (commit, blob, tree)
|
||||||
|
\end{description}
|
||||||
|
|
||||||
|
Данное устройство было выбрано как
|
||||||
|
|
||||||
|
\begin{enumerate}
|
||||||
|
\item Наиболее простое и очевидное
|
||||||
|
\item Минимизирующее оверхед по данным на уровне git объектов -- т.е уже
|
||||||
|
имеющиеся git объекты никогда не будут скачиваться повторно
|
||||||
|
\item Любая транзакция описывает полный, консистентный репозиторий, т.е можно
|
||||||
|
не иметь полного набора транзакций, но иметь, тем не менее, полный
|
||||||
|
репозиторий.
|
||||||
|
\item Каждый когда-либо созданный git object доступен в сети пиров по хэшу его
|
||||||
|
Merkle~Tree, что казалось довольно удобно (но практически бесполезно)
|
||||||
|
\end{enumerate}
|
||||||
|
|
||||||
|
Несмотря на стремление к минимизации оверхеда по данным, данное устройство его,
|
||||||
|
тем не менее, создаёт в другом месте:
|
||||||
|
|
||||||
|
Каждое Merkle~Tree это минимум один дополнительный блок -- т.е блок на
|
||||||
|
Merkle~Tree + блок на блок данных.
|
||||||
|
|
||||||
|
Как можно видеть, данное устройство располагает к образованию большого
|
||||||
|
количества маленьких объектов: сами объекты git как правило маленькие,
|
||||||
|
транзакции = маленькие объекты (ChunkSize).
|
||||||
|
|
||||||
|
Большое число: 10---20 тысяч для репозитория размером порядка 600 коммитов.
|
||||||
|
|
||||||
|
Что, если взять скорость 5 блоков (чанков) в секунду (из оценки выше), приводит
|
||||||
|
к нас с показателю 4000 секунд на 20'000 блоков, что примерно соответствует
|
||||||
|
наблюдаемой картине (в реальности чуть лучше).
|
||||||
|
|
||||||
|
Несмотря на то, что последующие скачивания будут выполняться быстро (объекты не
|
||||||
|
будут выкачиваться повторно, будут скачаны только недостающие объекты, их
|
||||||
|
немного), первоначальное развертывание репозитория происходит неприемлемо
|
||||||
|
медленно.
|
||||||
|
|
||||||
|
Это и есть проблема.
|
||||||
|
|
||||||
|
\section{Возможные решения}
|
||||||
|
|
||||||
|
Что мы в принципе хотим добиться:
|
||||||
|
|
||||||
|
\begin{enumerate}
|
||||||
|
\item Быстрое скачивание всего репозитория;
|
||||||
|
\item Минимизировать оверхед по данным, т.е что бы одни и те же объекты
|
||||||
|
(разбитые на блоки), по возможности, скачивались один раз.
|
||||||
|
\end{enumerate}
|
||||||
|
|
||||||
|
\subsection{Большой лог}
|
||||||
|
|
||||||
|
Основная идея --- не порождать большое число маленьких объектов, как в текущем
|
||||||
|
дизайне, вместо этого ввести \textit{лог}, куда будут упорядоченно
|
||||||
|
писаться объекты git, каждый объект только один раз.
|
||||||
|
|
||||||
|
Такой лог представляет собой просто большой файл, который может передаваться
|
||||||
|
обычными механизмами с максимально высокой скоростью ( десятки Mb/s ).
|
||||||
|
|
||||||
|
Данное решение имеет недостатки:
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\item[-] Более нет соответствия $ git object \leftrightarrow merkle tree $
|
||||||
|
|
||||||
|
\item[-] При отсутствии выравнивания, логи разных форков репозиториев
|
||||||
|
не сойдутся никогда, следовательно, всегда будет перевыкачиваться
|
||||||
|
различающаяся часть и эта часть будет расти со временем
|
||||||
|
|
||||||
|
\item[-] При наличии <<крупного>> (256K) выравнивания оверхед по данным возрастает
|
||||||
|
на порядки ( ~ 3Gb для числа объектов $ \approx 12000$ )
|
||||||
|
|
||||||
|
\item[-] При наличии <<мелкого>> выравнивания оверхед всё равно
|
||||||
|
существенный ( $ \approx 50\% $ для блока размером 1K ),
|
||||||
|
но растёт число мелких объектов и соответствующим падением скорости
|
||||||
|
передачи.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\subsubsection{Большой лог всех объектов}
|
||||||
|
|
||||||
|
В случае выровненной записи --- получаем описанные выше недостатки.
|
||||||
|
|
||||||
|
В случае невыровненной записи --- получаем расхождение логов у разных писателей
|
||||||
|
(форков), соответствовать будет лишь начальная часть.
|
||||||
|
|
||||||
|
Можно ввести специальную дисциплину работы с логом и мерж лога, тогда можно
|
||||||
|
будет их переупорядочивать у всех, и логи будут периодически сходиться. Высокая
|
||||||
|
сложность реализации и много неоднозначностей.
|
||||||
|
|
||||||
|
Можно писать лог каждый раз при push, тогда для одинаковых git репозиториев
|
||||||
|
будут одинаковые логи. Очень медленно и время будет расти с ростом репозитория.
|
||||||
|
|
||||||
|
\textbf{Плюсы:}
|
||||||
|
|
||||||
|
простая реализация, быстрое первоначальное развертывание.
|
||||||
|
|
||||||
|
\textbf{Минусы:}
|
||||||
|
|
||||||
|
всё остальное.
|
||||||
|
|
||||||
|
\subsubsection{Отсортированный по времени/высоте сегментированный лог}
|
||||||
|
|
||||||
|
Как можно видеть, ситуация с большим логом объектов, даже отсортированных по
|
||||||
|
высоте/времени может приводить к перевыкачиванию значительной части лога в
|
||||||
|
ситуации, когда кто-то отредактировал историю так, что новые объекты появились в
|
||||||
|
начале лога, что допустимо в git. В случае больших репозиториев это приведёт к
|
||||||
|
скачиванию большого количества данных и хранению этих данных. Что бы реже
|
||||||
|
сталкиваться с подобной проблемой, будем строить стейт следующим образом:
|
||||||
|
|
||||||
|
\begin{figure}[h!]
|
||||||
|
\centering
|
||||||
|
\begin{tikzpicture}[every node/.append style={font=\scriptsize}]
|
||||||
|
|
||||||
|
\node[ rectangle split
|
||||||
|
, rectangle split parts=3
|
||||||
|
, draw
|
||||||
|
, font=\scriptsize
|
||||||
|
, text width=3cm
|
||||||
|
, label={above:Reflog}
|
||||||
|
] (reflog)
|
||||||
|
{
|
||||||
|
Transaction~N
|
||||||
|
\nodepart{two}...
|
||||||
|
\nodepart{three}Transaction~1
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
\node[ draw
|
||||||
|
, right = 2cm of reflog.north east, anchor=north west
|
||||||
|
, rectangle split, rectangle split horizontal, rectangle split parts=4
|
||||||
|
, label={below:PUSH log}
|
||||||
|
] (pushlog)
|
||||||
|
{ \nodepart{one}$S_1$
|
||||||
|
\nodepart{two}...
|
||||||
|
\nodepart{three}$S_n$
|
||||||
|
\nodepart{four}HEAD
|
||||||
|
};
|
||||||
|
|
||||||
|
\draw[-latex] (reflog.12) -- (pushlog) node[midway,above] {merkle~tree};
|
||||||
|
|
||||||
|
\end{tikzpicture}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\item[-] Каждая транзация $T_x$ содержит
|
||||||
|
отсортированные по времени (высоте) и по хэшу объекты репозитория
|
||||||
|
\item[-] $S(R_x) = \bigcup$ объектов из всех транзакций Reflog
|
||||||
|
\item[-] Берем объекты, отсутствующие в $S(R_x)$, сортируем по времени/высоте и затем хэшам
|
||||||
|
и пишем в лог в сжатом виде можно сжать весь лог)
|
||||||
|
\item[-] Объект HEAD пишем в тот же лог, начало или конец (TBD)
|
||||||
|
\item[-] Публикуем транзацию $T_y$, которая содержит ссылку на Merkle~Tree лога из предыдущего
|
||||||
|
пункта
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\textbf{Следствия:}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\item[-] Стейт $S(R_x)$ каждой ссылки $R_x$ обновляется инкрементально
|
||||||
|
\item[-] Каждое обновление (транзакция $T_x$) соответствует push в соответствующий
|
||||||
|
\textit{git~remote}
|
||||||
|
\item[-] Каждое обновление содержит объекты, отсутствующие в $S(R_x)$, отсортированные
|
||||||
|
детерменированным образом, зависимости только состояния репозитория git
|
||||||
|
\item[-] Каждый сегмент лога, кроме первого, как правило, сравнительно небольшой -- содержит
|
||||||
|
только новые объекты, созданные пользователем (commit, tree, blob)
|
||||||
|
\item[-] В рамках одной ссылки логи перевыкачиваться не будут, как логи (содержимое
|
||||||
|
Merkle~Tree транзаций $T_x$ создаются детерменированным образом в завимости только
|
||||||
|
содержимого репозитория git
|
||||||
|
\item[-] Частичные логи могут иногда совпадать, так что они не всегда будут дублироваться между
|
||||||
|
ссылками. Различные кейсы рассмотрим далее отдельно
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\textbf{Ситуация 1:}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\item[-] Алиса делает \textit{EXPORT} существующего репозитория в свою ссылку $R_a$
|
||||||
|
\item[-] Боб делает \textit{git~clone} данного репозитория (ссылки $R_a$)
|
||||||
|
\item[-] Боб создаёт новую ссылку $R_b$
|
||||||
|
\item[-] Боб делает \textit{EXPORT} репозитория в эту ссылку
|
||||||
|
и сообщает ссылку $R_b$ Алисе
|
||||||
|
\item[-] Боб делает изменения и делает \textit{git~push} в ссылку $R_b$
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
В этом случае:
|
||||||
|
|
||||||
|
|
||||||
|
\begin{enumerate}
|
||||||
|
\item Создаётся лог, содержащий объекты $S(R_a)$ --- все объекты репозитория,
|
||||||
|
в порядке создания/высоты и далее в порядке хэшей
|
||||||
|
\item Публикуется транзация $T_x$ со ссылкой на Merkle~Tree лога объектов
|
||||||
|
\item Боб принимает данную транзацию, объектов $S(R_a)$ --- данных объектов
|
||||||
|
у него еще нет, следовательно дублирования нет
|
||||||
|
\item Объекты $S(R_a)$ распаковываются в репозиторий git
|
||||||
|
\item Создаётся новая ссылка $R_b$. При \textit{EXPORT} создаётся
|
||||||
|
первоначальная транзакция, которая ровно такая же, как и $T_x$,
|
||||||
|
так как содержимое лога определяется только объектами репозитория git,
|
||||||
|
а они все из $S(R_a)$
|
||||||
|
\item Боб делает свои изменения и делает \textit{git~push}
|
||||||
|
\item Создается $T_{b1}$, содержащая объекты $S(R_{b1})$, отсутствующие в
|
||||||
|
$S(R_a)$
|
||||||
|
\item Алиса получает $T_{b1}$, содержащая объекты $S(R_{b1})$ и импортирует
|
||||||
|
эти объекты в свой репозиторий git.
|
||||||
|
\end{enumerate}
|
||||||
|
|
||||||
|
На шаге (5) мог не выполниться \textit{EXPORT}, так как его выполнение зависит
|
||||||
|
от пользователя. В этом случае на шаге (7) будет создана $T_{b1}$ содержащая все
|
||||||
|
объекты репозитория, включая те, что были созданы Бобом на шаге (6). В этом
|
||||||
|
случае, какое-то количество объектов в зависимости от гранулярности разбиения
|
||||||
|
(BlockSize), попадут в те блоки, что уже есть у Алисы в $S(R_a)$. И чем больше
|
||||||
|
был размер лога $S(R_a)$, тем больше объектов Алиса не будет скачивать повторно,
|
||||||
|
так как порядок следования определяется только объектами в репозитории, Боб не
|
||||||
|
переписывал историю и его объекты будут следовать за объектами Алисы.
|
||||||
|
|
||||||
|
Если же Боб переписал историю, что, в целом, возможно, то Алиса не будет
|
||||||
|
повторно выкачивать только часть лога до изменений Боба.
|
||||||
|
|
||||||
|
Таким образом видно, что при создании ссылки $R_b$ нужно форсировать операцию
|
||||||
|
EXPORT.
|
||||||
|
|
||||||
|
Как можно её форсировать, ведь Боб может и не создавать ссылку, или создавать
|
||||||
|
её в любой момент времени.
|
||||||
|
|
||||||
|
Например, при операции PUSH нужно каким-то образом получить все объекты,
|
||||||
|
которые шли раньше изменений Боба и создать транзакцию $T_{b0}$ в которой
|
||||||
|
будут эти объекты.
|
||||||
|
|
||||||
|
Как система должна понять, что этот \textit{git~push} --- не первоначальная
|
||||||
|
инициация репозитория, а апдейт? Например, так: при \textit{FETCH}
|
||||||
|
в случае отсутствия транзакций создаётся (и публикуется) первая транзакция.
|
||||||
|
Тогда в большей части случаев она будет соответствовать инициации репозитория,
|
||||||
|
и при последующем \textit{PUSH} или \textit{EXPORT} в создаваемую транзакцию
|
||||||
|
попадут только те объекты, которые создал владелец новой ссылки (Боб в нашем
|
||||||
|
случае).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
\subsubsection{Merkle~Tree+лог с переменным разбиением}
|
||||||
|
|
||||||
|
\begin{figure}[h!]
|
||||||
|
\centering
|
||||||
|
\begin{tikzpicture}[every node/.append style={font=\scriptsize}]
|
||||||
|
|
||||||
|
\node[ rectangle split
|
||||||
|
, rectangle split parts=3
|
||||||
|
, draw
|
||||||
|
, font=\scriptsize
|
||||||
|
, text width=3cm
|
||||||
|
, label={above:Reflog}
|
||||||
|
] (reflog)
|
||||||
|
{
|
||||||
|
Transaction~N
|
||||||
|
\nodepart{two}...
|
||||||
|
\nodepart{three}Transaction~1
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
\node[ rectangle split
|
||||||
|
, rectangle split parts=5
|
||||||
|
, draw
|
||||||
|
, font=\scriptsize
|
||||||
|
, text width=3cm
|
||||||
|
, right=2cm of reflog.north east, anchor=north west
|
||||||
|
, label={above:Object~list}
|
||||||
|
] (objlist)
|
||||||
|
{
|
||||||
|
\nodepart{one}HEAD~Object
|
||||||
|
\nodepart{two}SmallObjectLog
|
||||||
|
\nodepart{three}...
|
||||||
|
\nodepart{four}BigGitObject
|
||||||
|
\nodepart{five}...
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
\node[draw,rectangle,below=1cm of objlist.north east,xshift=2cm,minimum
|
||||||
|
height=1cm,label={[xshift=1.2cm]above:Small~Object~Log}]
|
||||||
|
(smallmerkle) {Merkle~Tree};
|
||||||
|
|
||||||
|
\node[right=0.1cm of smallmerkle,text width=4.2cm,font=\scriptsize]
|
||||||
|
(smallmerklenote) {С переменным шагом разбиения:\\один объект=один лист};
|
||||||
|
|
||||||
|
\node[ rectangle split
|
||||||
|
, rectangle split parts=4
|
||||||
|
, draw
|
||||||
|
, font=\scriptsize
|
||||||
|
, text width=3cm
|
||||||
|
, below=1cm of smallmerkle
|
||||||
|
, label={below:SmallObjectLog}
|
||||||
|
] (objlog)
|
||||||
|
{
|
||||||
|
\nodepart{one}SmallObject~1
|
||||||
|
\nodepart{two}...
|
||||||
|
\nodepart{three}SmallObject~N
|
||||||
|
\nodepart{four}...
|
||||||
|
};
|
||||||
|
|
||||||
|
\node[draw,rectangle,below=2cm of objlist,xshift=-3cm,minimum
|
||||||
|
height=1cm,label={below:Git Object}]
|
||||||
|
(merkle) {Annotated~Merkle~Tree};
|
||||||
|
|
||||||
|
\draw[-latex] (reflog.15) -- (objlist.155);
|
||||||
|
|
||||||
|
\draw[-latex] (objlist.200) to [bend right,looseness=1] (merkle.north);
|
||||||
|
|
||||||
|
\draw[-latex] (objlist.12) -| (smallmerkle.north);
|
||||||
|
|
||||||
|
\draw[-latex] (smallmerkle.south) -- (objlog.north);
|
||||||
|
|
||||||
|
\end{tikzpicture}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Несмотря на то, что сейчас дерево разбивается на блоки фиксированного размера,
|
||||||
|
необходимости в этом нет. Это означает, что мы можем генерировать Merkle~Tree
|
||||||
|
таким образом, что каждому маленькому объекту будет соответствовать один объект
|
||||||
|
(лист) Merkle~Tree (т.е они не будут разбиваться: мы сначала генерируем лог
|
||||||
|
<<маленьких>> объектов, затем разбиваем его, принимая во внимание размер каждой
|
||||||
|
секции (заголовок+объект), так что каждая секция --- ровно один лист (хэш)
|
||||||
|
Merkle~Tree.
|
||||||
|
|
||||||
|
\textbf{Требование:} Каждый <<маленький>> объект должен быть достаточно
|
||||||
|
маленьким для того, что бы можно было его безопасно читать в память.
|
||||||
|
|
||||||
|
<<Большие>> объекты передаются ровно так, как сейчас, то есть каждый отдельно.
|
||||||
|
<<Большие>> это, допустим, от 1Mb.
|
||||||
|
|
||||||
|
|
||||||
|
\textbf{Плюсы:}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\item[+] Выполняется требование отсутствия оверхеда по данным
|
||||||
|
\item[+] Нет множества <<мелких>> объектов: лог <<большой>>,
|
||||||
|
отдельные объекты тоже <<большие>>.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\textbf{Минусы:}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\item[-] Передаваться будет дольше, чем <<большой>> лог
|
||||||
|
\item[-] Средняя сложность реализации
|
||||||
|
\item[-] <<Переменное разбиение>> $ => $ объекты git маленькие $ => $ много
|
||||||
|
маленьких объектов $ => $ \textbf{не работает}
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\subsection{Ускорение скачивания мелких объектов}
|
||||||
|
|
||||||
|
Невредно сделать в любом случае, однако полезность данного механизма может
|
||||||
|
варьироваться, а сложность может быть велика. Мы рассматриваем в первую очередь
|
||||||
|
ограничения UDP, так как в настоящий момент по TCP ходят в точности такие же
|
||||||
|
протоколы, как и по UDP. Это может быть изменено, но потребуется поддержка
|
||||||
|
механизмов, которые будут динамически разрешать или запрещать протоколы в
|
||||||
|
зависимости от типов транспорта. Это возможно, сложность --- ниже средней.
|
||||||
|
|
||||||
|
\subsubsection{Пакетные запросы}
|
||||||
|
|
||||||
|
Поскольку мы можем передать по UDP 1200 --- 1400 байт в одной датаграмме,
|
||||||
|
мы можем запросить $ 1200 / 32 \approx 32 $ объекта за один раз.
|
||||||
|
|
||||||
|
Но если речь идёт о UDP, то ответ, очевидно, не может быть получен в одной
|
||||||
|
датаграмме, значит, нужен какой-то механизм стриминга/сборки ответа из кусков,
|
||||||
|
и уже имеющийся механизм не подходит, так как ответ создаётся динамически и
|
||||||
|
отсутствует в хранилище. Таким образом, его надо или создавать в хранилище (и
|
||||||
|
увеличивать число данных, причём, мусора), либо как-то создавать временный
|
||||||
|
ответ и скачивать его текущими механизмами.
|
||||||
|
|
||||||
|
Это возможно, но неизящно:
|
||||||
|
|
||||||
|
\begin{enumerate}
|
||||||
|
\item Получить запрос
|
||||||
|
\item Сформировать ответ (записать данные на /tmp допустим)
|
||||||
|
\item Посчитать хэш ответа ( как блока(!) )
|
||||||
|
\item Добавить хэш в специальную таблицу <<временные объекты>>
|
||||||
|
\item Сообщить хэш и размер запрашивающей стороне
|
||||||
|
\item Запрашивающая сторона получает блок обычным способом
|
||||||
|
\item При обработке запроса сервер смотрит, не является ли блок временным
|
||||||
|
объектом и читает его, а не хранилище
|
||||||
|
\end{enumerate}
|
||||||
|
|
||||||
|
Кроме того, ускорение может получиться не столь значительным, так как
|
||||||
|
мы можем не знать все объекты заранее, и что бы их узнать, надо сначала
|
||||||
|
скачать другие объекты.
|
||||||
|
|
||||||
|
Из наблюдаемых явлений видно, что в какой-то момент времени очередь запросов
|
||||||
|
становится весьма большой ( $ \approx 10K $ объектов ), следовательно, можно
|
||||||
|
запрашивать объекты пачками, следовательно, будет иметь место ускорение в $
|
||||||
|
\approx 32 $ раза, что приведёт к скорости отдачи мелких объектов в районе
|
||||||
|
160Kb/s. Что не бог весть что, на самом деле. Текущие скорости отдачи
|
||||||
|
"нормальных" объектов составляют около ширины канала, десятки мегабайт в
|
||||||
|
секунду.
|
||||||
|
|
||||||
|
\subsubsection{Стриминг Merkle~Tree}
|
||||||
|
|
||||||
|
Специальная команда, в ответ на которую передаются все блоки Merkle~Tree, не
|
||||||
|
дожидаясь запросов.
|
||||||
|
|
||||||
|
Проблематичная реализация на UDP, в виду возможного реордеринга и потери
|
||||||
|
пакетов, а имеющийся текущий механизм, устойчивый к реордерингу и потерям,
|
||||||
|
неприменим.
|
||||||
|
|
||||||
|
Вернее, он применим, если мы создадим механизм <<временного>> хранилища --
|
||||||
|
аналогичного обычному, но существующему лишь некоторое время. Тогда
|
||||||
|
любые промежуточные объекты могут быть созданы, существовать в
|
||||||
|
течение сессии или некоторого времени и впоследствии удалены.
|
||||||
|
|
||||||
|
Можно и не создавать временное хранилище, а помечать объекты, как временные
|
||||||
|
и рекурсивно удалять через некоторое время.
|
||||||
|
|
||||||
|
Можно создавать временный объект <<транзитивного замыкания>> для дерева,
|
||||||
|
сообщать его хэш, и давать клиенту скачивать его обычным образом.
|
||||||
|
|
||||||
|
Можно видеть, что любые варианты пакетного скачивания объектов ведут к
|
||||||
|
работе <<сервера>> и расходованию его ресурсов, т.е при незначительном
|
||||||
|
усилии со строны клиента (один запрос) сервер вынужден сканировать десятки
|
||||||
|
тысяч объектов и создавать временные объекты потенциально любых размеров,
|
||||||
|
что открывает путь для намеренных и ненамеренных атак на пира.
|
||||||
|
|
||||||
|
Если у нас есть надёжный транспортный протокол (TCP) то можно стримить данные,
|
||||||
|
не создавая временных структур, тогда затраты локальных ресурсов будут ниже,
|
||||||
|
но тем ме менее, возможность DoS остаётся: одна команда со стороны <<клиента>>
|
||||||
|
приводит к несопоставимо большой вычислительной работе <<сервера>> и передачи
|
||||||
|
большого объема данных (много больше исходного запроса).
|
||||||
|
|
||||||
|
\end{document}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue