[django] get_or_create vs update_or_create

EMMA·2022년 4월 12일
1
post-custom-banner

get_or_create, update_or_create 에 대하여

부제: transaction


1
get_or_create

이미 데이터베이스에 있는 객체면 get하고, 없으면 create해주는 메소드다.
(object, created(혹은 is_created)) 의 튜플 형태로 반환하며,
새로 생성되거나 조회하는 객체는 object에 담기고 bool값은 created에 담긴다.

django 공식문서에 있는 예시를 보자.

try:
    obj = Person.objects.get(first_name = 'John', last_name = 'Lennon')
except Person.DoesNotExist:
    obj = Person(first_name = 'John', last_name = 'Lennon', birthday = date(1940,10,9))
    obj.save()

위 예시의 문제는, race condition 이 발생할 수 있다는 점이다.
race condition(경합 상황)이란, 리소스(공유 자원)를 여러 개의 프로세스가 접근해 어떤 수행 결과를 가져다줄지 알 수 없는 상황을 말한다.
Jonh Lennon을 추가/save 하려는 프로세스가 여러 곳에서 시도가 될 수 있다는 뜻.

(220414 update)
내가 경합 상황/공유 자원 등의 키워드 등이 왜 뭔가 익숙한가 했더니, 운영체제 책에서 읽었었다...! 그 기념으로 좀 더 덧붙이자면,

  • 공유 자원은 여러 프로세스가 공동으로 이용할 수 있는 변수,메모리,파일 등이다.
  • 따라서 프로세스의 접근 순서에 따라 실행 결과가 달라지는데, 이런 영역을 임계구역 (critical section)이다.
  • 임계구역은 프린터 같은 하드웨어에도 적용된다. 프린터 1대를 여러 명이 다른 자료를 출력하려고 접근할 경우, 프린터가 곧 임계 구역.
  • 임계구역을 해결하려면, 상호 배제(mutual exclusion)/한정 대기(bounded waiting)/진행의 융통성(progress flexibility)를 만족해야 한다.
  • 사실 위 3가지 조건은 결국엔 같은 이야기다. 한 프로세스가 임계구역에 접근하면 다른 프로세스는 들어갈 수 없고, 모든 프로세스는 임계구역에 접근할 수 있어야 한다(무한정 대기는 안됨).

Anyhow...
반면 get_or_created는 transaction이 적용되어 있어, 더 안전한 메소드다.
위 예시는 get_or_created를 사용하면 아래와 같다. 이렇게 사용하면, race condition을 피할 수 있게 된다.

obj, created = Person.objects.get_or_created(
	first_name = 'John',
    last_name = 'Lennon',
    defaults = {
    	'birthday' : date(1940,10,9)
    }
)

만약 Person DB에 John Lennon이 있다면, 해당 인스턴스와 함께 created=False를 반환한다.
만약 Person DB에 없다면, 해당 인스턴스를 새로 등록하고 created=True를 반환한다.
defaults는 create portion으로, 인스턴스를 새로 등록하는 경우에만 사용한다.

또 다른 예시(원본)를 보면 아래와 같다.
이는 내용보다, get_or_create 자체를 어떻게 응용할 수 있나에 초점을 두고 퍼온 예시다.

follow, is_follow = self.follow_relations.get_or_create(to_user=user)
#is_follow가 FALSE라면(이미 following하고 있다면) 언팔로우 
if not is_follow: follow.delete() 
#is_follow가 TRUE라면(following하지 않은 상태라면) 팔로우 
else: return follow

2
update_or_create

만약 boolean값이 FALSE라면(이미 데이터가 있다면) defaults를 업데이트하고,
TRUE라면(데이터가 없다면) 새로 등록한다.

obj, created = Person.objects.update_or_create(
    first_name='John', last_name='Lennon',
    defaults={'first_name': 'Bob'},
)

3
transaction / ACID

transaction은 업무 처리의 가장 기본이 되는 단위다.
예를 들어 입출금 문제(입금과 출금이 동시에 처리되는 문제)가 대표적인 경쟁 상태의 예시인데, 이러한 충돌을 해결하기 위해서 복수의 데이터베이스 업데이트를 하나의 단위로 처리하는 것을 말한다.

따라서, A계좌에서 1백만원을 인출하는 것은 성공했는데 B계좌로 송금하는 것을 실패했다고 가정하자. transaction 처리가 된 경우, 송금도 모두 취소되고 초기 상태로 되돌린다.

transation은 아래 4가지 특성을 갖는다.

  • 원자성 (Atomicity)
    • All or nothing, 트랜잭션 관련 작업이 모두 실행되던지 아니면 아예 안되던지
  • 일관성 (Consistency)
    • 하나의 트랜잭션이 완료되면, 데이터베이스는 항상 일관된 상태로 유지됨
  • 독립성 (Isolation)
    • 하나의 트랜잭션이 데이터베이스를 업데이트하는 동안엔 다른 트랜잭션은 access 불가능
  • 지속성 (Durability)
    • 트랜잭션이 완료되면 그 내용은 데이터베이스에 영구적으로 반영되어야함

일반적으로 database에 아래와 같이 설정하면 대부분의 http request에 transaction을 적용할 수 있다.

DATABASE = {
  'default': {
    #...생략
    'ATOMIC_REQUESTS': True,
  }
}

초기 구성 단계에서는 위와 같이 설정하는 것이 효과적일 것이나, 하나하나 transaction 처리하므로 전체적인 성능은 약간 떨어질 수 있다.

이 외에도, 모델링 단계에서 unique를 설정하는 것 또한 transaction을 적용하는 하나의 방법이라고 할 수 있다.

class Author(models.Model):
    first_name = models.CharField(max_length=255)
    last_name = models.CharField(max_length=255)

    class Meta:
        unique_together = ('first_name', 'last_name')

author, created = Author.objects.get_or_create(first_name='Leo', last_name='Tolstoy')

참고 자료
https://docs.djangoproject.com/en/4.0/ref/models/querysets/#django.db.models.query.QuerySet.get_or_create
https://www.queworx.com/django/django-get_or_create/

profile
예비 개발자의 기술 블로그 | explore, explore and explore
post-custom-banner

0개의 댓글