Git для Фоток. Большие репозитарии в Git

Идея использовать git для хранения всех своих фотографий.

Чего хотелось добиться?


  1. Скидывать фотки одной кучей (DCIM), а когда будет время сортировать по папкам.
  2. Скинуть фотки с одного компа, а работать с ними с другого.
  3. Чтобы перемещение-переименование фоток и папок волшебным образом синхронизировалось на всех компьютерах.
  4. Чтобы можно было редактировать фотки, но иметь возможность восстановить оригинал.
  5. Чтобы сохранялась история правок.

Как оказалось, GIT с большим трудом справляется с этой задачей.

Получившаяся конфигурация, кратко


Если нужны только директивы смотрите в последний параграф.

Я обнаружил, что на клиенте приходится отключать архивирование чтобы побыстрее проходили команды add и commit. А на сервере, кроме отключения архивирования нужно еще ограничить использование памяти, и скорректировать политику упаковки/распаковки объектов.
Клонировать по ssh довольно медленно и требует много ресурсов на сервере. Поэтому для таких репозиториев лучше использовать «тупой» http протокол. Кроме того, нужно обновить git до последней версии, т.к. у 1.7.1 проблемы с нехваткой памяти.
Есть еще возможность прописать *.* -delta в .gitattributes, это приведет к отключению дельта упаковки. Т.е. даже при изменении только exif данных файл будет копироваться в репозиторий целиком. Здесь я этот подход не рассмотрел.
Git For Fotos

Подробности


Для работы на сервере я выбрал gitolite. Это обертка для git, для управления репозиториями. По большому счету только из-за wild repo — репозиториев создающихся по требованию. Но также пригодилась его способность прописывать настройки в репозитории.

Установка gitolite


На сервере
useradd git
su - git
git clone git://github.com/sitaramc/gitolite
gitolite/install -to $HOME/bin
gitolite setup -pk sam.pub

Все расписано в книге, в главе 4.8.

Настройка gitolite производится на клиенте. Клонируем gitolite-admin, вносим измения, commit, push. Настроим wild репозитории — добавим в файл gitolite-admin/conf/gitolite.conf такой блок
# Repos to store photos, with name like 'Fotos-2013-03-13'
repo Fotos-[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]
    C       =   sam
    RW+     =   CREATOR
    RW      =   WRITERS
    R       =   READERS


Первый эксперимент (Добавление 3.7 гигабайт фотографий в git)


После клонирования пустого репозитория в первый комит был добавлен файл .gitignore содержащий одну строчку — Thumbs.db.
Во второй комит добавлена папка с 367 файлами, объемом 3.7 гигабайт.
293 фотографии в jpg — 439 мегабайт
37 видеороликов в avi — 3.26 гигабайт
37 THM файлов — 0.3 мегабайт

git clone git@my-server:Fotos-2013-03-01
cd 2013-03-01
echo Thumbs.db > .gitignore
git add .gitignore
git commit -m 'Initial commit'

git add <папка> # выполнялась 4 минуты 46 секунд
git commit      # выполнялась 47 секунд
git push --all  # выполнялась 23 минуты 50 секунд

9 минут push занимался локальными вычислениями, затем 14 минут пересылкой.

Второй эксперимент


Идея, подкрутить git, чтобы он не пытался сжимать видео и фото. Как выключить по типу файлов я не обнаружил, пришлось полностью отключить сжатие.
Клонируем пустой репозиторий и настраиваем его.

На клиенте
git clone git@my-server:Fotos-2013-03-02
cd 2013-03-02
git config core.bigFileThreshold 50m
git config core.looseCompression 0
git config pack.compression 0

Эти команды сохраняют настройки локально (для текущего репозитория, т.е. в файле .git/config)
core.bigFileThreshold — отключает поиск частичных изменений (дельт) в файлах больше 50 мегабайт (по умолчанию 512 мегабайт)
core.looseCompression — отключает упаковку арихватором объектов в «рыхлом» состоянии.
pack.compression — отключает упаковку архиватором объектов в упакованном состоянии.

