Являются ли РСУБД такими плохими, как описано в Hadoop: окончательное руководство?

Я читаю Hadoop: окончательный путеводитель Тома Уайта. В главе 13.6 "HBase vs RDMS" он сказал, что если у вас много данных, даже простые запросы, такие как получение 10 последних элементов, чрезвычайно дороги, и им пришлось переписать их с помощью python и PL/SQL.

В качестве примера он приводит следующий запрос:

SELECT id, stamp, type FROM streams 
WHERE type IN ('type1','type2','type3','type4',...,'typeN')
ORDER BY stamp DESC LIMIT 10 OFFSET 0;

И говорит: "Планировщик запросов RDBMS рассматривает этот запрос следующим образом:

MERGE (
  SELECT id, stamp, type FROM streams
    WHERE type = 'type1' ORDER BY stamp DESC,
  ...,
  SELECT id, stamp, type FROM streams
    WHERE type = 'typeK' ORDER BY stamp DESC
) ORDER BY stamp DESC LIMIT 10 OFFSET 0;

Проблема в том, что мы после только 10 лучших идентификаторов, но запрос планировщик фактически материализует все слияние, а затем лимиты на конец..... Мы действительно зашли так далеко, как написать пользовательский PL/Python scriptкоторый выполнял хаппорт.... В почти во всех случаях это превзошло встроенная реализация SQL и стратегия планировщиков запросов...

Ожидаемые перфорименты и экспериментальные результаты

Я не мог себе представить набор данных, который вызовет такие проблемы, которые вы должны написать pl/python, чтобы сделать такой простой запрос. Поэтому я немного поболтал об этой проблеме и придумал следующие наблюдения:

Производительность такого запроса ограничена O (KlogN). Потому что это можно перевести так:

SELECT * FROM (
  SELECT id, stamp, type FROM streams
    WHERE type = 'type1' ORDER BY stamp DESC LIMIT 10,
  UNION
  ...,
  SELECT id, stamp, type FROM streams
    WHERE type = 'typeK' ORDER BY stamp DESC LIMIT 10
) t ORDER BY stamp DESC LIMIT 10;

(обратите внимание на "LIMIT 10" в каждом запросе. Кстати, я знаю, что я не могу ограничивать и упорядочивать союзы, но я не разделял выборки для удобства чтения)

Каждый подзапрос должен выполняться так же быстро, как поиск правильной позиции в индексе O (logN) и возврат 10 элементов. Если мы повторим, что K раз, мы получаем O (KlogN).

И даже если планировщик запросов настолько плохой, что он не может оптимизировать первый запрос, мы всегда можем перевести его в запрос с объединениями и получить желаемую производительность без записи чего-либо в pl/python.

Чтобы дважды проверить мои вычисления, я выполнил запросы выше одного postgresql, заполненного 9 000 000 тестовых записей. Результаты подтвердили мои ожидания, что оба запроса были довольно быстрыми 100 мс для первого запроса и 300 мс для второго (одно с объединениями).

Итак, если запрос выполняется в 100 мс для 9 000 000 (logn = 23) записей, то для 9 000 000 000 (logn = 33) записей он должен работать в 140 мс.

Вопросы

  • Вы видите недостатки в рассуждениях выше?
  • Представляете ли вы набор данных, где вам нужно будет переписать такой запрос, как указано выше в файле pl/python?
  • Вы видите какую-либо ситуацию, когда такой запрос не будет работать в O (K log n)?

Ответ 1

Их утверждение о том, что планировщик запросов RDMBS принимает это решение для запроса, неверно, по крайней мере для Postgresql 9.0, и я должен представить себе и другие платформы. Я сделал быстрый тест с похожим запросом:

explain select * from client_attribute where client_attribute_type_code in ('UAG', 'RFR', 'IPA', 'FVD') order by client_attribute_id desc limit 10;

                                                      QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.00..0.93 rows=10 width=85)
   ->  Index Scan Backward using client_attribute_pkey on client_attribute  (cost=0.00..15516.47 rows=167234 width=85)
         Filter: (client_attribute_type_code = ANY ('{UAG,RFR,IPA,FVD}'::bpchar[]))
(3 rows)

Здесь client_attribute_id индексируется, так что он выполняет именно то, что вам нужно - проходит через индекс, применяет фильтр и останавливается, когда выход достигает предела.

Если столбец упорядочения не индексируется, сканирование и сортировка таблицы требуется, но только одно сканирование таблицы:

explain analyze select * from client_attribute where client_attribute_type_code in ('UAG', 'RFR', 'IPA', 'FVD') order by updated desc limit 10;

                                                              QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=13647.00..13647.03 rows=10 width=85) (actual time=180.961..180.964 rows=10 loops=1)
   ->  Sort  (cost=13647.00..14065.09 rows=167234 width=85) (actual time=180.960..180.961 rows=10 loops=1)
         Sort Key: updated
         Sort Method:  top-N heapsort  Memory: 26kB
         ->  Seq Scan on client_attribute  (cost=0.00..10033.14 rows=167234 width=85) (actual time=0.010..106.791 rows=208325 loops=1)
               Filter: (client_attribute_type_code = ANY ('{UAG,RFR,IPA,FVD}'::bpchar[]))

Это использует heapsort для поддержания 10 лучших результатов в ходе последовательного сканирования, что звучит точно так же, как и само решение, которое они пишут.

Ответ 2

Я не думаю, что Том Уайт говорит, что реляционные базы данных "плохие"; они не являются оптимальными для нереляционных, не установленных данных.

Долгое время хорошо известно, что графы глубоких объектов не поддаются реляционным базам данных. Они обычно встречаются в таких проблемах, как CAD-представления геометрических данных, где сборки состоят из сборок узлов. На самом деле цепочки ссылок очень длинные.

Базы данных объектов и графов были решением таких проблем, поскольку я знал о них в начале 90-х годов.

Реляционные базы данных потрясающие для реляционных, основанных на наборе данных данных. Но все данные не попадают в эту категорию. Вот почему NoSQL набирает долю ума.

Я думаю, что это то, о чем говорится в примере.

Ответ 3

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

Ответ 4

С SQL или NoSQL производительность будет ужасной, если вы создадите свои запросы неверным образом.

Я бы исправил этот пример, добавив проверку метки времени в предложение where. Если у вас много данных, вы можете предположить, что последние 10 записей с последней минуты - так зачем пытаться читать и сортировать все за последний месяц?

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