-
Notifications
You must be signed in to change notification settings - Fork 29.3k
[SPARK-15517][SQL][STREAMING] Add support for complete output mode in Structure Streaming #13286
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
469d69a
49746f4
2786090
02b10ac
61af057
a6e2bb5
bb0314d
3a79d41
074299c
58f88b8
1d0d13c
bbd6022
4973621
369e9d5
ab32567
85ce263
4784e18
e951798
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -500,6 +500,25 @@ def mode(self, saveMode): | |
| self._jwrite = self._jwrite.mode(saveMode) | ||
| return self | ||
|
|
||
| @since(2.0) | ||
| def outputMode(self, outputMode): | ||
| """Specifies how data of a streaming DataFrame/Dataset is written to a streaming sink. | ||
|
|
||
| Options include: | ||
|
|
||
| * `append`:Only the new rows in the streaming DataFrame/Dataset will be written to | ||
| the sink | ||
| * `update`:Only the changed rows in the streaming DataFrame/Dataset will be written to | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: remove this line
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good catch. fixed. |
||
| the sink every time there is some updates | ||
| * `complete`:All the rows in the streaming DataFrame/Dataset will be written to the sink | ||
| every time these is some updates | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. each time the trigger fires?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I want to write something that makes sense generally, without understanding trigger and all. As is, since the trigger is optional, one does not need to know about triggers at all to start running stuff in structured streaming. |
||
|
|
||
| >>> sdf.write.outputMode('append') | ||
| """ | ||
| if outputMode is not None: | ||
| self._jwrite = self._jwrite.outputMode(outputMode) | ||
| return self | ||
|
|
||
| @since(1.4) | ||
| def format(self, source): | ||
| """Specifies the underlying output data source. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,9 +15,10 @@ | |
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.apache.spark.sql.catalyst.analysis | ||
| package org.apache.spark.sql; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: move this file to sql/catalyst/src/main/java/org/apache/spark/sql/OutputMode.java
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. before that.... i realize that making this java enum prevents us from having output modes like |
||
|
|
||
| sealed trait OutputMode | ||
|
|
||
| case object Append extends OutputMode | ||
| case object Update extends OutputMode | ||
| public enum OutputMode { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: |
||
| Append, | ||
| Update, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This actually raises a good question. I'm not sure if we can use enums here as I think that we need to have a notion of a |
||
| Complete | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -77,7 +77,50 @@ final class DataFrameWriter private[sql](df: DataFrame) { | |
| case "ignore" => SaveMode.Ignore | ||
| case "error" | "default" => SaveMode.ErrorIfExists | ||
| case _ => throw new IllegalArgumentException(s"Unknown save mode: $saveMode. " + | ||
| "Accepted modes are 'overwrite', 'append', 'ignore', 'error'.") | ||
| "Accepted save modes are 'overwrite', 'append', 'ignore', 'error'.") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We might consider aliasing /cc @rxin
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking the same. |
||
| } | ||
| this | ||
| } | ||
|
|
||
| /** | ||
| * Specifies how data of a streaming DataFrame/Dataset is written to a streaming sink. | ||
| * - `OutputMode.Append`: only the new rows in the streaming DataFrame/Dataset will be | ||
| * written to the sink | ||
| * - `OutputMode.Update`: only the changed rows in the streaming DataFrame/Dataset will be | ||
| * written to the sink every time there is some updates | ||
| * - `OutputMode.Complete`: all the rows in the streaming DataFrame/Dataset will be written | ||
| * to the sink every time these is some updates | ||
| * | ||
| * @since 2.0.0 | ||
| */ | ||
| @Experimental | ||
| def outputMode(outputMode: OutputMode): DataFrameWriter = { | ||
| assertStreaming("outputMode() can only be called on continuous queries") | ||
| this.outputMode = outputMode | ||
| this | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * Specifies how data of a streaming DataFrame/Dataset is written to a streaming sink. | ||
| * - `append`: only the new rows in the streaming DataFrame/Dataset will be written to | ||
| * the sink | ||
| * - `update`: only the changed rows in the streaming DataFrame/Dataset will be written to | ||
| * the sink every time there is some updates | ||
| * - `complete`: all the rows in the streaming DataFrame/Dataset will be written to the sink | ||
| * every time these is some updates | ||
| * | ||
| * @since 2.0.0 | ||
| */ | ||
| @Experimental | ||
| def outputMode(outputMode: String): DataFrameWriter = { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @tdas do we need to think about how to support the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That we can decide later. That sounds too complicated to reason about right now when we have not even finalized how to specify Update mode. |
||
| assertStreaming("outputMode() can only be called on continuous queries") | ||
| this.outputMode = outputMode.toLowerCase match { | ||
| case "append" => OutputMode.Append | ||
| case "update" => OutputMode.Update | ||
| case "complete" => OutputMode.Complete | ||
| case _ => throw new IllegalArgumentException(s"Unknown output mode $outputMode. " + | ||
| "Accepted output modes are 'append', 'update', 'complete'") | ||
| } | ||
| this | ||
| } | ||
|
|
@@ -319,14 +362,19 @@ final class DataFrameWriter private[sql](df: DataFrame) { | |
| checkpointPath.toUri.toString | ||
| } | ||
|
|
||
| val sink = new MemorySink(df.schema) | ||
| if (!Seq(OutputMode.Append, OutputMode.Complete).contains(outputMode)) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: maybe move this logic to the constructor of
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The tricky thing is that we want to make the Memory Sink compatible with update internally, but we may not want public API to support update mode yet. |
||
| throw new IllegalArgumentException(s"Memory sink does not support output mode $outputMode") | ||
| } | ||
|
|
||
| val sink = new MemorySink(df.schema, outputMode) | ||
| val resultDf = Dataset.ofRows(df.sparkSession, new MemoryPlan(sink)) | ||
| resultDf.createOrReplaceTempView(queryName) | ||
| val continuousQuery = df.sparkSession.sessionState.continuousQueryManager.startQuery( | ||
| queryName, | ||
| checkpointLocation, | ||
| df, | ||
| sink, | ||
| outputMode, | ||
| trigger) | ||
| continuousQuery | ||
| } else { | ||
|
|
@@ -352,7 +400,8 @@ final class DataFrameWriter private[sql](df: DataFrame) { | |
| queryName, | ||
| checkpointLocation, | ||
| df, | ||
| dataSource.createSink(), | ||
| dataSource.createSink(outputMode), | ||
| outputMode, | ||
| trigger) | ||
| } | ||
| } | ||
|
|
@@ -705,6 +754,8 @@ final class DataFrameWriter private[sql](df: DataFrame) { | |
|
|
||
| private var mode: SaveMode = SaveMode.ErrorIfExists | ||
|
|
||
| private var outputMode: OutputMode = OutputMode.Append | ||
|
|
||
| private var trigger: Trigger = ProcessingTime(0L) | ||
|
|
||
| private var extraOptions = new scala.collection.mutable.HashMap[String, String] | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,7 @@ | |
|
|
||
| package org.apache.spark.sql.execution.aggregate | ||
|
|
||
| import org.apache.spark.sql.OutputMode | ||
| import org.apache.spark.sql.catalyst.expressions._ | ||
| import org.apache.spark.sql.catalyst.expressions.aggregate._ | ||
| import org.apache.spark.sql.execution.SparkPlan | ||
|
|
@@ -33,7 +34,7 @@ object Utils { | |
| resultExpressions: Seq[NamedExpression], | ||
| child: SparkPlan): Seq[SparkPlan] = { | ||
|
|
||
| val completeAggregateExpressions = aggregateExpressions.map(_.copy(mode = Complete)) | ||
| val completeAggregateExpressions = aggregateExpressions.map(_.copy(mode = aggregate.Complete)) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: not needed.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. removed. |
||
| val completeAggregateAttributes = completeAggregateExpressions.map(_.resultAttribute) | ||
| SortBasedAggregateExec( | ||
| requiredChildDistributionExpressions = Some(groupingExpressions), | ||
|
|
@@ -311,8 +312,8 @@ object Utils { | |
| aggregateExpressions.flatMap(_.aggregateFunction.inputAggBufferAttributes), | ||
| child = restored) | ||
| } | ||
|
|
||
| val saved = StateStoreSaveExec(groupingAttributes, None, partialMerged2) | ||
| val saved = StateStoreSaveExec( | ||
| groupingAttributes, stateId = None, returnAllStates = None, partialMerged2) | ||
|
|
||
| val finalAndCompleteAggregate: SparkPlan = { | ||
| val finalAggregateExpressions = functionsWithoutDistinct.map(_.copy(mode = Final)) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: add
.. note:: Experimental.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.