Кэшированные операции чтения ndb все еще считаются операциями чтения хранилища данных для целей выставления счетов? - PullRequest
0 голосов
/ 11 мая 2018

С NDB Кэширование :

NDB управляет кэшем для вас. Существует два уровня кэширования: контекстный кеш и шлюз к стандартному кешированию App Engine сервис, memcache. Оба кэша включены по умолчанию для всех объектов типы, но могут быть настроены в соответствии с продвинутыми потребностями.

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

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

Каждая последовательность действий состоит из каскада push-задач, запускающих друг друга, создающих несколько сотен изменчивых сущностей, изменяющих некоторые из них разное число раз, считывающих их все переменное число раз и, наконец, удаляющих их все ,

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

Подавляющее большинство операций чтения - это поиск объектов по ключу или идентификаторам, полученный из запросов keys_only или других связанных объектов.

Я не использую происхождение сущности. Большинство из этих задач выполняют межгрупповые транзакции, и я часто вижу сбои / повторные попытки транзакций из-за конфликта данных на нескольких «горячих» объектах (я бы оценил около 200-400 из них в этом прогоне, трудно подсчитать в Страница журнала Stackdriver).

После выполнения одной такой ~ 20-минутной последовательности заново после ежедневного сброса квоты на панели инструментов приложения отображается в 3 раза больше операций чтения из облачного хранилища данных (0,03 миллиона), чем из записи в облачное хранилище данных (0,01 миллиона). Число изменчивых объектов указывается удалением объекта хранилища данных Cloud (0,00089 миллионов), если это имеет значение.

Частота обращений к memcache составила 81%, но я не знаю, предназначено ли это только для явного использования memcache в моем приложении или оно включает использование memcache в ndb.

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

Эти наблюдения появляются , чтобы предположить, что чтение объекта из кэша все еще считается как чтение хранилища данных. Но здесь я предполагаю, что:

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

Я не нашел ничего об этом в документации, поэтому мне интересно, знает ли кто-то , что кэшированные чтения ndb действительно считаются чтением хранилища данных или могут указывать на недостатки в моей интерпретации или какой-либо официальной документации по субъект.

Ответы [ 2 ]

0 голосов
/ 18 мая 2018

После ответа Джима я продолжил копать, делясь своими выводами здесь, поскольку другие могут найти их полезными.

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

, у меня уже был db_data @property в этом универсальном классе для чтения сущности из хранилища данных по требованию, его было легко подключитьв моей собственной схеме кэширования на основе memcache и кода отслеживания, чтобы определить, происходят ли чтения из memcache или из ndb и выполняются ли они внутри транзакций или нет.Вот как это выглядит:

@property
def db_data(self):
    if not hasattr(self, self.attr_db_data):
        db_data = None
        if self.readonly or not ndb.in_transaction():
            # noinspection PyTypeChecker
            db_data = memcache.get(self.memcache_key)
        if db_data:
            if not isinstance(db_data, self.db_model):
                logging.error('mismatched cached db_data kind, expected %s got type %s %s' %
                              (self.kind, type(db_data), db_data))
                db_data = None
            else:
                if self.trace:
                    logging.debug('%s from memcache' % self.lid)
                self.cached = True
        if not db_data:
            if hasattr(self, self.attr_db_key):
                db_data = self.db_key.get()
            elif hasattr(self, self.attr_key_id):
                db_data = self.db_model.get_by_id(id=self.key_id)
            else:
                raise RuntimeError('missing db_data, db_key and key_id')
            if db_data:
                # use ndb model names as strings in the list below. TODO: don't leave them on!
                show_trace = self.kind in ['']
                if self.trace or show_trace:
                    if show_trace:
                        self.print_stack(4)
                    logging.debug('%s from datastore' % self.lid)
                self.cached = False
                # noinspection PyTypeChecker
                memcache.set(self.memcache_key, db_data)
        setattr(self, self.attr_db_data, db_data)
    return getattr(self, self.attr_db_data)

