Big Data

Graph processing with GraphX

This lecture processes graph data with Spark GraphX (property graphs, triplets, operators and graph algorithms). It builds a city graph for filters and neighbour sums, and runs PageRank on the followers graph. After this page you can compute degrees, triplet outputs and neighbour sums by hand.

Why graphsSearch on YouTube

Many datasets fit a table of rows and columns. Some datasets fit a graph better. The Internet is a graph of computers, routers and switches. The Web is a graph of pages joined by hyperlinks. Social networks, airports and train terminals are graphs too.

A graph gives an intuitive model for linked data. Special graph algorithms process such data fast. Spark GraphX is the Spark library for large-scale graph data.

Spark GraphXSearch on YouTube

GraphX is the Spark API for graphs and graph-parallel computation. It extends the Spark RDD with a Resilient Distributed Property Graph. A computation sees the same data as a graph and as a collection.

The property graph is a directed multigraph. Every vertex and every edge carries user-defined properties. Parallel edges allow several relationships between the same two vertices.

GraphX = RDD + property graph. A property graph is a directed multigraph with a property on each vertex and each edge.

VertexId is a 64-bit Long key. Each vertex has exactly one entry in the graph.

GraphX featuresSearch on YouTube

  • Flexibility. One engine does exploratory analysis, ETL and iterative graph computation. You can view data as graphs and as collections. You can transform and join graphs with RDDs. The Pregel API supports custom iterative algorithms.
  • Speed. GraphX is fast against other graph systems. It keeps Spark fault tolerance and ease of use. It also includes graph builders to simplify graph analytics tasks.
  • Algorithm library. PageRank, connected components, strongly connected components and triangle count ship with the library. The library also covers centralities (degrees, collectNeighborIds).

Property graphsSearch on YouTube

A property graph stores data on the vertices and on the edges. Each vertex has properties (attributes). Each edge has a label and properties. The slides use a Twitter social network as the example: users are vertices and follows edges link a follower to the followed user.

Formally, it is a directed multigraph with user-defined objects on every vertex and edge. It allows parallel edges between the same source and destination vertex. Each vertex key is a 64-bit VertexId.

Like an RDD, a property graph is immutable, distributed and fault-tolerant. GraphX spreads the graph over the cluster with vertex-partitioning heuristics.

Collaborator graphSearch on YouTube

The slides build a graph of GraphX collaborators. The vertex property holds username and occupation. The edge property holds the relationship string.

val users: RDD[(VertexId, (String, String))] = sc.parallelize(Seq(
  (3L, ("rxin", "student")),
  (7L, ("jgonzal", "postdoc")),
  (5L, ("franklin", "prof")),
  (2L, ("istoica", "prof"))))
val relationships: RDD[Edge[String]] = sc.parallelize(Seq(
  Edge(3L, 7L, "collab"),
  Edge(5L, 3L, "advisor"),
  Edge(2L, 5L, "colleague"),
  Edge(5L, 7L, "pi")))
val defaultUser = ("John Doe", "Missing")
val graph = Graph(users, relationships, defaultUser)

defaultUser covers vertices that appear in an edge but not in the vertex RDD. Here every edge endpoint exists, so the default stays unused.

2 5 3 7 colleague advisor collab pi rxin (3) jgonzal (7) istoica (2) franklin (5)
Collaborator graph. Vertices hold (name, role). Edges hold the relationship. Edge 2 to 5 reads "istoica is a colleague of franklin".

GraphX software stackSearch on YouTube

The slide stacks the layers from storage to user code. The property graph sits on RDDs of vertices and edges. Those sit on an HDFS-compatible store. Operators sit on the graph. Pregel and the algorithm library sit on the operators.

User applications PageRank · Connected components · Triangle count Graph processor · Pregel API Property · Structural · Aggregate · Join operators Property graph and graph builder Vertex RDDs · Edge RDDs HDFS-compatible store
GraphX layers. Each layer uses only the layer below it.

GraphX APISearch on YouTube

