Skip to content

Conversation

@brkyvz
Copy link
Contributor

@brkyvz brkyvz commented Jun 20, 2017

What changes were proposed in this pull request?

Time windowing in Spark currently performs an Expand + Filter, because there is no way to guarantee the amount of windows a timestamp will fall in, in the general case. However, for tumbling windows, a record is guaranteed to fall into a single bucket. In this case, doubling the number of records with Expand is wasteful, and can be improved by using a simple Projection instead.

Benchmarks show that we get an order of magnitude performance improvement after this patch.

How was this patch tested?

Existing unit tests. Benchmarked using the following code:

import org.apache.spark.sql.functions._

spark.time { 
  spark.range(numRecords)
    .select(from_unixtime((current_timestamp().cast("long") * 1000 + 'id / 1000) / 1000) as 'time)
    .select(window('time, "10 seconds"))
    .count()
}

Setup:

  • 1 c3.2xlarge worker (8 cores)

image

1 B rows ran in 287 seconds after this optimization. I didn't wait for it to finish without the optimization. Shows about 5x improvement for large number of records.

@SparkQA
Copy link

SparkQA commented Jun 20, 2017

Test build #78293 has finished for PR 18364 at commit 4d18d81.

  • This patch fails Spark unit tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

@SparkQA
Copy link

SparkQA commented Jun 20, 2017

Test build #78303 has started for PR 18364 at commit ced7c33.

@shaneknapp
Copy link
Contributor

i will retrigger this once jenkins restart

@shaneknapp
Copy link
Contributor

test this please

@SparkQA
Copy link

SparkQA commented Jun 20, 2017

Test build #78319 has started for PR 18364 at commit 407a5e9.

@SparkQA
Copy link

SparkQA commented Jun 20, 2017

Test build #78311 has finished for PR 18364 at commit ced7c33.

  • This patch fails Spark unit tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

@shaneknapp
Copy link
Contributor

test this please

@SparkQA
Copy link

SparkQA commented Jun 21, 2017

Test build #78336 has started for PR 18364 at commit 407a5e9.

@brkyvz
Copy link
Contributor Author

brkyvz commented Jun 21, 2017

retest this please

@SparkQA
Copy link

SparkQA commented Jun 21, 2017

Test build #78397 has finished for PR 18364 at commit 407a5e9.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

val window = windowExpressions.head

val metadata = window.timeColumn match {
case a: Attribute => a.metadata
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

existing: There is a comment above that says "not correct"?

Copy link
Contributor

@marmbrus marmbrus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty big speed up!

val windows = Seq.tabulate(maxNumOverlapping + 1) { i =>
val windowId = Ceil((PreciseTimestamp(window.timeColumn) - window.startTime) /
window.slideDuration)
def getWindow(i: Int, maxNumOverlapping: Int): Expression = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand maxNumOverlapping as a name that we tabulate over. Isn't it like the overlapNumber or something?

window.timeColumn < windowAttr.getField(WINDOW_END)
if (window.windowDuration == window.slideDuration) {
val windowStruct = Alias(getWindow(0, 1), WINDOW_COL_NAME)(
exprId = windowAttr.exprId, explicitMetadata = Some(metadata))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Wrapping is off. Prefer to break at = and if you wrap args, wrap all of them.

// For backwards compatibility we add a filter to filter out nulls
val filterExpr = IsNotNull(window.timeColumn)

replacedPlan.withNewChildren(Filter(filterExpr,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: wrapping, indent query plans like trees.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, should we even be doing a projection here? If its just a substitution / filter, perhaps we should just replace it inline?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't get inline replacing to work. It breaks EventTimeWatermark tests

replacedPlan.withNewChildren(Filter(filterExpr,
Project(windowStruct +: child.output, child)) :: Nil)
} else {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: no blank line

override def dataType: DataType = LongType
case class PreciseTimestampConversion(
child: Expression,
fromType: DataType,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The from type should just come from the child right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

expectsInputTypes does implicit casting at times

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we shouldn't be using it then? This is a purely internal expression?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is purely internal used for microsecond precision access of Timestamps

@SparkQA
Copy link

SparkQA commented Jun 23, 2017

Test build #78540 has finished for PR 18364 at commit c59a0de.

  • This patch fails Spark unit tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

@brkyvz
Copy link
Contributor Author

brkyvz commented Jun 23, 2017

retest this please

@SparkQA
Copy link

SparkQA commented Jun 23, 2017

Test build #78544 has finished for PR 18364 at commit c59a0de.

  • This patch fails PySpark pip packaging tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

@brkyvz
Copy link
Contributor Author

brkyvz commented Jun 23, 2017

retest this please

@SparkQA
Copy link

SparkQA commented Jun 24, 2017

Test build #78547 has finished for PR 18364 at commit c59a0de.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

@zsxwing
Copy link
Member

zsxwing commented Jun 26, 2017

LGTM. Merging to master.

@asfgit asfgit closed this in 5282bae Jun 26, 2017
robert3005 pushed a commit to palantir/spark that referenced this pull request Jun 29, 2017
## What changes were proposed in this pull request?

Time windowing in Spark currently performs an Expand + Filter, because there is no way to guarantee the amount of windows a timestamp will fall in, in the general case. However, for tumbling windows, a record is guaranteed to fall into a single bucket. In this case, doubling the number of records with Expand is wasteful, and can be improved by using a simple Projection instead.

Benchmarks show that we get an order of magnitude performance improvement after this patch.

## How was this patch tested?

Existing unit tests. Benchmarked using the following code:

```scala
import org.apache.spark.sql.functions._

spark.time {
  spark.range(numRecords)
    .select(from_unixtime((current_timestamp().cast("long") * 1000 + 'id / 1000) / 1000) as 'time)
    .select(window('time, "10 seconds"))
    .count()
}
```

Setup:
 - 1 c3.2xlarge worker (8 cores)

![image](https://user-images.githubusercontent.com/5243515/27348748-ed991b84-55a9-11e7-8f8b-6e7abc524417.png)

1 B rows ran in 287 seconds after this optimization. I didn't wait for it to finish without the optimization. Shows about 5x improvement for large number of records.

Author: Burak Yavuz <brkyvz@gmail.com>

Closes apache#18364 from brkyvz/opt-tumble.
@brkyvz brkyvz deleted the opt-tumble branch February 3, 2019 20:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants