База данных в памяти, оптимизированная для чтения (мало / нет записи), когда операции включают сортировку, агрегирование и фильтрацию по любому столбцу

Я хочу загрузить ~ 10 ГБ данных в память и выполнить на нем SQL в виде:

  • Сортировка по одному столбцу (любому столбцу)
  • Агрегировать по одному столбцу (любому столбцу)
  • Фильтр по одному столбцу (любому столбцу)

Что может быть хорошим выбором для производительности? Некоторые решения, с которыми я столкнулся, которые могли бы сработать: TimesTen, ExtremeDB и SQL In -память или даже фреймы данных, такие как Vaex или Cudf.

Я хочу оптимизировать время запросов - это действительно все, что меня волнует. В качестве концептуального примера представьте себе что-то вроде таблицы Excel, в которой пользователь может сортировать или фильтровать любой столбец (и приложение заранее не знает, какие столбцы нужно «индексировать», потому что могут использоваться все столбцы).


Обновление: ниже я публикую свои результаты тестов с панд. Я знаю, что pandas не идеален для этого, но его здорово прототипировать и получать результаты тестов:

Файл (20 млн строк, 1,2 ГБ): https://storage.googleapis.com/gcp-files/Sales20M.csv.

  • Время загрузки (pd.read_csv): 10,7 с
  • Агрегация: (df.groupby('currency_code_id').count): 3,3 с
  • Сортировка: (df.sort_values('price')): 6,8 с
  • Поворот: (df.pivot_table(index='code',columns='territory_id', values='id', aggfunc=len, fill_value=0)): 3,4 с.

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

Какой инструмент подойдет для этого лучше всего?


person David542    schedule 20.07.2020    source источник
comment
Я не думаю, что вам понадобится база данных в памяти так же, как база данных с большим объемом памяти. 10 гб ничего не стоит.   -  person bbaird    schedule 20.07.2020
comment
Что касается Oracle, сортировка и / или агрегирование могут выполняться без использования опции в памяти. 10гб это ничего.   -  person Roberto Hernandez    schedule 20.07.2020
comment
@RobertoHernandez, когда вы говорите, что это ничего не значит, вы имеете в виду, например, что он может выполнить агрегацию менее чем за 0,1 секунды? Или что такое «ничто»?   -  person David542    schedule 20.07.2020
comment
@bbaird не могли бы вы уточнить?   -  person David542    schedule 20.07.2020
comment
Я имею в виду, что с правильным процессором и памятью на вашем сервере, база данных Oracle, in-memory не даст вам лучшей производительности для j10GB. Конечно, на основе вашего вопроса, агрегации по одному столбцу и сортировки по любому столбцу. Однако, если вы планируете получать все больше и больше данных, это может быть вариантом, но требует некоторой настройки и дополнительных затрат, поскольку не входит в стандартную лицензию на корпоративную версию. Кроме того, вам необходимо понять, как в памяти работают таблицы, которые могут быть изменены с помощью пакетной обработки.   -  person Roberto Hernandez    schedule 20.07.2020
comment
в конце концов, это компромисс. Если вы планируете только аналитику, и ваш план постоянно растет, то этот вариант стоит рассмотреть. Если для вас разница между 1 и 10 секундами большая, то вы можете это рассмотреть. SQL Analytics по таблицам без индексов с использованием параллелизма может дать отличные результаты при нулевых затратах.   -  person Roberto Hernandez    schedule 21.07.2020
comment
@RobertoHernandez - спасибо. Не могли бы вы прислать ссылку на это, пожалуйста? SQL Analytics по таблицам без индексов с использованием параллелизма может дать отличные результаты при нулевых затратах. Это полностью OLAP?   -  person David542    schedule 21.07.2020
comment
Без использования OLAP. Это также имеет дополнительную стоимость, так что не достойно. Я бы предпочел платить за оперативную память, а не за OLAP. Позвольте мне попытаться найти вам ссылку для этого. Я действительно говорил об опыте. Я разработал приложение, которое запускает запросы к более чем 100 миллионам записей в таблицах с использованием параллельной сортировки и агрегирования. Избегайте индексации и предоставьте PGA TARGET достаточно большой для HASH JOIN. Не используйте MEMORY TARGET. В любом случае, я постараюсь послать вам что-нибудь по поводу параллели. Я не могу поставить вам тестовый пример в разделе комментариев :)   -  person Roberto Hernandez    schedule 21.07.2020
comment
@ David542 Сложно сравнивать отдельные продукты баз данных на этом сайте. Но я уверен, что вместо того, чтобы рассматривать TimesTen, вам следует изучить вариант Oracle In-Memory. Oracle кажется более приверженным In-Memory, чем TimesTen - я удивлен, что TimesTen все еще доступен. Опция In-Memory теперь бесплатна для первых 16 ГБ .   -  person Jon Heller    schedule 21.07.2020
comment
@JonHeller Я обновил вопрос и добавил награду. Как вы думаете, сможете ли вы дать эталон для вышеупомянутого случая?   -  person David542    schedule 23.07.2020
comment
@RobertoHernandez см. Обновленный вопрос - я тоже добавил награду!   -  person David542    schedule 23.07.2020
comment
Сколько одновременных пользователей вы планируете использовать? Сколько одновременных выполнений запросов? Вы в порядке с грязным чтением или это должны быть зафиксированные данные? Как часто случаются вставки / обновления? 10 ГБ в одной таблице или во многих таблицах?   -  person Preben Huybrechts    schedule 23.07.2020
comment
@PrebenHuybrechts для этого теста, просто предположите, что один читатель / без параллелизма и без обновлений (на данный момент).   -  person David542    schedule 23.07.2020
comment
@ David542, некоторые поставщики коммерческих СУБД, в частности Oracle и Microsoft, запрещают раскрывать контрольные показатели в своем лицензионном соглашении. Я предлагаю вам скакать на лошадях.   -  person Dan Guzman    schedule 24.07.2020
comment
FWIW, этот запрос к кластеризованной таблице columnstore SQL Server выполняется на моем ПК за 10 мс: SELECT currency_code_id, COUNT(*) FROM dbo.Sales20M GROUP BY currency_code_id;. YMMV.   -  person Dan Guzman    schedule 24.07.2020
comment
Я добавил несколько (очень) свободных тестов cuDF / pandas в ответ на случай, если это будет полезно. Обратите внимание, что pivot еще не доступен.   -  person Nick Becker    schedule 24.07.2020
comment
Я думаю, что @PrebenHuybrechts действительно задает ключевой вопрос, когда задается вопросом, должна ли это быть одна таблица или несколько таблиц. Вы указали время загрузки как релевантное, но не как релевантное. Таким образом, выбор базовой технологии не кажется основным отличительным признаком. Основным отличием, вероятно, будет то, сколько предварительной обработки вам разрешено выполнять? В качестве тривиального примера, если вы предварительно агрегируете каждый столбец во время загрузки, вам не нужно агрегировать во время выполнения. Оптимизация отдельных структур для каждого варианта использования - в любой технологии - конечно же, превзойдет любой универсальный подход.   -  person allmhuran    schedule 25.07.2020
comment
@allmhuran одна таблица. Я не думаю, что предварительная агрегация действительно будет жизнеспособным вариантом, поскольку мы не знаем, какие столбцы будут использоваться заранее. Например, предположим, что у пользователя 100 столбцов. Создаем ли мы 100 индексов / предварительную агрегацию для одной группы? Что, если пользователь хочет сделать group by x,y, теперь это 10 тыс. Индексов / агрегатов.   -  person David542    schedule 25.07.2020


