MongoDB in Action 7

김하영·2022년 2월 3일
0

7. 업데이트, 원자적 연산, 삭제

7.1 도큐먼트 업데이트

MongoDB에서 업데이트를 하려면 두 가지 방법을 사용할 수 있다.
도큐먼트 전체를 대치 하든지 아니면 도큐먼트 내의 특정 필드를 수정하기 위해 업데이트 연산자를 사용할 수 있다.

7.1.1 대치에 의한 수정

user_id = ObjectId("4c4b1476238d3b4dd5003981")
doc = db.users.findOne({_id:user_id})
doc['email'] = 'mongodb-user@mongodb.com'
db.users.update({_id:user_id},doc)

'사용자 컬렉션에서 주어진 _id로 도큐먼트를 찾아서 새로이 제공하는 도큐먼트로 대치하라!'는 의미이다.
기억해야 할 점은 업데이트 연산이 전체 도큐먼트를 대치한다는 점이며, 이것이 바로 업데이트 연산이 가장 먼저 패치되어야 하는 이유다.
어려 사용자가 동일한 도큐먼트를 업데이트 하는 경우 마지막으로 쓰기 연산이 수행된 내용이 저장된다.

7.1.2 연산자에 의한 수정

user_id = ObjectId("4c4b1476238d3b4dd5003981")  
db.users.update({_id: user_id},{$set: {email:'mongodb-user2@mongodb.com'}})

몇 가지 특수한 업데이트 연산자 중에 하나인 $set을 사용했다.
'주어진 조건으로 사용자 도큐먼트를 찾아서 email 필드를 해당 값으로 수정하라'와 같은 방식으로 좀 더 특정 필드를 목표로 하고 있다.

7.1.3 두 방법의 비교

이번에는 상품의 리뷰 수를 증가시키고자 한다. 도큐먼트 대치 방식은 다음과 같다.

대치

product_id = ObjectId("4c4b1476238d3b4dd5003982")  
doc = db.products.findOne({_id: product_id})  
doc ['total_reviews'] += 1        
db.products.update({_id: product_id}, doc) 

업데이트 연산자 방식은 다음과 같다.

연산자

  db.products.update({_id: product_id}, {$inc: {total_reviews: 1}}) 

(그냥 딱봐도 연산자 수정이 더 나은 성능을 갖는 것 처럼 보인다..)

연산자 방식의 수정이 더 나은 성능을 갖는다.
평균 크기가 200KB인 도큐먼트를 대치 방식으로 업데이트하려면 업데이트당 서버로 200KB를 보내는 것이 된다.
반면 $set, $push 등 업데이트 명령에 사용된 도큐먼트는 수정되는 도큐먼트의 크기에 상관없이 100바이트 이내다.

또한, 연산자 방식은 도큐먼트를 원자적(atomic)으로 업데이트하는데 적합하다.
연산자 방식은 대량의 동시적 업데이트일지라도 각각의 $inc는 고립적으로 적용되어 증가하거나 증가하지 않거나 둘 중의 한 가지만 가능하다는 의미이다. (원자성이 보장된다!)
반면 대치 방식은 일종의 낙관적 잠금(optimistic locking)을 통해 원자성을 보장할 수 있다.

  • 낙관적 잠금 (optimistic locking)

레코드를 잠그지 않고도 업데이트 연산이 제대로 수행되는 것을 보장하기 위한 기술이다.
사용자가 수정한 후에 업데이트하려고 할 때, 업데이트에 타임스탬프가 포함된다.
그 타임스탬프가 페이지의 최종 저장 시간보다 더 이전이면 업데이트를 할 수 없다.
하지만 아무도 그 페이지에 대해 수정하지 않았다면 업데이트가 가능하다.
예) wiki , zookeeper..?

<> 비관적 잠금을 사용하면 레코드가 트랙잭션에서 처음 엑세스할 때부터 완료될 때까지 레코드가 잠긴다.

multi update

매개변수를 이용하여 다중 업데이트를 할 수 있다.
multi 매개변수가 없으면 업데이트는 첫 번째로 일치하는 도큐먼트에만 영향을 미친다.

업서트(upsert)

