Vivek1106-04 commented on code in PR #57528:
URL: https://github.com/apache/spark/pull/57528#discussion_r3707009427


##########
core/src/test/scala/org/apache/spark/scheduler/MapStatusSuite.scala:
##########
@@ -289,4 +289,172 @@ class MapStatusSuite extends SparkFunSuite {
         "Only tracked skewed block size is accurate")
     }
   }
+
+  test("SPARK-48290: skewed blocks are recorded accurately with the default 
configuration") {
+    val emptyBlocksLength = 3
+    val smallBlocksLength = 3000
+    val skewedBlocksLength = 5
+    // Well above the median block size, but below 
SHUFFLE_ACCURATE_BLOCK_THRESHOLD, which is the
+    // case for a skewed partition whose rows are spread over a large number 
of map tasks.
+    val skewedBlockSize = 50 * 1024 * 1024L
+
+    // No skew related config is set: this asserts the out of the box behavior.
+    val conf = new SparkConf()
+    val env = mock(classOf[SparkEnv])
+    doReturn(conf).when(env).conf
+    SparkEnv.set(env)
+
+    val emptyBlocks = createArray(emptyBlocksLength, 0L)
+    val smallBlocks = Array.tabulate[Long](smallBlocksLength)(i => i + 1)
+    val skewedBlocks = createArray(skewedBlocksLength, skewedBlockSize)
+    val allBlocks = emptyBlocks ++: smallBlocks ++: skewedBlocks
+    assert(skewedBlockSize < conf.get(config.SHUFFLE_ACCURATE_BLOCK_THRESHOLD),
+      "the skewed blocks must not be tracked as huge blocks")
+    val avg = smallBlocks.sum / smallBlocks.length
+
+    val loc = BlockManagerId("a", "b", 10)
+    val mapTaskAttemptId = 5
+    val status = compressAndDecompressMapStatus(MapStatus(loc, allBlocks, 
mapTaskAttemptId))
+    assert(status.isInstanceOf[HighlyCompressedMapStatus])
+    for (i <- 0 until emptyBlocksLength) {
+      assert(status.getSizeForBlock(i) === 0L)
+    }
+    for (i <- 0 until smallBlocksLength) {
+      assert(status.getSizeForBlock(emptyBlocksLength + i) === avg,
+        "the average size must not be inflated by the skewed blocks")
+    }
+    for (i <- 0 until skewedBlocksLength) {
+      assert(status.getSizeForBlock(emptyBlocksLength + smallBlocksLength + i) 
===
+        compressAndDecompressSize(skewedBlockSize),
+        "skewed block sizes must be accurate so that AQE can detect the skew")
+    }
+  }
+
+  test("SPARK-48290: blocks tied at the cutoff size do not bypass the accurate 
skewed block " +
+    "limit") {
+    // The small blocks are the majority, so the median block size is 
smallBlockSize. Without the
+    // limit, all of the tied blocks would be recorded, which for a stage of 
10000 map tasks would
+    // add more than a gigabyte of map status entries.
+    val smallBlocksLength = 25001
+    val tiedBlocksLength = 24999
+    val smallBlockSize = 1024L
+    // Far more blocks share this size than may be recorded, and it is the 
cutoff size itself:
+    // it is above the median times the skew factor, so the skew threshold is 
exactly this size.
+    val tiedBlockSize = 8 * 1024L
+    val maxAccurateSkewedBlockNumber =
+      config.SHUFFLE_MAX_ACCURATE_SKEWED_BLOCK_NUMBER.defaultValue.get
+
+    // No skew related config is set: this asserts the out of the box behavior.
+    val conf = new SparkConf()
+    val env = mock(classOf[SparkEnv])
+    doReturn(conf).when(env).conf
+    SparkEnv.set(env)
+
+    val allBlocks = createArray(smallBlocksLength, smallBlockSize) ++:
+      createArray(tiedBlocksLength, tiedBlockSize)
+    assert(tiedBlockSize < conf.get(config.SHUFFLE_ACCURATE_BLOCK_THRESHOLD),
+      "the tied blocks must not be recorded as huge blocks")
+    assert(Utils.median(allBlocks, false) *
+      conf.get(config.SHUFFLE_ACCURATE_BLOCK_SKEWED_FACTOR) < tiedBlockSize,
+      "the cutoff size, not the median times the skew factor, must set the 
skew threshold")
+    val numSmallBlocks = allBlocks.length - maxAccurateSkewedBlockNumber
+    val avg =
+      (smallBlockSize * smallBlocksLength +
+        tiedBlockSize * (tiedBlocksLength - maxAccurateSkewedBlockNumber)) / 
numSmallBlocks
+
+    val loc = BlockManagerId("a", "b", 10)
+    val mapTaskId = 5L
+    val status = compressAndDecompressMapStatus(MapStatus(loc, allBlocks, 
mapTaskId))
+    assert(status.isInstanceOf[HighlyCompressedMapStatus])
+    for (i <- 0 until smallBlocksLength) {
+      assert(status.getSizeForBlock(i) === avg)
+    }
+    // The recorded ties are a window of maxAccurateSkewedBlockNumber tied 
blocks, in block index
+    // order, starting at the offset the map task id rotates to.
+    val firstAccurateTie = (mapTaskId % tiedBlocksLength).toInt
+    for (i <- 0 until tiedBlocksLength) {
+      val isAccurate =
+        i >= firstAccurateTie && i < firstAccurateTie + 
maxAccurateSkewedBlockNumber
+      val expected = if (isAccurate) compressAndDecompressSize(tiedBlockSize) 
else avg
+      assert(status.getSizeForBlock(smallBlocksLength + i) === expected,
+        "no more than maxAccurateSkewedBlockNumber blocks may be recorded 
accurately")
+    }
+  }
+
+  test("SPARK-48290: blocks tied at the cutoff size rotate across map tasks so 
that no reducer " +
+    "is hidden") {
+    // One more reducer is tied at the cutoff than may be recorded per map 
task. Every map task
+    // sees the same distribution, so a fixed tie break would hide the same 
reducer in every map
+    // status, and MapOutputTracker.getStatistics would report it as the 
average block size.
+    val smallBlocksLength = 1900
+    val tiedBlocksLength = 101
+    val smallBlockSize = 10 * 1024L
+    val tiedBlockSize = 100 * 1024L
+    val numMapTasks = 2000
+    val maxAccurateSkewedBlockNumber =
+      config.SHUFFLE_MAX_ACCURATE_SKEWED_BLOCK_NUMBER.defaultValue.get
+    assert(tiedBlocksLength > maxAccurateSkewedBlockNumber)
+
+    // No skew related config is set: this asserts the out of the box behavior.
+    val conf = new SparkConf()
+    val env = mock(classOf[SparkEnv])
+    doReturn(conf).when(env).conf
+    SparkEnv.set(env)
+
+    val allBlocks = createArray(smallBlocksLength, smallBlockSize) ++:
+      createArray(tiedBlocksLength, tiedBlockSize)
+    val loc = BlockManagerId("a", "b", 10)
+    val statuses = (0 until numMapTasks).map { mapTaskId =>
+      compressAndDecompressMapStatus(MapStatus(loc, allBlocks, mapTaskId))
+    }
+
+    val accurateSize = compressAndDecompressSize(tiedBlockSize)
+    statuses.foreach { status =>
+      val numAccurate =
+        (smallBlocksLength until 
allBlocks.length).count(status.getSizeForBlock(_) == accurateSize)
+      assert(numAccurate === maxAccurateSkewedBlockNumber,
+        "the accurate skewed block limit must still hold for every map task")
+    }
+
+    // Summing a reducer over the map statuses is what AQE sees. Each tied 
reducer loses the tie on
+    // one map task out of tiedBlocksLength, so its total is off by that 
fraction at worst, instead
+    // of collapsing to the average block size.
+    val realTotal = numMapTasks * accurateSize
+    for (i <- smallBlocksLength until allBlocks.length) {
+      val total = statuses.map(_.getSizeForBlock(i)).sum
+      assert(total > realTotal * 0.98,
+        s"reducer $i must not be hidden behind the average block size")
+      assert(total <= realTotal)
+    }
+  }
+
+  test("SPARK-48290: recorded skewed block sizes are held in compact storage") 
{
+    // The driver retains one map status per map task for the lifetime of the 
shuffle, so the
+    // accurately recorded sizes must not cost more than a few bytes each. A 
mutable.Map[Int, Byte]
+    // costs an order of magnitude more, through its nodes, bucket array and 
boxed reduce ids.
+    val smallBlocksLength = 1900

Review Comment:
   You're right, and the test was worse than under-powered: it passed because 
the feature never ran. 1900 + 100 = 2000 and MapStatus.apply compares with >, 
so both measurements built a CompressedMapStatus, perBlock was exactly 0.0, and 
0.0 < 16 held. The assertion was one-sided with no floor and no type check, so 
"the skew path stored nothing" and "the skew path stored something compact" 
were indistinguishable to it.
   
    Fixed in 4196cb402ee: 1,901 small blocks plus 100 skewed, an assertion that 
the block count exceeds spark.shuffle.minNumPartitionsToHighlyCompress, an 
isInstanceOf[HighlyCompressedMapStatus] check on each measured status, and a 
check that the expected number of skewed sizes actually came back accurate 
before anything is measured.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to