mirror of https://github.com/voidlizard/hbs2
628 lines
37 KiB
TeX
628 lines
37 KiB
TeX
\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}
|
||
|
||
|