На клиенте
git add <папка> # выполнялась 2 минуты 16 секунд
git commit      # выполнялась 47 секунд
git push --all  # выполнялась 7 минут 20 секунд
# из них 2 минуты 43 секунды вычисления 4 минуты 37 секунд передача.

После выполнения команды push на сервере репозиторий не содержал никаких дополнительных настроек (так и должно быть). Локальный репозиторий остался в рыхлом состоянии, а на сервере оказался упакован. Но при этом размер пакета оказался 3.7 гигабайт. На сервере не было настроек pack.packsizelimit=2g (но и они бы не спасли).

Клонирование репоизитория 3.7G в одном пакете по ssh


git clone git@my-server:Fotos-2013-03-02 # Выполнялась 46 минут.

После клонирования репозиторий оказался запакован, и пакет был размером 3.7 гигабайт, несмотря на локальные установки в 2 гигабайта
На клиенте
git config --system -l | grep packsize
pack.packsizelimit=2g

Перенастройка сервера


Предположение, что сервер не справляется (задыхается на свопе) из-за отсутствия ограничений на размер пакета, слишком большого bigFileThreshold и отсутствия ограничений на использование памяти для поиска дельт.
Я предпринял попытку перепаковать репозиторий на сервере. Установил на сервере
git config --system pack.packsizelimit 2g

И запустил, опять на сервере
git repack -A

После этого были созданы пакеты размером до 2 гигабайт, но огромный пакет не был удален. Попытка провести сборку мусора падала от нехватки памяти, или задыхалась на свопе. В общем я удалил системную настройку, и решил прописывать в каждый репозиторий.
В новых репозиториях для фоток мы пропишем
core.bigFileThreshold = 50m # Чтобы не искать дельты в видео
core.looseCompression = 0   # Чтобы не архивировать рыхлые объекты
pack.compression = 0        # Чтобы не архивировать упакованные объекты
pack.depth = 10             # Использовать только 10 (вместо 50) объектов для поиска дельт
pack.deltaCacheSize = 128m  # Какой-то кэш для дельт который может привести к свопингу

pack.packSizeLimit = 2g     # Ограничение максимального размера пакета
pack.windowMemory = 128m    # Ограничение на использование памяти при упаковке
transfer.unpackLimit = 2147483647 # Оставлять нераспакованными push пакеты только если в них больше N объектов
gc.auto = 300               # Если накопится больше 300 рыхлых объектов, провести упаковку

Все это мы будем прописывать в каждый репозиторий для фоток. Для этого сконфигурируем gitolite.
Перед тем, как добавлять конфиги нужно на сервере, в файле .gitolite.rc разрешить конфигурационные параметры. Вот такой строчкой
GIT_CONFIG_KEYS => '.*',

А вот как будет выглядеть конфиг gitolite.
# Repos to store photos, with name like 'Fotos-2013-03-13'
repo Fotos-[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]
    C       =   sam
    RW+     =   CREATOR
    RW      =   WRITERS
    R       =   READERS
    config core.bigFileThreshold = 50m  # Чтобы не искать дельты в видео
    config core.looseCompression = 0    # Чтобы не архивировать рыхлые объекты
    config pack.compression = 0         # Чтобы не архивировать упакованные объекты
    config pack.depth = 10              # Использовать только 10 (вместо 50) объектов для поиска дельт
    config pack.deltaCacheSize = 128m   # Какой-то кэш для дельт, может привести к свопингу
    config pack.packSizeLimit = 2g      # Ограничение максимального размера пакета
    config pack.windowMemory = 128m     # Ограничение на использование памяти при упаковке
    config transfer.unpackLimit = 2147483647 # Оставлять нераспакованными push пакеты только если в них больше N объектов
    config gc.auto = 300                # Если накопится больше 300 рыхлых объектов, провести упаковку

