이 문서에서는 아주 간단하게 Docker-compose를 이용해서 MongoDB를 구동하고 Golang을 통해서 연결하는 부분에 대해서 정리하고 있습니다. 환경은 맥북, VSCode, Golang, Docker 등이 이미 설치되어 있는 것을 기준으로 합니다.
RDBMS | MongoDB |
---|---|
Database | Database |
Table | Collection |
Tuple/Row | Document |
Column | Key/Field |
Table Join | Embedded Documents |
Primary Key | Primary Key (_id) |
주요 특징들은 다음과 같다.
장점
데이터를 쌓아놓고 삭제가 필요없는 경우가 가장 적합하다. (ex. 로그 데이터 등)
단점
정합성이 요구되어 트랜잭션 관리가 필요한 경우는 부적합하다. (ex. 금융, 회계, 회원정보 등)
데이터를 저장할 때 논리적으로 메모리에 먼저 저장하고 일정 주기에 따라서 메모리 블럭들을 디스크로 출력하는데 이 부분을 OS의 의존하고 있다. 실제 메모리가 작아도 OS의 가상메모리 운영 방식에 따라서 운영된다.
이런 운영 구조 때문에 메모리에서 데이터 블럭을 참조할 때 없다면 “Page Fault” 오류가 발생하고, 이 상황에서 디스크에서 해당 블록을 찾아서 메모리에 로드하여 처리하게 된다. 이 과정에서 모자라는 메모리 때문에 다른 블럭을 디스크에 쓰고 제거한 후 필요한 블럭을 메모리로 올리는 작업이 처리되기 때문에 디스크 I/O가 발생하므로 성능 저하가 발생할 수 밖에는 없다.
따라서 메모리 크기가 성능을 좌우한다는 것은 Page Fault 오류의 발생 반도에 근거하고 있다고 보면 된다. 따라서 데이터 설계를 할 때 자주 사용되는 데이터가 메모리에 상주할 수 있도록 key 설계를 하는 것이 매우 중요하다. 또한 테이블을 풀 스캔하는 작업은 무조건 Page Fault를 발생시키게 되므로 이런 경우는 Index Table (Summary Table) 등을 만들어서 운영하는 것이 성능을 위한 방법이라고 할 수 있다.
Document
RDBMS의 Tuple / Row와 대응되는 개념으로 Key-Value 쌍으로 구성되며, Value에는 또 다른 document가 설정될 수도 있다. 동적 스키마를 가지고 있기 때문에 같은 Collection (Table) 안에 있는 Document끼리도 다른 스키마를 가질 수 있다. (Free Schema)
{
"_id": ObjectId("5099803df3f4948bd2f98391"),
"username": "Morris",
"language": {
Nuxt: 3,
Go: 3,
}
}
Primary Key
RDBMS의 Primary Key와 대응되는 개념으로 ObjectId는 12bytes의 16진수 값으로 각 Document의 유일성을 보장하는 역할을 담당한다.
개념적으로는 RDBMS의 auto increment와 비슷한 개념으로 생각하면 될 듯 하다.
MongoDB에서 Collection에 저장된 각 Document들은 반드시 기본 키 역할을 담당하는 “_id” 라는 필드를 가져야 한다.
Collection
RDBMS의 Table에 대응되는 개념으로 Document의 그룹이며, Document들이 Collection 내부에 위치한다.
Database
RDBMS의 Database에 대응되는 개념으로 Collection들의 물리적인 컨테이너다. 따라서 각 Database는 물리적인 파일 시스템에 여러 개의 파일로 저장된다.
MongoDB 자체를 실행하는 것은 그렇게 어렵지 않다. 아래와 같이 아주 단순한 docker-compose.yml을 구성하면 바로 구동된다.
1version: "3.3"
2services:
3 mongodb:
4 image: mongo:latest # 사용할 Docker Image
5 container_name: mongdb # Docker Container 식별 명
6 restart: always
7 environment:
8 MONGO_INITDB_DATABASE: "사용할 데이터베이스 명 설정" # 정보만 존재하고 실제 데이터가 처리될 때 생성됨.
9 MONGO_INITDB_ROOT_USERNAME: "root 사용자 설정" # 최초 실행되서 DB 구성할 때 사용자 생성됨.
10 MONGO_INITDB_ROOT_PASSWORD: "root 사용바 비밀번호 설정" # 최초 실행되서 DB 구성할 때 사용자 생성됨.
11 volumes:
12 - ./data/mongodb/:/data/db # 로컬 경로를 컨테이너의 볼륨으로 연계
13 ports:
14 - "27017:27017"
위와 같이 구성하고 docker-compose.yml 파일이 존재하는 경로에서 아래의 명령으로 실행하면 된다.
1$ docker-compose up
2# Background로 실행하는 방법
3$ docker-compose up -d
4# 로그 확인하는 방법
5$ docker-compose logs
로그를 확인해 보면 중간에 root 계정을 생성하는 것을 확인할 수 있으며, 인증 모드로 동작하고 있는 것을 확인할 수 있다.
1...
2mongdb | 2020-01-07T10:58:38.416+0000 I INDEX [conn2] index build: done building index user_1_db_1 on ns admin.system.users
3mongdb | Successfully added user: {
4mongdb | "user" : "root",
5mongdb | "roles" : [
6mongdb | {
7mongdb | "role" : "root",
8mongdb | "db" : "admin"
9mongdb | }
10mongdb | ]
11mongdb | }
12...
13mongdb | 2020-01-07T10:58:48.466+0000 I CONTROL [initandlisten] MongoDB starting : pid=1 port=27017 dbpath=/data/db 64-bit host=7531e03b8054
14mongdb | 2020-01-07T10:58:48.466+0000 I CONTROL [initandlisten] db version v4.2.2
15mongdb | 2020-01-07T10:58:48.466+0000 I CONTROL [initandlisten] git version: a0bbbff6ada159e19298d37946ac8dc4b497eadf
16mongdb | 2020-01-07T10:58:48.466+0000 I CONTROL [initandlisten] OpenSSL version: OpenSSL 1.1.1 11 Sep 2018
17mongdb | 2020-01-07T10:58:48.467+0000 I CONTROL [initandlisten] allocator: tcmalloc
18mongdb | 2020-01-07T10:58:48.467+0000 I CONTROL [initandlisten] modules: none
19mongdb | 2020-01-07T10:58:48.467+0000 I CONTROL [initandlisten] build environment:
20mongdb | 2020-01-07T10:58:48.468+0000 I CONTROL [initandlisten] distmod: ubuntu1804
21mongdb | 2020-01-07T10:58:48.468+0000 I CONTROL [initandlisten] distarch: x86_64
22mongdb | 2020-01-07T10:58:48.468+0000 I CONTROL [initandlisten] target_arch: x86_64
23mongdb | 2020-01-07T10:58:48.468+0000 I CONTROL [initandlisten] options: { net: { bindIp: "*" }, security: { authorization: "enabled" } }
좀 더 많은 구성 옵션들과 실행과 관련된 스크립트들 (예를 들어 일반 사용자 추가 등)을 더 설정할 수 있지만, 여기서는 이 정도만 구성해도 충분하다.
처음 Golang으로 연결하면서 여러 가지 정보들을 확인해 봤지만 Golang 버전에 따라서 Mongo Driver 들에 따라서 다양한 글들과 방법들이 나오지만 이런저런 오류들이 발생하면서 오히려 헷갈리는 상황들이 존재한다.
이 문서에는 Golang 버전의 MongoDB Official 격이라고 판단되는 mongo-go-driver를 기준으로 한다.
아래의 코드는 mongodb driver의 go 라이브러리를 import 하는 것이다. 두 가지 방법 중에 무엇을 사용해도 상관없지만 코드 구성 후에 자동 import 처리되는 것을 확인해 보니 go.mongodb.org 로 사용되기 때문에 이를 기준으로 했다.
1...
2import (
3 ...
4 // 다른 소스들은 아래와 같이 처리하는 것도 많다.
5 "github.com/mongodb/mongo-go-driver/bson"
6 "github.com/mongodb/mongo-go-driver/bson/primitive"
7 "github.com/mongodb/mongo-go-driver/mongo"
8 "github.com/mongodb/mongo-go-driver/mongo/options"
9 ...
10 // 실제 코드는 CDN 격인 go.mongodb.org를 사용해서 처리했다.
11 "go.mongodb.org/mongo-driver/bson"
12 "go.mongodb.org/mongo-driver/bson/primitive"
13 "go.mongodb.org/mongo-driver/mongo"
14 "go.mongodb.org/mongo-driver/mongo/options"
15)
16...
구동 중인 mongodb가 authentication mode로 동작하고 있고, root 사용자만 만들어 놓은 상태기 때문에 이를 아래의 코드를 통해서 연결과 검증을 하면 된다.
1...
2// timeout 기반의 Context 생성
3ctx, _ := context.WithTimeout(context.Background(), conf.Timeout)
4
5// Authetication 처리를 위한 Client Option 구성 (docker-compose.yml에 구성한 port 기준)
6clientOptions := options.Client().ApplyURI("mongodb://localhost:27017)
7 .SetAuth(options.Credential{
8 AuthSource: "", // 지금은 필요없음
9 Username: "docker-compose.yml에 지정한 사용자",
10 Password: "docker-compose.yml에 지정한 사용자 비밀번호",
11 })
12
13// mongodb 연결
14client, err := mongo.Connect(ctx, clientOptions)
15if err != nil {
16 return nil, err
17}
18
19// 연결 검증
20err = client.Ping(context.Background(), nil)
21if err != nil {
22 return nil, err
23}
24...
클라이언트 옵션에 더 많은 구성들이 있지만 이 부분들은 mongodb 매뉴얼등을 검토해 보면서 적용하면 된다.
un-escaped character @ in user info 오류
위의 같은 오류 메시지는 ID나 PW에 @ 문자가 존재하는 경우에 직접 전달되면 발생하게 된다. 이를 해결하기 위해서는 "net/url"
패키지를 import 하고 아래와 같이 escape 처리를 해 줘야 한다.
1...
2import (
3 ...
4 "net/url"
5 ...
6)
7...
8// getConnectionURI - Returns the connection URI from the configuration
9func getConnectionURI(conf *Config) string {
10 return "mongodb://" + url.QueryEscape(conf.UserName) + ":" + url.QueryEscape(conf.Password) + "@" + conf.Host + ":" + conf.Port + "/" + conf.DatabaseName
11}
12...
url.QueryEscape 함수를 이용해서 데이터에 존재하는 특수문자를 안전하게 인식될 수 있도록 변환해 주면 된다.
위에 언급했던 Authentication Mode로 구동되고 있는 mongodb에 인증을 처리하지 않고 Connection을 연결한 후 실제 데이터를 처리할 때 인증되지 않은 사용자로 인해서 발생하는 오류다. 이 경우는 위의 코드에서 보여진 것과 같이 인증을 한 Connection을 사용하면 오류가 해결된다.
Authentication 처리를 구성한 Client Option 사용한 연결 처리가 필요하다.
이 오류는 mongodb의 인증 방식에 대한 문제로 위에서 보여준 연결 문자열을 사용해서 처리할 떄 발생하는 오류로 인증 방식이 맞지 않아서 발생하게 된다. 이 부분에 대해서는 MongoDB Manual - Authentication Mechanisms를 참고하면 된다.
제공되는 Authentication Mechanism은 다음과 같다.
표준 URI Connection Schema는 아래와 같다.
mongodb://[username:password@]host1[:port1][…hostN[:portN]][/[database][?options]]
위에서 보이는 것과 같이 여러 개의 host 연결 지정이 가능하고, 사용할 데이터베이스와 연결에 사용할 옵션들 지정이 가능하다.
옵션을 통해서 Authentication Mechanism을 지정할 수 있다. 자세한 내용은 Connection Options을 참고한다.
Connection String을 기준으로 각종 옵션을 적용해서 처리하는 것도 방법이지만 여러 가지 설정을 Configuration으로 처리하고 운영하기에는 Authenticated Client 를 사용하는 방식이 더 효율적인 것으로 판단된다.