Ответы (5)


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

Например, для SQL Server есть много вариантов:

или просто с использованием секционирования, или осколков PostgreSQL или MongoDB. Существует так много примеров и демонстраций таких технологий, показывающих производительность менее секунды ... но это зависит от случая, потому что есть ограничения.

Например:

  • индексы хранилища столбцов могут иметь проблемы при фильтрации и получении только нескольких строк по сравнению с традиционными индексами
  • OLTP в памяти

В вашем случае, имея 10 ГБ данных и желая хорошей производительности, вам не нужно делать что-то особенное. Просто проанализируйте и нормализуйте данные перед вставкой и создайте соответствующие индексы.

Well begun is half done и потратив некоторое время на правильную запись данных, вы получите необходимую производительность.

Например:

  1. Вставьте файл данных в таблицу

  2. Для каждого столбца в таблице

    • perform count distinct
    • если значение меньше count distinct / count меньше X, создайте отдельную таблицу со столбцами id и value
    • вставьте в него различные значения
    • добавить новый столбец в таблицу и добавить туда новые идентификаторы
    • создать индекс для этого столбца

Уменьшение размера таблицы увеличит количество операций ввода-вывода. Поиск и группировка по номерам выполняется быстрее, чем по тексту.

Конечно, вам нужно изменить приложение - вместо поиска по some city name вы будете фильтровать его по ID. И после count городов на countries ids вы выполните второй запрос, чтобы преобразовать эти ids в names.

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


На виртуальной машине с 8 ГБ ОЗУ и 4 виртуальными процессорами. К сожалению, он находится на жестком диске и отсюда довольно плохой ввод-вывод. Запуск SQL Server 2019 Standard edition. Итак, из-за оборудования загрузка данных происходит медленно.

  • 2 минуты для таблицы (я использую интерфейс SSMS для импорта данных, если вы используете bcp, будет лучше, но ... HDD в конце концов)
  • 1,5 минуты на добавление индекса по каждому полю

Итак, таблица выглядит так:

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

