Skip to content
This repository was archived by the owner on Oct 23, 2024. It is now read-only.

Commit fbf7f29

Browse files
Tim Harpertimcharper
Tim Harper
authored andcommitted
improve deployment plan computation perf by ~2000x
Summary: import mesosphere.marathon.stream.Implicits._ brought in a method that implicitly converted scala maps to java maps; we were calling the java `containsKey` Java map method instead of `contains` scala map method, and this led the map to be converted to a Java version, only to have the containsKey method invoked, and entire data structure discarded. Also adds a flat DependencyPlan computation benchmark for apps and pods. backport of 8e538f9 Test Plan: benchmark included Reviewers: jdef, ichernetsky, jenkins Reviewed By: jdef, ichernetsky, jenkins Subscribers: marathon-dev JIRA Issues: MARATHON-7479
1 parent 6e6ed36 commit fbf7f29

File tree

5 files changed

+119
-18
lines changed

5 files changed

+119
-18
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package mesosphere.marathon
2+
package upgrade
3+
4+
import java.util.concurrent.TimeUnit
5+
import mesosphere.marathon.core.pod.{ MesosContainer, BridgeNetwork }
6+
import mesosphere.marathon.raml.{ Endpoint, Image, ImageType, Resources }
7+
import mesosphere.marathon.state.Container
8+
9+
import mesosphere.marathon.state.PathId._
10+
import mesosphere.marathon.state._
11+
import mesosphere.marathon.core.deployment.DeploymentPlan
12+
import mesosphere.marathon.core.pod.PodDefinition
13+
import org.openjdk.jmh.annotations.{ Group => _, _ }
14+
import org.openjdk.jmh.infra.Blackhole
15+
16+
import scala.collection.breakOut
17+
18+
@State(Scope.Benchmark)
19+
object FlatDependencyBenchmark {
20+
21+
val version = VersionInfo.forNewConfig(Timestamp(1))
22+
23+
def makeApp(path: PathId) =
24+
AppDefinition(
25+
id = path,
26+
labels = Map("ID" -> path.toString),
27+
versionInfo = version,
28+
networks = Seq(BridgeNetwork()),
29+
container = Some(
30+
Container.Docker(Nil, "alpine", List(Container.PortMapping(2015, Some(0), 10000, "tcp", Some("thing")))))
31+
)
32+
33+
def makePod(path: PathId) =
34+
PodDefinition(
35+
id = path,
36+
networks = Seq(BridgeNetwork()),
37+
labels = Map("ID" -> path.toString),
38+
version = version.lastConfigChangeAt,
39+
40+
containers = Seq(
41+
MesosContainer(
42+
"container-1",
43+
resources = Resources(1.0),
44+
image = Some(Image(ImageType.Docker, "alpine")),
45+
endpoints = List(
46+
Endpoint(
47+
"service",
48+
Some(2015),
49+
Some(0),
50+
Seq("tcp"))))))
51+
52+
val ids = 0 to 900
53+
54+
val podPaths: Vector[PathId] = ids.map { podId =>
55+
s"/pod-${podId}".toPath
56+
}(breakOut)
57+
58+
val appPaths: Vector[PathId] = ids.map { appId =>
59+
s"/app-${appId}".toPath
60+
}(breakOut)
61+
62+
val appDefs: Map[PathId, AppDefinition] = appPaths.map { path =>
63+
path -> makeApp(path)
64+
}(breakOut)
65+
66+
val podDefs: Map[PathId, PodDefinition] = podPaths.map { path =>
67+
path -> makePod(path)
68+
}(breakOut)
69+
70+
val rootGroup = RootGroup(apps = appDefs, pods = podDefs)
71+
def upgraded = {
72+
val pathId = "/app-901".toPath
73+
RootGroup(
74+
apps = rootGroup.apps + (pathId -> makeApp(pathId)),
75+
pods = rootGroup.pods + (pathId -> makePod(pathId))
76+
)
77+
}
78+
}
79+
80+
@OutputTimeUnit(TimeUnit.MICROSECONDS)
81+
@BenchmarkMode(Array(Mode.Throughput, Mode.AverageTime))
82+
@Fork(1)
83+
class FlatDependencyBenchmark {
84+
import FlatDependencyBenchmark._
85+
86+
@Benchmark
87+
def deploymentPlanDependencySpeed(hole: Blackhole): Unit = {
88+
val deployment = DeploymentPlan(rootGroup, upgraded)
89+
hole.consume(deployment)
90+
}
91+
}

