Спорадическая ошибка в GAE с использованием deferred.defer — RequestTooLargeError: слишком большой запрос на вызов API datastore_v3.Put().

Похоже, что этот вопрос задавался много раз, и я знаю, что хранилище данных имеет ограничение на хранение 1 МБ на объект, как объяснено здесь.

Но. Я все еще застрял ...

Вот код, который я использую для отсрочки задач (в основном взяты из примера кода Google Mapper здесь ):

    class Mapper(object):
    # Subclasses should replace this with a model class (eg, model.Person).
    KIND = None

    # Subclasses can replace this with a list of (property, value) tuples to filter by.
    FILTERS = []

    def __init__(self):
        self.to_put = []
        self.to_delete = []

    def map(self, entity):
        """Updates a single entity.

        Implementers should return a tuple containing two iterables (to_update, to_delete).
        """
        return ([], [])

    def finish(self):
        """Called when the mapper has finished, to allow for any final work to be done."""
        pass

    def get_query(self):
        """Returns a query over the specified kind, with any appropriate filters applied."""
        q = self.KIND.query()
        for prop, value in self.FILTERS:
            q = q.filter(prop == value)
        return q

    def run(self, act_urlkey=None, batch_size=20):
        """Starts the mapper running."""
        self._continue(None, batch_size)

    def _batch_write(self):
        """Writes updates and deletes entities in a batch."""
        if self.to_put:
            ndb.put_multi(self.to_put)
            self.to_put = []
        if self.to_delete:
            ndb.delete_multi(self.to_delete)
            self.to_delete = []

    def _continue(self, curs_str=None, batch_size=20):
        logging.debug("entering _continue with curs_str: %s" % pprint.pformat(curs_str))
        q = self.get_query()
        # If we're resuming, pick up where we left off last time.
        if curs_str is not None:
            curs = Cursor.from_websafe_string(curs_str)
            entities, next_curs, more = q.fetch_page(batch_size, start_cursor=curs)
        else:
            entities, next_curs, more = q.fetch_page(batch_size)

        try:
            # Steps over the results, returning each entity and its index.
            for entity in entities:
                self.map(entity)
            if next_curs and more:
                logging.debug("Mapper._continue - sys.getsizeof(self): %d, sys.getsizeof(next_curs.to_websafe_string()): %d, sys.getsizeof(batch_size): %d" % (
                    sys.getsizeof(self),
                    sys.getsizeof(next_curs.to_websafe_string()),
                    sys.getsizeof(batch_size)
                ))
                deferred.defer(self._continue, next_curs.to_websafe_string(), batch_size)
            else:
                self.finish()
        except:
            exc_type, exc_value, exc_tb = sys.exc_info()
            logging.exception(traceback.format_exception(exc_type, exc_value, exc_tb))

Код работает хорошо, перебирает страницы запросов довольно много времени, но неизбежно завершается ошибкой с этим исключением через некоторое время:

Traceback (most recent call last):
  File "/base/data/home/apps/s~xxx-test/backendadmin:beta-0-11-9.388453478982695515/bp_content/themes/xxx/handlers/mappers.py", line 90, in _continue
    deferred.defer(self._continue, next_curs.to_websafe_string(), batch_size)
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/deferred/deferred.py", line 272, in defer
    key = _DeferredTaskEntity(data=pickled).put()
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/db/__init__.py", line 1077, in put
    return datastore.Put(self._entity, **kwargs)
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/api/datastore.py", line 605, in Put
    return PutAsync(entities, **kwargs).get_result()
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/api/apiproxy_stub_map.py", line 613, in get_result
    return self.__get_result_hook(self)
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/datastore/datastore_rpc.py", line 1881, in __put_hook
    self.check_rpc_success(rpc)
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/datastore/datastore_rpc.py", line 1371, in check_rpc_success
    rpc.check_success()
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/api/apiproxy_stub_map.py", line 579, in check_success
    self.__rpc.CheckSuccess()
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/api/apiproxy_rpc.py", line 134, in CheckSuccess
    raise self.exception
RequestTooLargeError: The request to API call datastore_v3.Put() was too large.

Журналы, показывающие размер аргументов, переданных deferred.defer, до появления ошибки, выглядят так (размер небольшой и никогда не увеличивается):

Mapper._continue - sys.getsizeof(self): 32, sys.getsizeof(next_curs.to_websafe_string()): 85, sys.getsizeof(batch_size): 12
Mapper._continue - sys.getsizeof(self): 32, sys.getsizeof(next_curs.to_websafe_string()): 85, sys.getsizeof(batch_size): 12

Где и как найти объект, который пытается сохранить хранилище данных, но он слишком велик?


person user2779653    schedule 09.11.2015    source источник


Ответы (1)


хорошо, глупый я, оказалось, что я хранил список как переменную экземпляра моего дочернего класса Mapper, который продолжал расти по мере того, как рекурсия становилась все глубже и глубже. Я удалил его, и теперь все идет гладко.

sys.getsizeof ввел меня в заблуждение, думая, что мой объект не увеличивается в размерах. См. это для объяснения того, что не так с getsizeof и почему размер объекта, казалось, не двигался, хотя это было на самом деле.

person user2779653    schedule 09.11.2015