Git deposunu veritabanı arka ucu olarak kullanma


119

Yapılandırılmış belge veritabanıyla ilgilenen bir proje yapıyorum. Bir kategori ağacım var (~ 1000 kategori, her seviyede ~ 50 kategori), her kategori birkaç bin (örneğin, ~ 10000'e kadar) yapılandırılmış belge içeriyor. Her belge, bazı yapılandırılmış biçimde birkaç kilobayt veridir (YAML'yi tercih ederim, ancak JSON veya XML de olabilir).

Bu sistemlerin kullanıcıları birkaç tür işlem gerçekleştirir:

  • bu belgelerin kimlik ile alınması
  • İçerisindeki yapısal özelliklerden bazılarına göre belge aramak
  • belgeleri düzenleme (yani ekleme / kaldırma / yeniden adlandırma / birleştirme); her düzenleme işlemi, bazı yorumlarla birlikte bir işlem olarak kaydedilmelidir
  • belirli bir belge için kaydedilen değişikliklerin geçmişini görüntüleme (belgeyi kimin, ne zaman ve neden değiştirdiğini görüntüleme, önceki sürümü alma - ve muhtemelen istenirse buna geri dönme dahil)

Elbette, geleneksel çözüm, bu sorun için bir tür belge veritabanı (CouchDB veya Mongo gibi) kullanmak olurdu - ancak bu sürüm kontrolü (geçmiş) olayı beni çılgın bir fikre yöneltti - neden gitdepoyu bir bu uygulama için veritabanı arka ucu?

İlk bakışta şu şekilde çözülebilir:

  • kategori = dizin, belge = dosya
  • kimliğe göre belge almak => dizinleri değiştirmek + çalışan bir kopyadaki bir dosyayı okumak
  • belgeleri düzenleme yorumlarıyla düzenleme => çeşitli kullanıcılar tarafından işlem yapma + kaydetme mesajlarını saklama
  • geçmiş => normal git günlüğü ve eski işlemlerin alınması
  • search => bu biraz daha karmaşık bir bölüm, sanırım bir kategorinin periyodik olarak ilişkisel veri tabanına aktarılmasını ve arama yapmamıza izin verdiğimiz sütunların indekslenmesini gerektirecektir.

Bu çözümde başka yaygın tuzaklar var mı? Hiç kimse böyle bir arka ucu uygulamaya çalıştı mı (yani herhangi bir popüler çerçevede - RoR, node.js, Django, CakePHP)? Bu çözümün performans veya güvenilirlik üzerinde herhangi bir olası etkisi var mı - yani git'in geleneksel veritabanı çözümlerinden çok daha yavaş olacağı veya herhangi bir ölçeklenebilirlik / güvenilirlik tuzağı olacağı kanıtlanmış mı? Birbirlerinin havuzunu iten / çeken bu tür sunuculardan oluşan bir kümenin oldukça sağlam ve güvenilir olması gerektiğini düşünüyorum.

Temelde, söyle eğer bu çözüm çalışacak ve neden o-ecek veya yapmayacak mısın?


Yanıtlar:


58

Kendi sorumu cevaplamak yapılacak en iyi şey değil, ama nihayetinde fikri bıraktığım için, durumumda işe yarayan mantığı paylaşmak istiyorum. Bu mantığın her durumda geçerli olmayabileceğini vurgulamak isterim, bu nedenle karar vermek mimarın sorumluluğundadır.

