Skip to content
Closed
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
import org.apache.spark.serializer.SerializationStream;
import org.apache.spark.serializer.Serializer;
import org.apache.spark.serializer.SerializerInstance;
import org.apache.spark.shuffle.IndexShuffleBlockResolver;
import org.apache.spark.shuffle.BinaryShuffleWriter;
import org.apache.spark.shuffle.ShuffleMemoryManager;
import org.apache.spark.shuffle.ShuffleWriter;
import org.apache.spark.storage.BlockManager;
Expand All @@ -67,14 +67,12 @@ public class UnsafeShuffleWriter<K, V> extends ShuffleWriter<K, V> {
static final int INITIAL_SORT_BUFFER_SIZE = 4096;

private final BlockManager blockManager;
private final IndexShuffleBlockResolver shuffleBlockResolver;
private final BinaryShuffleWriter binaryShuffleWriter;
private final TaskMemoryManager memoryManager;
private final ShuffleMemoryManager shuffleMemoryManager;
private final SerializerInstance serializer;
private final Partitioner partitioner;
private final ShuffleWriteMetrics writeMetrics;
private final int shuffleId;
private final int mapId;
private final TaskContext taskContext;
private final SparkConf sparkConf;
private final boolean transferToEnabled;
Expand All @@ -101,28 +99,25 @@ private static final class MyByteArrayOutputStream extends ByteArrayOutputStream

public UnsafeShuffleWriter(

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

The changes in this file are just to demonstrate how BinaryShuffleWriter might be used by non-core shuffle writers.

BlockManager blockManager,
IndexShuffleBlockResolver shuffleBlockResolver,
BinaryShuffleWriter binaryShuffleWriter,
TaskMemoryManager memoryManager,
ShuffleMemoryManager shuffleMemoryManager,
UnsafeShuffleHandle<K, V> handle,
int mapId,
Partitioner partitioner,
Option<Serializer> serializer,
TaskContext taskContext,
SparkConf sparkConf) throws IOException {
final int numPartitions = handle.dependency().partitioner().numPartitions();
final int numPartitions = partitioner.numPartitions();
if (numPartitions > UnsafeShuffleManager.MAX_SHUFFLE_OUTPUT_PARTITIONS()) {
throw new IllegalArgumentException(
"UnsafeShuffleWriter can only be used for shuffles with at most " +
UnsafeShuffleManager.MAX_SHUFFLE_OUTPUT_PARTITIONS() + " reduce partitions");
}
this.blockManager = blockManager;
this.shuffleBlockResolver = shuffleBlockResolver;
this.binaryShuffleWriter = binaryShuffleWriter;
this.memoryManager = memoryManager;
this.shuffleMemoryManager = shuffleMemoryManager;
this.mapId = mapId;
final ShuffleDependency<K, V, V> dep = handle.dependency();
this.shuffleId = dep.shuffleId();
this.serializer = Serializer.getSerializer(dep.serializer()).newInstance();
this.partitioner = dep.partitioner();
this.serializer = Serializer.getSerializer(serializer).newInstance();
this.partitioner = partitioner;
this.writeMetrics = new ShuffleWriteMetrics();
taskContext.taskMetrics().shuffleWriteMetrics_$eq(Option.apply(writeMetrics));
this.taskContext = taskContext;
Expand Down Expand Up @@ -226,7 +221,7 @@ void closeAndWriteOutput() throws IOException {
}
}
}
shuffleBlockResolver.writeIndexFile(shuffleId, mapId, partitionLengths);
binaryShuffleWriter.commit(partitionLengths);
mapStatus = MapStatus$.MODULE$.apply(blockManager.shuffleServerId(), partitionLengths);
}

