blob: 909b00fafb15bea3b5cc0014ec27a692bb5feebd [file] [view]
<!--
~ 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.
-->
# Example Uniffle/Spark docker cluster
This example creates a docker cluster consisting of
- two coordinators
- three shuffle servers
- one Spark master
- two Spark workers
## Build the docker images
First build the needed docker images:
```bash
./deploy/docker/build.sh
```
## Start the docker cluster
Then start the cluster:
```bash
docker compose -f deploy/docker/docker-compose.yml up
```
```
[+] Running 8/0
✔ Container rss-coordinator-1 Created 0.0s
✔ Container rss-coordinator-2 Created 0.0s
✔ Container rss-shuffle-server-1 Created 0.0s
✔ Container rss-shuffle-server-2 Created 0.0s
✔ Container rss-shuffle-server-3 Created 0.0s
✔ Container rss-spark-master-1 Created 0.0s
✔ Container rss-spark-worker-1 Created 0.0s
✔ Container rss-spark-worker-2 Created 0.0s
```
## Scale the docker cluster
You can scale up and down this cluster, easily.
Let's scale the shuffle servers up from 3 to 4, and the Spark workers from 2 to 4:
```bash
docker compose -f deploy/docker/docker-compose.yml scale shuffle-server=4 spark-worker=4
```
```
[+] Running 11/11
✔ Container rss-coordinator-1 Running 0.0s
✔ Container rss-coordinator-2 Running 0.0s
✔ Container rss-shuffle-server-1 Running 0.0s
✔ Container rss-shuffle-server-2 Running 0.0s
✔ Container rss-shuffle-server-3 Running 0.0s
✔ Container rss-shuffle-server-4 Started 0.0s
✔ Container rss-spark-master-1 Running 0.0s
✔ Container rss-spark-worker-1 Running 0.0s
✔ Container rss-spark-worker-2 Running 0.0s
✔ Container rss-spark-worker-3 Started 0.0s
✔ Container rss-spark-worker-4 Started 0.0s
```
## Use the Spark cluster
Start a Spark shell on the cluster:
```bash
docker exec -it rss-spark-master-1 /opt/spark/bin/spark-shell \
--master spark://rss-spark-master-1:7077 \
--conf spark.serializer=org.apache.spark.serializer.KryoSerializer \
--conf spark.shuffle.manager=org.apache.spark.shuffle.RssShuffleManager \
--conf spark.rss.coordinator.quorum=rss-coordinator-1:19999,rss-coordinator-2:19999 \
--conf spark.rss.storage.type=MEMORY_LOCALFILE \
--conf spark.speculation=true
```
You can view the Spark master UI at http://localhost:8080/
The following example runs a job where
- task two fails several times
- task four observes the execution of a speculative attempt
```Scala
import org.apache.spark.TaskContext
// fails iteration (at the end) or delays iteration (each element)
// failing tasks negates iterator values, shuffle data of failing task must not leak into next stage
case class FaultyIterator(it: Iterator[java.lang.Long], fail: Boolean, sleep: Option[Int]) extends Iterator[java.lang.Long] {
override def hasNext: Boolean = it.hasNext || fail
override def next(): java.lang.Long = {
// delay iteration if requested
if (sleep.isDefined) {
val start = System.nanoTime()
while (start + sleep.get >= System.nanoTime()) { }
}
// fail at the end if requested
if (fail && !it.hasNext) throw new RuntimeException()
// just iterate
if (fail) {
-it.next()
} else {
it.next()
}
}
}
// we fail task two 3 times and delay task four so we see a speculative execution
val result = (
spark.range(0, 10000000, 1, 100)
.mapPartitions { it => {
val ctx = TaskContext.get()
FaultyIterator(
it,
ctx.partitionId == 2 && ctx.attemptNumber < 3,
Some(ctx.partitionId == 4).filter(v => v).map(_ => 250000)
)
}}
.groupBy(($"value" / 1000000).cast("int"))
.as[Long, Long]
.mapGroups{(id, it) => (id, it.length)}
.sort("_1")
.collect
)
```
We can compare the result with the expected outcome:
```Scala
assert(result.sameElements(Array((0,1000000), (1,1000000), (2,1000000), (3,1000000), (4,1000000), (5,1000000), (6,1000000), (7,1000000), (8,1000000), (9,1000000))))
```
## Stop the docker cluster
Finally, stop the cluster:
```bash
docker compose -f deploy/docker/docker-compose.yml down
```
```
[+] Running 12/12
✔ Container rss-shuffle-server-1 Removed 10.5s
✔ Container rss-shuffle-server-2 Removed 10.7s
✔ Container rss-shuffle-server-3 Removed 10.5s
✔ Container rss-shuffle-server-4 Removed 10.6s
✔ Container rss-spark-worker-1 Removed 0.8s
✔ Container rss-spark-worker-2 Removed 1.0s
✔ Container rss-spark-worker-3 Removed 0.9s
✔ Container rss-spark-worker-4 Removed 1.1s
✔ Container rss-spark-master-1 Removed 1.6s
✔ Container rss-coordinator-1 Removed 10.4s
✔ Container rss-coordinator-2 Removed 10.5s
✔ Network rss_default Removed 0.4s
```
## Dependence
This example needs [docker](https://www.docker.com/get-started/) to be installed.