Genel olarak, sorumun gözden kaçırdığı ilk ana nokta , sunucumu ince bir istemciyle (yani sadece bir web tarayıcısı) kullanarak paralel, aynı anda çalışan çok kullanıcılı bir sistemle uğraşıyor olmamdır. Bu şekilde, hepsi için durumu korumalıyım . Bunun için birkaç yaklaşım var, ancak hepsi kaynaklar üzerinde çok zor ya da uygulanması çok karmaşık (ve bu nedenle, ilk başta tüm zor uygulama öğelerini git'e boşaltmanın asıl amacını yok ediyor):

  • "Kesintisiz" yaklaşım: 1 kullanıcı = 1 durum = sunucunun kullanıcı için sakladığı bir deponun 1 tam çalışan kopyası. Yaklaşık 100.000 kullanıcı ile oldukça küçük belge veritabanından (örneğin, 100'ler MiB'ler) bahsediyor olsak bile, tümü için tam depo klonunu sürdürmek, disk kullanımını çatıdan çalıştırır (yani 100K kullanıcı, 100MiB ~ 10 TiB) . Daha da kötüsü, her seferinde 100 MiB deposunu klonlamak, kabul edilebilir olmayan IMO gibi oldukça etkili bir yöntemle (yani, git ve yeniden paketleme malzemelerini kullanmadan) yapılsa bile birkaç saniye sürer. Ve daha da kötüsü - bir ana ağaca uyguladığımız her düzenleme, her kullanıcının deposuna çekilmelidir, bu (1) kaynak domuzudur, (2) genel durumda çözülmemiş düzenleme çakışmalarına yol açabilir.

    Temel olarak, disk kullanımı açısından O (düzenleme sayısı × veri × kullanıcı sayısı) kadar kötü olabilir ve bu tür disk kullanımı otomatik olarak oldukça yüksek CPU kullanımı anlamına gelir.

  • "Yalnızca aktif kullanıcılar" yaklaşımı: çalışma kopyasını yalnızca aktif kullanıcılar için koruyun. Bu şekilde, genellikle kullanıcı başına tam repo klonu değil, ancak:

    • Kullanıcı oturum açtıkça, depoyu klonlarsınız. Etkin kullanıcı başına birkaç saniye ve ~ 100 MiB disk alanı alır.
    • Kullanıcı sitede çalışmaya devam ettikçe verilen çalışma kopyası ile çalışır.
    • Kullanıcı oturumu kapatırken, depo klonu bir dal olarak ana depoya geri kopyalanır, böylece eğer varsa, oldukça az yer kaplayan "uygulanmamış değişiklikleri" depolar.

    Bu nedenle, bu durumda disk kullanımı O (düzenleme sayısı × veri × aktif kullanıcı sayısı) ile zirveye ulaşır, bu genellikle toplam kullanıcı sayısından ~ 100, 1000 kat daha azdır, ancak giriş / çıkış yapmayı daha karmaşık ve daha yavaş hale getirir , her oturum açmada kullanıcı başına bir dalın klonlanmasını ve bu değişikliklerin oturumu kapatırken veya oturum sona erdiğinde geri çekilmesini içerdiğinden (işlemsel olarak yapılmalıdır => başka bir karmaşıklık katmanı ekler). Mutlak sayılarda, benim durumumda 10 TiB disk kullanımını 10..100 GiB'ye düşürür, bu kabul edilebilir, ancak yine de, şimdi 100 MiB'lik oldukça küçük bir veritabanından bahsediyoruz .

  • "Seyrek ödeme" yaklaşımı: Aktif kullanıcı başına tam gelişmiş repo klonu yerine "seyrek ödeme" yapmak pek yardımcı olmuyor. Disk alanı kullanımında ~ 10 kat tasarruf sağlayabilir, ancak geçmişi içeren işlemlerde çok daha yüksek CPU / disk yükü pahasına, bu da amacı öldürür.

  • "İşçi havuzu" yaklaşımı: Aktif kişi için her seferinde tam gelişmiş klonlar yapmak yerine, kullanıma hazır bir "işçi" klonları havuzu tutabiliriz. Bu şekilde, bir kullanıcı her oturum açtığında, bir "işçi" işgal eder, şubesini ana depodan oraya çeker ve oturumu kapatırken, "işçi" yi serbest bırakır, bu da akıllıca git donanım sıfırlamasını bir kez daha adil hale getirir. giriş yapan başka bir kullanıcı tarafından kullanılmaya hazır bir ana depo klonu. Disk kullanımında pek bir faydası yoktur (hala oldukça yüksektir - aktif kullanıcı başına yalnızca tam klon), ancak en azından giriş / çıkış yapmayı masraf olarak daha hızlı hale getirir daha fazla karmaşıklık.

Bununla birlikte, oldukça küçük veritabanı ve kullanıcı tabanı sayılarını kasıtlı olarak hesapladığımı unutmayın: 100.000 kullanıcı, 1K aktif kullanıcı, 100 MiB toplam veritabanı + düzenleme geçmişi, 10 MiB çalışma kopyası. Daha öne çıkan kitle kaynaklı projelere bakarsanız, orada çok daha yüksek rakamlar var:

│              │ 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 │

Açıktır ki, bu miktardaki veri / faaliyet için, bu yaklaşım tamamen kabul edilemez olacaktır.

Genel olarak, eğer biri web tarayıcısını "kalın" bir istemci olarak kullanabilseydi, yani git işlemlerini yayınlayarak ve hemen hemen tüm ödünç vermeyi sunucu tarafında değil, istemci tarafında depolayabilseydi işe yarardı.

Ayrıca kaçırdığım başka noktalar da var, ama birincisine kıyasla o kadar da kötü değiller:

  • ActiveRecord, Hibernate, DataMapper, Tower, vb. Gibi normal ORM'ler açısından "kalın" kullanıcının düzenleme durumuna sahip olmanın modeli tartışmalıdır.
  • Aradığım kadarıyla, popüler çerçevelerden git'e bu yaklaşımı uygulamak için sıfır mevcut ücretsiz kod tabanı var.
  • Bunu bir şekilde verimli bir şekilde yapmayı başaran en az bir hizmet var - bu tabii ki github'dur - ancak, ne yazık ki, kod tabanları kapalı kaynaktır ve içeride normal git sunucuları / repo depolama tekniklerini kullanmadıklarından kesinlikle şüpheleniyorum, yani temelde uyguladılar alternatif "büyük veri" git.

Yani, alt çizgi : o olduğunu mümkün, ama en güncel usecases için her yerde en uygun çözümü yakın olmayacaktır. Kendi belge düzenleme geçmişinizi SQL'e dönüştürmek veya mevcut herhangi bir belge veritabanını kullanmaya çalışmak muhtemelen daha iyi bir alternatif olacaktır.


16
Muhtemelen partiye biraz geç kaldım, ama buna benzer bir şartım vardı ve aslında git rotasına girdim. Git internals ile biraz araştırma yaptıktan sonra, onu çalıştırmanın bir yolunu buldum. Buradaki fikir, çıplak bir depo ile çalışmaktır. Bazı dezavantajlar var, ancak bunun uygulanabilir olduğunu düşünüyorum. Kontrol etmek isteyebileceğiniz her şeyi bir gönderiye yazdım
Kenneth

Bunu yapmamamın bir diğer nedeni de sorgu yetenekleridir. Belge depoları genellikle belgeleri indeksler ve içlerinde aramayı kolaylaştırır. Git ile bu pek kolay olmayacak.
FrankyHollywood

12

Gerçekten ilginç bir yaklaşım. Verileri depolamanız gerekiyorsa, çok özel bir görev için tasarlanmış bir kaynak kod deposu değil, bir veritabanı kullanın diyebilirim. Git'i kullanıma hazır olarak kullanabilirseniz, sorun değil, ancak muhtemelen bunun üzerine bir belge havuzu katmanı oluşturmanız gerekir. Yani onu geleneksel bir veritabanı üzerinde de oluşturabilirsiniz, değil mi? Ve ilgilendiğiniz yerleşik sürüm kontrolü ise, neden yalnızca açık kaynak belge deposu araçlarından birini kullanmıyorsunuz ? Aralarından seçim yapabileceğiniz çok şey var.

Peki, yine de Git arka ucuna gitmeye karar verirseniz, açıklandığı gibi uyguladıysanız temelde gereksinimleriniz için işe yarayacaktır. Fakat:

1) "Birbirini iten / çeken sunucular kümesinden" bahsettiniz - Bunu bir süredir düşündüm ve hala emin değilim. Atomik bir işlem olarak birkaç depoyu itemez / çekemezsiniz. Eşzamanlı çalışma sırasında bazı karışık karışıklık olasılığı olup olmadığını merak ediyorum.

2) Belki ihtiyacınız yoktur, ancak listelemediğiniz bir belge havuzunun bariz bir işlevi erişim kontrolüdür. Alt modüller aracılığıyla bazı yollara (= kategoriler) erişimi kısıtlayabilirsiniz, ancak büyük olasılıkla belge düzeyinde erişim izni veremeyeceksiniz.


11

benim 2 pence değerim. Biraz özlem ama ...... kuluçka projelerimden birinde benzer bir ihtiyacım vardı. Sizinkine benzer şekilde, benim temel gereksinimlerim, burada bir belge veritabanı (benim durumumda xml), belge sürümleme ile. Çok sayıda işbirliği kullanım durumu olan çok kullanıcılı bir sistem içindi. Tercihim, temel gereksinimlerin çoğunu destekleyen mevcut açık kaynaklı çözümleri kullanmaktı.

Kısacası, her ikisini de yeterince ölçeklenebilir bir şekilde (kullanıcı sayısı, kullanım hacimleri, depolama ve bilgi işlem kaynakları) sağlayan tek bir ürün bulamadım. Tüm umut vaat eden yetenekler için git'e önyargılıydım ve (olası) bunlardan çıkarılabilecek çözümler. Git seçeneğini daha fazla oynadıkça, tek kullanıcı perspektifinden çoklu (milli) kullanıcı perspektifine geçmek bariz bir zorluk haline geldi. Maalesef, sizin yaptığınız gibi önemli bir performans analizi yapamadım. (.. tembel / erken çıkın .... sürüm 2 için, mantra) Güç size !. Her neyse, önyargılı fikrim o zamandan beri bir sonraki (hala önyargılı) alternatife dönüştü: kendi ayrı alanlarında, veritabanlarında ve sürüm kontrolünde en iyi olan araçların bir araya getirilmesi.