Import Spark, GraphX and the RDD package first:

import org.apache.spark._
import org.apache.spark.graphx._
import org.apache.spark.rdd.RDD

The API mirrors RDDs. RDDs have map, filter and reduceByKey. Graphs have operators that take user functions and return new graphs. It also implements the Pregel API from Google. Because GraphX lives inside Spark, user code can mix GraphX, RDD and DataFrame calls.

The slide computes the in-degree of each vertex with one call:

val inDegrees = graph.inDegrees

Data typesSearch on YouTube

TypeMeaning
VertexRDDDistributed collection of vertices. One entry per vertex: (VertexId, property). The key is a 64-bit Long.
EdgeOne directed edge: source id, destination id and edge property.
EdgeRDDDistributed collection of the edges of the graph.
EdgeTripletOne edge plus both endpoint vertices: ids and properties.
EdgeContextA triplet plus sendToSrc and sendToDst message methods.
GraphThe property graph itself. Immutable, distributed and fault-tolerant.

Edge tripletsSearch on YouTube

EdgeTriplet extends Edge with srcAttr and dstAttr. These hold the source and destination vertex properties. One triplet exists per edge.

The triplet view joins edge and vertex data in one record.

Building the city graphSearch on YouTube

The running example uses six US cities as vertices. Each vertex holds city name and population. Each edge holds the distance in kilometers.

Launch the shell and import the library:

/path/to/spark/bin/spark-shell
import org.apache.spark.graphx._
import org.apache.spark.graphx.{Edge, Graph}

Create the vertex array:

val verArray = Array(
  (1L, ("Philadelphia", 1580863)),
  (2L, ("Baltimore", 620961)),
  (3L, ("Harrisburg", 49528)),
  (4L, ("Wilmington", 70851)),
  (5L, ("New York", 8175133)),
  (6L, ("Scranton", 76089)))

The output echoes the array with its type:

verArray: Array[(Long, (String, Int))] = Array((1,(Philadelphia,1580863)),
  (2,(Baltimore,620961)), (3,(Harrisburg,49528)), (4,(Wilmington,70851)),
  (5,(New York,8175133)), (6,(Scranton,76089)))

Create the edge array. The first two arguments are source and destination ids. The third argument is the distance:

val edgeArray = Array(
  Edge(2L, 3L, 113),
  Edge(2L, 4L, 106),
  Edge(3L, 4L, 128),
  Edge(3L, 5L, 248),
  Edge(3L, 6L, 162),
  Edge(4L, 1L, 39),
  Edge(1L, 6L, 168),
  Edge(1L, 5L, 130),
  Edge(5L, 6L, 159))

Parallelize both arrays and build the graph:

val verRDD = sc.parallelize(verArray)
val edgeRDD = sc.parallelize(edgeArray)
val graph = Graph(verRDD, edgeRDD)
123456 11310612824816239168130159 PhiladelphiaBaltimoreHarrisburgWilmingtonNew YorkScranton
City graph. Six vertices, nine directed edges. Each edge label is the distance in kilometers.

Vertex filtersSearch on YouTube

graph.vertices.filter keeps the vertices that pass a predicate. The task: keep cities with population above 50000.

graph.vertices.filter {
  case (id, (city, population)) => population > 50000
}.collect.foreach {
  case (id, (city, population)) =>
    println(s"The population of $city is $population")
}

Worked example. Test each of the six vertices against population>50000population \gt 50000.

VertexCityPopulationKept?
1Philadelphia1580863yes
2Baltimore620961yes
3Harrisburg49528no (49528<5000049528 \lt 50000)
4Wilmington70851yes
5New York8175133yes
6Scranton76089yes

Output. Five lines, one per kept city.

The population of Philadelphia is 1580863
The population of Baltimore is 620961
The population of Wilmington is 70851
The population of New York is 8175133
The population of Scranton is 76089

Harrisburg is the only city dropped. Its population 49528 is below the cut.

Triplet queriesSearch on YouTube

