ProgramingTip

Django가 캐시를 무시하고 데이터를 다시로드해야합니까?

bestdevel 2020. 10. 24. 11:22
반응형

Django가 캐시를 무시하고 데이터를 다시로드해야합니까?


HTTP 요청에서 호출되지 않은 프로세스의 Django 데이터베이스 모델을 사용하고 있습니다. 이 프로세스는 몇 초마다 새 데이터를 폴링하고 일부 처리를 수행해야합니다. 몇 초 동안 자고 데이터베이스에서 처리되지 않은 모든 데이터를 가져올 수 있습니다.

내가 본 것은 첫 번째 가져 오기 프로세스에서 새로운 데이터를 볼 수 있습니다. 몇 가지 테스트를 실행하여 Django가 매번 새로운 QuerySet을 구축하고 있음에도 불구하고 결과를 캐싱하는 것처럼 보입니다. 이를 확인하기 위해 Python 셸에서 다음을 수행했습니다.

>>> MyModel.objects.count()
885
# (Here I added some more data from another process.)
>>> MyModel.objects.count()
885
>>> MyModel.objects.update()
0
>>> MyModel.objects.count()
1025

보시다시피 새 데이터를 추가해도 결과가 변경되지 않습니다. 그러나 관리자의 update () 메서드를 호출하면 문제가 해결 될 것입니다.

해당 업데이트 () 방법에 대한 문서를 기준 수 다른 나쁜 일이 무엇인지 알 수 없습니다.

질문은 왜 내 장고 문서가 말하는 것과 모순되는이 캐싱 동작을보고 있습니까? 그리고 그것을 방지해야합니까?


이 문제가 확실한 두 가지 확실한 솔루션을 찾았습니다.

이것은 MySQL의 기본 트랜잭션 모드의 문제입니다. Django는 처음에 트랜잭션을 모두 다. 즉, 기본적으로 데이터베이스에서 변경된 내용을 볼 수 없습니다.

이렇게 시연

터미널 1에서 django 셸 실행

>>> MyModel.objects.get(id=1).my_field
u'old'

그리고 2 번 터미널에서

>>> MyModel.objects.get(id=1).my_field
u'old'
>>> a = MyModel.objects.get(id=1)
>>> a.my_field = "NEW"
>>> a.save()
>>> MyModel.objects.get(id=1).my_field
u'NEW'
>>> 

문제를 보여주기 위해 터미널 1로 돌아갑니다. 우리는 여전히 데이터베이스에서 이전 값을 읽습니다.

>>> MyModel.objects.get(id=1).my_field
u'old'

이제 터미널 1에서 솔루션을 보여줍니다.

>>> from django.db import transaction
>>> 
>>> @transaction.commit_manually
... def flush_transaction():
...     transaction.commit()
... 
>>> MyModel.objects.get(id=1).my_field
u'old'
>>> flush_transaction()
>>> MyModel.objects.get(id=1).my_field
u'NEW'
>>> 

이제 새 데이터를 읽습니다.

다음은 독으로 쉽게 넣기 쉬운 블록의 코드입니다.

from django.db import transaction

@transaction.commit_manually
def flush_transaction():
    """
    Flush the current transaction so we don't read stale data

    Use in long running processes to make sure fresh data is read from
    the database.  This is a problem with MySQL and the default
    transaction mode.  You can fix it by setting
    "transaction-isolation = READ-COMMITTED" in my.cnf or by calling
    this function at the appropriate moment
    """
    transaction.commit()

대체 솔루션은 MySQL 용 my.cnf를 변경하여 기본 트랜잭션 모드를 변경하는 것입니다.

transaction-isolation = READ-COMMITTED

이것은 Mysql의 새로운 기능 이며 바이너리 로깅 / 슬레 이빙에 몇 가지 영향을 미칩니다 . 콘솔 관리자 장고 연결 프리앰블에 넣을 수도 있습니다.

3 년 후 업데이트

이제 Django 1.6이 MySQL에서 자동 커밋을 설정하고 더 이상 문제가되지 않습니다. 위의 예는 이제 flush_transaction()MySQL이 REPEATABLE-READ또는 READ-COMMITTED트랜잭션 격리 모드에 있는지 여부에 관계 없이 코드 없이 작동 합니다.

자동 커밋이 아닌 모드에서 실행 된 이전 버전의 Django에서 일어난 일은 첫 번째 select문이 트랜잭션을 열었다는 것입니다. MySQL의 기본-mode는의 REPEATABLE-READ데이터베이스에, 대한 업데이트가 후속 select명령문에서 읽히지 않음을 의미 하므로

flush_transaction()트랜잭션을 중지하고 새 트랜잭션을 시작하는 위 코드 가 필요합니다 .

