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에 사용 가능한 옵션은 다양함.

참고: https://www.mongodb.com/docs/manual/reference/operator/query/jsonSchema/#mongodb-query-op.-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/

 

Posted by bloodguy
,