Почему использование кеша в потоковых наборах данных с ошибкой "AnalysisException: запросы с потоковыми источниками должны выполняться с помощью writeStream.start()"?

SparkSession
  .builder
  .master("local[*]")
  .config("spark.sql.warehouse.dir", "C:/tmp/spark")
  .config("spark.sql.streaming.checkpointLocation", "C:/tmp/spark/spark-checkpoint")
  .appName("my-test")
  .getOrCreate
  .readStream
  .schema(schema)
  .json("src/test/data")
  .cache
  .writeStream
  .start
  .awaitTermination

Во время выполнения этого образца в искрах 2.1.0 я получил ошибку. Без опции .cache она работала по назначению, но с опцией .cache я получил:

Исключение в потоке "main" org.apache.spark.sql.AnalysisException: запросы с потоковыми источниками должны выполняться с помощью writeStream.start();; FileSource [SRC/тест/данные]     at org.apache.spark.sql.catalyst.analysis.UsupportedOperationChecker $.org $apache $spark $sql $катализатор $анализ $UnsupportedOperationChecker $$ throwError (UnsupportedOperationChecker.scala: 196)     at org.apache.spark.sql.catalyst.analysis.UsupportedOperationChecker $$ anonfun $checkForBatch $1.apply(UnsupportedOperationChecker.scala: 35)     at org.apache.spark.sql.catalyst.analysis.UsupportedOperationChecker $$ anonfun $checkForBatch $1.apply(UnsupportedOperationChecker.scala: 33)     at org.apache.spark.sql.catalyst.trees.TreeNode.foreachUp(TreeNode.scala: 128)     at org.apache.spark.sql.catalyst.analysis.UsupportedOperationChecker $.checkForBatch(UnsupportedOperationChecker.scala: 33)     at org.apache.spark.sql.execution.QueryExecution.assertSupported(QueryExecution.scala: 58)     at org.apache.spark.sql.execution.QueryExecution.withCachedData $lzycompute (QueryExecution.scala: 69)     at org.apache.spark.sql.execution.QueryExecution.withCachedData(QueryExecution.scala: 67)     at org.apache.spark.sql.execution.QueryExecution.optimizedPlan $lzycompute (QueryExecution.scala: 73)     at org.apache.spark.sql.execution.QueryExecution.optimizedPlan(QueryExecution.scala: 73)     at org.apache.spark.sql.execution.QueryExecution.sparkPlan $lzycompute (QueryExecution.scala: 79)     at org.apache.spark.sql.execution.QueryExecution.sparkPlan(QueryExecution.scala: 75)     at org.apache.spark.sql.execution.QueryExecution.executedPlan $lzycompute (QueryExecution.scala: 84)     at org.apache.spark.sql.execution.QueryExecution.executedPlan(QueryExecution.scala: 84)     at org.apache.spark.sql.execution.CacheManager $$ anonfun $cacheQuery $1.apply(CacheManager.scala: 102)     at org.apache.spark.sql.execution.CacheManager.writeLock(CacheManager.scala: 65)     at org.apache.spark.sql.execution.CacheManager.cacheQuery(CacheManager.scala: 89)     at org.apache.spark.sql.Dataset.persist(Dataset.scala: 2479)     at org.apache.spark.sql.Dataset.cache(Dataset.scala: 2489)     at org.me.App $.main(App.scala: 23)     на org.me.App.main(App.scala)

Любая идея?

Ответ 1

Ваш (очень интересный) случай сводится к следующей строке (которую вы можете выполнить в spark-shell):

scala> :type spark
org.apache.spark.sql.SparkSession