Если не указывать большой transfer.unpackLimit, то git, получив наш push, содержащий 3.7 гигабайта, оставляет его одним пакетом. Несмотря на ограничение pack.packSizeLimit = 2g.
gc.auto 300 необходимо, чтобы в репозитории на сервере не накапливалось слишком много рыхлых объектов. Чтобы "git gc --auto", когда сработает не зависала надолго. По умолчанию gc.auto = 6700.

Третий эксперимент


Клонируем новый пустой репозиторий и проверяем, чтобы на сервере в нем были прописаны необходимые настройки.
Клиент
git clone git@my-server:Fotos-2013-03-03
cd 2013-03-03

Сервер
cd repositories/Fotos-2013-03-03
cat config

Клиентская копия репозитория настроена по-умолчанию. Поэтому перед тем как добавлять 3.7 гигабайта фоток нужно снова настроить
Клиент
git config core.bigFileThreshold 50m
git config core.looseCompression 0
git config pack.compression 0
# Добавляем фотки
git add <folder> # Это заняло 2.5 минут
# Создаем комит
git commit # Это заняло 45 секунд
# Отсылаем на сервер
git push --all # 22.5 минут
# В том числе вычисления 7.5 минут, передача 15 минут

И на клиенте и на сервере остались рыхлые репозитории.
Попытки клонировать новый репозиторий по ssh провалились. На серверной стороне git 1.7.1 падал с Out Of Memory.
Обновление гит

Обновление гит на сервере


Нужен рабочий гит любой версии (yum install git)
На сервере
# 1. Достаем исходный код
# git://git.kernel.org/pub/scm/git/git.git

# 2. Устанавливаем всё необходимое для сборки (root)
yum install gcc
yum install openssl-devel
yum install curl
yum install libcurl-devel
yum install expat-devel
yum install asciidoc
yum install xmlto

# 3. собираем
make prefix=/usr/local all doc

# рекомендовали команду
# make prefix=/usr/local all doc info
# Но я застрял на "docbook2x-texi: command not found"

# 4. Удаляем 1.7.1, и ставим 1.8.2
yum remove git # Чтобы удалить 1.7.1
make prefix=/usr/local install install-doc install-html

/usr/local/bin/git --version
git version 1.8.2



Продолжение третьего экперимента.


После обновления гит перестал падать по out of memory. Но в начале клонирования выбрал всю память — 1 гигабайт, после начала пересылки сократил до 250 мегабайт.
Собственно клонирование заняло 48 минут. И на клиенте репозиторий содержал один пак размером 3.7 гигабайт.

Статический HTTP для fetch


Идея в том, чтобы на сервере отказаться от выполнения какой-либо работы при скачивании репозитория.
К счастью git умеет делать fetch через http. Все что для этого нужно выполнить git update-server-info после каждого обновления репозитория. Это делается обычно в hooks/post-update. И даже пример этого хука в git содержит именно эту команду.
Мы возьмем пример, и настроим gitolite прописывать этот хук всем репозиториям.
На сервере
cp repositories/testing.git/hooks/post-update.sample .gitolite/hooks/common/post-update
gitolite setup --hooks-only

Теперь, после следующего обновления репозиторий станет доступен для http.
Чтобы не зарываться в подробности настройки http, я воспользуюсь «игрушечным» веб сервером. Создам папку /home/git/http-root. Добавлю в нее сслыку git->../repositories. И запущу оттуда «игрушечный» сервер
На сервере
python -m SimpleHTTPServer > ../server-log.txt 2>&1 &

На клиенте
git clone http://my-server:8000/git/Fotos-2013-03-05.git

Время выполнения 4 минуты! 3.7 Гигабайт за 4 минуты! Это нам подходит.

Заключение


Для обслуживания больших репозиториев Git-ом необходимо Clone и Fetch выполнять по http. Прописать кучу настроек и желательно обновить гит.

