django의 경쟁 조건
-
06-07-2019 - |
문제
다음은 잠재적인 경쟁 조건이 있는 django 뷰의 간단한 예입니다.
# myapp/views.py
from django.contrib.auth.models import User
from my_libs import calculate_points
def add_points(request):
user = request.user
user.points += calculate_points(user)
user.save()
경쟁 조건은 상당히 명확해야 합니다.사용자는 이 요청을 두 번 할 수 있으며 애플리케이션이 잠재적으로 실행될 수 있습니다. user = request.user
동시에 요청 중 하나가 다른 요청을 재정의하게 됩니다.
함수를 가정해보자 calculate_points
상대적으로 복잡하며 단일 항목에 배치할 수 없는 모든 종류의 이상한 항목을 기반으로 계산을 수행합니다. update
저장 프로시저에 넣기가 어려울 수 있습니다.
내 질문은 다음과 같습니다.이와 유사한 상황을 처리하기 위해 django에서는 어떤 종류의 잠금 메커니즘을 사용할 수 있나요?
해결책
Django 1.4+ 지원 select_for_update, 이전 버전에서는 원시 SQL 쿼리를 실행할 수 있습니다. select ... for update
기본 DB에 따라 업데이트에서 행을 잠그는 경우 트랜잭션이 끝날 때까지 해당 행으로 원하는 모든 것을 수행 할 수 있습니다. 예를 들어
from django.db import transaction
@transaction.commit_manually()
def add_points(request):
user = User.objects.select_for_update().get(id=request.user.id)
# you can go back at this point if something is not right
if user.points > 1000:
# too many points
return
user.points += calculate_points(user)
user.save()
transaction.commit()
다른 팁
Django 1.1 현재 ORM의 F () 표현식을 사용 하여이 특정 문제를 해결할 수 있습니다.
from django.db.models import F
user = request.user
user.points = F('points') + calculate_points(user)
user.save()
자세한 내용은 문서를 참조하십시오.
https://docs.djangoproject.com/en/1.8/ref/models/instances/#updating-attributes 기반-온-존재하는 존재
https://docs.djangoproject.com/en/1.8/ref/models/expressions/#django.db.models.f
데이터베이스 잠금은 여기로가는 방법입니다. Django에 "업데이트를위한 선택"지원을 추가 할 계획이 있습니다 (여기), 그러나 지금은 가장 간단한 것은 RAW SQL을 사용하여 점수를 계산하기 전에 사용자 객체를 업데이트하는 것입니다.
비관적 잠금은 이제 기본 DB (예 : Postgres)가 지원할 때 Django 1.4의 ORM에 의해 지원됩니다. 참조 Django 1.4A1 릴리스 노트.
이런 종류의 작업을 단일 스레드로 처리하는 방법에는 여러 가지가 있습니다.
한 가지 표준 접근 방식은 다음과 같습니다. 먼저 업데이트.행에 대한 배타적 잠금을 확보하는 업데이트를 수행합니다.그런 다음 일을 하십시오.마지막으로 변경 사항을 커밋합니다.이것이 작동하려면 ORM의 캐싱을 우회해야 합니다.
또 다른 표준 접근 방식은 복잡한 계산에서 웹 트랜잭션을 분리하는 별도의 단일 스레드 응용 프로그램 서버를 보유하는 것입니다.
웹 애플리케이션은 채점 요청 대기열을 생성하고 별도의 프로세스를 생성한 다음 이 대기열에 채점 요청을 쓸 수 있습니다.스폰은 Django에 넣을 수 있습니다
urls.py
웹앱 시작시 발생합니다.아니면 따로 넣어도 되지만manage.py
관리 스크립트.또는 첫 번째 채점 요청을 시도할 때 "필요에 따라" 수행할 수 있습니다.urllib2를 통해 WS 요청을 수락하는 Werkzeug를 사용하여 별도의 WSGI 기반 웹 서버를 생성할 수도 있습니다.이 서버에 대한 포트 번호가 하나만 있는 경우 요청은 TCP/IP에 의해 대기열에 추가됩니다.WSGI 핸들러에 하나의 스레드가 있으면 직렬화된 단일 스레딩을 달성한 것입니다.채점 엔진은 WS 요청이고 어디에서나 실행될 수 있으므로 이는 약간 더 확장성이 뛰어납니다.
또 다른 접근 방식은 계산을 수행하기 위해 획득하고 보유해야 하는 다른 리소스를 확보하는 것입니다.
데이터베이스의 Singleton 개체입니다.고유 테이블의 단일 행을 세션 ID로 업데이트하여 제어권을 확보할 수 있습니다.세션 ID로 업데이트:
None
통제를 해제합니다.필수 업데이트에는 다음이 포함되어야 합니다.WHERE SESSION_ID IS NONE
다른 사람이 잠금을 보유하고 있을 때 업데이트가 실패하도록 필터링합니다.이는 본질적으로 경합이 없기 때문에 흥미롭습니다. 즉, SELECT-UPDATE 시퀀스가 아닌 단일 업데이트입니다.다양한 종류의 세마포어를 데이터베이스 외부에서 사용할 수 있습니다.대기열은 (일반적으로) 낮은 수준의 세마포어보다 작업하기가 더 쉽습니다.
이것은 상황을 과도하게 단순화 할 수 있지만 JavaScript 링크 교체는 어떻습니까? 다시 말해서 사용자가 링크를 클릭하거나 버튼을 클릭하면 링크를 즉시 비활성화 / "회색"으로 링크를 "로드 ..."또는 "제출 요청 ..."정보 등으로 대체하는 JavaScript 함수로 요청을 랩핑 할 때. 비슷한. 그게 당신을 위해 일할까요?
이제 사용해야합니다.
Model.objects.select_for_update().get(foo=bar)