Expand Down Expand Up @@ -260,7 +255,7 @@ void forceSorterToSpill() throws IOException {
* @return the partition lengths in the merged file.
*/
private long[] mergeSpills(SpillInfo[] spills) throws IOException {
final File outputFile = shuffleBlockResolver.getDataFile(shuffleId, mapId);
final File outputFile = binaryShuffleWriter.getDataFile();
final boolean compressionEnabled = sparkConf.getBoolean("spark.shuffle.compress", true);
final CompressionCodec compressionCodec = CompressionCodec$.MODULE$.createCodec(sparkConf);
final boolean fastMergeEnabled =
Expand Down Expand Up @@ -474,7 +469,7 @@ public Option<MapStatus> stop(boolean success) {
return Option.apply(mapStatus);
} else {
// The map task failed, so delete our output data.
shuffleBlockResolver.removeDataByMap(shuffleId, mapId);
binaryShuffleWriter.abort();
return Option.apply(null);
}
}
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/org/apache/spark/ContextCleaner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ private[spark] class ContextCleaner(sc: SparkContext) extends Logging {
}

/** Register a ShuffleDependency for cleanup when it is garbage collected. */
def registerShuffleForCleanup(shuffleDependency: ShuffleDependency[_, _, _]): Unit = {
def registerShuffleForCleanup(shuffleDependency: BaseShuffleDependency[_]): Unit = {
registerForCleanup(shuffleDependency, CleanShuffle(shuffleDependency.shuffleId))
}

Expand Down
27 changes: 20 additions & 7 deletions core/src/main/scala/org/apache/spark/Dependency.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import scala.reflect.ClassTag
import org.apache.spark.annotation.DeveloperApi
import org.apache.spark.rdd.RDD
import org.apache.spark.serializer.Serializer
import org.apache.spark.shuffle.ShuffleHandle
import org.apache.spark.shuffle.{BinaryShuffleWriter, ShuffleHandle}

/**
* :: DeveloperApi ::
Expand Down Expand Up @@ -68,15 +68,14 @@ abstract class NarrowDependency[T](_rdd: RDD[T]) extends Dependency[T] {
*/
@DeveloperApi
class ShuffleDependency[K: ClassTag, V: ClassTag, C: ClassTag](
@transient private val _rdd: RDD[_ <: Product2[K, V]],
_rdd: RDD[_ <: Product2[K, V]],
val partitioner: Partitioner,
val serializer: Option[Serializer] = None,
val keyOrdering: Option[Ordering[K]] = None,
val aggregator: Option[Aggregator[K, V, C]] = None,
val mapSideCombine: Boolean = false)
extends Dependency[Product2[K, V]] {

override def rdd: RDD[Product2[K, V]] = _rdd.asInstanceOf[RDD[Product2[K, V]]]
extends BaseShuffleDependency[Product2[K, V]](
_rdd.asInstanceOf[RDD[Product2[K, V]]], partitioner.numPartitions) {

private[spark] val keyClassName: String = reflect.classTag[K].runtimeClass.getName
private[spark] val valueClassName: String = reflect.classTag[V].runtimeClass.getName
Expand All @@ -85,14 +84,28 @@ class ShuffleDependency[K: ClassTag, V: ClassTag, C: ClassTag](
private[spark] val combinerClassName: Option[String] =
Option(reflect.classTag[C]).map(_.runtimeClass.getName)

val shuffleId: Int = _rdd.context.newShuffleId()

val shuffleHandle: ShuffleHandle = _rdd.context.env.shuffleManager.registerShuffle(
shuffleId, _rdd.partitions.size, this)
}


private[spark] abstract class BaseShuffleDependency[T: ClassTag](

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

The idea behind this change is that our existing ShuffleDependency is a poor fit for non-record-oriented shuffle or other low-level shuffle APIs, but we can't / shouldn't break or change it since it's a @DeveloperApi that several people depend on.

Changing the inheritance hierarchy here should not create any binary compatibility problems.

@transient private val _rdd: RDD[T],
val numPartitions: Int) extends Dependency[T] {

override def rdd: RDD[T] = _rdd

val shuffleId: Int = _rdd.context.newShuffleId()

_rdd.sparkContext.cleaner.foreach(_.registerShuffleForCleanup(this))
}

private[spark] class BinaryShuffleDependency[T: ClassTag](

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

This is the new dependency for use by the binary shuffle. It's only used to capture the structure of the wide dependency and to register a handler for the write-side of the shuffle.

_rdd: RDD[T],
numPartitions: Int,
val writeFunc: (TaskContext, BinaryShuffleWriter, Iterator[T]) => Unit)
extends BaseShuffleDependency[T](_rdd, numPartitions)


/**
* :: DeveloperApi ::
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/scala/org/apache/spark/MapOutputTracker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,11 @@ private[spark] abstract class MapOutputTracker(conf: SparkConf) extends Logging
/**
* Return statistics about all of the outputs for a given shuffle.
*/
def getStatistics(dep: ShuffleDependency[_, _, _]): MapOutputStatistics = {
def getStatistics(dep: BaseShuffleDependency[_]): MapOutputStatistics = {
val statuses = getStatuses(dep.shuffleId)
// Synchronize on the returned array because, on the driver, it gets mutated in place
statuses.synchronized {
val totalSizes = new Array[Long](dep.partitioner.numPartitions)
val totalSizes = new Array[Long](dep.numPartitions)
for (s <- statuses) {
for (i <- 0 until totalSizes.length) {
totalSizes(i) += s.getSizeForBlock(i)
Expand Down
115 changes: 115 additions & 0 deletions core/src/main/scala/org/apache/spark/rdd/BinaryShuffledRDD.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* 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.
*/

package org.apache.spark.rdd

import java.io.InputStream

import scala.reflect.ClassTag

import org.apache.spark.{BinaryShuffleDependency, Dependency, SparkEnv, TaskContext, Partition}
import org.apache.spark.shuffle.BinaryShuffleWriter
import org.apache.spark.storage.ShuffleBlockFetcherIterator

/**

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Err, ignore my earlier comment and please take a look at this class as the entry point to understanding my proposal here.

* This class allows non-core Spark components to perform shuffles with customized data writing
* and reading. It effectively allows non-core components to customize all aspects of shuffle except
* for the actual network transfer.
*
* Compared to the existing shuffle APIs, such as [[ShuffledRDD]] and
* [[org.apache.spark.ShuffleDependency]], this API is lower-level and thus more general:
*
* - The shuffle write and read paths are not record-oriented and are not coupled to Scala tuples
* or key-value pairs. This allows custom shuffle implementations to operate on batches of
* records or to write shuffle data in a column-oriented format.
* - This API says nothing about serialization, sorting, aggregation, or partitioning; instead,
* it only captures the structure of the wide dependency and exposes a low-level interface for
* writing opaque binary shuffle data.
* - The write interfaces only expose file names and success / abort methods. As a result, the
* actual shuffle writing may be performed by an external process or by native (non-JVM) code.
*
* Note that this is _not_ intended to be a DeveloperApi yet and thus should not be used by code
* outside of Spark itself. While we may eventually stabilize a version of this API, the current
* API is not subject to any binary compatibility guarantees and thus should not be used by
* third-party code.
*
* @param prev the RDD being shuffled.
* @param numPartitions the number of partitions that will result from the shuffle.
* @tparam ParentType the type of records in the RDD being shuffled.
* @tparam OutputType the type of records in the post-shuffle RDD. Note that this may be different
* than [[ParentType]] if the custom shuffle is performing aggregation or
* grouping.
*/
private[spark] abstract class BinaryShuffledRDD[ParentType: ClassTag, OutputType: ClassTag](
@transient var prev: RDD[ParentType],
numPartitions: Int
) extends RDD[OutputType](prev.context, Nil) {

// -- Methods to be implemented by subclasses ---------------------------------------------------

/**
* Called on the map side to write shuffle data.
*
* Note that it is the implementor's responsibility to perform any desired compression of the
* shuffle data.
*
* @param context the TaskContext.
* @param writer a handle which exposes the path where shuffle data should be written.
* See the documentation in [[BinaryShuffleWriter]] for a description of the
* expected file format.
* @param iter an iterator of records from the RDD being shuffled.
*/
def write(context: TaskContext, writer: BinaryShuffleWriter, iter: Iterator[ParentType]): Unit

/**
* Called on the reduce side to read shuffle data.
*
* @param context the TaskContext.
* @param iter an iterator of input streams, one per map partition. These streams correspond,
* verbatim, to the per-reducer data written on the map side.
* @return an iterator of records that were decoded from the input streams.
*/
def read(context: TaskContext, iter: Iterator[InputStream]): Iterator[OutputType]

// -- Private internal methods ------------------------------------------------------------------

final override def getPartitions: Array[Partition] = {
Array.tabulate[Partition](numPartitions)(i => new ShuffledRDDPartition(i))
}

final override def getDependencies: Seq[Dependency[_]] = {
List(new BinaryShuffleDependency[ParentType](prev, numPartitions, write))
}

final override def compute(split: Partition, context: TaskContext): Iterator[OutputType] = {
val dep = dependencies.head.asInstanceOf[BinaryShuffleDependency[ParentType]]
val blockManager = SparkEnv.get.blockManager
val blockFetcherItr = new ShuffleBlockFetcherIterator(
context,
blockManager.shuffleClient,
blockManager,
SparkEnv.get.mapOutputTracker.getMapSizesByExecutorId(dep.shuffleId, split.index),
// Note: we use getSizeAsMb when no suffix is provided for backwards compatibility
SparkEnv.get.conf.getSizeAsMb("spark.reducer.maxSizeInFlight", "48m") * 1024 * 1024)
read(context, blockFetcherItr.map(_._2))
}

override def clearDependencies() {
super.clearDependencies()
prev = null
}
}
22 changes: 11 additions & 11 deletions core/src/main/scala/org/apache/spark/scheduler/DAGScheduler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ class DAGScheduler(
* Get or create a shuffle map stage for the given shuffle dependency's map side.
*/
private def getShuffleMapStage(
shuffleDep: ShuffleDependency[_, _, _],
shuffleDep: BaseShuffleDependency[_],
firstJobId: Int): ShuffleMapStage = {
shuffleToMapStage.get(shuffleDep.shuffleId) match {
case Some(stage) => stage
Expand Down Expand Up @@ -326,7 +326,7 @@ class DAGScheduler(
private def newShuffleMapStage(
rdd: RDD[_],
numTasks: Int,
shuffleDep: ShuffleDependency[_, _, _],
shuffleDep: BaseShuffleDependency[_],
firstJobId: Int,
callSite: CallSite): ShuffleMapStage = {
val (parentStages: List[Stage], id: Int) = getParentStagesAndId(rdd, firstJobId)
Expand Down Expand Up @@ -361,7 +361,7 @@ class DAGScheduler(
* recovered from the MapOutputTracker
*/
private def newOrUsedShuffleStage(
shuffleDep: ShuffleDependency[_, _, _],
shuffleDep: BaseShuffleDependency[_],
firstJobId: Int): ShuffleMapStage = {
val rdd = shuffleDep.rdd
val numTasks = rdd.partitions.length
Expand Down Expand Up @@ -399,7 +399,7 @@ class DAGScheduler(
// we can't do it in its constructor because # of partitions is unknown
for (dep <- r.dependencies) {
dep match {
case shufDep: ShuffleDependency[_, _, _] =>
case shufDep: BaseShuffleDependency[_] =>
parents += getShuffleMapStage(shufDep, firstJobId)
case _ =>
waitingForVisit.push(dep.rdd)
Expand All @@ -415,8 +415,8 @@ class DAGScheduler(
}

/** Find ancestor shuffle dependencies that are not registered in shuffleToMapStage yet */
private def getAncestorShuffleDependencies(rdd: RDD[_]): Stack[ShuffleDependency[_, _, _]] = {
val parents = new Stack[ShuffleDependency[_, _, _]]
private def getAncestorShuffleDependencies(rdd: RDD[_]): Stack[BaseShuffleDependency[_]] = {
val parents = new Stack[BaseShuffleDependency[_]]
val visited = new HashSet[RDD[_]]
// We are manually maintaining a stack here to prevent StackOverflowError
// caused by recursively visiting
Expand All @@ -426,7 +426,7 @@ class DAGScheduler(
visited += r
for (dep <- r.dependencies) {
dep match {
case shufDep: ShuffleDependency[_, _, _] =>
case shufDep: BaseShuffleDependency[_] =>
if (!shuffleToMapStage.contains(shufDep.shuffleId)) {
parents.push(shufDep)
}
Expand Down Expand Up @@ -457,7 +457,7 @@ class DAGScheduler(
if (rddHasUncachedPartitions) {
for (dep <- rdd.dependencies) {
dep match {
case shufDep: ShuffleDependency[_, _, _] =>
case shufDep: BaseShuffleDependency[_] =>
val mapStage = getShuffleMapStage(shufDep, stage.firstJobId)
if (!mapStage.isAvailable) {
missing += mapStage
Expand Down Expand Up @@ -873,7 +873,7 @@ class DAGScheduler(
}

private[scheduler] def handleMapStageSubmitted(jobId: Int,
dependency: ShuffleDependency[_, _, _],
dependency: BaseShuffleDependency[_],
callSite: CallSite,
listener: JobListener,
properties: Properties) {
Expand Down Expand Up @@ -1499,7 +1499,7 @@ class DAGScheduler(
visitedRdds += rdd
for (dep <- rdd.dependencies) {
dep match {
case shufDep: ShuffleDependency[_, _, _] =>
case shufDep: BaseShuffleDependency[_] =>
val mapStage = getShuffleMapStage(shufDep, stage.firstJobId)
if (!mapStage.isAvailable) {
waitingForVisit.push(mapStage.rdd)
Expand Down Expand Up @@ -1577,7 +1577,7 @@ class DAGScheduler(
// have at least REDUCER_PREF_LOCS_FRACTION of data as preferred locations
if (shuffleLocalityEnabled && rdd.partitions.length < SHUFFLE_PREF_REDUCE_THRESHOLD) {
rdd.dependencies.foreach {
case s: ShuffleDependency[_, _, _] =>
case s: BaseShuffleDependency[_] =>
if (s.rdd.partitions.length < SHUFFLE_PREF_MAP_THRESHOLD) {
// Get the preferred map output locations for this reducer
val topLocsForReducer = mapOutputTracker.getLocationsWithLargestOutputs(s.shuffleId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

package org.apache.spark.scheduler

import org.apache.spark.ShuffleDependency
import org.apache.spark.{BaseShuffleDependency, ShuffleDependency}
import org.apache.spark.rdd.RDD
import org.apache.spark.storage.BlockManagerId
import org.apache.spark.util.CallSite
Expand All @@ -40,7 +40,7 @@ private[spark] class ShuffleMapStage(
parents: List[Stage],
firstJobId: Int,
callSite: CallSite,
val shuffleDep: ShuffleDependency[_, _, _])
val shuffleDep: BaseShuffleDependency[_])
extends Stage(id, rdd, numTasks, parents, firstJobId, callSite) {

override def toString: String = "ShuffleMapStage " + id
Expand Down
Loading