업데이트할 도큐먼트가 존재하지 않을 경우 새로운 도큐먼트를 인서트한다.
원자적으로 업데이트를 하거나 도큐먼트가 존재하는지의 여부가 불확실할 때 업서트는 유용하다.

7.3 원자적 도큐먼트 프로세싱

findAndModify 명령을 통해서 도큐먼트를 자동으로 업데이트하고 업데이트된 도큐먼트를 반환하는 것이 한 번에 가능해진다.

왜 이것이 유용할까?

도큐먼트를 패치하고 업데이트(또한 업데이트한 다음 패치)하면, 이러한 작업 사이에 다른 MongoDB 사용자가 도큐먼트를 변경할 수 있다. 따라서 findAndModify를 사용하지 않는 한 비록 업데이트가 원자적인 연산일지라도 업데이트 전이나 후의 도큐먼트의 실제 상태를 알 수 없다.

  • 'new' : true 속성을 적용해야 업데이트 된 도큐먼트를 반환한다. 기본 값으로는 이전의 도큐먼트를 반환한다.

7.4 실제적인 세부사항 : MongoDB 업데이트와 삭제

  • MongoDB 업데이트 연산자

$inc : 수치가 증가하거나 감소할 때 사용한다.

$set : 특정 키에 값을 정해주기 위해 사용한다.
키에 대한 값이 이미 존재하면 그 값을 덮어쓰고, 그렇지 않은 경우 새로운 키가 생성된다.

$unset : 도큐먼트에서 해당 키를 삭제한다.

$rename : 키의 이름을 바꿔야 할 경우에 사용한다.

$push : 배열에 값을 추가한다. 태그를 여러 개 추가하려면 $each를 $push와 함께 사용할 수 있다.
MongoDB v2.4부터 $pushAll은 deprecated 되었다.

$slice : 업데이트 후 배열에 남아 있어야 하는 항목 수를 지정한다.

$addToSet : 배열에 값을 추가하지만 배열에 존재하지 않을 경우에만 값을 추가한다.
하나 이상의 값에 대해서 수행하고자 한다면 $each 연산자와 함께 사용한다.

$pop : 마지막으로 추가된 아이템을 지운다. 배열의 첫 번째 아이템을 지우기 위해 -1이라는 또 다른 값을 받는다.

$pull : 배열에서 원소의 위치 대신 값을 지정해서 아이템을 삭제한다.
$pushAll은 삭제할 값의 리스트를 지정할 수 있다.

7.4.4 삭제

컬렉션 전체를 지울수도 있고,쿼리 셀렉터를 매개변수로 넘겨줘서 컬렉션 내의 일부분의 도큐먼트를 지울 수도 있다.

db.reviews.remove({user_id: ObjectId('4c4b1476238d3b4dd5000001')})  
db.reviews.remove({})  

7.4.5 동시성, 원자성, 고립

MongoDB v2.2 이전까지는 잠금 전략이 상당히 정교하지 못했다. (지금도.. 완벽하지는 않은 것 같은데..?)
MongoDB v3.0 에서 도큐먼트 수준의 잠금기능을 제공한다.

MongoDB의 잠금은 읽기가 많고 쓰기가 많은 작업 부하의 성능에 영향을 미칠 수 있다.
문제를 피하는 좋은 방법은 트래픽이 대량으로 발생하는 컬랙션을 별도의 데이터베이스에 저장하는 것이다.
( 이번 프로모션 모델링 때, 위와 같은 이유로 ppn 컬랙션을 생성함.. 개발 시 불편했음..)

$isolated (고립된)

여러 도큐먼트 업데이트에 다른 연산이 끼어드는(interleave) 것을 허용하지 않는다. 즉, 앵보되지 못하도록 한다.
어떤 연산이 수행되기 전에 반드시 모든 도큐먼트를 업데이트하거나 삭제해야만 하는 상황을 쉽게 생각해 볼 수 있다.
$isolated 연산자가 샤드된 컬렉션에서 작동하지 않는다는 사실과 결합하여 생각해 보면 이를 주의해서 사용해야 함을 알 수 있다.

profile
Back-end Developer

0개의 댓글