2020年3月30日月曜日

データをGitでのバージョン管理が可能な分散データベースNoms(Golang製)をさわってみる。Noms (manufactured by Golang), a distributed database that allows version control of data in Git.

https://qiita.com/ykshr/items/ac470aa828703e7578e8
シェアしました。



はじめに

データベースやデータのバージョン管理をしたいと思ったことはありませんか?
データのバージョン管理が簡単にできれば、好きなタイミングのデータで分析したり、移行データを必要な各断面で管理したり、データが更新されたトレースを取ることもできるかもしれません。
そんなことが簡単にできればいいのになーと思っていたのですが、先日Tech Crunchの記事でGitの考え方を取り入れたNomsというデータベースを知りました。Googleで検索してみても日本語の情報があまり出てこなかったため、(どんなものかいまいち分かっていないのですが)とりあえずインストールからさわってみるまでを書いておきます。

Nomsとは

Gitの考え方を取り入れたデータベース…らしいです。
nomsではなくてNoms…らしいです。けど、nomsでもいい…らしいです。
個人的には下記の比較が分かりやすかったです!
NomsGit
DatabaseRepository
DatasetBranch
CommitCommit
Bool, Number, String, Blob, List, Set, Map, StructBlob, File, Tree
ただ、Nomsでもファイルを扱うことはできそうです。
https://github.com/attic-labs/noms/tree/master/samples/go/nomsfs

環境構築

Ubuntu

今回はUbuntu環境で試してみます。
自分の場合は、以前にMesos/Marathon環境を作ったため、Mesos/Marathon環境にDockerのUbuntuイメージ(Ubuntu 14.04.4 LTS)をデプロイして試しました。設定値は次の通りです。
General
項目設定値
ID適当
CPUs適当
Memory適当
Disk Space適当
Command/usr/sbin/sshd -D
Docker Container
項目設定値
Imageykshr/ubuntu-ssh
NetworkBridged
Ports
項目設定値
Container Port22
Container Port8000
もし興味があれば、以前のブログにMesos/Marathon環境の作り方も記載しているので、作ってみて下さい。
Qiita - Mesos/Marathon/DockerでプライベートIaaSを作る

インストール

Goのインストール

NomsはGoベースなので、まずはGo(1.6+)をインストールします。
Goのインストーラをhttps://golang.org/dl/からダウンロードします。
$ wget https://storage.googleapis.com/golang/go1.6.3.linux-amd64.tar.gz
$ tar xzvf go1.6.3.linux-amd64.tar.gz -C /usr/local
.bashrcを編集して、パスを通します。
$ vi ~/.bashrc
export GOROOT=/usr/local/go
export GOPATH=$HOME/work
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin

Nomsのインストール

Gitからソースコードを落としてインストールします。
$ git clone https://github.com/attic-labs/noms $GOPATH/src/github.com/attic-labs/noms
$ go install github.com/attic-labs/noms/cmd/noms
Nomsがインストールできたかは、デモ用データベースのコミット履歴を表示することで確認します。
下記の通りコミット履歴が表示されればインストール成功です。
$ noms log http://demo.noms.io/cli-tour::sf-film-locations
commit m7iuf544vdtuemh2r80bspfs063bpvkf
Parent:    1a2aj8svslsu7g8hplsva6oq6iq3ib6c
Date:      "2016-07-31T07:35:01+0000"
InputPath: "http://localhost:8000/cli-tour::#c3feje9ftsvpgms1t0dj6ua8tsd59r1v.value"

commit 1a2aj8svslsu7g8hplsva6oq6iq3ib6c
Parent:    90fh1d2hadem3med2c7n2ois5o5ltmdc
Date:      "2016-06-20T00:00:00+0000"
InputPath: "http://demo.noms.io/cli-tour::#badri4p89a5gh2dgfq1712ecsck6pn4o.value"
[213] {
-   Locations: "Mission Delores Park (Mission District) via J-Church MUNI Train"
+   Locations: "Mission Dolores Park (Mission District) via J-Church MUNI Train"
  }
