Использование репозитория git в качестве базы данных

Я делаю проект, который занимается структурированной базой документов. У меня есть дерево категорий (~ 1000 категорий, до ~ 50 категорий на каждом уровне), каждая категория содержит несколько тысяч (до, скажем, ~ 10000) структурированных документов. Каждый документ содержит несколько килобайт данных в некоторой структурированной форме (я бы предпочел YAML, но это может быть также JSON или XML).

Пользователи этих систем выполняют несколько типов операций:

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

Конечно, для решения этой проблемы традиционным решением будет использование какой-то базы данных документов (например, CouchDB или Mongo) - однако эта функция управления версиями (история) соблазнила меня дикой идеей - почему я не должен использовать git в качестве базы данных для этого приложения?

На первый взгляд это можно решить следующим образом:

  • category = directory, document = file
  • получение документа по ID = > изменение каталогов + чтение файла в рабочей копии
  • редактирование документов с комментариями редактирования = > выполнение коммитов различными пользователями + сохранение сообщений фиксации
  • history = > обычный git журнал и поиск старых транзакций
  • search = > , что немного более сложная часть, я предполагаю, что это потребует периодического экспорта категории в реляционную базу данных с индексацией столбцов, которую мы разрешим искать по

Есть ли другие проблемы в этом решении? Кто-нибудь попытался реализовать такие бэкэнд уже (т.е. Для любых популярных фреймворков - RoR, node.js, Django, CakePHP)? Имеет ли это решение какие-либо последствия для производительности или надежности - т.е. Доказано, что git будет намного медленнее, чем традиционные решения для баз данных, или могут возникнуть проблемы с масштабируемостью/надежностью? Я предполагаю, что кластер таких серверов, которые нажимают/вытаскивают репозиторий друг друга, должен быть достаточно надежным и надежным.

В принципе, скажите, будет ли это решение работать и почему оно будет или не будет?

Ответ 1

Ответ на мой собственный вопрос - это не лучшая вещь, но, поскольку я в конечном итоге отказался от этой идеи, я хотел бы рассказать об обосновании, которое сработало в моем случае. Я хотел бы подчеркнуть, что это обоснование может быть не применимо ко всем случаям, поэтому решать его нужно до архитектора.