scala> spark.readStream.text("files").cache
org.apache.spark.sql.AnalysisException: Queries with streaming sources must be executed with writeStream.start();;
FileSource[files]
  at org.apache.spark.sql.catalyst.analysis.UnsupportedOperationChecker$.org$apache$spark$sql$catalyst$analysis$UnsupportedOperationChecker$$throwError(UnsupportedOperationChecker.scala:297)
  at org.apache.spark.sql.catalyst.analysis.UnsupportedOperationChecker$$anonfun$checkForBatch$1.apply(UnsupportedOperationChecker.scala:36)
  at org.apache.spark.sql.catalyst.analysis.UnsupportedOperationChecker$$anonfun$checkForBatch$1.apply(UnsupportedOperationChecker.scala:34)
  at org.apache.spark.sql.catalyst.trees.TreeNode.foreachUp(TreeNode.scala:127)
  at org.apache.spark.sql.catalyst.analysis.UnsupportedOperationChecker$.checkForBatch(UnsupportedOperationChecker.scala:34)
  at org.apache.spark.sql.execution.QueryExecution.assertSupported(QueryExecution.scala:63)
  at org.apache.spark.sql.execution.QueryExecution.withCachedData$lzycompute(QueryExecution.scala:74)
  at org.apache.spark.sql.execution.QueryExecution.withCachedData(QueryExecution.scala:72)
  at org.apache.spark.sql.execution.QueryExecution.optimizedPlan$lzycompute(QueryExecution.scala:78)
  at org.apache.spark.sql.execution.QueryExecution.optimizedPlan(QueryExecution.scala:78)
  at org.apache.spark.sql.execution.QueryExecution.sparkPlan$lzycompute(QueryExecution.scala:84)
  at org.apache.spark.sql.execution.QueryExecution.sparkPlan(QueryExecution.scala:80)
  at org.apache.spark.sql.execution.QueryExecution.executedPlan$lzycompute(QueryExecution.scala:89)
  at org.apache.spark.sql.execution.QueryExecution.executedPlan(QueryExecution.scala:89)
  at org.apache.spark.sql.execution.CacheManager$$anonfun$cacheQuery$1.apply(CacheManager.scala:104)
  at org.apache.spark.sql.execution.CacheManager.writeLock(CacheManager.scala:68)
  at org.apache.spark.sql.execution.CacheManager.cacheQuery(CacheManager.scala:92)
  at org.apache.spark.sql.Dataset.persist(Dataset.scala:2603)
  at org.apache.spark.sql.Dataset.cache(Dataset.scala:2613)
  ... 48 elided

Причиной этого оказалось довольно простое объяснение (без каламбура Spark SQL explain).

spark.readStream.text("files") создает так называемый потоковый набор данных.

scala> val files = spark.readStream.text("files")
files: org.apache.spark.sql.DataFrame = [value: string]

scala> files.isStreaming
res2: Boolean = true

Потоковые наборы данных являются основой Spark SQL Structured Streaming.

Как вы могли прочитать в Structured Streaming Быстрый пример:

И затем начните вычисление потоковой передачи с помощью start().

Цитирование скалядока DataStreamWriter start:

start(): StreamingQuery Запускает выполнение потокового запроса, который будет постоянно выводить результаты на указанный путь по мере поступления новых данных.

Итак, вы должны использовать start (или foreach) для запуска выполнения потокового запроса. Вы уже это знали.

Но... есть Неподдерживаемые операции в Structured Streaming:

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

Если вы попытаетесь выполнить любую из этих операций, вы увидите исключение AnalysisException, такое как "операция XYZ не поддерживается потоковыми DataFrames/Datasets".

Это выглядит знакомым, не так ли?

cache не в списке неподдерживаемых операций, но это потому, что он просто был пропущен (я сообщил SPARK-20927, чтобы исправить его).

cache должен был быть в списке, так как выполняет выполнение запроса до того, как запрос будет зарегистрирован в Spark SQL CacheManager.

Отпустите глубже в глубины Spark SQL... задерживайте дыхание...

cache persist, а persist запрашивает текущий CacheManager для кэширования запроса:

sparkSession.sharedState.cacheManager.cacheQuery(this)

При кешировании запроса CacheManager делает выполнить его:

sparkSession.sessionState.executePlan(planToCache).executedPlan

которому мы знаем, не разрешено, так как это start (или foreach) для этого.

Проблема решена!