graph.triplets.collect returns one triplet per edge. The slides print the distance stored on each edge.

The slides use it to print one sentence per relationship:

for (triplet <- graph.triplets.collect) {
  println(s"The distance between ${triplet.srcAttr._1} and " +
    s"${triplet.dstAttr._1} is ${triplet.attr} kilometers")
}

triplet.srcAttr._1 is the source city name. triplet.dstAttr._1 is the destination city name. triplet.attr is the edge property (distance).

Worked example. Nine edges give nine triplets. Each line joins source name, destination name and edge distance.

The distance between Baltimore and Harrisburg is 113 kilometers
The distance between Baltimore and Wilmington is 106 kilometers
The distance between Harrisburg and Wilmington is 128 kilometers
The distance between Harrisburg and New York is 248 kilometers
The distance between Harrisburg and Scranton is 162 kilometers
The distance between Wilmington and Philadelphia is 39 kilometers
The distance between Philadelphia and Scranton is 168 kilometers
The distance between Philadelphia and New York is 130 kilometers
The distance between New York and Scranton is 159 kilometers

Each distance matches the edge array exactly. The triplet count equals the edge count (9).

Edge filtersSearch on YouTube

graph.edges.filter keeps the edges that pass a predicate. The task: keep city pairs with distance below 150 kilometers.

graph.edges.filter {
  case Edge(city1, city2, distance) => distance < 150
}.collect.foreach {
  case Edge(city1, city2, distance) =>
    println(s"The distance between $city1 and $city2 is $distance")
}

Worked example. Test all nine distances against distance<150distance \lt 150.

EdgeDistanceKept?
2→32 \to 3113yes
2→42 \to 4106yes
3→43 \to 4128yes
3→53 \to 5248no
3→63 \to 6162no
4→14 \to 139yes
1→61 \to 6168no
1→51 \to 5130yes
5→65 \to 6159no

Result. Five edges pass the filter.

The distance between 2 and 3 is 113
The distance between 2 and 4 is 106
The distance between 3 and 4 is 128
The distance between 4 and 1 is 39
The distance between 1 and 5 is 130

The code prints vertex ids, not city names. Vertex 4 to vertex 1 (39 km) is the shortest kept link.

Neighbour aggregationSearch on YouTube

aggregateMessages sends a message along each edge and merges the messages at each vertex. The task: total population of the neighbouring cities of each vertex.

GraphX graphs are directed. To count neighbours in both directions, first add the reversed edges:

val undirectedEdgeRDD = graph.reverse.edges.union(graph.edges)
val ugraph = Graph(verRDD, undirectedEdgeRDD)
val neighbors = ugraph.aggregateMessages[Int](
  ectx => ectx.sendToSrc(ectx.dstAttr._2), _ + _)

sendToSrc sends the destination population to the source vertex. _ + _ adds the received values. Each vertex total is the sum of the populations of its neighbours.

24563 62096170851817513376089 sum = 8943034 neighbours of Harrisburg (3)
Neighbour sum for vertex 3. One message per incident edge. The four populations add to 8943034.

Worked example. Vertex 3 touches vertices 2, 4, 5 and 6 in the undirected graph. Add their populations.

neighbors(3)=620961+70851+8175133+76089=691812+8175133+76089=8866945+76089=8943034\begin{aligned} \text{neighbors}(3) &= 620961 + 70851 + 8175133 + 76089\\ &= 691812 + 8175133 + 76089\\ &= 8866945 + 76089\\ &= \mathbf{8943034} \end{aligned}

Full output. The first value is the vertex id. The second value is the neighbour-population total.

(4,2251352)
(1,8322073)
(5,1706480)
(2,120379)
(6,9805524)
(3,8943034)