Hala devam etmekte olan (... ve biraz ihmal edilmiş) olsa da, biçimlendirilmiş sürüm basitçe budur.

  • ön uçta: (kullanıcı yüzü) 1. seviye depolama için bir veritabanı kullanın (kullanıcı uygulamalarıyla arayüz oluşturma)
  • arka uçta, veritabanındaki veri nesnelerinin sürümlerini oluşturmak için bir sürüm kontrol sistemi (VCS) (git gibi) kullanın

Temelde bu, geliştirmeniz gerekebilecek, ancak çok daha kolay olabilecek bazı entegrasyon yapıştırıcısı ile veritabanına bir sürüm kontrol eklentisi eklemek anlamına gelir.

Nasıl çalışacağı (beklendiği), birincil çok kullanıcılı arayüz veri alışverişlerinin veri tabanı üzerinden gerçekleşmesidir. DBMS, çoklu kullanıcı, eşzamanlılık e, atomik işlemler vb. Gibi tüm eğlenceli ve karmaşık konuları ele alacaktır. Arka uçta VCS, tek bir veri nesnesi kümesi üzerinde sürüm kontrolü gerçekleştirecektir (eşzamanlılık veya çoklu kullanıcı sorunları). Veritabanındaki her etkin işlem için sürüm kontrolü yalnızca etkin bir şekilde değişmiş olacak veri kayıtları üzerinde gerçekleştirilir.

Arayüz yapıştırıcıya gelince, veritabanı ve VCS arasında basit bir birlikte çalışma işlevi biçiminde olacaktır. Tasarım açısından, basit yaklaşım, veritabanından veri güncellemelerinin sürüm kontrol prosedürlerini tetiklediği olay güdümlü bir arayüz olacaktır (ipucu: Mysql varsayımı , tetikleyicilerin kullanımı ve sys_exec () blah blah ...). Uygulama karmaşıklığı açısından, basit ve etkili (örneğin komut dosyası oluşturma) karmaşık ve harika (bazı programlanmış bağlayıcı arayüzleri) arasında değişecektir. Her şey, onunla ne kadar çılgınca gitmek istediğinize ve ne kadar ter sermaye harcamak istediğinize bağlıdır. Bence basit komut dosyası sihri yapmalı. Ve nihai sonuca, çeşitli veri sürümlerine erişmek için basit bir alternatif, veritabanının bir klonunu (daha çok veri tabanı yapısının bir klonu) VCS'de sürüm etiketi / id / karma ile referans verilen verilerle doldurmaktır. yine bu bit, bir arayüzün basit bir sorgulama / çeviri / eşleme işi olacaktır.