Серверный репозиторий
  • git config core.bigFileThreshold 50m # Чтобы не искать дельты в видео
  • git config core.looseCompression 0 # Чтобы не архивировать рыхлые объекты
  • git config pack.compression 0 # Чтобы не архивировать упакованные объекты
  • git config pack.depth 10 # Использовать только 10 (вместо 50) объектов для поиска дельт
  • git config pack.deltaCacheSize 128m # Какой-то кэш для дельт, может привести к свопингу
  • git config pack.packSizeLimit 2g # Ограничение максимального размера пакета
  • git config pack.windowMemory 128m # Ограничение на использование памяти при упаковке
  • git config transfer.unpackLimit 2147483647 # Оставлять нераспакованными push пакеты только если в них больше N объектов
  • git config gc.auto 300 # Если накопится больше 300 рыхлых объектов, провести упаковку
  • Сделать доступ на чтение по протоколу HTTP (dumb http, НЕ какой-то там gitolite-овский smart)

Клиентский репозиторий
  • Клонировать и обновлять по http
  • Чтобы комитить нужно настроить url для push: git remote set-url origin --push git@my-server:Fotos-2013-03-05
  • git config core.bigFileThreshold 50m # Чтобы не искать дельты в видео
  • git config core.looseCompression 0 # Чтобы не архивировать рыхлые объекты
  • git config pack.compression 0 # Чтобы не архивировать упакованные объекты

