{"action":"created","comment":{"url":"https://api.github.com/repos/apache/flink-web/pulls/comments/125818197","pull_request_review_id":48241738,"id":125818197,"diff_hunk":"@@ -0,0 +1,172 @@\n+---\n+layout: post\n+title:  \"A Deep Dive into Rescalable State in Apache Flink\"\n+excerpt: \"<p>A primer on stateful stream processing and an in-depth walkthrough of rescalable state in Apache Flink.</p>\"\n+date:   2017-07-04 09:00:00\n+author: \"Stefan Richter\"\n+author-twitter: \"StefanRRichter\"\n+categories: features\n+---\n+ _Apache Flink 1.2.0, released in February 2017, introduced support for rescalable state. This post provides a detailed overview of stateful stream processing and rescalable state in Flink._\n+ <br>\n+ <br>\n+\n+{% toc %}\n+\n+## An Intro to Stateful Stream Processing\n+\n+At a high level, we can consider state in stream processing as memory in operators that remembers information about past input and can be used to influence the processing of future input.\n+\n+In contrast, operators in _stateless_ stream processing only consider their current inputs, without further context and knowledge about the past. A simple example to illustrate this difference: let us consider a source stream that emits events with schema `e = {event_id:int, event_value:int}`. Our goal is, for each event, to extract and output the `event_value`. We can easily achieve this with a simple source-map-sink pipeline, where the map function extracts the `event_value` from the event and emits it downstream to an outputting sink. This is an instance of stateless stream processing.\n+\n+But what if we want to modify our job to output the `event_value` only if it is larger than the value from the previous event? In this case, our map function obviously needs some way to remember the `event_value` from a past event — and so this is an instance of stateful stream processing.\n+\n+This example should demonstrate that state is a fundamental, enabling concept in stream processing that is required for a majority of interesting use cases.\n+\n+## State in Apache Flink\n+\n+Apache Flink is a massively parallel distributed system that allows stateful stream processing at large scale. For scalability, a Flink job is logically decomposed into a graph of operators, and the execution of each operator is physically decomposed into multiple parallel operator instances. Conceptually, each parallel operator instance in Flink is an independent task that can be scheduled on its own machine in a network-connected cluster of shared-nothing machines.\n+\n+For high throughput and low latency in this setting, network communications among tasks must be minimized. In Flink, network communication for stream processing only happens along the logical edges in the job’s operator graph (vertically), so that the stream data can be transferred from upstream to downstream operators.\n+\n+However, there is no communication between the parallel instances of an operator (horizontally). To avoid such network communication, data locality is a key principle in Flink and strongly affects how state is stored and accessed.\n+\n+For the sake of data locality, all state data in Flink is always bound to the task that runs the corresponding parallel operator instance and is co-located on the same machine that runs the task.\n+\n+Through this design, all state data for a task is local, and no network communication between tasks is required for state access. Avoiding this kind of traffic is crucial for the scalability of a massively parallel distributed system like Flink.\n+\n+For Flink’s stateful stream processing, we differentiate between two different types of state: operator state and keyed state. Operator state is scoped per parallel instance of an operator (sub-task), and keyed state can be thought of as [“operator state that has been partitioned, or sharded, with exactly one state-partition per key”](https://ci.apache.org/projects/flink/flink-docs-release-1.3/dev/stream/state.html#keyed-state). We could have easily implemented our previous example as operator state: all events that are routed through the operator instance can influence its value.\n+\n+## Rescaling Stateful Stream Processing Jobs\n+\n+Changing the parallelism (that is, changing the number of parallel subtasks that perform work for an operator) in stateless streaming is very easy. It requires only starting or stopping parallel instances of stateless operators and dis-/connecting them to/from their upstream and downstream operators as shown in **Figure 1A**.\n+\n+On the other hand, changing the parallelism of stateful operators is much more involved because we must also (i) redistribute the previous operator state in a (ii) consistent, (iii) meaningful way. Remember that in Flink’s shared-nothing architecture, all state is local to the task that runs the owning parallel operator instance, and there is no communication between parallel operator instances at job runtime.\n+\n+However, there is already one mechanism in Flink that allows the exchange of operator state between tasks, in a consistent way, with exactly-once guarantees — Flink’s checkpointing!\n+\n+You can see detail about Flink’s checkpoints in [the documentation](https://ci.apache.org/projects/flink/flink-docs-release-1.3/internals/stream_checkpointing.html). In a nutshell, a checkpoint is triggered when a checkpoint coordinator injects a special event (a so-called checkpoint barrier) into a stream.\n+\n+Checkpoint barriers flow downstream with the event stream from sources to sinks, and whenever an operator instance receives a barrier, the operator instance immediately snapshots its current state to a distributed storage system, e.g. HDFS.\n+\n+On restore, the new tasks for the job (which potentially run on different machines now) can again pick up the state data from the distributed storage system.\n+\n+<br><center><i>Figure 1</i></center>\n+<center>\n+<img src=\"{{ site.baseurl }}/img/blog/stateless-stateful-streaming.svg\" style=\"width:70%;margin:10px\">\n+</center>\n+<br>\n+\n+We can piggyback rescaling of stateful jobs on checkpointing, as shown in **Figure 1B**. First, a checkpoint is triggered and sent to a distributed storage system. Next, the job is restarted with a changed parallelism and can access a consistent snapshot of all previous state from the distributed storage. While this solves (i) redistribution of a (ii) consistent state across machines there is still one problem: without a clear 1:1 relationship between previous state and new parallel operator instances, how can we assign the state in a (iii) meaningful way?\n+\n+We could again assign the state from previous `map_1` and `map_2` to the new `map_1` and `map_2`. But this would leave `map_3` with empty state. Depending on the type of state and concrete semantics of the job, this naive approach could lead to anything from inefficiency to incorrect results.\n+\n+In the following section, we’ll explain how we solved the problem of efficient, meaningful state reassignment in Flink. Each of Flink state’s two flavours, operator state and keyed state, requires a different approach to state assignment.\n+\n+## Reassigning Operator State When Rescaling\n+\n+First, we’ll discuss how state reassignment in rescaling works for operator state. A common real-world use-case of operator state in Flink is to maintain the current offsets for Kafka partitions in Kafka sources. Each Kafka source instance would maintain `<PartitionID, Offset>` pairs–one pair for each Kafka partition that the source is reading–as operator state. How would we redistribute this operator state in case of rescaling? Ideally, we would like to reassign all `<PartitionID, Offset>` pairs from the checkpoint in round robin across all parallel operator instances after the rescaling.\n+\n+As a user, we are aware of the “meaning” of Kafka partition offsets, and we know that we can treat them as independent, redistributable units of state. The problem remains how we can we share this domain-specific knowledge with Flink.\n+\n+**Figure 2A** illustrates the previous interface for checkpointing operator state in Flink. On snapshot, each operator instance returned an object that represented its complete state. In the case of a Kafka source, this object was a list of partition offsets.\n+\n+This snapshot object was then written to the distributed store. On restore, the object was read from distributed storage and passed to the operator instance as a parameter to the restore function.\n+\n+This approach was problematic for rescaling: how could Flink decompose the operator state into meaningful, redistributable partitions? Even though the Kafka source was actually always a list of partition offsets, the previously-returned state object was a black box to Flink and therefore could not be redistributed.\n+\n+As a generalized approach to solve this black box problem, we slightly modified the checkpointing interface, called `ListCheckpointed`. **Figure 2B** shows the new checkpointing interface, which returns and receives a list of state partitions. Introducing a list instead of a single object makes the meaningful partitioning of state explicit: each item in the list still remains a black box to Flink, but is considered an atomic, independently re-distributable part of the operator state.\n+\n+\n+<br><center><i>Figure 2</i></center>\n+<center>\n+<img src=\"{{ site.baseurl }}/img/blog/list-checkpointed.svg\" style=\"width:70%;margin:10px\">\n+</center><br>\n+\n+\n+Our approach provides a simple API with which implementing operators can encode domain-specific knowledge about how to partition and merge units of state. With our new checkpointing interface, the Kafka source makes individual partition offsets explicit, and state reassignment becomes as easy as splitting and merging lists.\n+\n+```java\n+public class FlinkKafkaConsumer<T> extends RichParallelSourceFunction<T> implements CheckpointedFunction {\n+\t // ...\n+\n+   private transient ListState<Tuple2<KafkaTopicPartition, Long>> offsetsOperatorState;\n+\n+   @Override\n+   public void initializeState(FunctionInitializationContext context) throws Exception {\n+\n+      OperatorStateStore stateStore = context.getOperatorStateStore();\n+      // register the state with the backend\n+      this.offsetsOperatorState = stateStore.getSerializableListState(\"kafka-offsets\");\n+\n+      // if the job was restarted, we set the restored offsets\n+      if (context.isRestored()) {\n+         for (Tuple2<KafkaTopicPartition, Long> kafkaOffset : offsetsOperatorState.get()) {\n+            // ... restore logic\n+         }\n+      }\n+   }\n+\n+   @Override\n+   public void snapshotState(FunctionSnapshotContext context) throws Exception {\n+\n+      this.offsetsOperatorState.clear();\n+\n+      // write the partition offsets to the list of operator states\n+      for (Map.Entry<KafkaTopicPartition, Long> partition : this.subscribedPartitionOffsets.entrySet()) {\n+         this.offsetsOperatorState.add(Tuple2.of(partition.getKey(), partition.getValue()));\n+      }\n+   }\n+\n+   // ...\n+\n+}\n+```\n+\n+## Reassigning Keyed State When Rescaling\n+The second flavour of state in Flink is keyed state. In contrast to operator state, keyed state is scoped by key, where the key is extracted from each stream event.\n+\n+To illustrate how keyed state differs from operator state, let’s use the following example. Assume we have a stream of events, where each event has the schema `{customer_id:int, value:int}`. We have already learned that we can use operator state to compute and emit the running sum of values for all customers.\n+\n+Now assume we want to slightly modify our goal and compute a running sum of values for each individual customer_id. This is a use case from keyed state, as one aggregated state must be maintained for each unique key in the stream.\n+\n+Note that keyed state is only available for keyed streams, which are created through the `keyBy()` operation in Flink. The `keyBy()` operation (i) specifies how to extract a key from each event and (ii) ensures that all events with the same key are always processed by the same parallel operator instance. As a result, all keyed state is transitively also bound to one parallel operator instance, because for each key, exactly one operator instance is responsible. This mapping from key to operator is deterministically computed through hash partitioning on the key.\n+\n+We can see that keyed state has one clear advantage over operator state when it comes to rescaling: we can easily figure out how to correctly split and redistribute the state across parallel operator instances. State reassignment simply follows the partitioning of the keyed stream. After rescaling, the state for each key must be assigned to the operator instance that is now responsible for that key, as determined by the hash partitioning of the keyed stream.\n+\n+While this automatically solves the problem of logically remapping the state to sub-tasks after rescaling, there is one more practical problem left to solve: how can we efficiently transfer the state to the subtasks’ local backends?\n+\n+When we’re not rescaling, each subtask can simply read the whole state as written to the checkpoint by a previous instance in one sequential read.\n+\n+When rescaling, however, this is no longer possible–the state for each subtask is now potentially scattered across the files written by all subtasks (think about what happens if you change the parallelism in `hash(key) mod parallelism`). We have illustrated this problem in **Figure 3A**. In this example, we show how keys are shuffled when rescaling from parallelism 3 to 4 for a key space of 0, 20, using identity as hash function to keep it easy to follow.","path":"_posts/2017-07-04-flink-rescalable-state.md","position":141,"original_position":141,"commit_id":"ab399d0754c10178ae0603289271a1c3907d1f98","original_commit_id":"ab399d0754c10178ae0603289271a1c3907d1f98","user":{"login":"tzulitai","id":5284370,"avatar_url":"https://avatars0.githubusercontent.com/u/5284370?v=3","gravatar_id":"","url":"https://api.github.com/users/tzulitai","html_url":"https://github.com/tzulitai","followers_url":"https://api.github.com/users/tzulitai/followers","following_url":"https://api.github.com/users/tzulitai/following{/other_user}","gists_url":"https://api.github.com/users/tzulitai/gists{/gist_id}","starred_url":"https://api.github.com/users/tzulitai/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tzulitai/subscriptions","organizations_url":"https://api.github.com/users/tzulitai/orgs","repos_url":"https://api.github.com/users/tzulitai/repos","events_url":"https://api.github.com/users/tzulitai/events{/privacy}","received_events_url":"https://api.github.com/users/tzulitai/received_events","type":"User","site_admin":false},"body":"\"possible-the state for ...\"\r\n\r\nempty spaces should surround '-'.","created_at":"2017-07-06T06:13:55Z","updated_at":"2017-07-06T06:16:38Z","html_url":"https://github.com/apache/flink-web/pull/73#discussion_r125818197","pull_request_url":"https://api.github.com/repos/apache/flink-web/pulls/73","_links":{"self":{"href":"https://api.github.com/repos/apache/flink-web/pulls/comments/125818197"},"html":{"href":"https://github.com/apache/flink-web/pull/73#discussion_r125818197"},"pull_request":{"href":"https://api.github.com/repos/apache/flink-web/pulls/73"}}},"pull_request":{"url":"https://api.github.com/repos/apache/flink-web/pulls/73","id":128829174,"html_url":"https://github.com/apache/flink-web/pull/73","diff_url":"https://github.com/apache/flink-web/pull/73.diff","patch_url":"https://github.com/apache/flink-web/pull/73.patch","issue_url":"https://api.github.com/repos/apache/flink-web/issues/73","number":73,"state":"open","locked":false,"title":"Add blog post about Flink's rescalable state","user":{"login":"wints","id":3689622,"avatar_url":"https://avatars0.githubusercontent.com/u/3689622?v=3","gravatar_id":"","url":"https://api.github.com/users/wints","html_url":"https://github.com/wints","followers_url":"https://api.github.com/users/wints/followers","following_url":"https://api.github.com/users/wints/following{/other_user}","gists_url":"https://api.github.com/users/wints/gists{/gist_id}","starred_url":"https://api.github.com/users/wints/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wints/subscriptions","organizations_url":"https://api.github.com/users/wints/orgs","repos_url":"https://api.github.com/users/wints/repos","events_url":"https://api.github.com/users/wints/events{/privacy}","received_events_url":"https://api.github.com/users/wints/received_events","type":"User","site_admin":false},"body":"This post gives readers an intro to stateful stream processing as well as a detailed overview of rescalable state in Flink. \r\n\r\n@StefanRRichter @rmetzger @tzulitai ","created_at":"2017-07-04T15:48:47Z","updated_at":"2017-07-06T06:16:38Z","closed_at":null,"merged_at":null,"merge_commit_sha":"5ed2870274970a54aeca05fefde187d9419014ff","assignee":null,"assignees":[],"requested_reviewers":[],"milestone":null,"commits_url":"https://api.github.com/repos/apache/flink-web/pulls/73/commits","review_comments_url":"https://api.github.com/repos/apache/flink-web/pulls/73/comments","review_comment_url":"https://api.github.com/repos/apache/flink-web/pulls/comments{/number}","comments_url":"https://api.github.com/repos/apache/flink-web/issues/73/comments","statuses_url":"https://api.github.com/repos/apache/flink-web/statuses/ab399d0754c10178ae0603289271a1c3907d1f98","head":{"label":"wints:asf-site","ref":"asf-site","sha":"ab399d0754c10178ae0603289271a1c3907d1f98","user":{"login":"wints","id":3689622,"avatar_url":"https://avatars0.githubusercontent.com/u/3689622?v=3","gravatar_id":"","url":"https://api.github.com/users/wints","html_url":"https://github.com/wints","followers_url":"https://api.github.com/users/wints/followers","following_url":"https://api.github.com/users/wints/following{/other_user}","gists_url":"https://api.github.com/users/wints/gists{/gist_id}","starred_url":"https://api.github.com/users/wints/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wints/subscriptions","organizations_url":"https://api.github.com/users/wints/orgs","repos_url":"https://api.github.com/users/wints/repos","events_url":"https://api.github.com/users/wints/events{/privacy}","received_events_url":"https://api.github.com/users/wints/received_events","type":"User","site_admin":false},"repo":{"id":63608877,"name":"flink-web","full_name":"wints/flink-web","owner":{"login":"wints","id":3689622,"avatar_url":"https://avatars0.githubusercontent.com/u/3689622?v=3","gravatar_id":"","url":"https://api.github.com/users/wints","html_url":"https://github.com/wints","followers_url":"https://api.github.com/users/wints/followers","following_url":"https://api.github.com/users/wints/following{/other_user}","gists_url":"https://api.github.com/users/wints/gists{/gist_id}","starred_url":"https://api.github.com/users/wints/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wints/subscriptions","organizations_url":"https://api.github.com/users/wints/orgs","repos_url":"https://api.github.com/users/wints/repos","events_url":"https://api.github.com/users/wints/events{/privacy}","received_events_url":"https://api.github.com/users/wints/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/wints/flink-web","description":"Mirror of Apache Flink Website","fork":true,"url":"https://api.github.com/repos/wints/flink-web","forks_url":"https://api.github.com/repos/wints/flink-web/forks","keys_url":"https://api.github.com/repos/wints/flink-web/keys{/key_id}","collaborators_url":"https://api.github.com/repos/wints/flink-web/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/wints/flink-web/teams","hooks_url":"https://api.github.com/repos/wints/flink-web/hooks","issue_events_url":"https://api.github.com/repos/wints/flink-web/issues/events{/number}","events_url":"https://api.github.com/repos/wints/flink-web/events","assignees_url":"https://api.github.com/repos/wints/flink-web/assignees{/user}","branches_url":"https://api.github.com/repos/wints/flink-web/branches{/branch}","tags_url":"https://api.github.com/repos/wints/flink-web/tags","blobs_url":"https://api.github.com/repos/wints/flink-web/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/wints/flink-web/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/wints/flink-web/git/refs{/sha}","trees_url":"https://api.github.com/repos/wints/flink-web/git/trees{/sha}","statuses_url":"https://api.github.com/repos/wints/flink-web/statuses/{sha}","languages_url":"https://api.github.com/repos/wints/flink-web/languages","stargazers_url":"https://api.github.com/repos/wints/flink-web/stargazers","contributors_url":"https://api.github.com/repos/wints/flink-web/contributors","subscribers_url":"https://api.github.com/repos/wints/flink-web/subscribers","subscription_url":"https://api.github.com/repos/wints/flink-web/subscription","commits_url":"https://api.github.com/repos/wints/flink-web/commits{/sha}","git_commits_url":"https://api.github.com/repos/wints/flink-web/git/commits{/sha}","comments_url":"https://api.github.com/repos/wints/flink-web/comments{/number}","issue_comment_url":"https://api.github.com/repos/wints/flink-web/issues/comments{/number}","contents_url":"https://api.github.com/repos/wints/flink-web/contents/{+path}","compare_url":"https://api.github.com/repos/wints/flink-web/compare/{base}...{head}","merges_url":"https://api.github.com/repos/wints/flink-web/merges","archive_url":"https://api.github.com/repos/wints/flink-web/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/wints/flink-web/downloads","issues_url":"https://api.github.com/repos/wints/flink-web/issues{/number}","pulls_url":"https://api.github.com/repos/wints/flink-web/pulls{/number}","milestones_url":"https://api.github.com/repos/wints/flink-web/milestones{/number}","notifications_url":"https://api.github.com/repos/wints/flink-web/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/wints/flink-web/labels{/name}","releases_url":"https://api.github.com/repos/wints/flink-web/releases{/id}","deployments_url":"https://api.github.com/repos/wints/flink-web/deployments","created_at":"2016-07-18T14:16:10Z","updated_at":"2017-01-26T14:23:07Z","pushed_at":"2017-07-04T15:45:54Z","git_url":"git://github.com/wints/flink-web.git","ssh_url":"git@github.com:wints/flink-web.git","clone_url":"https://github.com/wints/flink-web.git","svn_url":"https://github.com/wints/flink-web","homepage":null,"size":57551,"stargazers_count":0,"watchers_count":0,"language":"HTML","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"asf-site"}},"base":{"label":"apache:asf-site","ref":"asf-site","sha":"7b21e3ed77faf66e61e50f522a5b910217699690","user":{"login":"apache","id":47359,"avatar_url":"https://avatars3.githubusercontent.com/u/47359?v=3","gravatar_id":"","url":"https://api.github.com/users/apache","html_url":"https://github.com/apache","followers_url":"https://api.github.com/users/apache/followers","following_url":"https://api.github.com/users/apache/following{/other_user}","gists_url":"https://api.github.com/users/apache/gists{/gist_id}","starred_url":"https://api.github.com/users/apache/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/apache/subscriptions","organizations_url":"https://api.github.com/users/apache/orgs","repos_url":"https://api.github.com/users/apache/repos","events_url":"https://api.github.com/users/apache/events{/privacy}","received_events_url":"https://api.github.com/users/apache/received_events","type":"Organization","site_admin":false},"repo":{"id":35755560,"name":"flink-web","full_name":"apache/flink-web","owner":{"login":"apache","id":47359,"avatar_url":"https://avatars3.githubusercontent.com/u/47359?v=3","gravatar_id":"","url":"https://api.github.com/users/apache","html_url":"https://github.com/apache","followers_url":"https://api.github.com/users/apache/followers","following_url":"https://api.github.com/users/apache/following{/other_user}","gists_url":"https://api.github.com/users/apache/gists{/gist_id}","starred_url":"https://api.github.com/users/apache/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/apache/subscriptions","organizations_url":"https://api.github.com/users/apache/orgs","repos_url":"https://api.github.com/users/apache/repos","events_url":"https://api.github.com/users/apache/events{/privacy}","received_events_url":"https://api.github.com/users/apache/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/apache/flink-web","description":"Mirror of Apache Flink Website","fork":false,"url":"https://api.github.com/repos/apache/flink-web","forks_url":"https://api.github.com/repos/apache/flink-web/forks","keys_url":"https://api.github.com/repos/apache/flink-web/keys{/key_id}","collaborators_url":"https://api.github.com/repos/apache/flink-web/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/apache/flink-web/teams","hooks_url":"https://api.github.com/repos/apache/flink-web/hooks","issue_events_url":"https://api.github.com/repos/apache/flink-web/issues/events{/number}","events_url":"https://api.github.com/repos/apache/flink-web/events","assignees_url":"https://api.github.com/repos/apache/flink-web/assignees{/user}","branches_url":"https://api.github.com/repos/apache/flink-web/branches{/branch}","tags_url":"https://api.github.com/repos/apache/flink-web/tags","blobs_url":"https://api.github.com/repos/apache/flink-web/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/apache/flink-web/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/apache/flink-web/git/refs{/sha}","trees_url":"https://api.github.com/repos/apache/flink-web/git/trees{/sha}","statuses_url":"https://api.github.com/repos/apache/flink-web/statuses/{sha}","languages_url":"https://api.github.com/repos/apache/flink-web/languages","stargazers_url":"https://api.github.com/repos/apache/flink-web/stargazers","contributors_url":"https://api.github.com/repos/apache/flink-web/contributors","subscribers_url":"https://api.github.com/repos/apache/flink-web/subscribers","subscription_url":"https://api.github.com/repos/apache/flink-web/subscription","commits_url":"https://api.github.com/repos/apache/flink-web/commits{/sha}","git_commits_url":"https://api.github.com/repos/apache/flink-web/git/commits{/sha}","comments_url":"https://api.github.com/repos/apache/flink-web/comments{/number}","issue_comment_url":"https://api.github.com/repos/apache/flink-web/issues/comments{/number}","contents_url":"https://api.github.com/repos/apache/flink-web/contents/{+path}","compare_url":"https://api.github.com/repos/apache/flink-web/compare/{base}...{head}","merges_url":"https://api.github.com/repos/apache/flink-web/merges","archive_url":"https://api.github.com/repos/apache/flink-web/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/apache/flink-web/downloads","issues_url":"https://api.github.com/repos/apache/flink-web/issues{/number}","pulls_url":"https://api.github.com/repos/apache/flink-web/pulls{/number}","milestones_url":"https://api.github.com/repos/apache/flink-web/milestones{/number}","notifications_url":"https://api.github.com/repos/apache/flink-web/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/apache/flink-web/labels{/name}","releases_url":"https://api.github.com/repos/apache/flink-web/releases{/id}","deployments_url":"https://api.github.com/repos/apache/flink-web/deployments","created_at":"2015-05-17T07:00:05Z","updated_at":"2017-06-06T15:05:19Z","pushed_at":"2017-07-04T15:48:48Z","git_url":"git://github.com/apache/flink-web.git","ssh_url":"git@github.com:apache/flink-web.git","clone_url":"https://github.com/apache/flink-web.git","svn_url":"https://github.com/apache/flink-web","homepage":null,"size":55852,"stargazers_count":17,"watchers_count":17,"language":"HTML","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":49,"mirror_url":"git://git.apache.org/flink-web.git","open_issues_count":3,"forks":49,"open_issues":3,"watchers":17,"default_branch":"asf-site"}},"_links":{"self":{"href":"https://api.github.com/repos/apache/flink-web/pulls/73"},"html":{"href":"https://github.com/apache/flink-web/pull/73"},"issue":{"href":"https://api.github.com/repos/apache/flink-web/issues/73"},"comments":{"href":"https://api.github.com/repos/apache/flink-web/issues/73/comments"},"review_comments":{"href":"https://api.github.com/repos/apache/flink-web/pulls/73/comments"},"review_comment":{"href":"https://api.github.com/repos/apache/flink-web/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/apache/flink-web/pulls/73/commits"},"statuses":{"href":"https://api.github.com/repos/apache/flink-web/statuses/ab399d0754c10178ae0603289271a1c3907d1f98"}}}}
{"actor":{"display_login":"tzulitai"}}