Вы видите, какой я ленивый. Без нормализации и индексации каждого поля, что приводит к выделению 3.2 ГБ для данных и индексов:

exec sp_spaceused 'dbo.Sales20M'

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

Но некоторые результаты:

выберите count (*), currency_code_id из группы dbo.Sales20M по currency_code_id

конечно, 0 секунд, поскольку мы используем индекс:

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

select TOP 10 * from dbo.Sales20M   order by PRICE_IN_USD desc;

0 секунд, но обратите внимание, что я использую TOP - в основном, когда вы сортируете, вам нужно отображать часть строк, верно? И если я по какой-то причине сортирую целые строки:

select  * 
INTO #X
from dbo.Sales20M   order by PRICE_IN_USD desc;

он работает в течение 2 секунд (я вставляю данные в таблицу, так как рендеринг тоже требует времени).

Что касается PIVOT, он не очень быстр в SQL Server, но вы можете использовать R, если вам нужно что-то массивное. Я не понимаю вашего, но сделал PIVOT по Code и territory_id вычисляя среднюю цену в долларах:

SELECT *
FROM 
( 
    SELECT Code
              ,territory_id
              ,AVG(price_in_usd) price_in_usd
    FROM dbo.Sales20M
    GROUP BY Code
            ,territory_id
)DS
PIVOT
(
    MAX(price_in_usd) FOR territory_id IN ([AE], [AG], [AI], [AL], [AM], [AO], [AR], [AT], [AU], [AW], [AZ], [BA], [BE], [BF], [BG], [BH], [BJ], [BM], [BN], [BO], [BR], [BS], [BW], [BY], [BZ], [CA], [CH], [CI], [CL], [CO], [CR], [CV], [CY], [CZ], [DE], [DK], [DM], [DO], [EC], [EE], [EG], [ES], [FI], [FJ], [FM], [FR], [GA], [GB], [GD], [GH], [GM], [GR], [GT], [GW], [HK], [HN], [HR], [HT], [HU], [ID], [IE], [IL], [IN], [IS], [IT], [JM], [JO], [JP], [KG], [KH], [KN], [KR], [KW], [KY], [KZ], [LA], [LB], [LK], [LT], [LU], [LV], [MD], [MK], [ML], [MN], [MO], [MT], [MU], [MX], [MY], [MZ], [NA], [NE], [NI], [NL], [NO], [NP], [NZ], [OM], [PA], [PE], [PG], [PH], [PL], [PT], [PY], [QA], [RU], [RW], [SA], [SE], [SG], [SI], [SK], [SN], [SV], [SZ], [TG], [TH], [TJ], [TM], [TR], [TT], [TW], [TZ], [UA], [UG], [US], [UY], [UZ], [VE], [VG], [VN], [ZA], [ZM], [ZW])
) PVT;

Я снова ленив и не использую динамический PIVOT. Проходит 0-1 секунды.

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


Заключение:

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

person gotqn    schedule 24.07.2020
comment
В чем будет разница (за и против) между использованием хранилища в памяти и столбчатого хранилища для вышеуказанного? - person David542; 26.07.2020
comment
@ David542 Я хочу сказать, что для такого количества данных вам не нужны никакие причудливые технологии. Вы можете использовать, например, PostgreSQL, который является бесплатным и будет работать довольно хорошо. В любом случае, я поделюсь некоторыми результатами SQL Server, чтобы доказать свою точку зрения. - person gotqn; 27.07.2020

Я построил реальный тестовый пример с CSV, предоставленным с 20M записями. В качестве доказательства концепции я сравню два варианта в Oracle. Вариант Oracle In-Memory против параллельного запроса Oracle. Идея состоит в том, чтобы увидеть, соответствуют ли результаты вашим ожиданиям.

  • In Memory Option - это лицензионная плата, поэтому вам придется заплатить дополнительные расходы.
  • Параллельный запрос - это функция, включенная в Enterprise Edition базы данных Oracle.

Лабораторная работа: Сервер Linux Red Hat 7: 16 процессоров и 32 ГБ ОЗУ в разделе памяти = виртуальный сервер 4 ГБ на VMware с использованием серии оборудования IBM ESX.

Элементы для тестового примера

SQL> create tablespace tbtest datafile '/bbdd_odcgrc1r/datos/test.dbf' size 2g autoextend on next 100m maxsize 10g ;

Tablespace created.

SQL> create user test_perf identified by "Oracle_1" ;

User created.

SQL> grant connect to test_perf ;

Grant succeeded.

SQL> grant create table to test_perf ;

Grant succeeded.

SQL> alter user test_perf quota unlimited on tbtest ;

User altered.

SQL>

Параллельный запрос Oracle без опции в памяти

Я загрузил файл csv, используя загрузчик sql с прямым путем:

Table TEST_PERFORMANCE:
  20000000 Rows successfully loaded.
  0 Rows not loaded due to data errors.
  0 Rows not loaded because all WHEN clauses were failed.
  0 Rows not loaded because all fields were null.

