Использование нескольких «подсчитать отдельно» имеет огромное влияние на производительность - PullRequest
0 голосов
/ 05 февраля 2020

В Google Cloud Spanner у нас возникает проблема с памятью для некоторых запросов, которые анализируют много данных

GenerateArrayEvaluator ran out of memory during buffering one value (original error message 'GenerateArrayEvaluator ran out of memory. Requested: 9 byte(s). Already reserved: 294649856 byte(s). Limit: 294649856 byte(s). Details: Cannot reserve 9 bytes to create a new segment The operation HashJoinIterator is reserving the most memory (114717769 bytes).'). Requested: 0 byte(s). Already reserved: 294649856 byte(s). Limit: 294649856 byte(s). Max Memory Operation: The operation HashJoinIterator is reserving the most memory (114717769 bytes).

Я выяснил, что по некоторым причинам запрос выполняет очень неоптимизированные операции. Мне удалось выделить виновную часть запроса. Так что это минимальный запрос для воспроизведения этой ситуации:

SELECT 
  COUNT(DISTINCT a) a,
  COUNT(DISTINCT b) b
FROM foo
WHERE primary_split_key = "..."

Этот запрос имеет 2 предложения COUNT(DISTINCT ...), вот в чем проблема. Он создаст операцию map compute, которая умножит количество возвращаемых строк на число COUNT(DISTINCT ...) в предложении select.

Другими словами, если SELECT * FROM foo WHERE primary_split_key = "..." возвращает 10 строк, то при вычислении карты будет создано 20 строк (10row * 2countDistinct).

Если у нас будет 500 тыс. Строк и 3 count distinct, то получится 1,5 млн. Строк.

См. Объяснение запроса для 443 тыс. Строк и 2 COUNT(DISTINCT ...): query explanation

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

Мы работаем над его настройкой, чтобы он работал лучше. Однако мы хотели бы услышать от команды Cloud Spanner: это ожидаемое поведение гаечного ключа / счетчика, или это то, что вы хотите улучшить в ближайшем будущем?

Также всегда рады услышать альтернативы от опыт других пользователей.

РЕДАКТИРОВАТЬ: я обнаружил, что некоторые другие случаи запросов с ошибками с тем же сообщением об ошибке. Однако эти запросы явно используют функцию GENERATE_ARRAY следующим образом:

SELECT * 
FROM a
JOIN b ON a.id = b.id_a
LEFT JOIN UNNEST(GENERATE_ARRAY(0, a.some_number)) record

По этой причине я подозреваю, что generate_array не оптимизирован или имеет утечку памяти.

1 Ответ

1 голос
/ 21 февраля 2020

Что касается проблемы с памятью, то, согласно сообщению об ошибке, похоже, что HashJoinIterator потребляет больше всего памяти, GenerateArrayEvaluator просто является жертвой ситуации с нехваткой памяти, вызванной этим. Это также может быть в случае с вашим вторым запросом. Предположительно, в вашем текущем запросе у вас есть соединение ha sh, которое пытается построить таблицу ha sh на большом входе? Вы пытались повторно выполнить свой запрос с подсказкой join_method=apply_join?

Количество различных запросов, которые вы отправили, действительно делает столько же проходов по объединяемым данным, сколько и количество различных агрегатов (2 в вашем случае) , Хотя время выполнения 2s, показанное в плане, медленнее, чем я ожидал, время выполнения также зависит от ряда других факторов - загрузки ЦП вашего экземпляра и степени параллелизма вашего запроса, поэтому комментировать это сложно.

...