get_or_create
, update_or_create
에 대하여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가지 특성을 갖는다.
일반적으로 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/