Bind array size not used in direct path.
Column array  rows :    5000
Stream buffer bytes:  256000
Read   buffer bytes: 1048576

Total logical records skipped:          1
Total logical records read:      20000000
Total logical records rejected:         0
Total logical records discarded:        0
Total stream buffers loaded by SQL*Loader main thread:     4112
Total stream buffers loaded by SQL*Loader load thread:        0

Run began on Sat Jul 25 00:57:23 2020
Run ended on Sat Jul 25 00:57:34 2020

Очевидно, что загрузка файла в базу данных - это совсем не то же самое, что загрузка файла в panda. Поскольку Panda не нужно загружать данные ни во что (в данном случае в файл данных базы данных)

SQL> desc test_perf.test_performance
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 ID                                                 NUMBER
 DATE_ID                                            DATE
 INSTANCE_ID                                        NUMBER
 TERRITORY_ID                                       VARCHAR2(10 CHAR)
 CODE                                               VARCHAR2(10 CHAR)
 PRICE                                              NUMBER
 CURRENCY_CODE_ID                                   VARCHAR2(10 CHAR)
 PRICE_IN_USD                                       NUMBER

Собираю статистику таблицы и теперь посмотрим как она себя ведет

SQL> exec dbms_stats.gather_table_stats ( 'TEST_PERF' , 'TEST_PERFORMANCE' , block_sample => true );

PL/SQL procedure successfully completed.

Группировать по currency_code_id (без сортировки)

SQL> select count(*) , currency_code_id from test_perf.test_performance group by currency_code_id
90 rows selected.

Elapsed: 00:00:00.35

Группировка по currency_code_id, сортировка по счетчику

SQL>  select count(*) , currency_code_id from test_perf.test_performance group by currency_code_id order by count(*) desc ;

90 rows selected.

Elapsed: 00:00:00.70

Сортировка всех записей и их отображение по sqlplus занимает много времени только на создание вывода, но сам запрос выполняется очень быстро

SQL> select * from test_perf.test_performance order by PRICE_IN_USD desc ;

20000000 rows selected.

Elapsed: 00:01:31.48

Execution Plan
----------------------------------------------------------
Plan hash value: 1897103579

---------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation               | Name             | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |    TQ  |IN-OUT| PQ Distrib |
---------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |                  |    20M|   972M|       |  5940   (1)| 00:00:01 |        |      |            |
|   1 |  PX COORDINATOR         |                  |       |       |       |            |          |        |      |            |
|   2 |   PX SEND QC (ORDER)    | :TQ10001         |    20M|   972M|       |  5940   (1)| 00:00:01 |  Q1,01 | P->S | QC (ORDER) |
|   3 |    SORT ORDER BY        |                  |    20M|   972M|  1376M|  5940   (1)| 00:00:01 |  Q1,01 | PCWP |            |
|   4 |     PX RECEIVE          |                  |    20M|   972M|       |   435   (1)| 00:00:01 |  Q1,01 | PCWP |            |
|   5 |      PX SEND RANGE      | :TQ10000         |    20M|   972M|       |   435   (1)| 00:00:01 |  Q1,00 | P->P | RANGE      |
|   6 |       PX BLOCK ITERATOR |                  |    20M|   972M|       |   435   (1)| 00:00:01 |  Q1,00 | PCWC |            |
|   7 |        TABLE ACCESS FULL| TEST_PERFORMANCE |    20M|   972M|       |   435   (1)| 00:00:01 |  Q1,00 | PCWP |            |
---------------------------------------------------------------------------------------------------------------------------------

Note
-----
   - Degree of Parallelism is 32 because of table property


Statistics
----------------------------------------------------------
        364  recursive calls
          3  db block gets
      36963  consistent gets
      45558  physical reads
       2860  redo size
  703698256  bytes sent via SQL*Net to client
   14667271  bytes received via SQL*Net from client
    1333335  SQL*Net roundtrips to/from client
         64  sorts (memory)
          1  sorts (disk)
   20000000  rows processed

Реальное время, затрачиваемое на сам запрос,

SQL> select * from test_perf.test_performance order by PRICE_IN_USD desc ;
Elapsed: 00:00:02.03

Execution Plan
----------------------------------------------------------
Plan hash value: 1897103579

---------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation               | Name             | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |    TQ  |IN-OUT| PQ Distrib |
---------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |                  |    20M|   972M|       |  5940   (1)| 00:00:01 |        |      |            |
|   1 |  PX COORDINATOR         |                  |       |       |       |            |          |        |      |            |
|   2 |   PX SEND QC (ORDER)    | :TQ10001         |    20M|   972M|       |  5940   (1)| 00:00:01 |  Q1,01 | P->S | QC (ORDER) |
|   3 |    SORT ORDER BY        |                  |    20M|   972M|  1376M|  5940   (1)| 00:00:01 |  Q1,01 | PCWP |            |
|   4 |     PX RECEIVE          |                  |    20M|   972M|       |   435   (1)| 00:00:01 |  Q1,01 | PCWP |            |
|   5 |      PX SEND RANGE      | :TQ10000         |    20M|   972M|       |   435   (1)| 00:00:01 |  Q1,00 | P->P | RANGE      |
|   6 |       PX BLOCK ITERATOR |                  |    20M|   972M|       |   435   (1)| 00:00:01 |  Q1,00 | PCWC |            |
|   7 |        TABLE ACCESS FULL| TEST_PERFORMANCE |    20M|   972M|       |   435   (1)| 00:00:01 |  Q1,00 | PCWP |            |
---------------------------------------------------------------------------------------------------------------------------------

Note
-----
   - Degree of Parallelism is 32 because of table property

Выберите отдельный идентификатор_территории таблицы

SQL> select distinct territory_id from test_perf.test_performance order by territory_id desc ;

136 rows selected.

Elapsed: 00:00:00.58

В качестве опоры я выбрал этот простой пример

SQL> select * from test_perf.test_performance
pivot ( count(*) for TERRITORY_ID in 
(
'ZW',
'ZM',
'ZA',
'VN',
'VG',
'VE',
'UZ',
'UY',
'US',
'UG',
'UA',
'TZ',
'TW',
'TT',
'TR',
'TM',
'TJ',
'TH',
'TG',
'SZ',
'SV',
'SN',
'SK',
'SI',
'SG',
'SE',
'SA',
'RW',
'RU',
'QA',
'PY',
'PT',
'PL',
'PH',
'PG',
'PE',
'PA',
'OM',
'NZ',
'NP',
'NO',
'NL',
'NI',
'NE',
'NA',
'MZ',
'MY',
'MX',
'MU',
'MT',
'MO',
'MN',
'ML',
'MK',
'MD',
'LV',
'LU',
'LT',
'LK',
'LB',
'LA',
'KZ',
'KY',
'KW',
'KR',
'KN',
'KH',
'KG',
'JP',
'JO',
'JM',
'IT',
'IS',
'IN',
'IL',
'IE',
'ID',
'HU',
'HT',
'HR',
'HN',
'HK',
'GW',
'GT',
'GR',
'GM',
'GH',
'GD',
'GB',
'GA',
'FR',
'FM',
'FJ',
'FI',
'ES',
'EG',
'EE',
'EC',
'DO',
'DM',
'DK',
'DE',
'CZ',
'CY',
'CV',
'CR',
'CO',
'CL',
'CI',
'CH',
'CA',
'BZ',
'BY',
'BW',
'BS',
'BR',
'BO',
'BN',
'BM',
'BJ',
'BH',
'BG',
'BF',
'BE',
'BA',
'AZ',
'AW',
'AU',
'AT',
'AR',
'AO',
'AM',
'AL',
'AI',
'AG',
'AE'
) ) 
 order by id 

Elapsed: 00:00:04.74

Вариант Oracle In Memory

Я сконфигурировал 4 ГБ внутренней памяти, что не так уж и много.

Total System Global Area 1.2885E+10 bytes
Fixed Size                 12192520 bytes
Variable Size            5184161016 bytes
Database Buffers         3388997632 bytes
Redo Buffers                4583424 bytes
In-Memory Area           4294967296 bytes
Database mounted.
Database opened.
SQL> SHOW PARAMETER INMEMORY

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
inmemory_adg_enabled                 boolean     TRUE
inmemory_clause_default              string
inmemory_expressions_usage           string      ENABLE
inmemory_force                       string      DEFAULT
inmemory_max_populate_servers        integer     8
inmemory_query                       string      ENABLE
inmemory_size                        big integer 4G
inmemory_trickle_repopulate_servers_ integer     1
percent
inmemory_virtual_columns             string      MANUAL
optimizer_inmemory_aware             boolean     TRUE

SQL> ALTER TABLE TEST_PERF.TEST_PERFORMANCE INMEMORY PRIORITY HIGH;

Table altered.

SQL> select segment_name
,    partition_name
  2    3  ,    inmemory_size / 1024 / 1024 as inmemory_size_mb
,    bytes / 1024 / 1024 as bytes_mb
,    populate_status
  4    5    6  ,    trunc(bytes / inmemory_size, 1) * 100 as compression_ratio
from v$im_segments
  7    8  order by segment_name, partition_name;

SEGMENT_NAME
--------------------------------------------------------------------------------
PARTITION_NAME
--------------------------------------------------------------------------------
INMEMORY_SIZE_MB   BYTES_MB POPULATE_STAT COMPRESSION_RATIO
---------------- ---------- ------------- -----------------
TEST_PERFORMANCE

          362.25 514.046875 COMPLETED                   140

