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