READ-COMMITTED그래도 격리 격리 를 사용하려는 이유는 여전히 있습니다 . 터미널 1을 트랜잭션에 많은 터미널 2의 쓰기를보고 필요합니다 READ-COMMITTED.

이제 flush_transaction()코드는 Django 1.6에서 사용 중단 경고를 생성하는 것이 좋습니다.


우리는 django가 "캐시"를 새로 고치도록 강요하는 데 많은 어려움을 겪습니다. 실제로 캐시가 트랜잭션으로 인한 아티팩트였습니다. django 뷰에는 기본적으로 트랜잭션에 대한 암시 적 호출이 있고 mysql은 시작할 때 다른 프로세스에서 발생하는 변경 사항을 격리합니다.

우리는 @transaction.commit_manually데코레이터 를 사용하고 transaction.commit()최신 정보가 필요한 모든 경우 직전에 호출했습니다 .

내가 말했듯이 이는 뷰 내에서 실행되지 않는 것입니다. django 코드에 적용되지 않는 뷰에 확실히 적용됩니다.

여기에 자세한 정보 :

http://devblog.resolversystems.com/?p=439


count()처음으로 캐시로 이동하는 것 가변 . 다음은 QuerySet.count의 django 소스입니다.

def count(self):
    """
    Performs a SELECT COUNT() and returns the number of records as an
    integer.

    If the QuerySet is already fully cached this simply returns the length
    of the cached results set to avoid multiple SELECT COUNT(*) calls.
    """
    if self._result_cache is not None and not self._iter:
        return len(self._result_cache)

    return self.query.get_count(using=self.db)

update필요한 것 외에 추가 작업을 상당히 많이하는 것 같습니다.
그러나 카운트를 위해 자신의 SQL을 작성하는 것보다 더 나은 방법을 생각할 수 없습니다.
성능이 매우 중요하지 않은 경우, 그냥 전화, 당신이하고있는 일을 할 것입니다 update전에 count.

QuerySet.update :

def update(self, **kwargs):
    """
    Updates all elements in the current QuerySet, setting all the given
    fields to the appropriate values.
    """
    assert self.query.can_filter(), \
            "Cannot update a query once a slice has been taken."
    self._for_write = True
    query = self.query.clone(sql.UpdateQuery)
    query.add_update_values(kwargs)
    if not transaction.is_managed(using=self.db):
        transaction.enter_transaction_management(using=self.db)
        forced_managed = True
    else:
        forced_managed = False
    try:
        rows = query.get_compiler(self.db).execute_sql(None)
        if forced_managed:
            transaction.commit(using=self.db)
        else:
            transaction.commit_unless_managed(using=self.db)
    finally:
        if forced_managed:
            transaction.leave_transaction_management(using=self.db)
    self._result_cache = None
    return rows
update.alters_data = True

나는 그것을 추천 할 수 있을지 모르겠지만, 캐시를 직접 죽일 수 있습니다.

>>> qs = MyModel.objects.all()
>>> qs.count()
1
>>> MyModel().save()
>>> qs.count()  # cached!
1
>>> qs._result_cache = None
>>> qs.count()
2

그리고 여기에 QuerySet의 내부를 다루지 않는 더 나은 기술이 있습니다. 캐싱은 QuerySet 내에서 발생 하지만 데이터를 새로 고치 려면 기본 쿼리 를 다시 실행해야합니다. QuerySet은 실제로 Query 개체를 래핑하는 고수준 API와 쿼리 결과를위한 컨테이너 (캐싱 포함!)입니다. 따라서 쿼리 세트가 주어지면 다음은 강제 새로 고침을 수행하는 범용 방법입니다.

>>> MyModel().save()
>>> qs = MyModel.objects.all()
>>> qs.count()
1
>>> MyModel().save()
>>> qs.count()  # cached!
1
>>> from django.db.models import QuerySet
>>> qs = QuerySet(model=MyModel, query=qs.query)
>>> qs.count()  # refreshed!
2
>>> party_time()

아주 쉽습니다! 물론 이것을 도우미 함수로 구현하고 필요에 따라 사용할 수 있습니다.


.all()쿼리 셋에 추가 하면 DB에서 강제로 다시 읽습니다. MyModel.objects.all().count()대신 시도하십시오 MyModel.objects.count().


또한 작업을 수행하기 전에 호출 MyModel.objects._clone().count().의 모든 메서드를 사용하여 내부 캐시가 무효화되도록 할 수 있습니다.QuerySet_clone()

근본 원인은 MyModel.objects매번 동일한 인스턴스 이기 때문 입니다. 복제하면 캐시 된 값없이 새 인스턴스가 생성됩니다. 물론 동일한 인스턴스를 사용하려는 경우 언제든지 캐시에 접근하여 무효화 할 수 있습니다.

참고 URL : https://stackoverflow.com/questions/3346124/how-do-i-force-django-to-ignore-any-caches-and-reload-data

반응형