Спасибо, что сподвигли меня разобраться в этом вопросе.
Share post

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 37

    0
    Тема интересная, но под фото, видео и документы можно обойтись и Seafile`ом.
      +15
      Простите, а где картинка с трамваем из буханки?
        0
        Ах, вот, что вы имеете ввиду!
        Нет, не соглашусь. Здесь всё ванильно!
        Git в значительной степени лично Торвальдс сделал, а Sitaram Chamarty, основной разработчик gitolite, как минимум с Торвальдсом переписывался.
          +1
          Троллейбусом :3
          +3
          Ну хватит уже использовать неподходящие инструменты, ну пожалуйста!
          После фразы " Как оказалось, GIT с большим трудом справляется с этой задачей" должна была быть фраза «Наверное он для этого не подходит, возьму-ка я Фликр или Пикасу». И на этом закончить
            +2
            Вы про фликр или пикасу не подумав сказали? Откуда там версионность?

            Задачка то вполне себе близка к реальности — вот у меня ресурсы для игры, в том числе результирующие атласы в виде графических файлах в различных формата + файлы описание. Гигабата на полтора только результат + еще гигабайт на 5 промежуточных данных + еще гигабайтик исходников. Время конвертации всегода добра 40+ часов.

            При этом мне необходимо иметь возможность откатиться на нужный тег (чтобы иметь возможность вернуться к данным определенного релиза), нужно иметь возможность понимать какие файлы после очередной пересборке поменялис, нужно иметь возможность различным людям как и добавлять исходные файлы, так и забирать результат. При этом я таки хочу иметь возможность комитить в оффлайне.

            Что мне предлагаете кроме git/hg использовать?

            Так, что по мне так спасибо автору большое — глядишь у меня git status начнет работать не по 20 минут после очередной пересборки ресурсов (хотя у меня проблема скорее в количестве файлов).
              +5
              я предлагаю не использовать source control для бинарников. Потому что у слова «source» есть смысл, отличный от смысла слова «binary». Для бинарников предлагаю использовать хранилище бинарников. Вот такая оригинальная идея.

              П.С. На счет фликера и пикасы — виноват, погорячился, вспылил.
                0
                Критикуешь предлагай? А что использовать то? FTP с папочками?

                Привязываться к терминам — у меня вот по своей сути большая часть инфы это исходники для ресурсов, а уж их формат не важен. Вы же не только исходники храните в вашем проекте — там небось и файл проекта есть бинарный зачастую, и пару тройку картинок — что неужели в каком то web проекте, предлагаете не хранить картинки в репозитарии, а держать отдельно?

                Понятно что есть assets manager-ы типа AlienBrain — но цена у них…
                Так, что как бы вам не нравилось — но по миру тысячи компаний используют системы контроля версий для бинарных файлов.
                  +1
                  Предлагаю — binary repository manager.
                  Он умеет работать с бинарниками (правильно их версировать, дешево хранить, не пытаться делать diff, не пытаться клонировать весь репозиторий на каждую машину, и т.д.)
                  Есть несколько бесплатных альтернатив.
                  по миру тысячи компаний используют системы контроля версий для бинарных файлов.

                  Ну, когда-то тысячи компаний по миру строили свои проекты на машинах девелоперов. Ничего, научились CI, научатся различать между сорцами и бинарниками (большинство, кстати, уже умеют).
                    0
                    А можно все же конкретный пример? А то в гугле по запросу «binary repository manager» вылезает JFrog какой-то — который явно не то что мне нужно.

                    Задачи я описал выше, могу рассказать подробнее. Можно конкретную утилиту для этого?
                      +1
                      Binary repository — это то как раз то что вы пытаетесь сделать, и то что вы нашли — это как раз то что нужно.
                        0
                        Конечно третий час ночи не лучший момент, но я все же не понимаю — каким образом оное может мне помочь.

                        Вы сейчас говорите про Artifactory? Я посмотрел скринкаст — и совершенно не понимаю как оно мне может помочь.

                        Вы можете уделить 5 минут и последовательно рассказать чем мне оно может помочь?

                        Задача простая — есть 200к файлов размером 5гигов. Нужно иметь версионность, нужно иметь возможность просмотреть какие файлы изменились, нужно иметь возможность избранно комитить.

                        Какой сценарий с Artifactory?
                          0
                          Версионность у бинарников в названии. И пойнтер на latest.
                          Возможность просмотреть какие файлы изменились — запрос на изменения в окне времени.
                          Избранно комитить — не понял.
              0
              Мне показалось, что его «архитектура» вполне пригодна для больших репозиториев. За год-другой он дозреет. Ему бы «более поточный» алгоритм поиска «дельт», и отказаться от проецирования файлов в память в пользу файлов с произвольным доступом.
                +4
                Архитектура Git-а не пригодна для бинарников. Сколько костылей не строй, каменный цветок не выйдет.
                  0
                  А вот hg c bigfiles более менее пригодна, так что не нужно на dvcs гнать вообще — это сугубо git заморочки.

                  git еще много для чего не пригоден — типа для кучи файлов просто большой и т.п.

                  Но опять же альтернативы?
                    +3
                    Любая dvcs не пригодна для больших репозиториев, просто потому что она «d», и пихает на все машины весь репозиторий. Если в svn я еще мог сказать «давай мне src, а вот lib не давай», то в dvcs — никак. Получай, батенька, парочку террабайт, даже если они тебе не нужны.

                    Альтернативы см. выше.
                      +2
                      земляных байт?
                        +2
                        поймал, ага. Молодец.
                        +1
                        Да, в Git приходится тянуть всю историю, но тянуть можно с соседней машины, или из bundle (см. git help bundle).
                        В svn тянуть нужно только последний снимок, но только с сервера.
                          0
                          Я-ж про dvcs говорил, svn не «d». С SVN-ом другая проблема. SVN хранит диффы, а у бинарников диффы не имеют смысла. Поэтому SVN хранит каждую новую версию целиком, хотя он под это не заточен. В итоге — репозиторий просто умирает от нагрузки.
                0
                Думаю, это как раз тот случай, когда лучше не использовать cvs взять svn и запастись большими дисками и терпением :)

                  0
                  Зря вы зачеркнули.
                  Думаю, это как раз тот случай, когда лучше не использовать cvs.
                  +3
                  Почему все обсуждают непригодность репозиториев для версионирования бинарников? По-моему версионирование изображений как бинарников — изначально неверный путь. Наиболее логичный подход у Lightroom. Хранить исходник и правки в виде метаданных. Репозиторий не раздувается при каждом сохранении, накладные расходы почти нулевые.
                  Единственное, что не встречал пока распределенного решения с такой архитектурой.
                  • UFO just landed and posted this here
                      –1
                      Git это тоже система контроля версий. Для сорцов. Как и Perforce и SVN (про него выше только что написал). Разница не в том, что есть для версий, а есть нет, а в том, что есть для сорцов а есть для бинарников.
                      0
                      Идея хорошая, только если это личные фотографии то нужно иметь закрытый аккаунт.
                      Иначе может повториться история как из clip2net:
                      P.S. (топик на Хабре уже закрылся).
                        +1
                        Бинарный файл лучше хранить в оригинале и в RAW виде (если он действительно ценнен), а вот конфиги фильтров и правок как например в Lightroom (за исключением brush редактирования) очень даже имеет смысл хранить в версиях (возможно) — кроп, экспозиция, уровни, тонирование и тп. Версионные данные бинарных файлов изображений — избыточные, и вероятность их использования очень мала. Это мой взгляд из практики работы с большими массивами raw фото.
                          0
                          да, непонятно, зачем использовать git, если для таких применений предназначен rsync
                            0
                            4. Чтобы можно было редактировать фотки, но иметь возможность восстановить оригинал.
                            5. Чтобы сохранялась история правок.
                              +1
                              надо просто пользоваться подходящими инструментами :)
                              rsync + lightroom

                              и если ОЧЕНЬ хочется, каталог лайтрума все же хранить в git (хотя особенного смысла в этом нет)
                                0
                                rsync + lightroom — это другое дело.
                            –1
                            Но зачем?

                            Когда есть dropbox, skydrive, google drive и тп.
                              0
                              Вы случайно не git-annex пытаетесь изобрести?

                              git-annex.branchable.com/
                              статья на русском — welinux.ru/post/7157/
                                0
                                Спасибо за наводку. Очень интересно.
                                Не уверен, что хочу что-то изобрести. На мой взгляд Git должен (и, уверен, будет) справляться с репозиториями больших размеров.
                                * Если много мелких файлов — гит упакует их в пак.
                                * Большая глубина вложенности — не проблема (листинг одного каталога в отдельном файле со ссылками на листинги вложенных).
                                * Огромное количество правок одного файла — гит запишет последний вариант целиком, а предыдущие может сократить и заменить «дельтами».

                                Потенциально проблемное место — файлы более 2G. Их git не будет разбивать на несколько. Т.е. положить такой репозиторий на файловую систему не поддерживающую большие файлы не удастся. Или клонировать через древний http сервер, тоже могут быть проблемы. Однако более-менее современные системы без труда обслуживают такие файлы.

                                И еще одно место с потенциалом неоптимального функционирования — это дельты. Git хранит последнюю версию файла, а предыдущие, если есть резон упаковывает в дельты (при упаковке).
                                Т.е.
                                Добавим файл в 1000 строк и закомитим. (в репозитории лежит файл в 1000 строк)
                                Изменим 1 строчку и закомитим. (в репозитории лежат 2 файла по 1000 строк!)
                                Выполним git gc. (В репозитории 1 пак 1001 стока и 1 индек 2 строки — примерно)
                                Изменим еще одну строчку и закомитим. (в репозитории 1 индек 2 строки, 1 пак 1001 строка, 1 файл 1000 строк).
                                Опять git gc. И вот тут Git-у во-первых приходится переделывать все паки в которых есть версии файлов изменившихся с последней упаковки. А во-вторых ранее существовавшие паки оказывается устаревшим.
                                0
                                Я просто оставлю это здесь: layervault.com/tour

                                Это аналог Dropbox с неограниченным объемом хранения за фиксированную ежемесячную плату ($24), который имеет систему контроля версий специально для графических файлов
                                  0
                                  О! случайно наткнулся! Хорошая идея чтобы с домашнего компа делать бекапы фоток на внешний жесткий диск. Никогда не знаешь что в этом ворохе папок добавилось, а что, может переименовалось (например после опечатки), а тут настроить репозиторий и заливать на винч только то что изменилось. Спасибо. Обязательно попробую эти настройки Git'а.

                                  Only users with full accounts can post comments. Log in, please.