원래는 non-SQL 또는 non-Relational DB를 지칭하기 위해서 생긴 용어이다. 하지만 IT 산업 전반에 걸쳐서 SQL 활용도가 높기 때문에 NoSQL DB임에도 SQL을 지원하는 경우가 많아지고 있다. 따라서 현재는 Not-Only SQL의 의미로 쓰이는 경우도 있다.
RDBMS의 한계점( ACID 등 )을 NoSQL마다 저마다의 해결책을 제시하고 있어서 어떤 속성을 가져야 NoSQL이다 라고 정의할 수 없다.
데이터 형식이 자유롭다 (RDBMS는 미리 데이터의 타입과 길이/크기를 지정해주어야 함)
RDBMS에서 데이터 형식이 테이블로 정해져 있기 때문에 Java의 객체 모델에 100% 연결해서 표현하기 힘든 문제 (Object-Relation Impedance Mismatch)를 해결할 수 있다.
SQL이 아니더라도 전용 라이브러리나 함수등으로 쉽게 조회할 수 있다.
용도나 목적에 맞는 DB를 선택하면 데이터 저장/조회 속도를 굉장히 높일 수 있다.
빅데이터 NoSQL DB의 경우 더 많은 데이터를 저장하면서도 빠른 데이터 접근 속도를 보장할 수 있다.
단 NoSQL 데이터베이스는 종류가 너무 많기 때문에 이 장점들을 모두 가지고 있는 NoSQL DB는 없다.
위 장점 중 그것을 제공할 수 있는 NoSQL DB를 사용하면 된다.
기능이나 형식의 표준이 없다. 사용하는 DB별로 전용 라이브러리나 함수를 이용해서 각각 구현해야 한다.
다른 데이터 집합 (table / collection / index 등)에 대한 FK가 없거나, 있더라도 cascading (참조에 변경 사항이 생기면 같이 변경)옵션이 없기 때문에 정합성을 보장하지 못한다.
표준 SQL의 모든 기능을 제공하지 못한다.
Transaction ACID 특성을 모두 제공하지 못한다. eventually consistency (궁극적 일관성)를 상정하고 사용해야 한다.
Eventual consistency는 항목이 새롭게 업데이트되지 않는다는 전제하에 항목의 모든 읽기 작업이 최종적으로는 마지막으로 업데이트된 값을 반환한다는 것을 이론적으로 보장합니다. 인터넷 DNS(도메인 이름 시스템)는 eventual consistency 모델이 사용된 시스템의 예로 잘 알려져 있습니다.
NoSQL하면 대표적으로 떠오르는 DB이다. 가장 범용적으로 많은 기능을 담고있는 데이터베이스이다. RDBMS의 대표 기능인 Transaction 기능이 초기에는 없었지만, 발전을 거듭한 끝에 단일 Collection의 document단위로Atomicity 와 Transaction 기능을 제공한다. 빅데이터용 데이터베이스는 아니지만 sharding으로 확장성도 제공한다.
In-Memory 데이터베이스의 대표주자. 자료구조를 먼저 정하고 사용한다.(빠르게 하기 위해 범용이 없다.) 싱글스레드로 lock 을 제공하지 않으면서도 빠르게 데이터를 조회할 수 있는 장점이 있다. 자료구조의 특징을 이해한 뒤 성능에 주의해서 사용해야한다.
빠르지만 메모리에서 사용하기 때문에 프로세스가 내려가면 데이터가 유실된다.
빅데이터용 데이터베이스의 대표주자이다. 단일 key로만 데이터를 저장, 조회하는 것이 가장 큰 제약이자 장점이다. 데이터가 아무리 많아져도 단일 key에 대한 조회 속도는 거의 느려지지 않는다.(컬럼이나 필드를 여러개 둘 수 없다. 단일 조건으로만 조회할 수 있음 = 빠름) key를 제대로 사용하는 방법을 익혀야 한다.
Cube형 데이터베이스의 대표주자. 집계 연산에 최적화되어있다. 대용량의 데이터에 대한 합, 카운트 등의 연산을 빠르게 수행할 수 있다. 집계할 조건에 맞추어 Cube modeling 을 미리 해놓아야 한다. Cube Modeling을 위한 개념과 방법을 배워야 한다.
MongoDB | RDBMS |
---|---|
데이터베이스(Database) | 데이터베이스(Database) |
컬렉션(Collection) | 테이블(Table) |
도큐먼트(Document) | 레코드(Record OR Row) |
필드(Field) | 컬럼(Column) |
인덱스(Index) | 인덱스(Index) |
쿼리의 결과로 "커서(Cursor)" 반환 | 쿼리의 결과로 "레코드(Record)" 반환 |
MongoDB에서 데이터를 저장하는 단위는 Document이다.
각 Document의 형식은 미리 지정되어있지 않고, 생성 시점에 어떤 형식의 내용이 와도 된다.
같은 Collection(table)에 속한 Document라고해서 모두 같은 형식을 따를 필요는 없다.
하나의 Document가 자신의 데이터에 대해서 완결성을 가진다.
Document에 가지는 필드나 값은 BSON Type을 따른다. 단, _id 필드는 각 document의 Primary Key로서 반드시 가져야 한다. 직접 지정할 수도 있고, 지정하지 않으면 ObjectId 형식의 데이터가 자동 생성되어 데이터 생성(삽입) 시점에 할당된다.
모든 Document는 고유값을 뜻하는 _id 필드를 반드시 가진다. _id는 직접 지정할 수도 있고, 지정하지 않으면 ObjectId 형식의 데이터가 자동 생성되어 데이터 생성(삽입) 시점에 할당된다.
ObjectId는 12bytes로 다음의 데이터의 조합이다.
ObjectId.getTimestamp()
를 통해서 별도의 필드 없이 생성 시점을 가져올 수 있다.JSON style의 binary 포멧.
BSON이 가질 수 있는 데이터 타입은 좀 더 상세하다
참조
Document를 저장하기 위한 논리적인 묶음. (RDBMS의 Table과 유사)
MongoDB는 Document Store로서 Document의 형식에 제약이 없지만, Collection 단위에서도 같은 형식을 가지도록 제약을 걸 수 있다.
Schema Validation을 사용하지 않더라도 스키마 규칙을 프로그래밍 모델로라도 가지고 사용하는 것을 추천 (이 경우 optional field가 많아도 상관 없음. 다만 optional이 많으면 정적타입을 쓰는 경우 null-check를 잘 해주어야 한다.)
하나의 MongoDB 서버 혹은 클러스터에서 논리적으로 Database를 구분할 수 있다. 사용하는 서비스의 종류가 다름. client 별로 특정 Database만 접속하도록 구분하고 접속의 제한을 두기 위해서 사용. (특정 Collection에 특정 client가 필요한 데이터를 모아놓고 접속을 제한한다.)
이 경우를 제외하고는 Database를 잘게 쪼갤 필요는 없다. (코드를 짤 때 DB를 명시하고 Collection을 명시하기 때문에 DB를 나눈다면 여러 객체를 왔다갔다 해야하는 번거로움이 있다.)
MongoDB는 BSON의 형식에 따라서 하나의 필드가 BSON Object(Json object)를 가질 수 있다. (무한으로 Inner Field를 가질 수 있음) 그리고 이 Object에 대해 검색도 가능하다.
이렇게 계속 inner Field를 가지는 경우 검색, 집계 연산에서 그만큼 속도가 느려진다. 따라서 데이터를 저장에서의 편의성만 생각하는 것이 아니라, 검색에서의 효율성도 고려해서 Document의 형식을 정하는 것이 좋다.
다만 데이터가 많지 않으면 큰 차이가 나지 않는다. 시스템의 서비스의 규모가 작다면 아직 발생하지 않는 성능 문제에 이것저것 고려하지 않고 객체지향 모델에 맞게 Document를 저장하고 사용할 수 있는 장점을 살려 개발속도를 끌어올리는 방법도 좋다. 나중에 문제가 되면 그때 튜닝이나 리팩토링을 하는 것도 늦지 않다.
참고
MongoDB에서 RDBMS에서 FK를 지정하는 것처럼 다른 DB나 Collection의 특정 Document를 참조할 수 있다.
하지만 RDBMS처럼 Cascading을 제공하지 않기 때문에 Atomicity를 완벽하게 제공할 수 없다. (자동으로 참조관계에 데이터를 수정해주지 않음)
MongoDB에서는 참조를 위해 다음과 같은 두가지 기능을 제공한다.
참고
가장 직관적이고 MongoDB의 초기 API로 실행할 수 있는 방법이다.
참조하고 싶은 Document의 _id 필드에 해당하는 값을, 다른 Document의 특정 필드의 값에 놓고, 직접 찾는 것이다. 결국 참조하고 싶은 데이터를 가져오려면 두 번 쿼리를 날려야 한다.
Document를 참조할 수 있는 Convention을 제공한다. 제공한다. 참조하고 싶은 Document의 Collection, _id, database, another fields와 기타 정
보를 객체로 담는다.
추가적으로 구현된 기능이기 때문에 각 언어의 클라이언트마다 지원 여부와 기능의 정도가 다르니 확인하고 사용해야한다.
MongoDB에서 기본적으로 하나의 Document에 대해서 하나의 operation만 Atomic operation을 제공한다. Transaction을 이용하면 하나 이상의 document에 대해서, 두 번 이상을 operation에 대해서 Atomicity를 제공할 수 있다.
다만, 모든 Collection, Document에서 Transaction 을 제공하지는 못하고 일부 제약사항이 있다. production level로 Transaction을 사용하려면 문서에서 한정하는 범위나 기능을 정확하게 파악하고 사용해야한다.
문서
터미널에서 mongosh로 서버의 접속 후 myDB schema를 만들고 movies라는 collection에 데이터를 넣었다.
// MongoDB 서버 구동 community 6.0 ver
MacBookPro ~ % brew services start mongodb-community@6.0
// MongoDB 접속
MacBookPro ~ % mongosh
// 접속 확인
test> db.getMongo()
mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+1.6.1
// myDB 생성과 동시에 스위치
test> use myDatabase
switched to db myDatabase
// Collection (Table) 생성 데이터 넣기
// 한가지 데이터만 넣기 insertOne
myDB> db.movies.insertOne(
{
title: "The Favorite",
genres: [ "Drama", "History" ],
runtime: 121,
rated: "R",
year: 2018,
directors: [ "Yorgos Lanthimos" ],
cast: [ "Olivia Colman", "Emma Stone", "Rachel Weisz" ],
type: "movie"
})
// 성공
{
acknowledged: true,
insertedId: ObjectId("63946ef9328243ffba521ade")
}
// 데이터 여러개 한번에 넣기
myDB> db.movies.insertMany([
{
title: "Jurassic World: Fallen Kingdom",
genres: [ "Action", "Sci-Fi" ],
runtime: 130,
rated: "PG-13",
year: 2018,
directors: [ "J. A. Bayona" ],
cast: [ "Chris Pratt", "Bryce Dallas Howard", "Rafe Spall" ],
type: "movie"
},
{
title: "Tag",
genres: [ "Comedy", "Action" ],
runtime: 105,
rated: "R",
year: 2018,
directors: [ "Jeff Tomsic" ],
cast: [ "Annabelle Wallis", "Jeremy Renner", "Jon Hamm" ],
type: "movie"
}
])
// 성공
{
acknowledged: true,
insertedIds: {
'0': ObjectId("63946f43328243ffba521adf"),
'1': ObjectId("63946f43328243ffba521ae0")
}
}
dependencies 추가
dependencies {
//MongoDB 드라이버
implementation 'org.mongodb:mongodb-driver-sync:4.7.1'
//lombok은 코드를 편하게 짜기 위한 annotation이 추가되어 있음
compileOnly 'org.projectlombok:lombok:1.18.24'
annotationProcessor 'org.projectlombok:lombok:1.18.24'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}
연결 확인
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
import static com.mongodb.client.model.Filters.eq;
public class Main {
public static void main(String[] args) {
String uri = "mongodb://localhost:27017";
// MongoClient 객체 하나가 하나의 connection을 의미
MongoClient mongoClient = MongoClients.create(uri);
MongoDatabase mongoDatabase = mongoClient.getDatabase("myDB");
// Document는 org.bson 으로 설정 -> key value 형식의 map을 상속받았기 때문에
// key value 형식의 mongoDB가 저장할 수 있는 모든 데이터를 표현할 수 있는 가장 대표 클래스이다.
MongoCollection<Document> collection = mongoDatabase.getCollection("movies");
// title 필드의 값이 The Favorite 인 영화의 정보를 가져온다
Document document = collection.find(eq("title","The Favorite")).first();
// 불러온 데이터를 Json 형태로 출력
System.out.println(document.toJson());
}
}
값이 올바르게 나온 것으로 connection이 잘 되었음을 확인할 수 있다.
{"_id": {"$oid": "639476aeba6a90c45388323c"}, "title": "The Favorite", "genres": ["Drama", "History"], "runtime": 121, "rated": "R", "year": 2018, "directors": ["Yorgos Lanthimos"], "cast": ["Olivia Colman", "Emma Stone", "Rachel Weisz"], "type": "movie"}