SQL> select count(*),length(date_id) from test_perf.test_performance group by length(date_id)

Execution Plan
----------------------------------------------------------
Plan hash value: 3227171220

-----------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                         | Name             | Rows  | Bytes | Cost (%CPU)| Time     |    TQ  |IN-OUT| PQ Distrib |
-----------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                  |                  |    11 |   121 |    29  (59)| 00:00:01 |        |      |            |
|   1 |  PX COORDINATOR                   |                  |       |       |            |          |        |      |            |
|   2 |   PX SEND QC (RANDOM)             | :TQ10001         |    11 |   121 |    29  (59)| 00:00:01 |  Q1,01 | P->S | QC (RAND)  |
|   3 |    HASH GROUP BY                  |                  |    11 |   121 |    29  (59)| 00:00:01 |  Q1,01 | PCWP |            |
|   4 |     PX RECEIVE                    |                  |    11 |   121 |    29  (59)| 00:00:01 |  Q1,01 | PCWP |            |
|   5 |      PX SEND HASH                 | :TQ10000         |    11 |   121 |    29  (59)| 00:00:01 |  Q1,00 | P->P | HASH       |
|   6 |       HASH GROUP BY               |                  |    11 |   121 |    29  (59)| 00:00:01 |  Q1,00 | PCWP |            |
|   7 |        PX BLOCK ITERATOR          |                  |    20M|   209M|    14  (15)| 00:00:01 |  Q1,00 | PCWC |            |
|   8 |         TABLE ACCESS INMEMORY FULL| TEST_PERFORMANCE |    20M|   209M|    14  (15)| 00:00:01 |  Q1,00 | PCWP |            |
-----------------------------------------------------------------------------------------------------------------------------------

Note
-----
   - Degree of Parallelism is 32 because of table property

Проверим несколько запросов.

Группировать по территории

SQL> select count(*),TERRITORY_ID from test_perf.test_performance group by TERRITORY_ID ;

136 rows selected.

Elapsed: 00:00:00.24

Группировать по instance_id

SQL> select count(*) , INSTANCE_ID from test_perf.test_performance group by INSTANCE_ID

11251 rows selected.

Elapsed: 00:00:00.27

Группировать по 2 полям

SQL> select count(*), instance_id, territory_id from test_perf.test_performance group by instance_id, territory_id ;

278269 rows selected.

Elapsed: 00:00:00.84

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

Несколько статистических функций и группировка по

SQL> select territory_id,sum(to_number(price)),avg(to_number(price)),max(to_number(price)),min(to_number(price))
  2  from test_perf.test_performance group by territory_id ;

Elapsed: 00:00:00.57

Однако PIVOT обычно не подходит для хранения в памяти, поскольку для таблиц используется хранилище столбцов.

The same query with pivot executed before takes 

    Elapsed: 00:00:15.93

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

SQL> alter table test_perf.TEST_PERFORMANCE noparallel ;

Table altered.

SQL> select count(*) , currency_code_id from test_perf.test_performance group by currency_code_id
  2  ;

90 rows selected.

Elapsed: 00:00:02.14

Execution Plan
----------------------------------------------------------
Plan hash value: 151279035

------------------------------------------------------------------------------------------------
| Id  | Operation                   | Name             | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |                  |    90 |   450 |   909  (62)| 00:00:01 |
|   1 |  HASH GROUP BY              |                  |    90 |   450 |   909  (62)| 00:00:01 |
|   2 |   TABLE ACCESS INMEMORY FULL| TEST_PERFORMANCE |    20M|    95M|   411  (16)| 00:00:01 |
------------------------------------------------------------------------------------------------

IMPQ

В последней части я оставляю лучший из всех вариантов, комбинацию параллельного и inmemory, который называется IMPQ или In Memory Parallel Query. Эта функция обеспечивает лучшее из обоих миров, и для того, чтобы начать работу, вы должны включить параллелизм для своей таблицы, поместить таблицу в область памяти и определить для параметра PARALLEL_DEGREE_POLICY значение AUTO.

Примером этого может быть

SQL> alter session set parallel_degree_policy=auto ;

Session altered.

SQL> set autotrace traceonly explain
SQL> select count(*),territory_id from test_perf.test_performance group by territory_id

Execution Plan
----------------------------------------------------------
Plan hash value: 3227171220

