pymongo의 insert_many를 이용하여 여러 개의 문서를 삽입하는 과정에서 중복키 에러가 발생했던 상황을 공유하려고 합니다.
아래와 같이, 동일한 내용의 document를 여러 번 insert 하려는 코드가 있을 때, 어떠한 문제가 발생할 수 있을까요?
from pymongo import MongoClient
client = MongoClient("mongodb://localhost:27017/")
db = client["mydatabase"]
collection = db["mycollection"]
document = {
"data": "example"
}
documents_to_insert = [document] * 5
result = collection.insert_many(documents_to_insert)
위 코드를 실행하면 다음과 같은 오류가 발생할 수 있습니다.
# pymongo.errors.BulkWriteError: batch op errors occurred, full error:
{
'writeErrors': [
{
'index': 1, # 두 번째 문서(index=1)에서 오류 발생
'code': 11000, # MongoDB의 Duplicate Key Error 코드
'keyPattern': {'_id': 1}, # 중복된 키가 '_id' 필드임을 의미
'keyValue': {'_id': ObjectId('67c7f9e56674ed3dfa230c9f')}, # 충돌된 ObjectId 값
'errmsg': "E11000 duplicate key error collection: OIT.slot index: _id_ dup key: { _id: ObjectId('67c7f9e56674ed3dfa230c9f') }",
'op': { # 실패한 문서의 내용
"data": "example",
'_id': ObjectId('67c7f9e56674ed3dfa230c9f')
}
}
],
'writeConcernErrors': [],
'nInserted': 1, # 1개의 문서만 삽입됨
'nUpserted': 0,
'nMatched': 0,
'nModified': 0,
'nRemoved': 0,
'upserted': []
}
document = {
"data": "example"
}
documents_to_insert = [document] * 5
documents_to_insert
라는 리스트를 생성하는데, document
를 5번 반복해서 리스트에 넣습니다.documents_to_insert
리스트의 모든 요소는 동일한 document
객체를 가리키게 됩니다.# L960
inserted_ids: list[ObjectId] = []
def gen() -> Iterator[tuple[int, Mapping[str, Any]]]:
"""A generator that validates documents and handles _ids."""
for document in documents:
common.validate_is_document_type("document", document)
if not isinstance(document, RawBSONDocument):
if "_id" not in document:
document["_id"] = ObjectId() # type: ignore[index]
inserted_ids.append(document["_id"])
yield (message._INSERT, document)
inserted_ids
리스트를 만들어, 삽입된 문서의 _id
를 저장.common.validate_is_document_type(...)
)._id
가 없으면 자동으로 ObjectId()
를 생성해서 추가.yield
를 통해 삽입할 데이터를 제너레이터(generator)로 반환.리스트 내부 요소가 동일한 객체를 참조
document = {"data": "example"}
documents_to_insert = [document] * 5
위 코드에서 documents_to_insert
리스트의 모든 요소는 동일한 객체를 참조합니다. 즉, 리스트에 5개의 개별 객체가 있는 것이 아니라 하나의 document
객체가 5번 반복된 상태입니다.
insert_many()
의 동작 방식
PyMongo는 _id
필드가 없는 문서에 대해 자동으로 ObjectId()
를 생성합니다. 하지만 모든 요소가 동일한 객체를 가리키므로 _id
가 하나만 생성되고, 이 _id
가 리스트의 모든 요소에 적용됩니다.
[{"data": "example", "_id": ObjectId('604a8f032d23d2e9c3a2c1e1')},
{"data": "example", "_id": ObjectId('604a8f032d23d2e9c3a2c1e1')},
{"data": "example", "_id": ObjectId('604a8f032d23d2e9c3a2c1e1')},
{"data": "example", "_id": ObjectId('604a8f032d23d2e9c3a2c1e1')},
{"data": "example", "_id": ObjectId('604a8f032d23d2e9c3a2c1e1')}]
결과적으로 _id
가 중복되어 Duplicate Key Error(11000)
가 발생합니다.
얕은 복사가 문제가 된다는 것을 알았으니 해결 방법은 간단합니다.
dict()
를 사용한 깊은 복사(Deep Copy)dict(document)
를 사용하면 개별 객체를 새로 생성할 수 있습니다.
from pymongo import MongoClient
client = MongoClient("mongodb://localhost:27017/")
db = client["mydatabase"]
collection = db["mycollection"]
document = {"data": "example"}
documents_to_insert = [dict(document) for _ in range(5)]
result = collection.insert_many(documents_to_insert)
print("Inserted IDs:", result.inserted_ids)
이 방식은 _id
가 각 문서에 대해 개별적으로 생성되도록 보장하여 중복 오류를 방지할 수 있습니다.
_id
필드를 명시적으로 추가각 문서에 수동으로 _id
를 할당하면 충돌을 방지할 수 있습니다.
from bson import ObjectId
documents_to_insert = [{"_id": ObjectId(), "data": "example"} for _ in range(5)]
result = collection.insert_many(documents_to_insert)
이 방법을 사용하면 PyMongo의 자동 _id
생성이 아닌, 우리가 직접 지정한 _id
가 사용됩니다.
PyMongo에서 insert_many()
를 사용할 때, 같은 객체를 복제하는 방식은 _id
중복 오류를 유발할 수 있습니다. 이를 방지하기 위해서는 아래와 같은 방법을 사용하면 됩니다.
dict()
를 사용하여 개별 객체를 생성_id
필드를 직접 추가PyMongo의 내부 동작을 이해하면, 예상치 못한 오류를 줄이고 안전하게 데이터를 삽입할 수 있습니다. 얕은 복사(Shallow Copy)를 조심합시다!