С этим на месте я обнаружил:

  • много ненужных чтений из ndb, в частности, объекты конфигурации последовательности, которые сделалине меняются в течение всей последовательности и к ним обращаются сотни раз
  • некоторые чтения для одних и тех же объектов повторяются внутри одного и того же запроса
  • самые важные : подробности для моегомногие транзакции с ошибками TransactionFailedError(The transaction could not be committed. Please try again.) или TransactionFailedError: too much contention on these datastore entities. please try again вызваны всеми этими ненужными операциями чтения из многих транзакций, выполняемых параллельно.Я знал (или, скорее, подозревал) основную причину - см. Связанные проблемы с конфликтами в Google App Engine - но с этими подробными сведениями я теперь могу реально работать над их уменьшением.

Длясо стороны записи я добавил в общий класс метод db_data_put() (с множеством проверок и поддержкой отслеживания) и заменил на него все вызовы .db_data.put(), разбросанные по всему коду приложения.Вот как это выглядит:

def db_data_put(self, force=False):
    #assert ndb.in_transaction()  TODO: re-enable after clearing all violations

    self.db_key = self.db_data.put()

    show_trace = False
    if not ndb.in_transaction():
        logging.error('%s: db_data_put called outside transaction, readonly=%s' % (self.lid, self.readonly))
        show_trace = True
    if self.readonly:
        logging.error('%s: db_data_put called with readonly set' % self.lid)
        show_trace = True
    if force:
        if self.trace:
            logging.warn('%s: db_data_put called with force arg set' % self.lid)
    if self.cached:
        logging.error('%s: db_data_put called with cached data' % self.lid)
        show_trace = True
    if self.put_count:
        logging.warn('%s: db_data_put already called %d time(s)' % (self.lid, self.put_count))
        show_trace = True
    self.put_count += 1

    if  self.update_needed:
        self.update_needed = False
    elif not force:
        if self.trace:
            logging.warn('%s: db_data_put called without update_needed set' % self.lid)
            show_trace = True

    # noinspection PyTypeChecker
    memcache.set(self.memcache_key, self.db_data)

    # use ndb model names as strings in the list below. TODO: don't leave them on!
    show_kind = self.kind in ['']

    if show_trace or show_kind:
        self.print_stack(4)

    if self.trace or show_trace or show_kind:
        logging.debug('%s: db_data_put %s' % (self.lid, self.kind))

С его помощью я обнаружил некоторые возможности для улучшений и некоторые скрывающиеся ошибки:

  • сущностей, написанных вне транзакций (пространство для повреждения данных)
  • сущности, записанные несколько раз в ответ на один и тот же запрос (что нарушает ограничение макс. 1 запись / секунда / лимит хранилища данных группы сущностей)
  • несколько горячих запросов, иногда кластеризующихся очень близко к каждому-почему, способствуя тем самым проблеме конкуренции и, при записи тех же сущностей, также нарушая ограничение 1 запись / сек.

После установки флага self.readonly для всех объектов конфигурации последовательности и парыиз более часто считываемых без необходимости (чтобы включить кэширование даже внутри транзакций), сериализацию запросов на запись hot и исправление наиболее важных обнаруженных ошибок. Я повторил тест на чистые измерения:

  • всей последовательностивремя выполнения сократилось до ~ 13 минут, вероятно, из-за сериализации hot транзакций, которые помогли сократитье "шум" последовательности и общее количество логических переходов состояний и задач принудительной очереди
  • количество сбоев транзакций, связанных с конфликтом данных, уменьшилось на ~ 60% - я связываю большую часть этого с моим частным кэшированиемобъектов, которые не должны изменяться во время транзакции (ndb не имеет ни малейшего представления о том, будет ли в конечном итоге записана сущность, к которой осуществляется доступ в транзакции, или нет) и сериализации hot транзакций
  • объекта хранилища данных CloudКоличество операций записи уменьшилось до 0,0055 млн. Операций - вероятно, из-за меньшего количества переходов состояний последовательности, упомянутых выше
  • Операции чтения хранилища данных в облаке упали до 0,01 миллиона операций - мое кэширование помогает во время транзакций

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

0 голосов
/ 11 мая 2018

Частота попаданий в memcache включает в себя попадания для сущностей, написанных ndb. Он не включает в себя число попаданий в кэш в контексте с помощью ndb.

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

Еще одна вещь заключается в том, что ndb создает новый контекстный кэш для каждой транзакции, поэтому контекстный кэш не очень эффективен в отношении транзакций.

Быстрый ответ заключается в том, что попадание в memcache для объекта хранилища данных не оплачивается как чтение хранилища данных для целей выставления счетов.

...