-----------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                         | Name             | Rows  | Bytes | Cost (%CPU)| Time     |    TQ  |IN-OUT| PQ Distrib |
-----------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                  |                  |   136 |   408 |    78  (59)| 00:00:01 |        |      |            |
|   1 |  PX COORDINATOR                   |                  |       |       |            |          |        |      |            |
|   2 |   PX SEND QC (RANDOM)             | :TQ10001         |   136 |   408 |    78  (59)| 00:00:01 |  Q1,01 | P->S | QC (RAND)  |
|   3 |    HASH GROUP BY                  |                  |   136 |   408 |    78  (59)| 00:00:01 |  Q1,01 | PCWP |            |
|   4 |     PX RECEIVE                    |                  |   136 |   408 |    78  (59)| 00:00:01 |  Q1,01 | PCWP |            |
|   5 |      PX SEND HASH                 | :TQ10000         |   136 |   408 |    78  (59)| 00:00:01 |  Q1,00 | P->P | HASH       |
|   6 |       HASH GROUP BY               |                  |   136 |   408 |    78  (59)| 00:00:01 |  Q1,00 | PCWP |            |
|   7 |        PX BLOCK ITERATOR          |                  |    20M|    57M|    38  (16)| 00:00:01 |  Q1,00 | PCWC |            |
|   8 |         TABLE ACCESS INMEMORY FULL| TEST_PERFORMANCE |    20M|    57M|    38  (16)| 00:00:01 |  Q1,00 | PCWP |            |
-----------------------------------------------------------------------------------------------------------------------------------

Note
-----
   - automatic DOP: Computed Degree of Parallelism is 12

Проверьте в приведенном выше операторе изменение DOP с 32 на 12. Это означает, что IMPQ определил, что наилучшая степень для оператора равна 12 вместо 32, которая была получена из CPU_COUNT * 2 (поскольку таблица была создана со степенью по умолчанию автомат).

Этот запрос ниже теперь с IMPQ выполняется всего за 0,14 секунды вместо 0,35.

SQL> select count(*),territory_id from test_perf.test_performance group by territory_id
  2  ;

136 rows selected.

Elapsed: 00:00:00.14

Этот другой запрос теперь занимает 0,14 секунды, тогда как раньше занимал 0,70 секунды.

SQL> select count(*) , currency_code_id from test_perf.test_performance group by currency_code_id order by count(*) desc ;

90 rows selected.

Elapsed: 00:00:00.14

Резюме

Если вам нужно время меньше секунды, и вы в основном используете аналитику, вариант Oracle in-memory может быть хорошей идеей. В этом случае вы можете также включить IMPQ для получения наилучших результатов. Необходимо будет поработать над тем, какие столбцы следует или не включать, как таблица заполняется в области памяти и т. Д. Чем сложнее аналитика, тем выше производительность, которую вы заметите.

Тем не менее, если вы можете жить с временем от 1 до 5 секунд, параллельный запрос без индексов может быть бесплатным решением, которое довольно легко настроить.

Не стесняйтесь комментировать что-либо или просить каких-либо разъяснений относительно тестового примера.

person Roberto Hernandez    schedule 25.07.2020
comment
вау, какой обширный ответ. Спасибо, что нашли время! Из любопытства, почему с точки зрения производительности с Pivot-запросами все так сильно отличается? Чем же сводный запрос так сильно отличается от обычного агрегатного? - person David542; 26.07.2020
comment
Запросы PIvot основаны на преобразовании строк в столбцы. В памяти используется так называемое хранилище столбцов. По сути, когда вы вызываете PIVOT, вы переходите от столбцов к строкам, чтобы вернуться от строк к столбцам. - person Roberto Hernandez; 26.07.2020
comment
Понятно. Тогда какой обычно лучший или предлагаемый способ обработки сводных запросов в Oracle? Или они считаются довольно редкими и не совсем оптимизированными (точнее, не первостепенным фактором при выборе типа хранилища). - person David542; 28.07.2020
comment
@ David542, вы можете оптимизировать запросы PIVOT в INMEMORY, если удалите их из хранилища столбцов. Это обычная операция, также вы можете изменить таблицу, добавить больше записей и т. Д. Лучший подход, например, для таблиц, которые заполняются пакетными процессами, - удалить их из памяти в начале, запустить пакет, собрать статистику и поместить их назад к области inmemory, определяющей, какие столбцы должны подлежать хранению столбцов (обычно те, для которых вы не хотите сводить) - person Roberto Hernandez; 28.07.2020
comment
@ David542, также хорошая рекомендация - создавать сводные таблицы как материализованные результаты пакета. Конечно, вам нужно больше места, но это позволяет лучше использовать данные, поскольку сводные результирующие таблицы не являются частью области INMEMORY. - person Roberto Hernandez; 28.07.2020

Если это полезно, вот несколько быстрых сравнений, которые я использую для чтения, агрегирования и сортировки вашего файла на 32-гигабайтном V100. Для SQL вы можете посмотреть BlazingSQL

В общем, по сравнению с пандами на моей машине, похоже, что чтение этого файла происходит примерно в 20 раз быстрее, агрегация примерно в 150 раз быстрее, а сортировка примерно в 50 раз быстрее. Обратите внимание, что поддержка cuDF для pivot пока недоступна, поэтому ниже она не указана.