[221] {
-   FunFacts: "Mission Delores' official name is Mission San Francisco de Assis. It is the 

(以下、省略)

さわってみる

さわってみないことにはどんなものか分かりません。
Gitに載っているGoのサンプルを順番に実行してみます。
Git - A Short Tour of Noms for Go

Nomsサーバーの起動(ローカルデータベースの作成)

noms serveでNomsサーバーが起動します。
$ noms serve ldb:/tmp/noms-go-tour
Listening on port 8000...

Databaseへの接続

別のターミナルを立ち上げて、GoのスクリプトからNomsサーバーにアクセスしてみます。
スクリプトファイル格納用のディレクトリを作成します。
$ mkdir noms-tour
$ cd noms-tour
接続できるか確認するためのスクリプトを作成します。
$ vi noms-go-tour1.go
package main

import (
  "fmt"
  "os"

  "github.com/attic-labs/noms/go/spec"
)

func main() {
  db, err := spec.GetDatabase("http://localhost:8000")
  if err != nil {
    fmt.Fprintf(os.Stderr, "Could not access database: %s\n", err)
    return
  }
  defer db.Close()
}
作成したスクリプトを実行してみます。
なにも出力されなければ接続成功です!
$ go run noms-go-tour1.go

Datasetの確認

Datasetが存在するか確認するためのスクリプトを作成します。
$ vi noms-go-tour2.go
package main

import (
  "fmt"
  "os"

  "github.com/attic-labs/noms/go/spec"
)

func main() {
  ds, err := spec.GetDataset("http://localhost:8000::people")
  if err != nil {
    fmt.Fprintf(os.Stderr, "Could not create dataset: %s\n", err)
    return
  }
  defer ds.Database().Close()

  if _, ok := ds.MaybeHeadValue(); !ok {
    fmt.Fprintf(os.Stdout, "head is empty\n")
  }
}
作成したスクリプトを実行してみます。
Datasetはまだ登録していないため、head is emptyが出力されます。
$ go run noms-go-tour2.go
head is empty

Datasetの新規登録

Datasetが存在しないことが確認できたので、新規データを登録するためのスクリプトを作成します。
$ vi noms-go-tour3.go
package main

import (
  "fmt"
  "os"

  "github.com/attic-labs/noms/go/spec"
  "github.com/attic-labs/noms/go/types"
)

func newPerson(givenName string, male bool) types.Struct {
  return types.NewStruct("Person", types.StructData{
    "given": types.String(givenName),
    "male":  types.Bool(male),
  })
}

func main() {
  ds, err := spec.GetDataset("http://localhost:8000::people")
  if err != nil {
    fmt.Fprintf(os.Stderr, "Could not create dataset: %s\n", err)
    return
  }
  defer ds.Database().Close()

  data := types.NewList(
    newPerson("Rickon", true),
    newPerson("Bran", true),
    newPerson("Arya", false),
    newPerson("Sansa", false),
  )

  fmt.Fprintf(os.Stdout, "data type: %v\n", data.Type().Describe())

  _, err = ds.CommitValue(data)
  if err != nil {
    fmt.Fprint(os.Stderr, "Error commiting: %s\n", err)
  }
}
作成したスクリプトを実行してみます。
ここでは、登録したDatasetのデータタイプが出力されます。
$ go run noms-go-tour3.go
data type: List<struct  {
  given: String
  male: Bool
}>

Datasetの読込み

先ほど登録したDatasetを読込むためのスクリプトを作成します。
スクリプトでは、下の流れでデータを表示しています。
  1. Head Valueを取得
  2. List型に変換
  3. indexが0のデータをStruct(構造体)に変換
  4. "given"を表示
$ vi noms-go-tour4.go
package main

import (
  "fmt"
  "os"

  "github.com/attic-labs/noms/go/spec"
  "github.com/attic-labs/noms/go/types"
)

func main() {
  ds, err := spec.GetDataset("http://localhost:8000::people")
  if err != nil {
    fmt.Fprintf(os.Stderr, "Could not create dataset: %s\n", err)
    return
  }
  defer ds.Database().Close()

  if headValue, ok := ds.MaybeHeadValue(); !ok {
    fmt.Fprintf(os.Stdout, "head is empty\n")
  } else {
    // type assertion to convert Head to List
    personList := headValue.(types.List)
    // type assertion to convert List Value to Struct
    personStruct := personList.Get(0).(types.Struct)
    // prints: Rickon
    fmt.Fprintf(os.Stdout, "given: %v\n", personStruct.Get("given"))
  }
}
作成したスクリプトを実行してみます。
index 0のデータが出力されるため、given: Rickonが出力されます。
$ go run noms-go-tour4.go
given: Rickon

Datasetの追加(データとデータタイプの追加)

Datasetをさらに追加します。
サンプルコードでは、追加するDatasetは、"given"、"male"に加えて"family"が定義されています。
$ vi noms-go-tour5.go
package main

import (
  "fmt"
  "os"

  "github.com/attic-labs/noms/go/spec"
  "github.com/attic-labs/noms/go/types"
)

func main() {
  ds, err := spec.GetDataset("http://localhost:8000::people")
  if err != nil {
    fmt.Fprintf(os.Stderr, "Could not create dataset: %s\n", err)
    return
  }
  defer ds.Database().Close()

  if headValue, ok := ds.MaybeHeadValue(); !ok {
    fmt.Fprintf(os.Stdout, "head is empty\n")
  } else {
    // type assertion to convert Head to List
    personList := headValue.(types.List)
    data := personList.Append(
      types.NewStruct("Person", types.StructData{
        "given":  types.String("Jon"),
        "family": types.String("Snow"),
        "male":   types.Bool(true),
      }),
    )

    fmt.Fprintf(os.Stdout, "data type: %v\n", data.Type().Describe())

    _, err = ds.CommitValue(data)
    if err != nil {
      fmt.Fprint(os.Stderr, "Error commiting: %s\n", err)
    }
  }
}
作成したスクリプトを実行してみます。
このスクリプトでもDatasetのデータタイプを出力するのですが、これまでのデータと追加したデータではデータタイプが異なるため、2つのデータタイプが出力されます。
$ go run noms-go-tour5.go
data type: List<struct Person {
  family: String,
  given: String,
  male: Bool,
} | struct Person {
  given: String,
  male: Bool,
}>

コミットログの表示

Nomsで最も大切な部分ですね。
これまでの操作によるコミットログを表示してみましょう。
コミットログはnomsコマンドで表示します。
$ noms log http://localhost:8000::people
commit b6953vj5tq7h0ckkv8gmfb86d8n089ab
Parent: hshltip9kss28uu910qadq04mhk9kuko
(root) {
+   Person {
+     family: "Snow",
+     given: "Jon",
+     male: true,
+   }
  }

commit hshltip9kss28uu910qadq04mhk9kuko
Parent: None
Gitのサンプルでは、3つのコミットがありますが、2つしか表示されませんでした。
ただ、今回は下の2回しかデータセットを更新していないため、2つで正しいのかなと思います。
  • noms-go-tour3.go
  • noms-go-tour5.go

さわってみて

とりあえずデータベースを作って、データセットを登録し、コミットログを確認するところまでやってみました。印象としては、使い始めるまでのハードルも低く、おもしろいと思いました。開発も活発に行われているみたいなので、もうちょっと追ってみようかと思います。最後に感想です。
  • 3rdパーティのDBとうまく連携できる仕組みがほしい
    なんだかんだ、DBの移行や、新たなDBを使い始めることは、ハードルが高いと思います。特にビジネスでは。最初は、システムとしてはPostgreSQLを使って、その裏でNomsがバージョンを管理してくれるみたいなことができればいいなと思いました。そんな使い方は本末転倒なのでしょうか…(´・ω・`)
  • イケてるUIで操作したい
    Gitでもそうですが、分散管理・バージョン管理で頭を悩ませるのが、各コミットの状態がよく分からなくなってくることと、競合が発生してくることだと思います。Gitでは、SourceTreeなどのツールを用いることで、ある程度ビビることなくこの辺の問題に対処することができているのかなと思います。Nomsに求めることではないですが、そんなツールが出てくれれば適用範囲が広がるのかなと思いました。

ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます

0 コメント:

コメントを投稿