공부일지

[MongoDB] '오늘' 수집한 데이터 전부 지우기(부제: ObjectId와 Unix Time의 timestamp와의 관계) 본문

Computer/공부정리

[MongoDB] '오늘' 수집한 데이터 전부 지우기(부제: ObjectId와 Unix Time의 timestamp와의 관계)

이르리의 공부일지 2024. 9. 23. 23:20

작성일: 2024-09-23

 

 

 

상황


데이터를 수집해 MongoDB에 적재하고 있다.

오늘은 데이터를 수집하는데 데이터 품질이 좋지 않아

프로그램을 수정한 뒤 수집한 데이터만 남기려고 했다.

그러기 위해서 마지막으로 수집하기 전에 적재한 데이터들을 전부 지워줘야 한다.

날짜를 기준으로 데이터를 삭제하는 쿼리를 작성하게 됐다.


* 왜 기준이 날짜인가?

 데이터를 조회 후 삭제할 때 특정 필드 값 아무거나 이용해 조건을 달면 되지만

나의 경우는 날짜 이외에 여러 개를 삭제하기에 최적의 요소가 없어서 그렇다.

 

 

 

해결


1.  연산자를 이용한 필터링

 만약 document에 ‘dateField’(날짜를 나타내는 필드 예시)가 있다면 바로 필터링 연산자 이용해서 삭제해주면 된다.

그 방법이 가장 간단하기 때문에 아래에는 조회를 한 뒤 삭제하는 쿼리를 담아봤다.

const today=new Date();
today.setHours(0,0,0,0);

// 삭제하기 전 find 로 조회부터
db.collection.find({
		"dateField":{$gte: today}
	});

// count 혹은 countDocuments 이용하면 개수만 추출 가능
db.collection.count({
		"dateField":{$gte: today}
	});	

// 삭제
db.collection.deleteMany({
		"dateField":{$gte: today}
	});
	
/**
new Date(): 오늘 날짜
setHours(0, 0, 0, 0): 자정으로 시간 설정
$gte: 필드 _id의 연산자로 지정된 값보다 크거나 같은 값을 조회할 때 쓴다.
*/

 

 

MongoDB NoSQL 연산자

Query and Projection Operators

 

쿼리 및 프로젝션 연산자 - MongoDB 매뉴얼 v7.0

$text 자체 관리(비Atlas) 배포를 위한 텍스트 쿼리 기능을 제공합니다. MongoDB Atlas에서 호스팅되는 데이터의 경우, MongoDB는 향상된 전체 텍스트 쿼리 솔루션인 Atlas Search를 제공합니다.

www.mongodb.com

 

 

 

그러나 나는 ‘updated_at’, ‘created_at’과 같이

데이터를 import할 때 업로드 혹은 수정 시간을 업데이트한 필드를 만든 걸 까먹고 다른 방법을 찾게 된다.

MongoDB ObjectId의 특징을 이용한 방법인데,

흥미로운 점이 MongoDB의 ObjectId의 첫 4바이트(8자리)는 Unix Time의 Timestamp라고 한다.

그래서 이를 이용해 업데이트한 날짜를 확인해보도록 하자.

 

2. Python 스크립트로 확인해보자: MongoDB ObjectId 16진수를 Timestamp로 만들기


import time

objectid = "66f0f75d0000000000000000"
timestamp = objectid[:8] #8자리 = 4바이트가 unix time의 timestamp
hexatime = int(timestamp, 16)

timestamp = time.localtime(hexatime)
timestamp = time.strftime("%Y-%m-%d", timestamp)
print(timestamp) # 2024-09-23: 오늘 날짜

 

삭제하고자 하는 객체의 ObjectId를 보면 앞의 8자리(4바이트 해당)이

Unix Time의 Timestamp 역할을 한다.

ObjectId.getTimestamp() 공식문서 내용 일부

코드를 보면,

ObjectId의 Timestamp에 해당하는 8자릿수를 16진수로 변환한 뒤