src/main/scala/mesosphere/marathon/core/pod/PodDefinition.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ case class PodDefinition(
3333
override val killSelection: KillSelection = KillSelection.DefaultKillSelection
3434
) extends RunSpec with plugin.PodSpec with MarathonState[Protos.Json, PodDefinition] {
3535

36+
/**
37+
* As an optimization, we precompute and cache the hash of this object
38+
* This is done to speed up deployment plan computation.
39+
*/
40+
override val hashCode: Int = scala.util.hashing.MurmurHash3.productHash(this)
41+
3642
val endpoints: Seq[Endpoint] = containers.flatMap(_.endpoints)
3743
val resources = aggregateResources()
3844

src/main/scala/mesosphere/marathon/state/AppDefinition.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ case class AppDefinition(
8686
override val killSelection: KillSelection = KillSelection.DefaultKillSelection) extends RunSpec
8787
with plugin.ApplicationSpec with MarathonState[Protos.ServiceDefinition, AppDefinition] {
8888

89+
/**
90+
* As an optimization, we precompute and cache the hash of this object
91+
* This is done to speed up deployment plan computation.
92+
*/
93+
override val hashCode: Int = scala.util.hashing.MurmurHash3.productHash(this)
8994
import mesosphere.mesos.protos.Implicits._
9095

9196
require(

src/main/scala/mesosphere/marathon/state/PathId.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,12 @@ case class PathId(path: Seq[String], absolute: Boolean = true) extends Ordered[P
8383

8484
override def equals(obj: Any): Boolean = {
8585
obj match {
86-
case that: PathId => (that eq this) || (that.toString == toString)
86+
case that: PathId => (that eq this) || (that.hashCode == hashCode && that.absolute == absolute && that.path == path)
8787
case _ => false
8888
}
8989
}
9090

91-
override def hashCode(): Int = toString.hashCode()
91+
override val hashCode: Int = scala.util.hashing.MurmurHash3.productHash(this)
9292
}
9393

9494
object PathId {

src/main/scala/mesosphere/marathon/upgrade/DeploymentPlan.scala

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import mesosphere.marathon.raml.{ ArgvCommand, ShellCommand }
1313
import mesosphere.marathon.state._
1414
import mesosphere.marathon.storage.TwitterZk
1515
import mesosphere.marathon.storage.repository.legacy.store.{ CompressionConf, ZKData }
16-
import mesosphere.marathon.stream._
1716
import org.slf4j.LoggerFactory
1817

1918
import scala.collection.SortedMap
@@ -96,19 +95,20 @@ case class DeploymentPlan(
9695

9796
lazy val nonEmpty: Boolean = !isEmpty
9897

99-
lazy val affectedRunSpecs: Set[RunSpec] = steps.flatMap(_.actions.map(_.runSpec)).toSet
98+
lazy val affectedRunSpecs: Set[RunSpec] = steps.flatMap(_.actions.map(_.runSpec))(collection.breakOut)
10099

101100
/** @return all ids of apps which are referenced in any deployment actions */
102-
lazy val affectedRunSpecIds: Set[PathId] = steps.flatMap(_.actions.map(_.runSpec.id)).toSet
101+
lazy val affectedRunSpecIds: Set[PathId] = steps.flatMap(_.actions.map(_.runSpec.id))(collection.breakOut)
103102

104-
def affectedAppIds: Set[PathId] = affectedRunSpecs.collect{ case app: AppDefinition => app }.map(_.id)
105-
def affectedPodIds: Set[PathId] = affectedRunSpecs.collect{ case pod: PodDefinition => pod }.map(_.id)
103+
def affectedAppIds: Set[PathId] = affectedRunSpecs.collect{ case app: AppDefinition => app.id }
104+
def affectedPodIds: Set[PathId] = affectedRunSpecs.collect{ case pod: PodDefinition => pod.id }
106105

107106
def isAffectedBy(other: DeploymentPlan): Boolean =
108107
// FIXME: check for group change conflicts?
109108
affectedRunSpecIds.intersect(other.affectedRunSpecIds).nonEmpty
110109

111110
lazy val createdOrUpdatedApps: Seq[AppDefinition] = {
111+
import mesosphere.marathon.stream.toRichTraversableLike
112112
target.transitiveApps.filterAs(app => affectedRunSpecIds(app.id))(collection.breakOut)
113113
}
114114

@@ -117,6 +117,7 @@ case class DeploymentPlan(
117117
}
118118

119119
lazy val createdOrUpdatedPods: Seq[PodDefinition] = {
120+
import mesosphere.marathon.stream.toRichTraversableLike
120121
target.transitivePodsById.values.filterAs(pod => affectedRunSpecIds(pod.id))(collection.breakOut)
121122
}
122123

@@ -213,6 +214,8 @@ object DeploymentPlan {
213214
import org.jgrapht.graph.DefaultEdge
214215

215216
def longestPathFromVertex[V](g: DirectedGraph[V, DefaultEdge], vertex: V): Seq[V] = {
217+
import mesosphere.marathon.stream.RichSet
218+
216219
val outgoingEdges: Set[DefaultEdge] =
217220
if (g.containsVertex(vertex)) g.outgoingEdgesOf(vertex)
218221
else Set.empty[DefaultEdge]
@@ -313,17 +316,13 @@ object DeploymentPlan {
313316
)
314317

315318
// applications that are either new or the specs are different should be considered for the dependency graph
316-
val addedOrChanged: Set[PathId] = targetRuns.flatMap {
317-
case (runSpecId, spec) =>
318-
if (!originalRuns.containsKey(runSpecId) ||
319-
(originalRuns.containsKey(runSpecId) && originalRuns(runSpecId) != spec)) {
320-
// the above could be optimized/refined further by checking the version info. The tests are actually
321-
// really bad about structuring this correctly though, so for now, we just make sure that
322-
// the specs are different (or brand new)
323-
Some(runSpecId)
324-
} else {
325-
None
326-
}
319+
val addedOrChanged: Set[PathId] = targetRuns.collect {
320+
case (runSpecId, spec) if (!originalRuns.contains(runSpecId) ||
321+
(originalRuns.contains(runSpecId) && originalRuns(runSpecId) != spec)) =>
322+
// the above could be optimized/refined further by checking the version info. The tests are actually
323+
// really bad about structuring this correctly though, so for now, we just make sure that
324+
// the specs are different (or brand new)
325+
runSpecId
327326
}(collection.breakOut)
328327
val affectedApplications = addedOrChanged ++ (originalRuns.keySet -- targetRuns.keySet)
329328

0 commit comments

Comments
 (0)