Check each line. Vertex 1 touches 4, 5 and 6: 70851+8175133+76089=832207370851 + 8175133 + 76089 = 8322073. Vertex 2 touches 3 and 4: 49528+70851=12037949528 + 70851 = 120379. Vertex 4 touches 1, 2 and 3: 1580863+620961+49528=22513521580863 + 620961 + 49528 = 2251352. Vertex 5 touches 1, 3 and 6: 1580863+49528+76089=17064801580863 + 49528 + 76089 = 1706480. Vertex 6 touches 1, 3 and 5: 1580863+49528+8175133=98055241580863 + 49528 + 8175133 = 9805524. Every line matches.

Reverse the edges before you aggregate both directions. Without the union, each vertex sees only its out-neighbours. Vertex 6 has no out-edges, so its total would be 0 instead of 9805524.

Graph operatorsSearch on YouTube

Call an operator on the graph object. The operators fall in four groups.

Build Property Structures Jointly: 5-3-4-2

Basic holds 5 operators. Property holds 3. Structural holds 4. Join holds 2.

Basic operatorsSearch on YouTube

OperatorMeaning
numEdgesCount of edges in the graph.
numVerticesCount of vertices in the graph.
inDegreesPer vertex, count of edges that end at it.
outDegreesPer vertex, count of edges that start at it.
degreesPer vertex, in-degree plus out-degree.
val numEdges = socialGraph.numEdges
val numVertices = socialGraph.numVertices
val inDegrees = socialGraph.inDegrees
inDegrees.collect
val outDegrees = socialGraph.outDegrees
outDegrees.collect
val degrees = socialGraph.degrees
degrees.collect

graph.vertices, graph.edges and graph.triplets give collection views of each part. triplets.take(3) shows the first three triplets.

Property operatorsSearch on YouTube

OperatorMeaning
mapVerticesReturn a new graph with a transformed vertex property. Structure is unchanged.
mapEdgesReturn a new graph with a transformed edge property. Structure is unchanged.
mapTripletsReturn a new graph with a transformed edge property computed from the full triplet.

Social graph degreesSearch on YouTube

The slides build a second graph of ten users and fifteen follows edges. Vertex 11 has no user record, so it takes the default:

val users = List((1L, User("Alex", 26)), (2L, User("Bill", 42)),
  (3L, User("Carol", 18)), (4L, User("Dave", 16)), (5L, User("Eve", 45)),
  (6L, User("Farell", 30)), (7L, User("Garry", 32)),
  (8L, User("Harry", 36)), (9L, User("Ivan", 28)), (10L, User("Jill", 48)))
val usersRDD = sc.parallelize(users)
val follows = List(Edge(1L, 2L, 1), Edge(2L, 3L, 1), Edge(3L, 1L, 1),
  Edge(3L, 4L, 1), Edge(3L, 5L, 1), Edge(4L, 5L, 1), Edge(6L, 5L, 1),
  Edge(7L, 6L, 1), Edge(6L, 8L, 1), Edge(7L, 8L, 1), Edge(7L, 9L, 1),
  Edge(9L, 8L, 1), Edge(8L, 10L, 1), Edge(10L, 9L, 1), Edge(1L, 11L, 1))
val followsRDD = sc.parallelize(follows)
val defaultUser = User("NA", 0)
val socialGraph = Graph(usersRDD, followsRDD, defaultUser)

numEdges is 15 (count the list). numVertices is 11 (vertices 1 to 10 plus vertex 11 with the default user).

Worked example. Count in-edges and out-edges per vertex from the fifteen-edge list.

VertexIn-edgesIn-degreeOut-edgesOut-degreeDegree
1from 31to 2, 1123
2from 11to 312
3from 21to 1, 4, 534
4from 31to 512
5from 3, 4, 63none03
6from 71to 5, 823
7none0to 6, 8, 933
8from 6, 7, 93to 1014
9from 7, 102to 813
10from 81to 912
11from 11none01

Check. In-degrees add to 1+1+1+1+3+1+0+3+2+1+1=151+1+1+1+3+1+0+3+2+1+1 = 15. Out-degrees add to 2+1+3+1+0+2+3+1+1+1+0=152+1+3+1+0+2+3+1+1+1+0 = 15. Both equal numEdges. Vertex 5 (three followers) and vertex 8 (three followers) have the top in-degree.