내장 라이브러리 time의 localtime() 메소드를 이용해 현재 지역에 맞는 time 객체로 만든다.

그 뒤에 strftime 메소드를 이용해 원하는 날짜 형식으로 바꾼 뒤 출력한다.

더보기

 

3. MongoDB MongoShell에서 시도: ObjectId의 Timestamp를 이용해 오늘 올린 데이터 다수 삭제


 이 방법은 document 필드 중에 날짜 관련 필드가 없는데 오늘 올린 데이터를 지우고 싶을 때 쓰면 된다.

다소 복잡하지만 대략적인 방법은 이렇다.

 

1. 오늘의 시작시간(startOfToday)(오늘 정각)를 16진수로 바꾼 뒤 String 객체로 만든다.

2. 그러고는 0을 16개를 이어붙인 string 값과 결합한다. 

그 이유는 ObjectId 완전체를 만들기 위함인데, 앞의 8자리(4바이트)가 timestamp로 준비돼있고, 뒤의 16자리(8바이트)에 가장 작은 값인 0으로 붙여주려고 하기 때문이다.

그래서 '오늘'을 의미하는 ObjectId 중 가장 최솟값이 만들어진다고 보면 된다.

3. 그 값보다 큰 모든 데이터를 $gte 연산자를 이용해 조회해주면 된다.

// 1. 간단히 delete 하는 법
const startOfToday = new Date();
startOfToday.setHours(0, 0, 0, 0);  // 오늘의 자정 시간 (00:00:00)

const timestamp = Math.floor(startOfToday.getTime() / 1000);  // 타임스탬프를 초 단위로 변환

db.collection.deleteMany({
  _id: { $gte: ObjectId(timestamp.toString(16) + "0000000000000000") }
});

/**
new Date(): 오늘 날짜
setHours(0, 0, 0, 0): 자정으로 시간 설정
Math.floor(시간): 시간을 밀리초에서 초 단위로 변환하기 위해서로, ObjectId에 있는 Timestamp가 초 단위이기 때문.
밀리초 단위의 타임스탬프를 1000으로 나누면 소수점이 포함된 값이 나올 수 있는데, 초 단위로 정확하게 표현하려면 소숫점을 버리고 정수로 만들기 때문에, Math.floor()를 사용해 소수점 이하 값을 버린다.
getTime(): 밀리초 단위의 타임스탬프 반환
ObjectId(): 타임스탬프를 16진수로 변환해, 첫 4바이트에 해당하는 시간을 기준으로 오늘 생성된 _id를 찾는다.
$gte: 필드 _id의 연산자로 지정된 값보다 크거나 같은 값을 조회할 때 쓴다.
*/
// 2. 오늘 하루만을 범위로 구분하고 싶으면 아래와 같이 endTimestamp까지
// 오늘의 자정 시간을 계산
const startOfToday = new Date();
startOfToday.setHours(0, 0, 0, 0);

// 내일 자정 시간을 계산 (오늘이 끝나는 시점)
const startOfTomorrow = new Date();
startOfTomorrow.setHours(24, 0, 0, 0);

// 각각의 자정 시간을 초 단위로 변환 (MongoDB ObjectId는 초 단위로 타임스탬프를 저장함)
// Math.floor()를 이용해 나누기 연산을 함
const startTimestamp = Math.floor(startOfToday.getTime() / 1000);
const endTimestamp = Math.floor(startOfTomorrow.getTime() / 1000);

// 문서 개수를 세는 쿼리
const count = db.auctions.countDocuments({
_id: {
$gte: ObjectId(tartTimestamp.toString(16) + "0000000000000000"),
$lt: ObjectId(endTimestamp.toString(16) + "0000000000000000")
}
});

print("오늘 생성된 문서 수: " + count);

 

c.f. GPT

 

 

 

돌아보며


 최근에 책에서 컴퓨터의 데이터 처리와 관련된 내용을 읽었는데

 MongoDB ObjectId를 통해서 진수와 바이트 등을 한 번 더 이해할 수 있는 좋은 경험이었다.