Как правило, первый основной вопрос, который мой вопрос пропускает, заключается в том, что я имею дело с многопользовательской системой, которая работает параллельно, одновременно, используя мой сервер с тонким клиентом (т.е. просто веб-браузером). Таким образом, я должен поддерживать состояние для всех. Существует несколько подходов к этому, но все они либо слишком сложны для ресурсов, либо слишком сложны для реализации (и, таким образом, могут убить первоначальную цель разгрузить весь материал жесткой реализации до git):

  • "Блантовый" подход: 1 пользователь = 1 состояние = 1 полная рабочая копия репозитория, поддерживаемого сервером для пользователя. Даже если мы говорим о довольно небольшой базе данных документов (например, 100 000 пользователей MiB) с ~ 100 тыс. Пользователей, поддерживая полный клон репозитория для всех из них, использование диска проходит через крышу (то есть 100 тыс. Пользователей раз 100 Мбайт ~ 10 ТиБ), Что еще хуже, клонирование 100 репозиториев MiB каждый раз занимает несколько секунд, даже если сделано в довольно эффективном манеже (т.е. Не используется с помощью git и распаковки-переупаковки), что неприемлемо, IMO. И что еще хуже - каждое редактирование, которое мы применяем к основному дереву, нужно вытаскивать в каждый пользовательский репозиторий, который является (1) ресурсом hog, (2) может привести к неразрешенным конфликтам редактирования в общем случае.

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

  • "Только активные пользователи": поддерживайте рабочую копию только для активных пользователей. Таким образом, вы обычно сохраняете не полный-repo-clone-per-user, а:

    • Когда пользователь входит в систему, вы клонируете репозиторий. Требуется несколько секунд и ~ 100 Мбайт дискового пространства для активного пользователя.
    • Когда пользователь продолжает работать на сайте, он работает с данной рабочей копией.
    • Когда пользователь выходит из системы, его клон репозитория копируется обратно в основной репозиторий как ветвь, тем самым сохраняя только его "непримененные изменения", если они есть, что довольно эффективно с точки зрения пространства.

    Таким образом, использование диска в этом случае достигает максимума в O (количество редактирований и раз, данные и время, количество активных пользователей), которое обычно ~ 100..1000 раз меньше количества пользователей, но делает вход в/более сложный и медленный, поскольку он включает клонирование ветки каждого пользователя при каждом входе в систему и вытягивание этих изменений на выходе или завершении сеанса (что должно быть сделано transactionally = > добавляет еще один уровень сложности). В абсолютных цифрах в моем случае он упадет на 10 тиб дисков до 10..100 гигабайт, что может быть приемлемым, но, опять же, теперь мы говорим о довольно небольшой базе данных из 100 MiB.

  • Подход с разреженной проверкой: создание "разреженной проверки" вместо полномасштабного клонирования репо для активного пользователя не помогает. Это может сэкономить ~ 10 раз на использование дискового пространства, но за счет гораздо большей загрузки CPU/диска на операции, связанные с историей, какой тип убивает цель.

  • "Пул работников": вместо того, чтобы делать полномасштабные клоны каждый раз для активного человека, мы можем сохранить пул "рабочих" клонов, готовых к использованию. Таким образом, каждый раз, когда пользователь входит в систему, он занимает одного "рабочего", вытягивая туда свою ветку из основного репо, и, выйдя из системы, он освобождает "рабочего", который делает умный git hard reset снова становится просто основным клоном репо, готовым к использованию другим пользователем, входящим в систему. Не очень помогает использование диска (он все еще довольно высокий - только полный клон для активного пользователя), но, по крайней мере, он ускоряет вход в систему, как расход еще большей сложности.

Тем не менее, обратите внимание, что я намеренно рассчитал количество довольно небольшой базы данных и базы пользователей: пользователей 100K, активных пользователей 1K, общей базы данных 100 MiBs + истории изменений, 10 миров рабочей копии. Если вы посмотрите на более заметные проекты поиска источников, там будет намного больше:

│              │ Users │ Active users │ DB+edits │ DB only │
├──────────────┼───────┼──────────────┼──────────┼─────────┤
│ MusicBrainz  │  1.2M │     1K/week  │   30 GiB │  20 GiB │
│ en.wikipedia │ 21.5M │   133K/month │    3 TiB │  44 GiB │
│ OSM          │  1.7M │    21K/month │  726 GiB │ 480 GiB │

Очевидно, что для этих объемов данных/активности этот подход был бы совершенно неприемлемым.

Как правило, это сработало бы, если бы можно было использовать веб-браузер как "толстый" клиент, т.е. выдавать операции git и хранить в значительной степени полную проверку на стороне клиента, а не на стороне сервера.

Есть и другие моменты, которые я пропустил, но они не так уж плохи по сравнению с первым:

  • Сама картина, имеющий "толстые" пользователь редактировать состояние является спорным с точки зрения обычных ORMs, таких как ActiveRecord, Hibernate, DataMapper, башни и т.д.
  • Насколько я искал, существует нулевая свободная кодовая база для выполнения этого подхода к git из популярных фреймворков.
  • Существует, по крайней мере, одна служба, которая каким-то образом справляется с этим эффективно - это, очевидно, github - но, увы, их кодовая база и я сильно подозреваю, что они не используют обычные методы хранения git серверов/репо внутри, т.е. они в основном реализуют альтернативные "большие данные" git.

Итак, нижняя строка: это возможно, но для большинства текущих приложений он не будет нигде рядом с оптимальным решением. Вероятно, лучшей альтернативой может стать продвижение вашей собственной версии document-edit-history-to-SQL или попытка использовать любую существующую базу данных документов.

Ответ 2

Интересный подход. Я бы сказал, что если вам нужно хранить данные, используйте базу данных, а не репозиторий исходного кода, которая предназначена для очень конкретной задачи. Если вы можете использовать Git готовый вариант, то это хорошо, но вам, вероятно, потребуется создать слой репозитория документов поверх него. Таким образом, вы могли бы построить его и в традиционной базе данных, верно? И если это встроенный контроль версий, который вас интересует, почему бы просто не использовать один из инструментов репозитория документов с открытым исходным кодом? Есть много вариантов.

Ну, если вы решите пойти на бэкэнд Git в любом случае, тогда в основном это будет работать для ваших требований, если вы внедрили его, как описано. Но:

1) Вы упомянули "кластер серверов, которые толкают/тянут друг друга" - я подумал об этом некоторое время, и все же я не уверен. Вы не можете нажать/вытащить несколько репозиториев в качестве атомной операции. Интересно, может ли быть возможность слияния во время параллельной работы.

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

Ответ 3

мой 2 пенсов. Немного тоска, но...... У меня было подобное требование в одном из моих проектов инкубации. Как и ваши, мои ключевые требования, когда база данных документов (xml в моем случае), с версией документа. Это было для многопользовательской системы с множеством случаев совместной работы. Мое предпочтение заключалось в использовании доступных решений с открытым исходным кодом, которые поддерживают большинство ключевых требований.

Чтобы прервать погоню, я не смог найти ни одного продукта, который предоставил оба, достаточно масштабируемым (количество пользователей, объемы использования, ресурсы хранения и вычислений). Я был привязан к git для всех многообещающие возможности и (вероятные) решения, которые можно было бы изготовить из него. Поскольку я больше играл с опцией git, переходом от одной точки зрения пользователя к многомиллионной перспективе пользователя стала очевидная проблема. К сожалению, мне не удалось сделать существенный анализ производительности, как вы. (.. ленивый/бросить рано.... для версии 2, мантра) Сила вам!. Во всяком случае, моя предвзятая идея с тех пор превратилась в следующую (все еще предвзятую) альтернативу: сбор инструментов, которые лучше всего подходят для их отдельных сфер, баз данных и контроля версий.

Пока работа продолжается (... и немного пренебрегается), измененная версия - это просто.

  • в интерфейсе: (пользовательский интерфейс) использует базу данных для 1-го уровня хранилище (взаимодействие с пользовательскими приложениями)
  • на бэкэнд, используйте систему управления версиями (VCS) (например, git) для выполнения управление версиями объектов данных в базе данных

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

Как это работает (предполагается), так это то, что первичные обмен данными между несколькими пользователями через базу данных. СУБД будет обрабатывать все интересные и сложные проблемы, такие как многопользовательские, concurrency e, атомные операции и т.д. На бэкэнд VCS будет выполнять управление версиями на одном наборе объектов данных (no concurrency или multi- проблемы пользователей). Для каждой эффективной транзакции в базе данных управление версиями выполняется только в тех записях данных, которые были бы эффективно изменены.

Что касается интерфейсного клея, он будет иметь форму простой межсетевой функции между базой данных и VCS. С точки зрения дизайна, поскольку простым подходом был бы интерфейс, управляемый событиями, с обновлениями данных из базы данных, запускающими процедуры управления версиями (подсказка: предполагается Mysql, использование триггеров и sys_exec() blah blah...). С точки зрения сложности реализации он будет варьироваться от простого и эффективного (например, сценариев) до сложного и замечательного (некоторый программный интерфейс разъема). Все зависит от того, насколько сумасшедший вы хотите с ним поработать, и сколько капитала пота вы готовы потратить. Я считаю, что простые скрипты должны делать магию. А для доступа к конечным результатам различные версии данных - простая альтернатива - заполнять клон базы данных (больше клонов структуры базы данных) данными, указанными тегом version/id/hash в VCS. снова этот бит будет простым заданием запроса/перевода/отображения интерфейса.