Hala ele alınması gereken bazı zorluklar ve bilinmeyenler var, ancak bunların çoğunun etkisi ve alaka düzeyinin büyük ölçüde uygulama gereksinimlerinize ve kullanım örneklerinize bağlı olacağını düşünüyorum. Bazıları problemsiz hale gelebilir. Sorunlardan bazıları, yüksek frekanslı veri güncelleme etkinliğine sahip bir uygulama için 2 temel modül, veritabanı ve VCS arasındaki performans eşleştirmesini, veri olarak git tarafında kaynakların zaman içinde ölçeklendirilmesini (depolama ve işleme gücü) ve kullanıcıları içerir. büyüme: sabit, üstel veya nihayetinde platolar

Yukarıdaki kokteylin, şu anda hazırladığım şey

  • VCS için Git kullanmak (başlangıçta 2 sürüm arasında yalnızca değişiklik kümelerinin veya deltaların kullanılması nedeniyle eski güzel CVS olarak kabul edilir)
  • mysql kullanma (verilerimin oldukça yapılandırılmış doğası nedeniyle, katı xml şemalarına sahip xml)
  • MongoDB ile uğraşmak (git'te kullanılan yerel veritabanı yapısıyla yakından eşleşen bir NoSQl veritabanını denemek için)

Bazı eğlenceli gerçekler - git, sıkıştırmayı ve nesnelerin revizyonları arasında yalnızca deltaların depolanması gibi depolamayı optimize etmek için aslında net şeyler yapar - EVET, git, uygulanabilir olduğunda, veri nesnelerinin revizyonları arasında yalnızca değişiklik kümelerini veya deltaları saklar (bilir ne zaman ve nasıl) . Referans: Git'in kısımlarının derinliklerinde bulunan packfiles - Git'in nesne depolamasının (içerik-adreslenebilir dosya sistemi) incelemesi, mongoDB gibi noSQL veritabanları ile (kavram açısından) şaşırtıcı benzerlikler göstermektedir. Yine, ter sermayesi pahasına, 2'yi ve performans ayarlamasını entegre etmek için daha ilginç olanaklar sağlayabilir.

Buraya kadar geldiyseniz, yukarıdakilerin sizin durumunuz için geçerli olup olmadığını ve olacağını varsayarsak, son kapsamlı performans analizinizdeki bazı yönleri nasıl karşılayacağını bana bildirin.


4

Bir uygulamaya Ruby kütüphanesi üstünde libgit2bu oldukça kolay uygulamak ve keşfetmek için yapar. Bazı bariz sınırlamalar var, ancak tam git araç zincirini aldığınız için oldukça özgürleştirici bir sistem.

Belgeler, performans, değiş tokuşlar vb. Hakkında bazı fikirler içerir.


2

Bahsettiğiniz gibi, çok kullanıcılı vakanın üstesinden gelmek biraz daha zordur. Olası bir çözüm, kullanıcıya özgü Git dizin dosyalarını kullanmak olabilir ve sonuçta

  • ayrı çalışma kopyalarına gerek yoktur (disk kullanımı değiştirilen dosyalarla sınırlıdır)
  • zaman alan hazırlık çalışmalarına gerek yok (kullanıcı oturumu başına)

İşin püf noktası, Git işlemlerini GIT_INDEX_FILEmanuel olarak oluşturmak için Git'in çevresel değişkenini araçlarla birleştirmektir :

Bir çözüm özeti aşağıda verilmiştir (gerçek SHA1 hash değerleri komutlardan çıkarılır):

# 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>

Verilerinize bağlı olarak, yeni referansları birleştirmek için bir cron işi kullanabilirsiniz, masterancak çatışma çözümü muhtemelen buradaki en zor kısımdır.

Bunu kolaylaştıracak fikirlere açığız.


Manuel çakışma çözümü için tam gelişmiş bir işlem konseptine ve kullanıcı arayüzüne sahip olmak istemediğiniz sürece, bu genellikle hiçbir yere götürmeyen bir yaklaşımdır. Çatışmalarla ilgili genel fikir, kullanıcının işlem sırasında sorunu doğrudan çözmesini sağlamaktır (yani, "üzgünüm, düzenlemekte olduğunuz belgeyi başka biri düzenledi -> lütfen onun düzenlemelerine ve düzenlemelerinize bakın ve bunları birleştirin"). İki kullanıcının başarılı bir şekilde bağlanmasına izin verdiğinizde ve ardından eşzamansız bir cronjob içinde işlerin kötüye gittiğini öğrendiğinizde, genellikle işleri çözecek kimse yoktur.
GreyCat
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.