Общие сведения о Datastore Get RPC в Google App Engine

Я использую сегментированные счетчики (https://cloud.google.com/appengine/articles/sharding_counters) в моем приложении GAE из соображений производительности, но мне трудно понять, почему оно работает так медленно и как его ускорить.

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

Метрики
При включенном Appstats и чистом кеше я заметил, что получение итогов для 20 счетчиков составляет 120 RPC с помощью datastore_v3.Get, что занимает 2500 мс.

Мысли
Похоже, что вызовов RPC довольно много, а для чтения всего 20 счетчиков требуется совсем немного времени. Я предполагал, что это будет быстрее, и, возможно, здесь я ошибаюсь. Это должно быть быстрее, чем это?

Дальнейшая проверка
Я еще немного углубился в статистику, взглянув на эти две строки в методе get_count:

all_keys = GeneralCounterShardConfig.all_keys(name)
for counter in ndb.get_multi(all_keys):

Если я закомментирую строку get_multi, я увижу, что существует 20 вызовов RPC от datastore_v3.Get общей продолжительностью 185 мс.

Как и ожидалось, это оставляет get_multi виновником 100 вызовов RPC от datastore_v3. Получить взятие свыше 2500 мс. Я проверил это, но здесь я запутался. Почему вызов get_multi с 20 ключами вызывает 100 вызовов RPC?

Обновление №1
Я проверил Traces в консоли GAE и увидел дополнительную информацию. Они там тоже показывают разбивку вызовов RPC - но в прицелах говорят "Пакетировать добирается, чтобы уменьшить количество удаленных вызовов процедур". Не уверен, как это сделать без использования get_multi. Думал, что сделал работу. Любой совет здесь?

Обновление №2
Вот несколько снимков экрана, на которых показана статистика, которую я просматриваю. Первая — моя базовая линия — функция без каких-либо встречных операций. Второй — после вызова get_count только для одного счетчика. Это показывает разницу в 6 RPC datastore_v3.Get.

Базовая линия введите здесь описание изображения

После вызова get_count на одном счетчике введите здесь описание изображения

Обновление №3
По просьбе Патрика я добавляю скриншот информации из консольного инструмента трассировки. введите здесь описание изображения


person Brandon    schedule 07.01.2016    source источник
comment
Сколько ключей в all_keys?   -  person Tim Hoffman    schedule 07.01.2016
comment
Похоже, вы вызываете get_multi в цикле, но мы не видим этого из предоставленного вами небольшого фрагмента кода.   -  person Andrei Volgin    schedule 07.01.2016
comment
Привет @TimHoffman, это 20 ключей.   -  person Brandon    schedule 07.01.2016
comment
Привет @AndreiVolgin, это правильно.   -  person Brandon    schedule 07.01.2016
comment
Это должен быть 1 вызов, а не 120 вызовов. Где-то есть проблема.   -  person Andrei Volgin    schedule 07.01.2016
comment
Вот о чем я думаю. Я добавил некоторые статистические данные до и после вызова get_count для одного счетчика в исходном сообщении.   -  person Brandon    schedule 07.01.2016
comment
К сожалению, get_multi ndb не является простым выполнением одного вызова RPC. Если вы посмотрите, как get_multi() реализован, вы увидите, что он использует преимущества AutoBatcher. По умолчанию ndb должен быть готов объединить до 1000 Gets вместе, поэтому он должен обработать их все за вас.   -  person Patrick Costello    schedule 08.01.2016
comment
В трассировках в консоли, если вы нажмете на трассировку datastore_v3.Get, она должна показать вам, сколько ключей задействовано в каждом вызове. В качестве примечания я бы отключил appstats, прежде чем делать это, так как Appstats также записывает в хранилище данных, поэтому отследить любые ошибки может быть сложнее. Не могли бы вы сообщить нам, как выглядят эти следы?   -  person Patrick Costello    schedule 08.01.2016
comment
Подойдет @PatrickCostello. Спасибо!   -  person Brandon    schedule 08.01.2016
comment
Привет, @PatrickCostello, я добавил скриншот из Traces. Похоже на 10 ключей, верно? Я обязательно отключил Appstats. Существует множество пакетов из 5 datastore_v3.Gets.   -  person Brandon    schedule 09.01.2016
comment
Интересный. Можете ли вы сообщить об ошибке на странице ndb на github? github.com/GoogleCloudPlatform/datastore-ndb-python/issues Если возможно, Вы можете включить полный фрагмент кода?   -  person Patrick Costello    schedule 09.01.2016


Ответы (2)


Попробуйте разделить цикл for, который проходит через каждый элемент, и сам вызов get_multi. Итак, что-то вроде:

all_values = ndb.get_multi(all_keys)
for counter in all_values:
    # Insert amazeballs codes here

У меня такое чувство, что это один из этих:

  1. Шаблон генератора (выход из цикла for) вызывает что-то странное с путями выполнения get_multi
  2. Возможно, ожидаемое количество элементов не соответствует фактическому количеству результатов, что может выявить проблему с GeneralCounterShardConfig.all_keys(name)
  3. Установлено слишком большое количество осколков. Я понял, что все, что превышает 10 осколков, вызывает проблемы с производительностью.
person Bardia D.    schedule 07.01.2016
comment
Я попытался удалить get_multi из цикла, и у него та же проблема, но я еще не пробовал ограничивать количество осколков. Я попробую. - person Brandon; 07.01.2016

Когда я исследовал подобные проблемы, я понял одну вещь: get_multi может вызвать отправку нескольких RPC из вашего приложения. Похоже, что по умолчанию в SDK установлено значение 1000 ключей на получение, но размер пакета, который я наблюдал в рабочих приложениях, намного меньше: что-то вроде 10 (идет из памяти).

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

Однако, если вам не нужно считывать самое последнее абсолютное значение, вы можете попробовать установить параметр db.EVENTUAL_CONSISTENCY, но, похоже, он доступен только в старой библиотеке db, а не в ndb. (Хотя он также доступен через API облачного хранилища данных).

Подробнее

Если вы посмотрите на код Python в SDK App Engine, в частности на файл google/appengine/datastore/datastore_rpc.py, вы увидите следующие строки:

max_count = (Configuration.max_get_keys(config, self.__config) or
             self.MAX_GET_KEYS)
...

if is_read_current and txn is None:
  max_egs_per_rpc = self.__get_max_entity_groups_per_rpc(config)
else:
  max_egs_per_rpc = None

...

pbsgen = self._generate_pb_lists(indexed_keys_by_entity_group,
                                 base_req.ByteSize(), max_count,
                                 max_egs_per_rpc, config)

rpcs = []
for pbs, indexes in pbsgen:
  rpcs.append(make_get_call(base_req, pbs,
                            self.__create_result_index_pairs(indexes)))

Мое понимание этого:

  • Установите max_count из объекта конфигурации или 1000 по умолчанию
  • Если запрос должен считывать текущее значение, установите max_gcs_per_rpc из конфигурации или 10 по умолчанию.
  • Разделите входные ключи на отдельные RPC, используя max_count и max_gcs_per_rpc в качестве ограничений.

Итак, этим занимается библиотека Python Datastore.

person Evan Jones    schedule 12.12.2016