import cudf
​
cudf.set_allocator(
    pool=True, # half the GPU by default
)
​
%time df = cudf.read_csv("Sales20M.csv")
%time x = df.groupby('currency_code_id').count()
%time x = df.sort_values('price')
CPU times: user 402 ms, sys: 157 ms, total: 559 ms
Wall time: 557 ms
CPU times: user 17.1 ms, sys: 8.97 ms, total: 26.1 ms
Wall time: 25.8 ms
CPU times: user 96.2 ms, sys: 32 ms, total: 128 ms
Wall time: 128 ms
import pandas as pd
​
%time df = pd.read_csv("Sales20M.csv")
%time x = df.groupby('currency_code_id').count()
%time x = df.sort_values('price')
CPU times: user 8.23 s, sys: 1.67 s, total: 9.9 s
Wall time: 9.89 s
CPU times: user 3.32 s, sys: 355 ms, total: 3.68 s
Wall time: 3.67 s
CPU times: user 5.77 s, sys: 586 ms, total: 6.35 s
Wall time: 6.35 s
person Nick Becker    schedule 24.07.2020
comment
спасибо, это действительно здорово видеть. Два вопроса: почему нет поворота? Эта операция не работает на GPU или вообще возможна? Как операция загрузки такая быстрая? Вы указываете тип для каждого столбца или как он определяет тип и загружается так быстро? Наконец, есть ли какие-либо предостережения в отношении графического процессора - например, известные запросы аналитического типа, которые не подходят для него (возможно, поиск «сводная таблица» и т. Д.). Спасибо еще раз! - person David542; 26.07.2020
comment
Всем хороших вопросов. Однако на некоторые из них будет сложно дать эффективный ответ в комментариях. Я разделю их для наглядности. 1) Pivot еще не реализован еще, но нет фундаментальной причины, по которой его нельзя сделать на графическом процессоре (github.com/rapidsai/cudf/issues/1214). - person Nick Becker; 26.07.2020
comment
Операция загрузки выполняется быстро по нескольким причинам, но в общих чертах можно сказать, что чтение данных с помощью cuDF создает карту памяти, а затем копирует байты из памяти хоста в память графического процессора. Реализации для синтаксического анализа и распаковки с ускорением на GPU работают быстрее. Как и в пандах, типы dtypes могут быть указаны пользователем, но по умолчанию они являются предполагаемыми. - person Nick Becker; 26.07.2020
comment
Библиотеки RAPIDS предназначены для обеспечения скорости графического процессора в Python без ущерба для гибкости или общих API-интерфейсов модели программирования PyData. Есть предостережения, но эффективное лечение слишком долго для комментариев. Приведу один пример: наивная итерация строка за строкой через серию / кадр / массив графического процессора обходится дороже, чем эквивалент процессора. Построчные пользовательские функции должны использовать Numba. Вы можете посмотреть cuDF, cuPy (по предпочтительным сетям), BlazingSQL и другие. docs.rapids.ai/overview/latest.pdf может предоставить дополнительную информацию. Отказ от ответственности: я являюсь членом этой команды. - person Nick Becker; 26.07.2020

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

  1. фильтрация,
  2. присоединение
  3. группировка
  4. агрегирование
  5. Как они масштабируются.

Эти знания помогают подготовить оптимальную физическую среду для запросов, таких как

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

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

Другой важной частью настройки запроса является нестандартное мышление, выходящее за рамки алгоритмов, доступных оптимизатору при использовании очевидных инструментов. Будь креативным. Допустим, у вас есть запрос, который плохо работает, даже если вы организовали оптимальную физическую среду. Для используемых конструкций запроса алгоритмы, доступные оптимизатору, - это x, y и z, и оптимизатор выбрал лучшее, что он мог в данных обстоятельствах. Тем не менее, запрос работает плохо. Можете ли вы представить себе теоретический план с алгоритмом, который может дать гораздо более эффективный запрос? Если вы можете себе это представить, скорее всего, вы сможете добиться этого с помощью некоторого переписывания запроса, возможно, с менее очевидными конструкциями запроса для задачи.

Для получения дополнительной информации перейдите по ссылке ниже.

Профессионалы

person Nadeem Taj    schedule 28.07.2020

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

TimesTen больше предназначен для транзакций ACID с низкой задержкой / высокой пропускной способностью и запросов аналитического типа.

person ScalableDBDoug    schedule 16.10.2020
comment
Спасибо за этот ответ - не могли бы вы пояснить разницу между ними? Например, не будут ли все запросы, которые я перечислил в вопросе, рассматривать как вариант запроса аналитического типа? - person David542; 17.10.2020
comment
Привет, Дэвид, все твои запросы носят аналитический характер. Oracle In-Memory подойдет, так как вам не нужно создавать какие-либо индексы для быстрого сканирования по столбцам. TimesTen может выполнять аналитику, но это хранилище строк, а не столбцов. - person ScalableDBDoug; 18.10.2020