🍃

golang-migrateでMongoDBマイグレーションやってみた

2022/05/20に公開

概要

MongoDBへの利用を検討しておりマイグレーションはどうしようかなと思っているのですが、golang-migrateが使いやすそうだったので試してみました。
何番煎じかは分かりませんが、執筆練習も兼ねて投稿してみます。
golang-migrateはdockerコンテナで利用します。

https://github.com/golang-migrate/migrate

https://hub.docker.com/r/migrate/migrate

検証環境構築

マイグレーション先のMongoDBとgolang-migrateの環境をdocker-composeで構築しておきます。
MongoDBはレプリカセットで構築します。
構築の詳細は本記事の目的と離れるため割愛します。

構築

docker-compose.ymlを作成

docker-compose.yml
version: "3.9"

x-conf: &conf
  image: mongo:5.0.7
  restart: always
  environment:
    MONGO_INITDB_ROOT_USERNAME: root
    MONGO_INITDB_ROOT_PASSWORD: root
    MONGO_INITDB_DATABASE: sample
  volumes:
    - ./replicaset.key:/etc/replicaset.key
  command: --keyFile /etc/replicaset.key --replSet rs0

services:
  mongo-primary:
    ports: 
      - 27017:27017
    <<: *conf

  mongo-secondary:
    ports: 
      - 27018:27017
    <<: *conf

  mongo-tertiary:
    ports:
      - 27019:27017
    <<: *conf

  mongo-arbiter:
    ports:
      - 27020:27017
    <<: *conf
    
  migrate:
    image: migrate/migrate:v4.15.2
    entrypoint: sh
    tty: true
    volumes:
      - .:/var/task
    working_dir: /var/task
    command: ""

認証鍵(replicaset.key)の作成

$ openssl rand -base64 756 > replicaset.key
$ chmod 600 replicaset.key
$ chown 999 replicaset.key

起動

$ docker compose up -d

DBに接続

$ mongosh -u root -p root

レプリカセット設定を適用

> rs.initiate({
    _id: "rs0",
    members: [
        {_id: 0, host: "mongo-primary:27017", priority: 3},
        {_id: 1, host: "mongo-secondary:27017", priority: 2},
        {_id: 2, host: "mongo-tertiary:27017", priority: 1},
        {_id: 3, host: "mongo-arbiter:27017", arbiterOnly: true},
    ],
})

{ ok: 1 }

上記構築後、golang-migrate実行環境に入っておきます。

$ docker exec -it migrate-test-migrate-1 sh
/var/task #

使用方法

マイグレーションファイル作成

他のマイグレーションツール同様、up/downの2つ作成します。
ファイル名のフォーマットは
{VERSION}_{任意のファイル名}.{up|down}.json
としておけばいいようです。
  
公式リポジトリのサンプルに従い、ユーザを作成/削除するファイルを作成します。
versionsというディレクトリを作成し、そこに作成します。

versions/001_create_users.up.json
[
    {
      "createUser": "app",
      "pwd": "app",
      "roles": [
        {
          "role": "readWrite",
          "db": "sample"
        }
      ]
    }
]
versions/001_create_users.down.json
[
    {
      "dropUser": "app"
    }
]

マイグレーション

下記フォーマットのコマンドでマイグレーションが実行できます。

migrate -database "{DRIVER}://{USER}:{PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}" -path {DIR} {CMD} {VERSION}
  • {CMD}
    • 指定バージョンまでup|downしたい場合はgotoを利用します。
    • バージョン指定せずに全てのバージョンを対象にする場合はupもしくはdownを利用します。

今回は全てのバージョンを対象とする以下のようなコマンドで実行します。

migrate -database "mongodb://root:root@mongo-primary:27017/sample?replicaSet=rs0&authSource=admin" -path ./versions up
  • DRIVERはmongodbにします(Atlasの場合はmongodb+srvでよさそう[1]
  • レプリカセット(rs0)なのでreplicaSet=rs0を追加します
  • レプリカセットに管理者ユーザで接続するのでauthSource=adminを追加します
  • マイグレーションファイルのバージョン全てを対象としたいので{VERSION}は指定しません

実行結果は以下のようになりました。

up

マイグレーション実行

$ migrate -database "mongodb://root:root@mongo-primary:27017/sample?replicaSet=rs0&authSource=admin" -path ./versions up
1/u create_users (229.5216ms)

確認

rs0 [direct: primary] sample> show users
[
  {
    _id: 'sample.app',
    userId: UUID("564f59d4-9d7e-47a8-9bd3-7eaaea8a8ef7"),
    user: 'app',
    db: 'sample',
    roles: [ { role: 'readWrite', db: 'sample' } ],
    mechanisms: [ 'SCRAM-SHA-1', 'SCRAM-SHA-256' ]
  }
]

down

マイグレーション実行

$ migrate -database "mongodb://root:root@mongo-primary:27017/sample?replicaSet=rs0&authSource=admin" -path ./versions down
Are you sure you want to apply all down migrations? [y/N]
y
Applying all down migrations
1/d create_users (160.9787ms)

確認

rs0 [direct: primary] sample> show users
[]

無事実行できました!

色々マイグレーションしてみる

次に以下のマイグレーションを試してみます。

  • usersコレクションを作成、ageフィールドにインデックス作成
  • usersコレクションにデータを登録

実行できる操作はdb.runCommand()で実行できる操作を参考にすればいいようです。

ファイル作成

コレクションとインデックス作成

002_create_collection_users.up.json
[
    {
        "create": "users"
    },
    {
        "createIndexes": "users",
        "indexes": [
            {
                "key": {
                    "age": 1
                },
                "name": "index_age"
            }
        ]
    }
]
002_create_collection_users.down.json
[
    {
      "drop": "users"
    }
]

データの登録(実際の運用でデータを洗い替えするイメージなのでupの最初に削除を入れています)

003_insert_users.up.json
[
    {
        "delete": "users",
        "deletes": [{"q": {}, "limit": 0}]
    },
    {
        "insert": "users",
        "documents": [
            {
                "name": "Taro",
                "age": 26
            },
            {
                "name": "Kota",
                "age": 32
            }
        ]
    }
]
003_insert_users.down.json
[
    {
        "delete": "users",
        "deletes": [{"q": {}, "limit": 0}]
    }
]

up

実行

$ migrate -database "mongodb://root:root@mongo-primary:27017/sample?replicaSet=rs0&authSource=admin" -path ./versions up
1/u create_users (193.4746ms)
2/u create_collection_users (417.5746ms)
3/u insert_users (591.2593ms)

確認

rs0 [direct: primary] sample> db.users.getIndexes()
[
  { v: 2, key: { _id: 1 }, name: '_id_' },
  { v: 2, key: { age: 1 }, name: 'index_age' }
]
rs0 [direct: primary] sample> db.users.find()
[
  { _id: ObjectId("62873f73711e1795197ffbbf"), name: 'Taro', age: 26 },
  { _id: ObjectId("62873f73711e1795197ffbc0"), name: 'Kota', age: 32 }
]

down

実行

$ migrate  -database "mongodb://root:root@mongo-primary:27017/sample?replicaSet=rs0&authSource=admin" -path ./versions down
Are you sure you want to apply all down migrations? [y/N]
y
Applying all down migrations
3/d insert_users (162.5102ms)
2/d create_collection_users (358.8213ms)
1/d create_users (538.2104ms)

確認

rs0 [direct: primary] sample> show collections
migrate_advisory_lock
schema_migrations

まとめ

シンプルに利用でき、db.runCommand()で実行できるコマンドは全て使えそうなので柔軟性もありそうです。
欲を言うとデータ登録のdocumentsを別ファイルの外出しとかできたらなとは思います。
トランザクション利用もできそうなので引き続き検証してみて、何か気づいたこと等あればまた追記・執筆します!

脚注
  1. mongodb+srvをドライバに指定した場合は挙動が少し異なるといった旨の内容がドキュメントに書いてあり、Atlasで利用する際は少し注意が必要そうです。 ↩︎

Discussion