Django с Единорогом теряет локальное хранилище потоков во время запроса

#python #django #multithreading #gunicorn

Вопрос:

Я настроил кэш области запросов с помощью промежуточного программного обеспечения и попытался настроить его так, чтобы он был доступен из любого места с помощью потоковой передачи.локальная() переменная. Однако иногда процессы с длинными запросами завершаются со следующей ошибкой:

   File "label.py", line 29, in get_from_request_cache
    label = cache.labels[url]
AttributeError: '_thread._local' object has no attribute 'labels'
 

Однако некоторые элементы обрабатываются правильно, и все они зависят от наличия этого кэша.

Объект кэша инициализируется один раз и очищается в конце запроса с помощью следующего промежуточного программного обеспечения:

request_cache.py

 import threading

_request_cache = threading.local()

def get_request_cache():
    return _request_cache

class RequestCacheMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        global _request_cache
        response = self.get_response(request)
        _request_cache.__dict__.clear()

        return response

    def process_exception(self, request, exception):
        _request_cache.__dict__.clear()
        return None
 

И единственный код, который напрямую обращается к объекту кэша, — это эта часть:

label.py

 import django.db.models

from request_cache import get_request_cache
from base import Model


class Label(Model):
    **model fields**

    @staticmethod
    def create_for_request_cache(urls):
        cache = get_request_cache()
        urls = set(urls)
        if not hasattr(cache, 'labels'):
            cache.labels = {}
        new_urls = urls.symmetric_difference(set(cache.labels.keys()))
        entries = [Label(url=url) for url in new_urls]
        if entries:
            Label.objects.bulk_create(entries)
            for entry in entries:
                cache.labels[entry.url] = entry

    @staticmethod
    def get_from_request_cache(url):
        cache = get_request_cache()
        label = cache.labels[url]
        return label
 

Запрос, который завершает работу, разбивается в коде на пакеты, и перед каждым пакетом берутся новые уникальные URL-адреса и добавляются в кэш со следующим кодом, и именно там происходит сбой процесса:

fill_labels.py

 class LabelView(django.views.generic.base.View):
    def _fill_labels_batch(items_batch):
        VersionLabel.create_for_request_cache([item.get('url', '') for item in items_batch])

        for item in items_batch:
            **process items** - CRASHES HERE

    @transaction.atomic
    def post(self, request, subcategory):
        item_batches = **split items into batches**
        for item_batch in item_batches:
            _fill_labels_batch(item_batch)
 

Если я правильно понимаю, как работают Django и Gunicorn, локальный объект потока должен быть локальным либо для потока, если не было исправления обезьяны, либо для гринлета, если Gunicorn выполняет исправление обезьяны внутри, и Django использует один и тот же поток на протяжении всего запроса, что означает, что локальное хранилище потока не должно изменяться в середине запроса ни в одном из этих случаев. Однако возможно, что одновременно обрабатывается несколько запросов, и каждый запрос может содержать входные данные размером около 200 МБ, а время, необходимое для обработки запроса, может составлять несколько часов — последний сбой произошел после 4 часов обработки.

В чем может быть причина потери этого кэша в процессе запроса? Если бы он вообще не был создан, запрос рухнул бы намного быстрее, и я не могу придумать причину, по которой Django мог бы изменить или потерять threading.local() объект в середине запроса.

Ответ №1:

Добавление явного monkey.patch_all() вызова каким-то образом устранило проблему. Корень проблемы так и остался неизвестным.