소개
FIELDNOTE는 소형 장비 키트를 대여합니다. 인계받은 MongoDB 카탈로그에는 유용한 재고 수량과 함께 오래된 문자열 및 불확실한 값이 섞여 있습니다. 수량이 0임을 아는 것은 키트를 이용할 수 없다는 뜻이고, 수량 누락은 매장이 모른다는 뜻입니다. 둘 다 0으로 바꾸면 데이터 문제를 숨기게 됩니다. 할 일은 이 경우들을 구분하고 나중에 공급업체가 검토하도록 신뢰할 수 없는 레코드를 보존하는 것입니다.
활성 컬렉션에서 제거하기 전에 완전한 원본 문서를 유지하는 재사용 가능한 분류 정책과 격리 스크립트를 저장합니다. 기본적인 터미널 탐색과 MongoDB 문서, 중첩 필드, find, 일반 갱신에 익숙해야 합니다. 여기서 사용하는 JavaScript 구문, 스키마 규칙, BSON 세부 사항은 사용 전에 설명합니다. MongoDB, 셸, 합성 카탈로그 데이터는 준비되어 있습니다. 전체 과정에서 Terminal을 사용하세요. 소요 시간은 읽기와 일반적인 실수를 포함한 저자의 추정입니다.
/home/labex/project/catalog-desk에 policy.js와 quarantine.js를 작성하세요. 모든 원래 ID, 완전한 원래 필드와 자료형, 무관한 확장 필드, 컬렉션 식별 정보, 제공된 도구를 보존하세요. 이 실습에서는 유효한 레코드와 수정 가능한 레코드를 변경하지 마세요. 컬렉션을 삭제/재구성하거나 기존 격리 원본을 덮어쓰지 마세요. 이 과정 카탈로그에서만 작업하세요. 동시 쓰기가 없는 통제된 유지보수 시간이 전제입니다. 레코드 복사와 삭제는 별도 작업이며 카탈로그 전체를 감싸는 트랜잭션이 아닙니다.
인계받은 카탈로그 읽고 분류하기
이 단계에서는 일반적인 사용 가능 레코드를 읽고 저장된 수량 자료형을 구분하며 명시적인 분류 정책을 저장합니다. Terminal에서 시작하세요. cd는 디렉터리를 변경하며 잘못된 디렉터리에 들어갔을 때도 이 절대 경로로 복구할 수 있습니다:
cd /home/labex/project/catalog-desk
./catalog는 이 디렉터리에 제공된 연결 실행 도구이며 ./는 로컬 실행 파일을 선택합니다. show 명령은 실제 저장 문서를 읽습니다. 문서 ID 하나를 전달하세요:
./catalog show KIT-101
name: "Tripod", stock.rack: "A", 그리고 "$numberInt": "12"를 포함하는 stock.available을 찾으세요. 출력은 BSON 자료형 태그를 명시한 텍스트 표현인 정규 Extended JSON을 사용합니다. BSON은 MongoDB의 저장 문서 형식입니다. $numberInt는 32비트 정수, $numberLong은 64비트 정수를 식별합니다. 출력의 태그는 저장된 숫자를 설명하며 추가해야 할 일반 중첩 재고 필드가 아닙니다.
매장은 가용 수량이 0 이상 50 이하인 두 정수 자료형을 모두 허용합니다. 원래 stock.rack과 supplier 필드는 유용한 정보이므로 남아야 합니다. _id가 레코드를 식별합니다. 이름이 Tripod인 두 레코드도 여전히 다른 레코드입니다.
모든 수량에 대해 서버의 실제 자료형을 확인하세요:
./catalog types
이 읽기 도구는 stock.available에 MongoDB 집계 $type 표현식을 사용합니다. actualType 열은 int, long, string, null, missing, array, double을 구분합니다. double은 부동소수점 BSON 자료형입니다. 정수처럼 보이는 Double 값 12도 이 매장의 정수 전용 계약 밖입니다. missing은 필드가 없다는 뜻이며 null은 명시적 null 값을 가진 필드가 있다는 뜻입니다. [12] 같은 배열은 스칼라 수량 하나가 아닙니다.
자료형을 원래 레코드와 연결하도록 전체 카탈로그를 읽으세요:
./catalog show
쿼리를 쓰기 전에 이 판단 표를 사용하세요:
| 저장 수량 | 이 실습의 판단 | 이유 |
|---|---|---|
| Int32 12 또는 Long 0 | 사용 가능으로 유지 | 알고 있는 0을 포함한 허용 스칼라 정수 |
문자열 "12" 또는 "0" |
승인된 수정을 위해 유지 | 합의한 범위의 정규 십진수 표현 |
"12 kits", "012" 또는 "51" |
격리 | 알 수 없는 의미, 미승인 표현 또는 범위 초과 |
| Null, 누락, 배열 또는 Double 12 | 격리 | 합의된 스칼라 정수 계약을 만족하지 않음 |
수정 가능한 문자열은 0부터 50까지 숫자의 정규 ASCII 숫자만 포함해야 합니다. 부호, 공백, 앞에 붙은 0, 소수점, 단위 접미부는 허용하지 않습니다. 이는 인계받은 이 데이터에 대한 명시적 합의이며 숫자처럼 보이는 모든 문자열을 안전하게 변환할 수 있다는 약속이 아닙니다. 격리는 검토용 완전한 원본을 유지한다는 뜻이며 대체 수량을 만들어 낸다는 뜻이 아닙니다.
이후 명령도 모두 이 Terminal과 디렉터리에서 실행합니다. 파일 이름을 잘못 입력하면 ls로 실제 로컬 이름을 확인하세요. 철자를 고치고 다시 실행하세요. 파일 오류는 데이터베이스 거부가 아니며 환경 초기화가 필요하지 않습니다.
분류 정책 저장하기
MongoDB 선택 조건은 어떤 레코드가 일치하는지 설명하는 문서입니다. $jsonSchema는 문서 구조와 BSON 자료형을 검사합니다. required는 존재해야 할 필드를 지정하며 그 자체로 값의 null을 금지하지 않습니다. properties는 각 필드를 설명하고 bsonType은 BSON 자료형을 제한하며 minimum/maximum은 경계값을 포함합니다. 중첩 stock 규칙은 별도로 available을 요구합니다. 언급하지 않은 필드는 계속 허용하므로 rack, supplier, 이후 확장 필드가 남을 수 있습니다.
여기서는 JSON Schema의 스칼라 자료형 규칙을 사용하세요. {"stock.available": {$type: "int"}} 같은 일반 쿼리 조건은 정수를 포함한 배열에도 일치할 수 있습니다. 그것만으로 이 카탈로그에 필요한 스칼라 수량이 입증되지는 않습니다.
MongoDB 셸인 mongosh의 JavaScript를 작성합니다. const는 값에 이름을 붙이고 =는 초기값을 대입합니다. 중괄호는 객체, 대괄호는 배열을 만들며 쉼표는 항목을 구분합니다. 따옴표 안의 텍스트는 문자열이고 따옴표 없는 0과 50은 숫자입니다. 세미콜론으로 문장을 끝냅니다. schema는 허용되는 전체 레코드를 설명하고 valid는 이를 MongoDB 선택 조건으로 감쌉니다.
승인 문자열 정책은 수량 규칙만 바뀌고 같은 구조를 가집니다. JSON.stringify는 이 일반 정책 객체를 JSON 텍스트로 만들고 JSON.parse는 별도 사본을 읽습니다. 사본을 바꿔도 원래 정책은 그대로입니다. 이 복사법은 일반 정책용이며 저장된 BSON 레코드 보존용이 아닙니다. stringSchema.properties.stock.properties.available은 바꿀 중첩 규칙에 접근합니다.
pattern은 허용 텍스트의 정규식입니다. ^는 시작을 고정하고 \z는 문자열의 절대 끝을 뜻합니다. |는 대안을 나누고 [1-9]는 1부터 9까지 한 숫자, [1-4][0-9]는 10부터 49까지 두 숫자를 허용합니다. 0, 50과 함께 정확히 승인 범위를 구성합니다. JavaScript 문자열은 역슬래시를 이스케이프로 사용하므로 소스는 \\z를 써서 MongoDB에 \z를 전달합니다. 이 절대 끝 앵커는 마지막 줄바꿈도 거부합니다. 일반적인 $ 끝 앵커는 그 줄바꿈 앞에서 일치를 허용할 수 있습니다. $nor: [valid, repairable]는 두 선택 조건 어디에도 일치하지 않는 레코드를 선택합니다.
here-document로 정책 파일을 만드세요. cat > policy.js는 다음 텍스트를 파일에 쓰고 기존 내용을 덮어씁니다. 따옴표로 지정한 <<'POLICY' 구분자는 셸에서 달러 기호와 다른 JavaScript 텍스트를 그대로 유지합니다. POLICY만 한 줄에 써서 끝내세요. 전체 블록을 복사하세요:
cat > policy.js <<'POLICY'
const schema = {
bsonType: "object",
required: ["_id", "name", "stock"],
properties: {
_id: { bsonType: "string", minLength: 1 },
name: { bsonType: "string", minLength: 1 },
stock: {
bsonType: "object",
required: ["available"],
properties: {
available: { bsonType: ["int", "long"], minimum: 0, maximum: 50 }
}
}
}
};
const valid = { $jsonSchema: schema };
const stringSchema = JSON.parse(JSON.stringify(schema));
stringSchema.properties.stock.properties.available = {
bsonType: "string",
pattern: "^(0|[1-9]|[1-4][0-9]|50)\\z"
};
const repairable = { $jsonSchema: stringSchema };
const bad = { $nor: [valid, repairable] };
POLICY
제공된 분류기는 읽기 전용 데이터베이스 계정으로 저장 파일을 변경 없이 평가합니다. 이어서 실제 MongoDB 쿼리에 valid, repairable, bad 선택 조건을 사용합니다:
./catalog classify policy.js
개수만 보지 말고 실제 문서 그룹을 확인하세요. valid에는 원래 Long 0을 포함한 KIT-101과 KIT-102가 있어야 합니다. repairable에는 원래 문자열이 변경되지 않은 KIT-103과 KIT-104가 있어야 합니다. outside에는 원래 null, 누락 필드, 배열, Double을 포함한 KIT-105부터 KIT-111이 있어야 합니다. KIT-105의 완전한 공급업체 검토 확장 필드도 계속 보여야 합니다.
JavaScript 구문 오류가 나오면 파일이 동작하는 정책으로 받아들여진 것이 아닙니다. 전체 here-document를 다시 실행해 손상된 파일을 덮어쓴 뒤 분류기를 다시 실행하세요. 구문 오류는 파일/표현식 문제를 나타냅니다. 데이터베이스 연결 불가는 다른 오류이며 분류 일치가 없다는 뜻이 아닙니다. 이 분류기는 데이터를 수정하거나 제거하지 않았습니다.
불확실한 레코드 제거 전에 원본 보존하기
이 단계에서는 격리 스크립트를 저장하고 첫 복사 뒤의 실제 중단을 관찰한 다음 변경하지 않은 스크립트를 다시 실행해 완료합니다. Terminal의 /home/labex/project/catalog-desk에서 시작하세요.
각 격리 문서는 원래 _id, 완전한 저장 레코드를 담은 original, reason: "outside-approved-catalog-contract"를 가집니다. 사유는 공급업체 검토가 필요한 이유를 설명하며 재고가 0이라고 주장하지 않습니다. 원래 수량은 누락, null, 배열, 다른 자료형일 수 있으므로 표시 값이나 이름만 복사하면 근거를 잃습니다.
또 다른 경계가 있습니다. 셸이 읽을 때 저장된 BSON 숫자를 일반 JavaScript 숫자로 변환할 수 있습니다. 정수처럼 보이는 JavaScript 숫자를 저장하면 원래 Double이어도 Int32가 될 수 있습니다. 모든 원본 복사와 비교에 세 번째 find/findOne 인수 {promoteValues: false, bsonRegExp: true}를 사용하세요. JavaScript 값으로 변환하지 않고 BSON 숫자 래퍼와 정규식 값을 유지합니다. 두 번째 인수 {}는 모든 필드를 유지하는 빈 투영입니다. EJSON.stringify(value, {relaxed: false})는 보존된 자료형으로 정규 Extended JSON을 만들므로 비교 중에도 Double 12와 Int32 12가 구분됩니다.
스크립트 반복문은 범위 밖 그룹을 한 번 읽고 각 레코드를 따로 처리합니다. sort({_id: 1})는 읽기 쉬운 ID 오름차순을 만들고 toArray()는 결과를 모읍니다. for (const doc of ...)는 결과마다 안의 문장을 반복합니다. upsert 작업은 원래 ID로 격리 레코드를 찾고 upsert: true는 없으면 만듭니다. $setOnInsert는 레코드를 만들 때만 원본과 사유를 써서 기존 사본은 그대로 둡니다. findOne은 저장 사본을 다시 읽습니다.
if는 조건을 검사합니다. !==는 다름을, ||는 어느 한 조건이 참임을 뜻합니다. 원본이나 사유가 다르면 throw Error(...)로 스크립트를 0이 아닌 종료 상태로 멈춰 해당 원본 레코드의 삭제를 막아야 합니다. deleteOne은 검증된 원본 ID만 제거합니다. 앞선 레코드는 이미 완료되었을 수 있습니다. 개별 작업이며 전부 성공하거나 전부 취소되는 배치가 아닙니다. 동시 쓰기가 없으므로 삭제 전 매번 완전한 비교에 성공하면 여기서 사용하는 재시도 경로를 제공할 수 있습니다.
스크립트를 쓰세요. QUARANTINE here-document는 앞과 같은 내용을 그대로 쓰는 구문입니다:
cat > quarantine.js <<'QUARANTINE'
const typed = { promoteValues: false, bsonRegExp: true };
for (const doc of db.equipment.find(bad, {}, typed).sort({_id: 1}).toArray()) {
db.quarantine.updateOne(
{_id: doc._id},
{$setOnInsert: {original: doc, reason: "outside-approved-catalog-contract"}},
{upsert: true}
);
const saved = db.quarantine.findOne({_id: doc._id}, {}, typed);
if (EJSON.stringify(saved.original, {relaxed: false}) !== EJSON.stringify(doc, {relaxed: false}) ||
saved.reason !== "outside-approved-catalog-contract") {
throw Error("Existing quarantine copy differs; keep source and investigate");
}
db.equipment.deleteOne({_id: doc._id});
}
QUARANTINE
먼저 제공된 copy-only 연결을 사용하세요. 읽기와 복사는 허용되지만 삭제는 거부되는 실제 데이터베이스 계정입니다. 실행 도구는 셸 호출 하나에서 policy.js 다음 quarantine.js를 변경 없이 평가합니다. 처음 삭제를 시도할 때 데이터베이스 자체 권한 오류와 0이 아닌 종료 상태가 예상됩니다:
./catalog copy-only policy.js quarantine.js
메시지에는 not authorized와 equipment의 delete 명령이 포함됩니다. 그 거부 전에 복사는 성공했습니다. 실제 레코드 두 개를 모두 읽으세요:
./catalog archive KIT-105
./catalog show KIT-105
이제 KIT-105는 양쪽에 존재합니다. 격리 원본에는 재고 텍스트와 전체 extension이 있어야 하며 숫자, 날짜, ObjectId 자료형 태그도 포함되어야 합니다. 활성 원본은 그대로입니다. 따라서 명령 실패가 앞선 쓰기를 모두 취소했다는 뜻은 아닙니다.
일반 과정용 쓰기 연결로 정확히 같은 저장 파일을 다시 실행하세요:
./catalog run policy.js quarantine.js
성공한 스크립트에 출력이 꼭 필요한 것은 아닙니다. 첫 기존 사본을 검증하고 유지하며 대응하는 활성 원본을 제거합니다. 나머지 불확실한 레코드도 차례로 복사, 검증, 제거합니다. 실제 결과를 읽으세요:
./catalog show
./catalog archive
활성 카탈로그에는 수정 대기 중인 승인 문자열 두 개를 포함해 변경되지 않은 KIT-101부터 KIT-104만 있어야 합니다. 격리 영역에는 완전한 원래 문서와 합의한 사유를 가진 KIT-105부터 KIT-111이 있어야 합니다. 특히 KIT-109의 원래 Double은 여전히 $numberDouble로 표시되고 KIT-107의 원본에는 여전히 available 필드가 없습니다.
마이그레이션을 다시 실행하세요:
./catalog run policy.js quarantine.js
./catalog archive
옮길 범위 밖 레코드가 없으므로 저장된 원본은 그대로 남습니다. 이는 멱등적인 재실행입니다. 완료된 작업을 반복해도 업무 결과가 바뀌지 않습니다. 아카이브 키가 중복 복사를 막고 비교가 일치하지 않는 기존 사본을 거부하므로 복사 후 중단도 재개할 수 있습니다. 충돌 오류가 나오면 조사할 수 있도록 원본과 이전 아카이브를 유지하세요. 아카이브 덮어쓰기나 원본 삭제로 “수정”하지 마세요. 이 스크립트는 읽기와 삭제 사이 동시 편집의 안전성을 입증하거나 배치 전체를 롤백할 수 없습니다.
요약
재사용 가능한 MongoDB 정책으로 사용 가능한 정수 수량, 명시적으로 승인된 문자열, 불확실한 레코드를 구분했습니다. 활성 원본을 삭제하기 전에 자료형을 유지한 완전한 원본을 보존하고, 실제 부분 실패를 관찰한 뒤 같은 저장 스크립트를 재개했습니다. 이제 카탈로그에는 사용 가능하거나 수정 가능한 레코드가 남고 격리 영역은 공급업체 검토 근거를 보존합니다. 변환과 향후 쓰기 검증은 다음 독립 단원에서 수행합니다.



