blob: 2686a2708cc84aeb3963cdc90aa67447ce788fe2 [file]
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import unittest
from pyspark.sql.functions import udf
from pyspark.sql.types import LongType
from pyspark.testing.sqlutils import ReusedSQLTestCase
class PipelinedShuffleTests(ReusedSQLTestCase):
@classmethod
def master(cls):
return "local[8]"
@classmethod
def conf(cls):
return (
super()
.conf()
.set(
"spark.shuffle.manager.incremental",
"org.apache.spark.shuffle.local.pipelined.PipelinedChannelShuffleManager",
)
.set("spark.sql.shuffle.localPipelined.enabled", "true")
.set("spark.sql.shuffle.partitions", "4")
.set("spark.sql.classic.shuffleDependency.fileCleanup.enabled", "false")
)
def assert_regular_rdd(self, rdd):
pending = [rdd]
visited = set()
while pending:
current = pending.pop()
if current.id() in visited:
continue
visited.add(current.id())
dependencies = current.dependencies().iterator()
while dependencies.hasNext():
dependency = dependencies.next()
self.assertNotIn("PipelinedShuffleDependency", dependency.getClass().getName())
pending.append(dependency.rdd())
def test_rdd_consumers(self):
for aqe in ("false", "true"):
with self.subTest(aqe=aqe), self.sql_conf({"spark.sql.adaptive.enabled": aqe}):
df = self.spark.range(0, 4000, 1, 2).repartition(4)
self.assertEqual(len(df.collect()), 4000)
rdd = df.rdd
# Check lineage before running consumers that would hang with a channel.
self.assert_regular_rdd(rdd._jrdd.rdd())
self.assertEqual(rdd.coalesce(1).count(), 4000)
self.assertEqual(
sorted(
rdd.map(lambda r: (r.id % 2, 1)).reduceByKey(lambda a, b: a + b).collect()
),
[(0, 2000), (1, 2000)],
)
def test_iterator_reuses_shuffle_output(self):
for aqe in ("false", "true"):
for prefetch in (False, True):
with (
self.subTest(aqe=aqe, prefetch=prefetch),
self.sql_conf({"spark.sql.adaptive.enabled": aqe}),
):
evaluated = self.sc.accumulator(0)
@udf(LongType())
def record_row(value):
evaluated.add(1)
return value
df = (
self.spark.range(0, 1000, 1, 2)
.select(record_row("id").alias("id"))
.repartition(4)
)
self.assertEqual(len(df.collect()), 1000)
self.assertEqual(evaluated.value, 1000)
for run in (1, 2):
rows = list(df.toLocalIterator(prefetchPartitions=prefetch))
self.assertEqual(sorted(r.id for r in rows), list(range(1000)))
self.assertEqual(evaluated.value, 2000)
self.assertEqual(
self.spark.conf.get("spark.sql.shuffle.localPipelined.enabled"), "true"
)
if __name__ == "__main__":
unittest.main()