По-прежнему существуют некоторые проблемы и неизвестность, но я полагаю, что влияние и значимость большинства из них будут во многом зависеть от ваших требований к приложениям и использования. Некоторые могут просто перестать быть проблемами. Некоторые из проблем включают совпадение производительности между двумя ключевыми модулями, базой данных и VCS для приложения с активностью обновления высокочастотных данных, масштабированием ресурсов (память и мощность обработки) со временем на стороне git в качестве данных, и пользователи растут: устойчивые, экспоненциальные или в конечном итоге плато

Из коктейля выше, вот что я сейчас завариваю

  • с использованием git для VCS (изначально считался старым старым CVS для использования только изменений или дельтах между 2 версиями)
  • с использованием mysql (из-за высокоструктурированного характера моих данных, xml со строгими xml-схемами)
  • с MongoDB (чтобы попробовать базу данных NoSQl, которая тесно соответствует структуре собственной базы данных, используемой в git)

Некоторые интересные факты - git действительно очищает вещи для оптимизации хранилища, такие как сжатие и хранение только дельт между ревизией объектов - ДА, git хранит только изменения или дельта между ревизиями объектов данных, где это применимо (он знает, когда и как). Ссылка: packfiles, в глубине кистей git внутренних элементов - Обзор хранилища объектов git (контент-адресуемая файловая система), показывает сходство (с точки зрения концепции) с базами данных noSQL, такими как mongoDB. Опять же, за счет капитала пота, он может предоставить более интересные возможности для интеграции 2, и улучшение производительности

Если вы зашли так далеко, позвольте мне, если вышеуказанное может быть применимо к вашему делу, и предположим, что это будет так, как он будет соответствовать одному из аспектов вашего последнего всестороннего анализа производительности.

Ответ 4

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

  • нет необходимости в отдельных рабочих копиях (использование диска ограничено измененными файлами)
  • нет необходимости в длительной подготовительной работе (за сеанс пользователя)

Трюк состоит в объединении Git GIT_INDEX_FILE переменной окружения с помощью инструментов для создания Git, выполняемых вручную:

Далее следует схема решения (фактические хэши SHA1 опущены из команд):

# Initialize the index
# N.B. Use the commit hash since refs might changed during the session.
$ GIT_INDEX_FILE=user_index_file git reset --hard <starting_commit_hash>

#
# Change data and save it to `changed_file`
#

# Save changed data to the Git object database. Returns a SHA1 hash to the blob.
$ cat changed_file | git hash-object -t blob -w --stdin
da39a3ee5e6b4b0d3255bfef95601890afd80709

# Add the changed file (using the object hash) to the user-specific index
# N.B. When adding new files, --add is required
$ GIT_INDEX_FILE=user_index_file git update-index --cacheinfo 100644 <changed_data_hash> path/to/the/changed_file

# Write the index to the object db. Returns a SHA1 hash to the tree object
$ GIT_INDEX_FILE=user_index_file git write-tree
8ea32f8432d9d4fa9f9b2b602ec7ee6c90aa2d53

# Create a commit from the tree. Returns a SHA1 hash to the commit object
# N.B. Parent commit should the same commit as in the first phase.
$ echo "User X updated their data" | git commit-tree <new_tree_hash> -p <starting_commit_hash>
3f8c225835e64314f5da40e6a568ff894886b952

# Create a ref to the new commit
git update-ref refs/heads/users/user_x_change_y <new_commit_hash>

В зависимости от ваших данных вы можете использовать задание cron для объединения новых ссылок на master, но разрешение конфликтов, пожалуй, самое сложное здесь.

Идеи, чтобы сделать это проще, приветствуются.

Ответ 5

Я внедрил библиотеку Ruby поверх libgit2, что делает ее довольно простой в реализации и исследовании. Есть некоторые очевидные ограничения, но это также довольно освобождающая система, так как вы получаете полную git toolchain.

Документация содержит некоторые идеи о производительности, компромиссах и т.д.