Batch & Stream Graph Processing with Apache Flink
Vasia Kalavri vasia@apache.org @vkalavri
Batch & Stream Graph Processing with Apache Flink Vasia - - PowerPoint PPT Presentation
Batch & Stream Graph Processing with Apache Flink Vasia Kalavri vasia@apache.org @vkalavri Outline Distributed Graph Processing Gelly: Batch Graph Processing with Flink Gelly-Stream: Continuous Graph Processing with Flink WHEN
Vasia Kalavri vasia@apache.org @vkalavri
Big Data Ninja
MISCONCEPTION #1
▸ Naive Who(m) to Follow:
▸ compute a friends-of-friends
list per user
▸ exclude existing friends ▸ rank by common
connections
Data Science Rockstar
MISCONCEPTION #2
Expectation…
Reality!
▸ Iterative value propagation
▸ PageRank, Connected Components, Label Propagation
▸ Traversals and path exploration
▸ Shortest paths, centrality measures
▸ Ego-network analysis
▸ Personalized recommendations
▸ Pattern mining
▸ Finding frequent subgraphs
Adjacency Matrix
1 2 3 4 5 1 1 1 2 1 1 3 4 1 1 1 5 1
1 5 4 3 2
1 5 4 3 2
1 2 3 4 5 1 1 1 2 1 1 3 4 1 1 1 5 1
1 5 4 3 2
1 2 3 4 5 1 1 1 2 1 1 3 4 1 1 1 5 1
1 5 4 3 2
1
X =
1 1 1 2 3 4 5 1 1 1 2 1 1 3 4 1 1 1 5 1
1 5 4 3 2
1
X =
1 1 1 2 3 4 5 1 1 1 2 1 1 3 4 1 1 1 5 1
1 5 4 3 2
1 1
X =
1 1 1 1 2 3 4 5 1 1 1 2 1 1 3 4 1 1 1 5 1
RECENT DISTRIBUTED GRAPH PROCESSING HISTORY
2004
MapReduce Pegasus
2009
Pregel
2010
Signal-Collect PowerGraph
2012 Iterative value propagation
Giraph++
2013 Graph Traversals
NScale
2014 Ego-network analysis
Arabesque
2015 Pattern Matching
Tinkerpop
1 5 4 3 2 1 3, 4 2 1, 4 5 3
. . .
(Vi+1, outbox) <— compute(Vi, inbox)
1
3,
2
1,
5
3
. .
1
3,
2
1,
5
3
. .
Superstep i Superstep i+1
PAGERANK: THE WORD COUNT OF GRAPH PROCESSING
VertexID Out-degree Transition Probability
1 2 1/2 2 2 1/2 3
3 1/3 5 1 1
1 5 4 3 2
PAGERANK: THE WORD COUNT OF GRAPH PROCESSING
VertexID Out-degree Transition Probability
1 2 1/2 2 2 1/2 3
3 1/3 5 1 1
1 5 4 3 2
PR(3) = 0.5*PR(1) + 0.33*PR(4) + PR(5)
void compute(messages): sum = 0.0 for (m <- messages) do sum = sum + m end for setValue(0.15/numVertices() + 0.85*sum) for (edge <- getOutEdges()) do sendMessageTo( edge.target(), getValue()/numEdges) end for
sum up received messages
update vertex rank
distribute rank to neighbors
1
3,
2
1,
5
3
. .
1
3,
2
1,
5
3
. .
Superstep i
Vi+1 <— collect(inbox)
1
3,
2
1,
5
3
. .
Signal Collect Superstep i+1
void signal(): for (edge <- getOutEdges()) do sendMessageTo( edge.target(), getValue()/numEdges) end for void collect(messages): sum = 0.0 for (m <- messages) do sum = sum + m end for setValue(0.15/numVertices() + 0.85*sum)
sum up received messages update vertex rank distribute rank to neighbors
1
. . . . . .
Gather Sum
1 2 5
. . .
Apply
3 1 5 5 3 1
. . .
Gather
3 1 5 5 3
Superstep i Superstep i+1
double gather(source, edge, target): return target.value() / target.numEdges() double sum(rank1, rank2): return rank1 + rank2 double apply(sum, currentRank): return 0.15 + 0.85*sum
compute partial rank combine partial ranks update rank
▸ Excessive communication ▸ Worker load imbalance ▸ Global Synchronization ▸ High memory requirements
▸ inbox /outbox can grow too large ▸ overhead for low-degree vertices in GSA
Vertex-Centric Connected Components
value through the graph
propagates one hop
supersets to converge
1 5 4 3 2
each partition
partitions, not vertices
1 5 4 3 2
Subgraph-Centric Connected Components
propagates throughout each subgraph
partitions only
supersteps to converge
RECENT DISTRIBUTED GRAPH PROCESSING HISTORY
2004
MapReduce Pegasus
2009
Pregel
2010
Signal-Collect PowerGraph
2012 Iterative value propagation
Giraph++
2013 Graph Traversals
NScale
2014 Ego-network analysis
Arabesque
2015 Pattern Matching
Tinkerpop
▸ Data pipeline integration: built on top of an
efficient distributed processing engine
▸ Graph ETL: high-level API with abstractions and
methods to transform graphs
▸ Familiar programming model: support popular
programming abstractions
the Apache Flink Graph API
Gelly Table ML SAMOA DataSet (Java/Scala) DataStream (Java/Scala)
Hadoop M/R
Local Remote Yarn Embedded Dataflow
Dataflow (WiP)
Table Cascading Streaming dataflow runtime CEP
Input
Iterative Update Function
Result Replace Workset
Iterative Update Function
Result Solution Set
State
Push work “out of the loop” Maintain state as index Cache Loop-invariant Data
API to easily implement applications that use both record-based and graph-based analysis
ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); DataSet<Edge<Long, NullValue>> edges = getEdgesDataSet(env); Graph<Long, Long, NullValue> graph = Graph.fromDataSet(edges, env); DataSet<Vertex<Long, Long>> verticesWithMinIds = graph.run( new ConnectedComponents(maxIterations)); val env = ExecutionEnvironment.getExecutionEnvironment val edges: DataSet[Edge[Long, NullValue]] = getEdgesDataSet(env) val graph = Graph.fromDataSet(edges, env) val components = graph.run(new ConnectedComponents(maxIterations))
Java Scala
Graph Properties
getVertexIds getEdgeIds numberOfVertices numberOfEdges getDegrees ...
Transformations
map, filter, join subgraph, union, difference reverse, undirected getTriplets
Mutations
add vertex/edge remove vertex/edge
// increment each vertex value by one val graph = Graph.fromDataSet(...) // increment each vertex value by one val updatedGraph = graph.mapVertices(v => v.getValue + 1)
4 2 8 5 5 3 1 7 4 5
val graph: Graph[Long, Long, Long] = ... // keep only vertices with positive values // and only edges with negative values val subGraph = graph.subgraph( vertex => vertex.getValue > 0, edge => edge.getValue < 0 )
neighborhood of each vertex in parallel
graph.reduceOnNeighbors( new MinValue, EdgeDirection.OUT)
final class SSSPComputeFunction extends ComputeFunction {
var minDistance = if (vertex.getId == srcId) 0 else Double.MaxValue while (messages.hasNext) { val msg = messages.next if (msg < minDistance) minDistance = msg } if (vertex.getValue > minDistance) { setNewVertexValue(minDistance) for (edge: Edge <- getEdges) sendMessageTo(edge.getTarget, vertex.getValue + edge.getValue) }
single-pass stream graph processing with Flink
Real Graphs are dynamic
Graphs are created from events happening in real-time
How we’ve done graph processing so far
from disk and partition it in memory
mutate the graph state
How we’ve done graph processing so far
from disk and partition it in memory
graph state back to disk
How we’ve done graph processing so far
mutate the graph state
from disk and partition it in memory
What’s wrong with this model?
any result
scale
Graph Streaming Challenges
dynamic graph structure
results with low latency
state only
Single-Pass Graph Streaming
windows
Graph Summaries
graph summary algorithm algorithm
~
R1 R2
1 4 3 2 5
i=0
6 7 8
1 4 3 2 5 6 7 8
i=0
1 4 3 4 5 2 3 5 2 4 7 8 6 7 6 8
1 2 1 2 2
i=1
6 6 6
1 2 1 1 2 6 6 6
i=1
2 1 2 2 1 1 2 1 2 7 6 6 6
1 1 1 1 1
i=2
6 6 6
5 4 7 6 8 6 4 2 3 1 5 2
Stream Connected Components
Graph Summary: Disjoint Set (Union-Find)
and vertex IDs
5 4 7 6 8 6 4 2 4 3 3 1 5 2 1 3
Cid = 1
5 4 7 6 8 6 4 2 4 3 8 7 3 1 5 2 1 3
Cid = 1
2 5
Cid = 2
5 4 7 6 8 6 4 2 4 3 8 7 4 1 3 1 5 2 1 3
Cid = 1
2 5
Cid = 2
4
5 4 7 6 8 6 4 2 4 3 8 7 4 1 3 1 5 2 1 3
Cid = 1
2 5
Cid = 2
4 6 7
Cid = 6
5 4 7 6 8 6 4 2 4 3 8 7 4 1 3 1 5 2 1 3
Cid = 1
2 5
Cid = 2
4 6 7
Cid = 6
8
5 4 7 6 8 6 4 2 4 3 8 7 4 1 3 1 5 2 1 3
Cid = 1
2 5
Cid = 2
4 6 7
Cid = 6
8
5 4 7 6 8 6 4 2 4 3 8 7 4 1 5 2 6 7
Cid = 6
8 1 3
Cid = 1
2 5
Cid = 2
4
5 4 7 6 8 6 4 2 4 3 8 7 4 1 5 2 1 3
Cid = 1
2 5 4 6 7
Cid = 6
8
Distributed Stream Connected Components
DataStream<DisjointSet> cc = edgeStream .keyBy(0) .timeWindow(Time.of(100, TimeUnit.MILLISECONDS)) .fold(new DisjointSet(), new UpdateCC()) .flatMap(new Merger()) .setParallelism(1);
DataStream<DisjointSet> cc = edgeStream .keyBy(0) .timeWindow(Time.of(100, TimeUnit.MILLISECONDS)) .fold(new DisjointSet(), new UpdateCC()) .flatMap(new Merger()) .setParallelism(1);
Partition the edge stream
DataStream<DisjointSet> cc = edgeStream .keyBy(0) .timeWindow(Time.of(100, TimeUnit.MILLISECONDS)) .fold(new DisjointSet(), new UpdateCC()) .flatMap(new Merger()) .setParallelism(1);
Define the merging frequency
DataStream<DisjointSet> cc = edgeStream .keyBy(0) .timeWindow(Time.of(100, TimeUnit.MILLISECONDS)) .fold(new DisjointSet(), new UpdateCC()) .flatMap(new Merger()) .setParallelism(1);
merge locally
DataStream<DisjointSet> cc = edgeStream .keyBy(0) .timeWindow(Time.of(100, TimeUnit.MILLISECONDS)) .fold(new DisjointSet(), new UpdateCC()) .flatMap(new Merger()) .setParallelism(1);
merge globally
DataStream DataSet Distributed Dataflow Deployment
Gelly Gelly-Stream
DataStream
Gelly-Stream enriches the DataStream API with two new additional ADTs:
.getEdges() .getVertices() .numberOfVertices() .numberOfEdges() .getDegrees() .inDegrees() .outDegrees()
GraphStream -> DataStream
.mapEdges(); .distinct(); .filterVertices(); .filterEdges(); .reverse(); .undirected(); .union();
GraphStream -> GraphStream
Property Streams Transformations
result aggregate property stream
graph stream
(window) fold combine fold reduce
local summaries global summary
edges agg global aggregates can be persistent or transient
graphStream.aggregate( new MyGraphAggregation(window, fold, combine, transform))
graphStream.slice(Time.of(1, MINUTE));
11:40 11:41 11:42 11:43
graphStream.slice(Time.of(1, MINUTE), direction)
.reduceOnEdges(); .foldNeighbors(); .applyOnNeighbors();
information
graphs
source target
Aggregations
graphStream.filterVertices(GraphGeeks()) .slice(Time.of(15, MINUTE), EdgeDirection.IN) .applyOnNeighbors(FindPairs())
slice
GraphStream :: graph geek check-ins
wendy checked_in soap_bar steve checked_in soap_bar tom checked_in joe’s_grill sandra checked_in soap_bar rafa checked_in joe’s_grill
wendy steve
sandrasoap bar tom rafa joe’s grill
FindPairs {wendy, steve} {steve, sandra} {wendy, sandra} {tom, rafa}
GraphWindow :: user-place
https://ci.apache.org/projects/flink/flink-docs-master/libs/ gelly_guide.html
https://github.com/vasia/gelly-streaming
https://fosdem.org/2016/schedule/event/graph_processing_apache_flink/
http://www.citeulike.org/user/vasiakalavri/tag/graph-streaming