Structural operatorsSearch on YouTube

// signatures only
abstract class Graph[VD, ED] {
  def reverse: Graph[VD, ED]
  def subgraph(epred: EdgeTriplet[VD, ED] => Boolean,
    vpred: (VertexId, VD) => Boolean): Graph[VD, ED]
  def mask[VD2, ED2](other: Graph[VD2, ED2]): Graph[VD, ED]
  def groupEdges(merge: (ED, ED) => ED): Graph[VD, ED]
}
  • reverse. Return a new graph with every edge direction flipped. Vertex and edge properties stay the same. Edge count stays the same. It needs no data movement. Use it for inverse PageRank.
  • subgraph. Keep only vertices that pass vpred and edges that pass epred and join two kept vertices. Use it to restrict the graph to the part of interest or to drop broken edges.
  • mask. Keep only vertices and edges that also exist in the other graph. Use it to intersect two graphs over the same vertex set.
  • groupEdges. Merge parallel edges between the same vertices with the merge function. Use it after a union (as in the neighbour-sum example) to combine duplicate edges.

Join operatorsSearch on YouTube

// signatures only
abstract class Graph[VD, ED] {
  def joinVertices[U](table: RDD[(VertexId, U)])
    (map: (VertexId, VD, U) => VD): Graph[VD, ED]
  def outerJoinVertices[U, VD2](table: RDD[(VertexId, U)])
    (map: (VertexId, VD, Option[U]) => VD2): Graph[VD2, ED]
}
  • joinVertices. Join an RDD of (VertexId, value) pairs into the vertex properties. Only vertices present in the table change. The vertex type stays VD.
  • outerJoinVertices. Same join, but every vertex gets a new property. Vertices missing from the table get None. The vertex type may change to VD2. Use it to attach optional new data to all vertices.
joinVerticesouterJoinVertices
Updates only matched verticesUpdates every vertex (Some or None)
Keeps the old vertex typeMay set a new vertex type
Use to refresh known valuesUse to add possibly-missing data

PageRankSearch on YouTube

PageRank scores the importance of each vertex. An edge from u to v is an endorsement of v by u. A user followed by many users ranks high.

Run it on the bundled social-network files. Load the edge list, run PageRank, then join ranks back to usernames:

import org.apache.spark.graphx.GraphLoader
val graph = GraphLoader.edgeListFile(sc, "data/graphx/followers.txt")
val ranks = graph.pageRank(0.0001).vertices
val users = sc.textFile("data/graphx/users.txt").map { line =>
  val fields = line.split(",")
  (fields(0).toLong, fields(1))
}
val ranksByUsername = users.join(ranks).map {
  case (id, (username, rank)) => (username, rank)
}
println(ranksByUsername.collect().mkString("\n"))

pageRank(0.0001) iterates until ranks move less than the tolerance 0.0001. The join pairs each id with its (username, rank) tuple.

PageRank iteration (extra). The slides name PageRank but give no formula. With damping factor dd over NN vertices:

PR(v)=1−dN+d∑u→vPR(u)outdeg(u)PR(v) = \frac{1-d}{N} + d \sum_{u \to v} \frac{PR(u)}{\text{outdeg}(u)}

Each vertex shares its rank equally among its out-neighbours. The sum of all ranks stays 1. Vertices with no out-edges (sinks) need extra handling, or the total rank leaks below 1.

Worked example (extra). Three vertices: 1→21 \to 2, 1→31 \to 3, 2→32 \to 3, 3→13 \to 1. Damping d=0.85d = 0.85, N=3N = 3. Start with PR=1/3PR = 1/3 everywhere. The base term is (1−0.85)/3=0.05(1-0.85)/3 = 0.05.

