Почему Apache Calcite оценивает 100 строк для всех таблиц, содержащихся в запросе?

Недавно я попытался выполнить запрос в Apache Calcite, используя три файла CSV в виде таблиц.

  • TTLA_ONE содержит 59 строк
  • TTLR_ONE содержит 61390 строк
  • EMPTY_T содержит 0 строк

Это запрос, который выполняется:

EXPLAIN PLAN FOR SELECT COUNT(*) as NUM 
FROM TTLA_ONE A 
INNER JOIN TTLR_ONE B1 ON A.X = B1.X
INNER JOIN TTLR_ONE B2 ON B2.X = B1.X
INNER JOIN EMPTY_T C1 ON C1.X = B2.Y
INNER JOIN EMPTY_T C2 ON C2.X = C2.X

Результат запроса всегда равен нулю, потому что мы соединяемся с пустой таблицей. Полученный план:

EnumerableAggregate(group=[{}], NUM=[COUNT()])
  EnumerableJoin(condition=[=($1, $4)], joinType=[inner])
    EnumerableJoin(condition=[=($0, $1)], joinType=[inner])
      EnumerableInterpreter
        BindableTableScan(table=[[STYPES, TTLA_ONE]])
      EnumerableCalc(expr#0..1=[{inputs}], X=[$t0])
        EnumerableInterpreter
          BindableTableScan(table=[[STYPES, TTLR_ONE]])
    EnumerableJoin(condition=[=($1, $3)], joinType=[inner])
      EnumerableJoin(condition=[true], joinType=[inner])
        EnumerableCalc(expr#0=[{inputs}], expr#1=[IS NOT NULL($t0)], X=[$t0], $condition=[$t1])
          EnumerableInterpreter
            BindableTableScan(table=[[STYPES, EMPTY_T]])
        EnumerableInterpreter
          BindableTableScan(table=[[STYPES, EMPTY_T]])
      EnumerableInterpreter
        BindableTableScan(table=[[STYPES, TTLR_ONE]])

Можно отметить, что пустые таблицы используются в плане в самом конце.

Я добавляю пример к этому тестовый код.

Я углубился в код, включил журнал для отладки и увидел, что все строки таблицы оцениваются как 100, но это неверно.

Ниже можно найти оценку плана с журналами, установленными в режиме отладки:

  EnumerableJoin(condition=[=($1, $4)], joinType=[inner]): rowcount = 3.0375E7, cumulative cost = {3.075002214917643E7 rows, 950.0 cpu, 0.0 io}, id = 26284
EnumerableJoin(condition=[=($0, $1)], joinType=[inner]): rowcount = 1500.0, cumulative cost = {2260.517018598809 rows, 400.0 cpu, 0.0 io}, id = 26267
  EnumerableInterpreter: rowcount = 100.0, cumulative cost = {50.0 rows, 50.0 cpu, 0.0 io}, id = 26260
    BindableTableScan(table=[[STYPES, TTLA_ONE]]): rowcount = 100.0, cumulative cost = {1.0 rows, 1.01 cpu, 0.0 io}, id = 7789
  EnumerableCalc(expr#0..1=[{inputs}], X=[$t0]): rowcount = 100.0, cumulative cost = {150.0 rows, 350.0 cpu, 0.0 io}, id = 26290
    EnumerableInterpreter: rowcount = 100.0, cumulative cost = {50.0 rows, 50.0 cpu, 0.0 io}, id = 26263
      BindableTableScan(table=[[STYPES, TTLR_ONE]]): rowcount = 100.0, cumulative cost = {1.0 rows, 1.01 cpu, 0.0 io}, id = 7791
EnumerableJoin(condition=[=($1, $3)], joinType=[inner]): rowcount = 135000.0, cumulative cost = {226790.8015771949 rows, 550.0 cpu, 0.0 io}, id = 26282
  EnumerableJoin(condition=[true], joinType=[inner]): rowcount = 9000.0, cumulative cost = {9695.982870329724 rows, 500.0 cpu, 0.0 io}, id = 26277
    EnumerableCalc(expr#0=[{inputs}], expr#1=[IS NOT NULL($t0)], X=[$t0], $condition=[$t1]): rowcount = 90.0, cumulative cost = {140.0 rows, 450.0 cpu, 0.0 io}, id = 26288
      EnumerableInterpreter: rowcount = 100.0, cumulative cost = {50.0 rows, 50.0 cpu, 0.0 io}, id = 26270
        BindableTableScan(table=[[STYPES, EMPTY_T]]): rowcount = 100.0, cumulative cost = {1.0 rows, 1.01 cpu, 0.0 io}, id = 7787
    EnumerableInterpreter: rowcount = 100.0, cumulative cost = {50.0 rows, 50.0 cpu, 0.0 io}, id = 26275
      BindableTableScan(table=[[STYPES, EMPTY_T]]): rowcount = 100.0, cumulative cost = {1.0 rows, 1.01 cpu, 0.0 io}, id = 7787
  EnumerableInterpreter: rowcount = 100.0, cumulative cost = {50.0 rows, 50.0 cpu, 0.0 io}, id = 26280
    BindableTableScan(table=[[STYPES, TTLR_ONE]]): rowcount = 100.0, cumulative cost = {1.0 rows, 1.01 cpu, 0.0 io}, id = 7791

Мы определенно видим, что для каждой таблицы оценка всегда составляет 100 rowcount = 100.0.

Запрос выполняется правильно, но план не оптимизирован. Кто-нибудь знает, почему некорректно оценивается статистика таблицы?


person Salvatore Rapisarda    schedule 16.02.2019    source источник
comment
Чем это отличается от вашего вопроса stackoverflow.com/q/54101174/3404097?   -  person philipxy    schedule 17.02.2019
comment
да, я полагаю, что это та же проблема, которую Flink унаследовал от apache Calcite. Это породило новый вопрос, а также проблему stackoverflow.com/questions/54278508/   -  person Salvatore Rapisarda    schedule 17.02.2019
comment
В этом примере используется только Apache Calcite.   -  person Salvatore Rapisarda    schedule 17.02.2019
comment
почему это помечено как relational-algebra?   -  person AntC    schedule 17.02.2019
comment
Потому что каждый логический план выполнения создается с использованием реляционной алгебры. После этого среди всех возможных полученных планов выбирается тот, который имеет минимальную стоимость. Конечно, проблема не в реляционной алгебре, потому что в конкретном случае расчетная мощность строк каждого отношения всегда одинакова, поэтому оценка стоимости ложная.   -  person Salvatore Rapisarda    schedule 17.02.2019
comment
Потому что каждый логический план выполнения создается с использованием реляционной алгебры. Нет, это не так; эта страница в Википедии говорит о теоретической основе, а не о «плане выполнения». Я нигде в вашем q не вижу RA. EnumerableJoin не является оператором RA. Я указываю на это, чтобы помочь вам: если вы спрашиваете о чем-то низкоуровневом в SQL, то мысль, что RA имеет к этому какое-то отношение, только запутает вас.   -  person AntC    schedule 18.02.2019
comment
@AntC calcite.apache.org/docs/algebra.html   -  person philipxy    schedule 22.05.2019


Ответы (2)


Ответ здесь, кажется, такой же, как и на вопросы, уже связанные с комментариями.

Flink (пока) не изменяет порядок присоединений

В текущей версии (1.7.1, январь 2019 г.) ... Calcite использует значение по умолчанию, равное 100.

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

В общем, оптимизация SQL определяется как доступностью индексов, так и количеством таблиц.

Единственный способ ввести оценки количества элементов для таблиц - использовать ExternalCatalog.

Ты это делаешь?

Если вы загружаете эти таблицы в виде файлов CSV, объявляете ли вы ключи, индексы и другие вещи, необходимые для каталога?

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

person AntC    schedule 18.02.2019
comment
Хотя это правда, что Calcite есть куда улучшаться, он существует уже около 7 лет и находит довольно широкое применение. Тем не менее, для любого нетривиального использования Calcite придется немного поработать, чтобы он работал наилучшим образом. Мы рады принять любые пожертвования для улучшения! (Отказ от ответственности: член Calcite PMC.) - person Michael Mior; 18.02.2019
comment
Реализация ExternalCatalog в apache Flink была выполнена, но была обнаружена проблема с конкретным CsvBatchTableSourceFactory stackoverflow.com/questions/54278508/. Я изучаю проблему github.com/srapisarda/flink/commit/ - person Salvatore Rapisarda; 19.02.2019

Проблема в том, что в классе CsvTable необходимо переопределить метод свойства getStatistic, сделав что-то вроде этого:

 private Statistic statistic;
 // todo: assign statistics  

  @Override
  public Statistic getStatistic() {
    return statistic;
  }

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

На данный момент он возвращает только Statistics.UNKNOWN, который находится в реализации суперкласса AbstractTable`. Конечно, без статистики ориентировочная стоимость плана неверна.

person Salvatore Rapisarda    schedule 18.02.2019