v3.2부터 사용 가능.
$jsonSchema는 v3.6부터 사용 가능.
개요
필드에 데이터 타입 등의 룰을 지정해 유효성 체크를 할 수 있는 기능.
MongoDB는 원래 자유롭게 아무거나 때려넣으며 쓸 수 있지만, 그럼에도 반드시 필요한 룰이 있을 경우 이를 강제하는데 사용하여 데이터 품질의 최소기준을 지킬 수 있음.
생성, 수정, 보기
// 생성시
db.createCollection('my_col', {
validator: {
$jsonSchema: {
bsonType: 'object',
title: 'my validation',
// 필수값 필드 지정
required: [ 'name', 'age' ],
properties: {
name: {
bsonType: 'string',
description: '이름은 필수값이며 문자열이어야 함'
},
age: {
bsonType: 'int',
minimum: 0,
maximum: 200,
description: '나이는 필수값이며 0~200 사이의 숫자여야함'
},
address: {
bsonType: 'string',
description: '주소는 문자열이어야 함'
}
}
}
}
})
// 수정시
db.runCommand({collMod: 'my_col',
validator: {
$jsonSchema: {
bsonType: 'object',
title: 'my modified validation (v2)',
required: [ 'name', 'age' ],
properties: {
name: {
bsonType: 'string',
description: '이름은 필수값이며 문자열이어야 함'
},
age: {
bsonType: 'int',
minimum: 0,
maximum: 150,
description: '나이는 필수값이며 0~150 사이의 숫자여야함'
},
address: {
bsonType: 'string',
description: '주소는 문자열이어야 함'
}
}
}
}
})
// 현재 컬렉션에 설정되어 있는 schema validation 정보 보기
db.getCollectionInfos({name: 'my_col'})[0].options.validator
{
'$jsonSchema': {
bsonType: 'object',
title: 'my modified validation (v2)',
required: [ 'name', 'age' ],
properties: {
name: { bsonType: 'string', description: '이름은 필수값이며 문자열이어야 함' },
age: {
bsonType: 'int',
minimum: 0,
maximum: 150,
description: '나이는 필수값이며 0~150 사이의 숫자여야함'
},
address: { bsonType: 'string', description: '주소는 문자열이어야 함' }
}
}
}
insert/update
insert/update 시 schema validation 에 어긋나는 필드가 하나라도 있으면 에러가 발생하며 유효성 오류 정보가 반환됨.
db.my_col.insertOne({some:'field'})
// 필수값 전부 없음
Uncaught:
MongoServerError: Document failed validation
Additional information: {
failingDocumentId: ObjectId("64b741d00b256d19778a1979"),
details: {
operatorName: '$jsonSchema',
title: 'my modified validation (v2)',
schemaRulesNotSatisfied: [
{
operatorName: 'required',
specifiedAs: { required: [ 'name', 'age' ] },
missingProperties: [ 'age', 'name' ]
}
]
}
}
db.my_col.insertOne({name:'백충덕'})
// 필수값 age 없음
Uncaught:
MongoServerError: Document failed validation
Additional information: {
failingDocumentId: ObjectId("64b741ec0b256d19778a197a"),
details: {
operatorName: '$jsonSchema',
title: 'my modified validation (v2)',
schemaRulesNotSatisfied: [
{
operatorName: 'required',
specifiedAs: { required: [ 'name', 'age' ] },
missingProperties: [ 'age' ]
}
]
}
}
db.my_col.insertOne({name:1,age:'1',address:1})
// 데이터 타입 오류
Uncaught:
MongoServerError: Document failed validation
Additional information: {
failingDocumentId: ObjectId("64b7421b0b256d19778a197e"),
details: {
operatorName: '$jsonSchema',
title: 'my modified validation (v2)',
schemaRulesNotSatisfied: [
{
operatorName: 'properties',
propertiesNotSatisfied: [
{
propertyName: 'name',
description: '이름은 필수값이며 문자열이어야 함',
details: [ [Object] ]
},
{
propertyName: 'age',
description: '나이는 필수값이며 0~150 사이의 숫자여야함',
details: [ [Object] ]
},
{
propertyName: 'address',
description: '주소는 문자열이어야 함',
details: [ [Object] ]
}
]
}
]
}
}
$jsonSchema
$jsonSchema에 사용 가능한 옵션은 다양함.
db.runCommand({collMod:'my_col',
validator: {
$jsonSchema: {
bsonType: 'object',
title: '여러가지 조건들',
properties: {
country: {
enum: [ 'Korea', 'Japan', 'China' ],
description: 'country는 다음 중 하나만 가능합니다 - 한국(Korea), 일본(Japan), 중국(China)'
},
address: {
bsonType: 'object',
required: [ 'zipcode' ],
properties: {
street: {
bsonType: 'string',
description: 'address.street는 문자열만 가능합니다.'
},
zipcode: {
bsonType: 'string',
description: 'address.zipcode는 문자열만 가능합니다.'
}
}
}
}
}
}
})
Validation Level
validation rule이 중간에 변경될 경우,
그 전에 이미 존재하던 유효하지 않은 documents의 update는 validationLevel에 따라 다르게 동작함.
Validation Level | 동작 |
strict | 기본값. 모든 insert/update에 대해 현시점의 validation rule 적용. |
moderate | validation rule이 적용되기 이전에 들어있던 데이터에 대해선 update시 validation 체크하지 않음. |
// 이전 validation rule (validation level = strict)
db.runCommand({collMod:'my_col',
validator: {
$jsonSchema: {
bsonType: 'object',
required: [ 'user_id', 'age' ],
properties: {
user_id: {
bsonType: 'string'
},
age: {
bsonType: 'int',
minimum: 100,
maximum: 200
}
}
}
}
})
// document insert
db.my_col.insertOne({user_id: 'user1', age: 150})
db.my_col.insertOne({user_id: 'user2', age: 180})
// validation rule 변경
db.runCommand({collMod:'my_col',
validator: {
$jsonSchema: {
bsonType: 'object',
required: [ 'user_id', 'age' ],
properties: {
user_id: {
bsonType: 'string'
},
age: {
bsonType: 'int',
minimum: 100,
maximum: 130
}
}
}
}
})
// update
// 이미 들어있는 데이터의 필드 중 age가 변경된 validation rule에 유효하지 않으므로 실패
db.my_col.updateOne({user_id:'user1'}, {$set:{name:'백충덕'}})
// validation level을 moderate로 변경
db.runCommand({collMod:'my_col',
validator: {
$jsonSchema: {
bsonType: 'object',
required: [ 'user_id', 'age' ],
properties: {
user_id: {
bsonType: 'string'
},
age: {
bsonType: 'int',
minimum: 100,
maximum: 130
}
}
}
},
validationLevel: 'moderate'
})
// update
// validation rule이 변경되기 전에 들어가있던 데이터이고 validationLevel=moderate이므로,
// 변경된 validation rule을 체크하지 않고 update 성공
db.my_col.updateOne({user_id:'user1'}, {$set:{name:'백충덕'}})
Validation Action
유효하지 않은 document의 경우에도 무조건 에러를 내며 reject하지 않고 경고만 로그에 기록하는 형태로 운용 가능.
Validation Action | 동작 |
error | 기본값. validation check를 통과하지 못한 insert/update는 에러 메세지와 함께 reject |
warn | insert/updater 동작은 실행되나, 에러 메시지는 MongoDB 로그에 기록됨. |
// validation rule 설정
db.runCommand({collMod: 'my_col',
validator: {
$jsonSchema: {
bsonType: 'object',
required: [ 'age' ]
}
}
})
// insert
// validation rule에 어긋나므로 에러 메시지와 함께 reject
db.my_col.insertOne({ name: '백충덕' })
// validation action = warn
db.runCommand({collMod: 'my_col',
validator: {
$jsonSchema: {
bsonType: 'object',
required: [ 'age' ]
}
},
validationAction: 'warn'
})
// insert
// 아무 문제없이 insert는 성공함
db.my_col.insertOne({ name: '백충덕' })
// 하지만 primary shard의 primary 서버의 로그파일에 보면 아래와 같은 내용의 로그가 남아있음을 확인 가능
{"t":{"$date":"2023-07-19T15:14:02.287+09:00"},"s":"W", "c":"STORAGE", "id":20294, "ctx":"conn5358","msg":"Document would fail validation","attr":{"namespace":"db.my_col","document":{"_id":{"$oid":"64b77f2a0b256d19778a19a4"},"name":"백충덕"},"errInfo":{"failingDocumentId":{"$oid":"64b77f2a0b256d19778a19a4"},"details":{"operatorName":"$jsonSchema","schemaRulesNotSatisfied":[{"operatorName":"required","specifiedAs":{"required":["age"]},"missingProperties":["age"]}]}}}}
invalid document 확인
validation rule을 변경하기 전에 미리 validation rule에 어긋나는 데이터가 있는지 확인 가능.
// 우선 몇개 insert
db.my_col.insertMany([
{uid:'u1'},
{uid:'u2', age:10},
{uid:'u3', age:100},
{uid:'u4', age:200},
{uid:'u5', age:'150'}
])
// validation rule
validator = {
$jsonSchema: {
bsonType: 'object',
required: [ 'age' ],
properties: {
age: {
bsonType: 'int',
minimum: 100,
maximum: 200
}
}
}
}
// 이미 들어가 있는 데이터 중 validator에 설정된 유효성 체크를 통과하지 못하는 documents 확인
db.my_col.find({$nor: [ validator ]})
// 이렇게 나옴
[
{ _id: ObjectId("64b788500b256d19778a19a5"), uid: 'u1' },
{ _id: ObjectId("64b788500b256d19778a19a6"), uid: 'u2', age: 10 },
{ _id: ObjectId("64b788f90b256d19778a19a9"), uid: 'u5', age: '150' }
]
// 이제 이 데이터를 어떻게 할지, validator를 수정할지는 각자의 몫...
// 유효하게 어떻게 update하거나, isValid: false 같은 꼬리표를 달아서 따로 관리하거나,
// validationLevel이나 validationAction을 조절하거나,
// 삭제하거나...
bypass
schema validation이 이미 설정되어 있는데 어떻게든 유효하지 않은 데이터를 write 하고 싶을 때 bypassDocumentValidation: true로 유효성 체크를 무시하고 그냥 write 할 수도 있음.
// validator
db.runCommand({collMod: 'my_col',
validator: {
$jsonSchema: {
bsonType: 'object',
required: [ 'age' ],
properties: {
age: {
bsonType: 'int',
minimum: 100,
maximum: 200
}
}
}
}
})
// insert 시도 - 전부 실패함
db.my_col.insertOne({name: '백충덕'})
db.my_col.insertOne({name: '백충덕', age: 500})
// bypassDocumentValidation: true로 그냥 밀어 넣기
// bypassDocumentValidation을 사용할 수 있는 커맨드는 따로 있으니 확인 필요
// insertOne() 같은 helper method에는 옵션을 지정할 수 없기 때문에 runCommand()로 해야함
// 각 언어별 드라이버 문서를 보고 맞게 할 것
db.runCommand({
insert: 'my_col',
documents: [
{
name: '백충덕',
age: 500
}
],
bypassDocumentValidation: true
})
// insert 성공 확인 가능
[참고]
https://www.mongodb.com/docs/manual/core/schema-validation/
'DataBase' 카테고리의 다른 글
[MongoDB] mongosh editor (0) | 2024.02.20 |
---|---|
[MongoDB] .mongoshrc.js (0) | 2024.02.19 |
[MongoDB] collection에 해당하는 chunk 리스트 가져오기 (get chunks list from config.chunks) (0) | 2023.07.13 |
[MongoDB] Time Series Collection (0) | 2023.07.11 |
[MongoDB] ChangeStream (0) | 2023.07.05 |