PR(1)=0.05+0.85×1/31=0.05+0.2833=0.3333PR(2)=0.05+0.85×1/32=0.05+0.1417=0.1917PR(3)=0.05+0.85×(1/32+1/31)=0.05+0.425=0.4750\begin{aligned} PR(1) &= 0.05 + 0.85 \times \frac{1/3}{1} = 0.05 + 0.2833 = \mathbf{0.3333}\\ PR(2) &= 0.05 + 0.85 \times \frac{1/3}{2} = 0.05 + 0.1417 = \mathbf{0.1917}\\ PR(3) &= 0.05 + 0.85 \times \left(\frac{1/3}{2} + \frac{1/3}{1}\right) = 0.05 + 0.425 = \mathbf{0.4750} \end{aligned}

Check. 0.3333+0.1917+0.4750=1.00000.3333 + 0.1917 + 0.4750 = 1.0000. Vertex 3 wins: two vertices endorse it, and vertex 1 splits its vote between vertices 2 and 3.

Connected componentsSearch on YouTube

The connected-components algorithm labels each component with an id. GraphX ships it in the ConnectedComponents object. It is one use of the aggregation operator (like shortest paths and smallest reachable vertex id). The Pregel operator then runs it in super-steps: each vertex receives the sum (or minimum) of inbound messages per step.

import org.apache.spark.graphx.GraphLoader
val graph = GraphLoader.edgeListFile(sc, "data/graphx/followers.txt")
val cc = graph.connectedComponents().vertices
val users = sc.textFile("data/graphx/users.txt").map { line =>
  val fields = line.split(",")
  (fields(0).toLong, fields(1))
}
val ccByUsername = users.join(cc).map {
  case (id, (username, cc)) => (username, cc)
}
println(ccByUsername.collect().mkString("\n"))

Worked example. Five vertices, edges 1→21 \to 2, 2→32 \to 3, 4→54 \to 5. Ignoring direction, the graph has two pieces: {1,2,3}\{1,2,3\} and {4,5}\{4,5\}. Each piece takes its smallest vertex id as its label.

VertexComponent
11
21
31
44
54

The city graph has a single component (label 1): every city reaches every other city through the undirected edges.

Triangle countingSearch on YouTube

triangleCount returns, per vertex, the count of triangles through it. A triangle measures clustering: it shows how tightly the neighbours of a vertex link to each other.

Two requirements hold. Edges must sit in canonical orientation (srcId<dstId\text{srcId} \lt \text{dstId}). The graph must use partitionBy first:

import org.apache.spark.graphx.{GraphLoader, PartitionStrategy}
val graph = GraphLoader.edgeListFile(sc, "data/graphx/followers.txt", true)
  .partitionBy(PartitionStrategy.RandomVertexCut)
val triCounts = graph.triangleCount().vertices
val users = sc.textFile("data/graphx/users.txt").map { line =>
  val fields = line.split(",")
  (fields(0).toLong, fields(1))
}
val triCountByUsername = users.join(triCounts).map {
  case (id, (username, tc)) => (username, tc)
}
println(triCountByUsername.collect().mkString("\n"))

The true flag in edgeListFile requests canonical orientation. RandomVertexCut hashes the (srcId, dstId) pair, so an edge and its reverse land in the same partition.

Worked example. Four vertices with undirected links 1-2, 2-3, 1-3 and 3-4. Canonical edges: (1,2)(1,2), (1,3)(1,3), (2,3)(2,3), (3,4)(3,4). Only {1,2,3}\{1,2,3\} closes into a triangle. Vertex 4 hangs off vertex 3 with no second link.

VertexTriangles
11
21
31
40

In the social graph, {1,2,3}\{1,2,3\} forms a triangle through edges (1,2)(1,2), (2,3)(2,3) and (3,1)(3,1). So each of vertices 1, 2 and 3 has at least one triangle.

Quick self-testSearch on YouTube

1. What is a property graph, and how big is a VertexId?

A property graph is a directed multigraph with user-defined properties on every vertex and edge. It allows parallel edges between the same vertices. Each vertex key (VertexId) is a 64-bit Long.

