deleteOne과 deleteMany로 안전하게 문서 삭제하기
문서를 하나 또는 여러 개 삭제하고 실수로 대량 삭제하는 일을 방지하는 전략을 논의합니다.
deleteOne과 deleteMany로 안전하게 문서 삭제하기은(는) CoddyKit의 무료 MongoDB Academy 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 MongoDB Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. MongoDB Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
MongoDB에서 영구 삭제
MongoDB는 문서를 제거하는 두 가지 메서드를 제공합니다.
- deleteOne(filter) — 필터와 일치하는 첫 번째 문서를 제거하고
{ acknowledged, deletedCount }를 반환합니다. - deleteMany(filter) — 필터와 일치하는 모든 문서를 제거하고 총
deletedCount를 반환합니다.
SQL의 DELETE와 달리 MongoDB의 삭제는 영구적이며 즉시 수행됩니다. 작업이 승인된 후에는 트랜잭션을 기반으로 실행 취소할 수 없습니다(아직 커밋되지 않은 다중 문서 트랜잭션 내부인 경우는 제외). 삭제 작업에는 항상 신중하고 범위를 정확히 지정한 필터를 사용하십시오.
// deleteOne: removes first matching document
const res1 = await db.collection('sessions').deleteOne(
{ token: expiredToken }
);
console.log(res1.deletedCount); // 0 or 1
// deleteMany: removes all matching documents
const res2 = await db.collection('sessions').deleteMany(
{ expiresAt: { $lt: new Date() } }
);
console.log(res2.deletedCount); // 0 or moredeleteMany({})의 위험성
deleteMany에서 빈 필터 {}를 사용하면 collection의 모든 문서가 삭제됩니다. 이는 SQL의 TRUNCATE TABLE과 같으며 MongoDB에서 실수로 실행할 수 있는 가장 위험한 작업 중 하나입니다. SQL과 달리 확인 대화 상자가 없으므로 모든 문서가 즉시 삭제됩니다.
MongoDB에는 기본적으로 '소프트 삭제'를 수행하는 메커니즘이 없습니다. 삭제하면 데이터가 사라집니다. 사고를 방지하는 모범 사례는 다음과 같습니다. 범위가 넓은 deleteMany를 실행하기 전에 필터를 항상 다시 확인하고, 먼저 countDocuments로 필터를 테스트하며, 관리자 도구에서 명시적인 확인을 요구하는 방안을 고려하십시오.
// DANGER: Deletes ALL documents in the collection!
db.users.deleteMany({});
// Before you run deleteMany - always verify your filter first:
// Step 1: Count what will be deleted
const count = await db.collection('users').countDocuments(
{ status: 'temp' } // Your intended filter
);
console.log('Will delete:', count, 'documents');
// Step 2: Only delete after reviewing the count
if (count > 0 && count < SAFE_LIMIT) {
await db.collection('users').deleteMany({ status: 'temp' });
}소프트 삭제 패턴
많은 애플리케이션은 물리적으로 삭제하는 대신 소프트 삭제를 구현합니다. deletedAt 타임스탬프 필드(또는 isDeleted: true 플래그)를 추가하고 값을 갱신하는 방식으로, 문서를 제거하지 않습니다. 문서는 데이터베이스에 남아 있지만 삭제된 레코드를 제외하도록 필터링하여 일반 조회에서는 나타나지 않습니다.
소프트 삭제를 사용하면 삭제 취소 및 복원 기능, 감사 추적(무엇을 언제 누가 삭제했는지 기록), 영구 삭제 지연(30일보다 오래된 레코드 정리), 데이터 보존 정책 준수가 가능합니다. 단점은 모든 조회에 항상 deletedAt: { $exists: false } 필터를 포함해야 한다는 점이며, 이를 잊기 쉽습니다.
// Soft delete: mark as deleted instead of removing
async function softDelete(collection, id) {
await db.collection(collection).updateOne(
{ _id: new ObjectId(id) },
{ $set: { deletedAt: new Date(), deletedBy: currentUserId } }
);
}
// All queries must exclude deleted records:
db.posts.find({ deletedAt: { $exists: false } });
// Purge records deleted more than 30 days ago:
const cutoff = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
db.posts.deleteMany({ deletedAt: { $lt: cutoff } });findOneAndDelete: 삭제 후 반환
findOneAndDelete(filter, options)는 한 번의 작업으로 문서를 원자적으로 찾고 삭제한 다음, 삭제된 문서를 반환합니다. 삭제된 문서의 데이터를 처리해야 할 때 유용합니다. 예를 들어 대기 중인 작업을 가져와 제거할 때 사용할 수 있습니다.
반환되는 값은 삭제 이전 상태의 문서입니다. 필터와 일치하는 문서가 없으면 null이 반환됩니다. 이 원자적 찾기-삭제 방식은 문서를 읽은 후 삭제하는 사이에 발생할 수 있는 경쟁 조건을 제거합니다.
// Atomic pop from a task queue
async function claimNextTask() {
const task = await db.collection('taskQueue').findOneAndDelete(
{ status: 'pending' },
{ sort: { priority: -1, createdAt: 1 } } // Highest priority, oldest first
);
if (!task) {
return null; // Queue is empty
}
// Process the task - it's already removed from the queue
await processTask(task);
return task._id;
}연쇄 삭제: 참조 처리
MongoDB는 SQL 데이터베이스처럼 외래 키 제약 조건을 적용하지 않습니다. 상위 문서(예: user)를 삭제해도 다른 컬렉션의 문서에서 해당 사용자의 _id(예: orders, posts)를 참조하고 있다면 해당 문서는 계속 존재하며, 이제 고아 참조를 갖게 됩니다.
애플리케이션 코드나 트랜잭션에서 연쇄 삭제를 명시적으로 처리해야 합니다. 먼저 상위 문서를 삭제한 다음 관련된 모든 하위 문서를 삭제하십시오. 원자성이 중요하다면 연쇄 삭제를 항상 트랜잭션으로 감싸야 합니다. 상위 문서는 삭제되었지만 하위 문서 정리가 실패하는 상황을 피해야 합니다.
// Cascading delete with transaction
async function deleteUser(userId) {
const session = client.startSession();
try {
await session.withTransaction(async () => {
const id = new ObjectId(userId);
// Delete user
await db.collection('users').deleteOne({ _id: id }, { session });
// Delete all user's posts
await db.collection('posts').deleteMany({ authorId: id }, { session });
// Delete user's sessions
await db.collection('sessions').deleteMany({ userId: id }, { session });
});
console.log('User and all related data deleted');
} finally {
await session.endSession();
}
}자동 만료를 위한 TTL 인덱스
예약된 삭제 작업을 작성하는 대신 MongoDB에서 제공하는 TTL(Time-To-Live) 인덱스를 사용하면 설정한 시간이 지난 후 문서가 자동으로 삭제됩니다. Date 필드의 TTL 인덱스는 해당 필드에 저장된 날짜로부터 지정된 초가 지난 뒤 문서를 삭제합니다.
TTL 인덱스는 세션 토큰, 이메일 인증 코드, 임시 속도 제한 레코드, 작업 대기열 항목 및 모든 임시 데이터에 적합합니다. 삭제는 백그라운드에서 실행됩니다. MongoDB의 TTL 모니터 스레드는 60초마다 확인하므로 삭제는 즉시 이루어지지 않고 최종적으로 일관되게 적용됩니다.
// Create a TTL index: delete sessions 24 hours after createdAt
db.sessions.createIndex(
{ createdAt: 1 },
{ expireAfterSeconds: 86400 } // 24h in seconds
);
// Sessions are automatically deleted 24h after their createdAt date
// No cron job needed!
// For user-defined expiry per document:
db.verificationCodes.createIndex(
{ expiresAt: 1 },
{ expireAfterSeconds: 0 } // Delete at the date stored in expiresAt
);
// Each document controls its own expiry: expiresAt: new Date(Date.now() + 3600000)대량 삭제와 성능
많은 수의 문서를 삭제할 때 비어 있거나 범위가 넓은 필터를 사용한 단일 deleteMany는 컬렉션을 잠그고 작업이 진행되는 동안 다른 작업에 영향을 줄 수 있습니다. 운영 환경에서 대량 삭제를 수행할 때는 다음을 따르십시오.
- 작은 단위로 나누기: 한 번에 1000개의 문서를 반복문으로 삭제하고 각 단위 사이에 짧게 대기하십시오.
- 인덱스가 있는 필드로 필터링하기: 컬렉션 전체 검색을 피하려면 필터에 항상 인덱스가 있는 필드를 포함하십시오.
- 사용량이 적은 시간에 예약하기: 대량 정리는 트래픽이 적은 시간에 실행하십시오.
// Batched deletion of old logs (prevents locking)
async function purgeOldLogs(cutoffDate) {
const BATCH_SIZE = 1000;
let totalDeleted = 0;
while (true) {
const result = await db.collection('logs').deleteMany(
{ createdAt: { $lt: cutoffDate } },
{ limit: BATCH_SIZE } // Note: deleteMany has no built-in limit
// Instead, use find + deleteOne in a loop for true batching
);
totalDeleted += result.deletedCount;
if (result.deletedCount === 0) break;
await new Promise(r => setTimeout(r, 100)); // Brief pause between batches
}
return totalDeleted;
}컬렉션 삭제와 deleteMany 비교
컬렉션에서 모든 문서를 제거하는 방법은 두 가지입니다.
db.collection.deleteMany({})— 모든 문서를 제거하지만 인덱스와 설정이 그대로 유지된 컬렉션은 남겨 둡니다. 각 문서 삭제가 작업 로그에 개별적으로 기록되므로 더 느립니다.db.collection.drop()— 모든 인덱스와 메타데이터를 포함하여 컬렉션 전체를 제거합니다. 저장소 파일을 직접 삭제하므로 대규모 컬렉션에서 훨씬 빠릅니다. 이 작업 후에는 컬렉션이 더 이상 존재하지 않습니다.
처음부터 다시 시작하려는 경우(예: 시험 데이터를 초기화할 때)에는 drop()을 사용하십시오. 컬렉션의 인덱스 구조를 유지하려면 deleteMany({})를 사용하십시오.
// Slow: deletes each document individually (keeps collection + indexes)
await db.collection('testData').deleteMany({});
// Fast: drops entire collection (collection and indexes are gone)
await db.collection('testData').drop();
// Recreate with indexes after drop:
await db.collection('testData').createIndex({ email: 1 }, { unique: true });
// Or just start inserting - collection recreates on first insert대량 삭제 전 확인 방식
대량 삭제를 위한 방어적 코딩 방식은 삭제를 실행하기 전에 항상 시험 실행으로 개수를 확인하고, 개수가 안전 기준을 초과하면 명시적인 확인을 요구하는 것입니다. 이는 관리자 스크립트와 마이그레이션 작업에서 특히 중요합니다.
감사를 위해 삭제 내역을 기록하십시오. 삭제를 실행한 사람, 실행 시점, 삭제된 문서 수, 사용한 필터를 기록해야 합니다. 이렇게 하면 준수를 위한 추적 기록이 남고 사고 후 조사가 가능해집니다.
async function safeDeleteMany(collection, filter, options = {}) {
const { dryRun = false, maxAllowed = 1000 } = options;
// Always count first
const count = await db.collection(collection).countDocuments(filter);
console.log(`[DRY RUN] Would delete ${count} documents from ${collection}`);
if (dryRun) return { wouldDelete: count };
if (count > maxAllowed) {
throw new Error(`Safety: ${count} > max allowed ${maxAllowed}. Pass override to proceed.`);
}
const result = await db.collection(collection).deleteMany(filter);
console.log(`[DELETED] ${result.deletedCount} from ${collection}`);
return result;
}삭제 대신 보관하기
영구 삭제와 소프트 삭제의 대안으로 보관을 사용할 수 있습니다. 오래된 문서를 삭제하는 대신 별도의 보관 컬렉션으로 옮기는 방식입니다. 이렇게 하면 기본 컬렉션을 작고 빠르게 유지하면서 감사나 분석에 필요한 과거 데이터를 보존할 수 있습니다.
집계 파이프라인의 $merge 또는 $out 단계를 사용해 보관한 다음 기본 컬렉션에서 삭제하십시오. Atlas Data Federation을 사용하면 S3의 보관 컬렉션을 조회할 수 있으므로, 대량의 과거 데이터를 비용 효율적으로 장기간 저장할 수 있습니다.
// Archive old orders then delete from primary collection
async function archiveOldOrders(beforeDate) {
// Step 1: Copy old orders to archive collection
await db.collection('orders').aggregate([
{ $match: { createdAt: { $lt: beforeDate }, status: 'completed' } },
{ $merge: { into: 'orders_archive', whenMatched: 'keepExisting' } }
]).toArray();
// Step 2: Delete them from primary collection
const del = await db.collection('orders').deleteMany({
createdAt: { $lt: beforeDate },
status: 'completed'
});
console.log('Archived and deleted:', del.deletedCount);
}백업에서 삭제된 데이터 복원
실수로 삭제했는데 소프트 삭제가 적용되어 있지 않다면 복구 방법은 백업 전략에 따라 달라집니다.
- MongoDB Atlas: 연속 백업에서 특정 초 단위 시점까지 복원
- 복제 세트 작업 로그: 특정 타임스탬프까지 작업 로그 작업을 재생(작업 로그에 해당 기간이 아직 포함되어 있어야 함)
- mongodump 백업: 마지막 덤프에서 복원하며, 경우에 따라 몇 시간 분량의 데이터가 손실될 수 있음
핵심은 신중한 삭제 관행을 대신할 수 있는 백업 전략은 없다는 것입니다. 작업 로그 보존 기간(Atlas에서는 일반적으로 24~72시간)은 안전망을 제공하지만, 신중한 필터와 소프트 삭제를 통한 예방이 복구보다 항상 낫습니다.
// Atlas point-in-time restore via Atlas API or UI
// No mongosh command - done through Atlas console or API
// Using oplog to find what was deleted (expert operation)
// Connect to replica set and inspect oplog:
use local
db.oplog.rs.find({
op: 'd', // delete operation
ns: 'myapp.users', // your namespace
wall: { : new Date(Date.now() - 3600000) } // last hour
}).sort({ ts: -1 });
// Each entry shows the _id of the deleted document빠른 확인
이 레슨에서 배운 MongoDB 및 NoSQL 데이터베이스 개념을 제대로 이해했는지 확인해 보십시오.
레슨 요약
이 레슨에서는 다음을 배웠습니다. deleteOne과 deleteMany는 영구적입니다. 따라서 항상 먼저 countDocuments로 필터를 확인하고, 명확한 의도 없이 deleteMany({})를 실행해서는 안 됩니다. 소프트 삭제(deletedAt으로 표시)는 삭제 취소, 감사 추적, 점진적 정리를 가능하게 하므로 사용자에게 표시되는 콘텐츠에 더 안전합니다. 또한 TTL 인덱스를 사용하면 크론 작업 없이 문서를 자동으로 만료시킬 수 있어 세션, 인증 코드, 임시 레코드에 적합합니다. 이로써 문서 갱신 및 삭제 과정이 끝났으며, 다음에는 데이터 포함과 참조 전략을 사용한 데이터 모델링을 살펴보겠습니다.
AI 튜터와 함께 JavaScript을(를) 배우세요 — 무료
브라우저에서 실제 코드를 작성하고 실행하며, 24/7 AI 튜터로부터 즉각적인 도움을 받고, 웹이나 앱에서 중단한 부분부터 계속 학습하세요.
- 코스
- 30
- 레슨
- 120
자주 묻는 질문
“deleteOne과 deleteMany로 안전하게 문서 삭제하기” 강의는 무료인가요?
네 — “deleteOne과 deleteMany로 안전하게 문서 삭제하기” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 MongoDB Academy 강의 전체를 잠금 해제할 수 있습니다. MongoDB Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“deleteOne과 deleteMany로 안전하게 문서 삭제하기”에서 뭘 배우나요?
문서를 하나 또는 여러 개 삭제하고 실수로 대량 삭제하는 일을 방지하는 전략을 논의합니다. 브라우저에서 직접 실행하는 실습 코드로 MongoDB Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
MongoDB Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 MongoDB Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.
“deleteOne과 deleteMany로 안전하게 문서 삭제하기” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 MongoDB Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 MongoDB Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- $set 및 $unset을 사용한 updateOne과 updateMany
- 증가, 곱셈, 최솟값/최댓값 연산자
- 배열 업데이트 연산자: $push, $pull, $addToSet
- deleteOne과 deleteMany로 안전하게 문서 삭제하기