2. The city graph has nine edges. They run 2 to 3, 2 to 4 and 3 to 4. The rest run 3 to 5, 3 to 6, 4 to 1, 1 to 6, 1 to 5 and 5 to 6. Find the in-degree, out-degree and degree of vertex 3.

In-degree is 1 (only from vertex 2). Out-degree is 3 (to vertices 4, 5 and 6). Degree is 1+3=41 + 3 = 4.

3. Which cities pass the filter "population above 50000", and which city fails it?

Philadelphia, Baltimore, Wilmington, New York and Scranton pass. Harrisburg fails with 49528, which is below 50000.

4. Why does the neighbour-sum code union the graph with its reverse first?

GraphX edges are directed. The plain graph gives each vertex only its out-neighbours. The union adds every edge in both directions. Without it, vertex 6 (no out-edges) would total 0 instead of 9805524.

5. Which city pairs pass the filter "distance below 150 km"? Give all five with distances.

(2,3) 113, (2,4) 106, (3,4) 128, (4,1) 39 and (1,5) 130. Edges of 248, 162, 168 and 159 km fail the test.

6. Differentiate subgraph and mask.

subgraph keeps vertices and edges that pass given predicates. mask keeps vertices and edges that also exist in another graph. Use subgraph to select by property, and mask to intersect two graphs.

7. Compute the neighbour-population total for vertex 4 of the city graph.

Vertex 4 touches vertices 1, 2 and 3 in the undirected graph. Total =1580863+620961+49528=2251352= 1580863 + 620961 + 49528 = 2251352.

8. Differentiate joinVertices and outerJoinVertices.

joinVertices updates only vertices found in the joined table and keeps the old vertex type. outerJoinVertices updates every vertex (missing ones get None) and may set a new vertex type.

9. Four vertices have undirected links 1-2, 2-3, 1-3 and 3-4. What is the triangle count of each vertex?

Vertices 1, 2 and 3 sit on one triangle, so each has count 1. Vertex 4 has count 0. It hangs off vertex 3 with no closing link.

10. What two setup steps does triangleCount require, and what does pageRank(0.0001) mean?

Edges must use canonical orientation (srcId<dstId\text{srcId} \lt \text{dstId}) and the graph must use partitionBy first. pageRank(0.0001) iterates PageRank until ranks move less than the tolerance 0.0001.

  • A graph suits linked data (web, social networks, transport). GraphX extends the Spark RDD with a Resilient Distributed Property Graph.
  • A property graph is a directed multigraph with properties on every vertex and edge. Each vertex key is a 64-bit VertexId. Graphs are immutable, distributed and fault-tolerant.
  • Key types: VertexRDD, Edge, EdgeRDD, EdgeTriplet (edge plus srcAttr and dstAttr), EdgeContext (triplet plus sendToSrc/sendToDst), Graph.
  • The city graph has 6 vertices and 9 directed edges with distances. Build it with sc.parallelize on arrays, then Graph(verRDD, edgeRDD).
  • Vertex filter above 50000 keeps five cities and drops Harrisburg (49528). Edge filter below 150 km keeps five edges (113, 106, 128, 39, 130).
  • Triplets give one record per edge. Nine edges give nine distance sentences.
  • aggregateMessages with sendToSrc and _ + _ sums neighbour populations. Reverse the graph first for both directions. Vertex 3 totals 8943034.
  • Operators: basic 5, property 3, structural 4, join 2 ("Build Property Structures Jointly: 5-3-4-2").
  • reverse flips edge directions. subgraph selects by predicate. mask intersects graphs. groupEdges merges parallel edges.
  • joinVertices updates matched vertices only. outerJoinVertices updates all vertices with Some or None.
  • PageRank scores vertex importance through endorsements: PR(v)=(1−d)/N+d∑PR(u)/outdeg(u)PR(v) = (1-d)/N + d\sum PR(u)/\text{outdeg}(u).
  • Connected components label each piece with an id. Triangle count needs canonical orientation and partitionBy.