{"action":"created","comment":{"url":"https://api.github.com/repos/benesch/cockroach/pulls/comments/101838187","pull_request_review_id":22580557,"id":101838187,"diff_hunk":"@@ -0,0 +1,421 @@\n+- Feature Name: System Jobs\n+- Status: draft\n+- Start Date: 2017-02-13\n+- Authors: Nikhil Benesch\n+- RFC PR: (PR # after acceptance of initial draft)\n+- Cockroach Issue: [#12555]\n+\n+# Summary\n+\n+Add a system table to track the progress of backups, restores, and schema\n+changes.\n+\n+# Motivation\n+\n+When performing a schema change, only one bit of progress information is\n+available: whether the change has completed, indicated by whether the query has\n+returned. Similarly, when performing a backup or restore, status is only\n+reported after the backup or restore has completed. Given that a full backup of\n+a 2TB database takes on the order of several hours to complete, the lack of\n+progress information is a serious pain point for users.\n+\n+Additionally, if the leader of a backup or restore job fails, the backup or\n+restore will abort, even if the individual workers in the job were otherwise\n+successful.\n+\n+This RFC proposes a new system table, `system.jobs`, that tracks the status of\n+these long-running backup, restore, and schema change \"jobs.\" This table will\n+directly expose the desired progress information via `SELECT` queries over the\n+table, enable an API endpoint to expose the same progress information in the\n+admin UI, and enable an internal daemon that periodically adopts and resumes\n+orphaned jobs.\n+\n+# Detailed design\n+\n+## Prior art\n+\n+Adding a `system.jobs` table has been proposed and unsuccessfully implemented\n+several times. [@vivekmenezes]'s initial attempt in [#7037] was abandoned since,\n+at the time, there was no way to safely add a new system table to an existing\n+cluster. Several months later, after [@a-robinson] and [@danhhz] built the\n+necessary cluster migration framework, @a-robinson submitted a migration to add\n+a `system.jobs` table in [#11722] as an example of using the new framework. His\n+PR was rejected because the table's schema hadn't been fully considered. This\n+RFC describes a revised `system.jobs` schema for us to thoroughly vet before we\n+proceed with a final implementation.\n+\n+## The proposal\n+\n+Within this RFC, the term \"job\" or \"long-running job\" refers only to backups,\n+restores, and schema changes. Other types of long-running queries, like slow\n+`SELECT` statements, are explicitly out of scope. A \"job-creating query,\" then,\n+is any query of one of the following types:\n+\n+- `ALTER TABLE`, which creates a schema change job\n+- `BACKUP`, which creates a backup job\n+- `RESTORE`, which creates a restore job\n+\n+To track the progress of these jobs, the following table will be injected into\n+the `system` database using the [cluster migration framework]:\n+\n+```sql\n+CREATE TABLE system.jobs (\n+    id               INT DEFAULT unique_rowid() PRIMARY KEY,\n+    status           STRING NOT NULL,\n+    percentCompleted DECIMAL(5, 2),\n+    lastUpdated      TIMESTAMP DEFAULT now(),\n+    payload          BYTES,\n+    INDEX status_idx (status)\n+)\n+```\n+\n+Each job is identified by a unique `id`, which is assigned when the job is\n+created. Currently, this ID serves only to identify the job to the user, but\n+future SQL commands to e.g. abort running jobs will need this ID to\n+unambiguously specify the target job.\n+\n+The `status` column reprents a state machine with `pending`, `running`,\n+`succeeded`, and `failed` states. Jobs are created in the `pending` state when\n+the job-creating query is accepted, move to the `running` state once work on the\n+job has actually begun, then move to a final state of `succeeded` or `failed`.\n+Once a job has entered a final state, it becomes immutable. The `pending` state\n+warrants additional explanation: it's used to track jobs that are enqueued but\n+not currently performing work. Schema changes, for example will sit in the\n+`pending` state until all prior schema change jobs have completed.\n+\n+The `percentCompleted` field, as expected, starts as `NULL` and is periodically\n+updated from 0 to 100 while the job is `running`. Jobs in the `succeeded` state\n+will always have a `percentCompleted` of 100, while jobs in the `failed` state\n+may have any `percentCompleted` value. This value is stored as a decimal with a\n+scale of two to allow tracking sub-percent progress on large jobs.\n+\n+The `lastUpdated` field is updated to the current time whenever any change is\n+made to the record.\n+\n+We want to avoid future schema changes to `system.jobs` if at all possible.\n+Every schema change requires a cluster migration, and every cluster migration\n+introduces node startup time overhead, plus risk and complexity. To that end,\n+fields that are not fundamental to the schema are stashed in the `payload`\n+column, which stores a protobuf that can be evolved per the usual protobuf\n+forwards-compatibility support. A potential structure of the `payload` follows.\n+\n+```protobuf\n+message BackupJobPayload {\n+    string to = 1;\n+    repeated uint32 target_id = 2;\n+}\n+\n+message RestoreJobPayload {\n+    string from = 1;\n+    repeated uint32 target_id = 2;\n+}\n+\n+message SchemaChangeJobPayload {\n+    uint32 mutation_id = 1;\n+}\n+\n+message JobPayload {\n+    string query = 1;\n+    string creator = 2;\n+    int64 created = 3;\n+    int64 started = 4;\n+    string error = 6;\n+    oneof details {\n+        BackupJob backup_details;\n+        RestoreJobPayload restore_details;\n+        SchemaChangeJobPayload schema_change_details;\n+    }\n+}\n+```\n+\n+The `query` field stores the raw text of the job-creating query for display in\n+the UI.\n+\n+The `creator` column records the user who launched the job. I'm unsure whether\n+this field is necessary; see [Unresolved questions](#unresolved-questions)\n+below.\n+\n+Next up are two fields to track the timing of the job. The `created` field\n+tracks when the job-creating query was executed. The `started` field tracks when\n+the job switches from `pending` to `running`. From these fields, plus the\n+`lastUpdated` field in the table itself, we can compute the duration of the job\n+(`lastUpdated` - `started`) and the delay in starting the job (`started` -\n+`created`).\n+\n+Every job that is `pending` will have a null `started` column, while every job\n+that is not `pending` will have a non-null `started` column. It would be\n+possible to instead use the same status for both `pending` and `running`,\n+allowing the presence of the `started` column to distinguish between the two\n+states, but the design as presented is more obvious.\n+\n+The `error` field stores the reason for failure, if any. This is the\n+error message that is reported to the user through the normal query failure\n+path, but is recorded in the table for posterity.\n+\n+Finally, the type of job can be determined by reflection on the `details` oneof,\n+which stores additional details relevant to a specific job type. The\n+`BackupJobPayload`, for example, will store the IDs of the tables in the backup,\n+so that the admin UI can visually flag these tables in other views.\n+\n+Which fields belong in the table proper and which fields belong in the protobuf\n+is not entirely clear. See [Unresolved questions](#unresolved-questions).\n+\n+## Expected queries\n+\n+To help evaluate which fields belong in the `payload` protobuf, following is a\n+selection of SQL queries expected to be run against the `system.jobs` table.\n+Most of these queries will be executed by the database internals, though some\n+are expected to be run manually by users monitoring job progress.\n+\n+To create a new job:\n+\n+```sql\n+-- {} is imaginary syntax for a protobuf literal.\n+INSERT\n+  INTO system.jobs (jobType, status, payload)\n+  VALUES ('backup', 'pending', {\n+    query = 'BACKUP foodb TO barstorage',\n+    creator = 'root',\n+    created = now(),\n+    backup_details = {\n+      to = 'barstorage',\n+      targets = ['foodb.tbla', 'foodb.tblb']\n+    }\n+  })\n+  RETURNING id\n+```\n+\n+To mark a job as running:\n+\n+```sql\n+-- {...old, col = 'new-value' } is imaginary syntax for a protobuf literal that\n+-- has the same values as old, except col is updated to 'new-value'.\n+UPDATE system.jobs\n+  SET status = 'running',\n+      percentCompleted = 0,\n+      lastUpdated = now(),\n+      payload = {...payload, started = now()}\n+```\n+\n+To update the status of a running job:\n+\n+```sql\n+UPDATE system.jobs\n+  SET percentCompleted = 24.42,\n+      lastUpdated = now()\n+```\n+\n+To mark a job as successful:\n+\n+```sql\n+UPDATE system.jobs\n+  SET percentCompleted = 100,\n+      status = 'succeeded',\n+      lastUpdated = now()\n+```\n+\n+To mark a job as failed:\n+\n+```sql\n+UPDATE system.jobs\n+  SET status = 'failed',\n+      lastUpdated = now(),\n+      payload = {...payload, error = 's3.aws.amazon.com: host unreachable'}\n+```\n+\n+To find queued or running jobs (e.g., for the default \"System jobs\" admin view):\n+\n+```sql\n+SELECT * FROM system.jobs WHERE status IN ('pending', 'running');\n+```\n+\n+In the above query, the admin interface will need to extract the `created` field\n+from the `payload` protobuf in from each record in order to sort the records\n+sensibly. It is currently impossible to do the sorting in the SQL CLI directly.\n+(Similarly, it is not possible to limit the query to only jobs of a certain type\n+without decoding the protobuf.)\n+\n+Under the proposed design, though, it's possible to retrieve the crucial\n+progress information for a job with a known ID from the SQL CLI:\n+\n+```sql\n+SELECT status, percentCompleted FROM system.jobs WHERE id = ?;\n+```\n+\n+We currently have a customer requesting the ability to do at least the above\n+from SQL. Additionally, support for reaching into protobuf columns from a SQL\n+query, is planned.\n+\n+## Minor considerations\n+\n+- Cloud storage credentials can be passed in the query for `BACKUP` and\n+  `RESTORE` commands. Care will be taken to strip those credentials from the\n+  query text before the text is inserted into the `system.jobs` table.\n+\n+- Unlike past proposals, the schema intentionally lacks a `name` or\n+  `description` column. As it stands, none of the job-creating queries allow the\n+  user to specify such a name or description.\n+\n+# Drawbacks\n+\n+Requiring the job leader to periodically issue `UPDATE system.jobs SET\n+percentCompleted = ...` queries to update the progress of running jobs is\n+somewhat unsatisfying. One wishes to be able to conjure the `percentCompleted`\n+column only when the record is read, but this design introduces significant\n+implementation complexity.\n+\n+# Alternatives\n+\n+## Wider protobufs\n+\n+To further minimize the chances that we'll need to modify the `system.jobs`\n+schema, we could instead stuff all the data into the `payload` protobuf:\n+\n+```sql\n+CREATE TABLE system.jobs (\n+    id      INT PRIMARY KEY,\n+    jobType STRING NOT NULL,\n+    payload BYTES,\n+)\n+```\n+\n+This allows for complete flexibility in adjusting the schema, but prevents\n+essentially all useful SQL queries over the table until protobuf columns are\n+natively supported.\n+\n+## Narrow protobufs\n+\n+We could also allow all data to be filtered by widening the `system.jobs` table\n+to include all the fields proposed to be stored in the `payload` protobuf,\n+potentially dropping `payload` entirely.\n+\n+```sql\n+CREATE TABLE system.jobs (\n+    id                INT DEFAULT unique_rowid() PRIMARY KEY,\n+    status            STRING NOT NULL,\n+    percentCompleted  DECIMAL(5, 2),\n+    payload           BYTES,\n+    query             STRING,\n+    creator           STRING,\n+    created           TIMESTAMP NOT NULL,\n+    started           TIMESTAMP,\n+    lastUpdated       TIMESTAMP NOT NOULL,\n+    error             STRING,\n+    backup_to         STRING,\n+    backup_targets    STRING,\n+    restore_from      STRING,\n+    schema_change_tbl STRING,\n+    INDEX status_idx  (status)\n+)\n+```\n+\n+This option allows the table to be indexed on any column or queried on any\n+column with SQL, but poses a significant risk if we need to adjust the schema.\n+This design does unlock some useful SQL queries, however. Retrieving the second\n+page of completed backup jobs can be expressed very naturally, for example:\n+\n+```sql\n+SELECT * FROM system.jobs\n+  WHERE jobType = 'backup'\n+        status IN ('succeeded', 'failed')\n+  ORDER BY created\n+  LIMIT 50\n+  OFFSET 50\n+```\n+\n+## More protobufs\n+\n+Orthogonal to the above concern is whether we separate common fields from\n+job-specific fields in the protobuf. For example, with wide protobufs, the table\n+schema would look like this\n+\n+```sql\n+CREATE TABLE system.jobs (\n+    id             INT PRIMARY KEY,\n+    jobType        STRING NOT NULL,\n+    jobPayload     BYTES,\n+    detailsPayload BYTES,","path":"docs/RFCS/system_jobs.md","position":337,"original_position":337,"commit_id":"42d811fa32603fd6a74a2eebb44c06c1f1031fa0","original_commit_id":"42d811fa32603fd6a74a2eebb44c06c1f1031fa0","user":{"login":"danhhz","id":52528,"avatar_url":"https://avatars.githubusercontent.com/u/52528?v=3","gravatar_id":"","url":"https://api.github.com/users/danhhz","html_url":"https://github.com/danhhz","followers_url":"https://api.github.com/users/danhhz/followers","following_url":"https://api.github.com/users/danhhz/following{/other_user}","gists_url":"https://api.github.com/users/danhhz/gists{/gist_id}","starred_url":"https://api.github.com/users/danhhz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/danhhz/subscriptions","organizations_url":"https://api.github.com/users/danhhz/orgs","repos_url":"https://api.github.com/users/danhhz/repos","events_url":"https://api.github.com/users/danhhz/events{/privacy}","received_events_url":"https://api.github.com/users/danhhz/received_events","type":"User","site_admin":false},"body":"I would delete this whole alternative (or shorten it). it feels like a lot of text for something that doesn't get any any particular advantages","created_at":"2017-02-17T20:10:48Z","updated_at":"2017-02-17T20:11:19Z","html_url":"https://github.com/benesch/cockroach/pull/1#discussion_r101838187","pull_request_url":"https://api.github.com/repos/benesch/cockroach/pulls/1","_links":{"self":{"href":"https://api.github.com/repos/benesch/cockroach/pulls/comments/101838187"},"html":{"href":"https://github.com/benesch/cockroach/pull/1#discussion_r101838187"},"pull_request":{"href":"https://api.github.com/repos/benesch/cockroach/pulls/1"}}},"pull_request":{"url":"https://api.github.com/repos/benesch/cockroach/pulls/1","id":106599484,"html_url":"https://github.com/benesch/cockroach/pull/1","diff_url":"https://github.com/benesch/cockroach/pull/1.diff","patch_url":"https://github.com/benesch/cockroach/pull/1.patch","issue_url":"https://api.github.com/repos/benesch/cockroach/issues/1","number":1,"state":"open","locked":false,"title":"[WIP] System jobs","user":{"login":"benesch","id":882976,"avatar_url":"https://avatars.githubusercontent.com/u/882976?v=3","gravatar_id":"","url":"https://api.github.com/users/benesch","html_url":"https://github.com/benesch","followers_url":"https://api.github.com/users/benesch/followers","following_url":"https://api.github.com/users/benesch/following{/other_user}","gists_url":"https://api.github.com/users/benesch/gists{/gist_id}","starred_url":"https://api.github.com/users/benesch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/benesch/subscriptions","organizations_url":"https://api.github.com/users/benesch/orgs","repos_url":"https://api.github.com/users/benesch/repos","events_url":"https://api.github.com/users/benesch/events{/privacy}","received_events_url":"https://api.github.com/users/benesch/received_events","type":"User","site_admin":false},"body":"","created_at":"2017-02-16T19:44:14Z","updated_at":"2017-02-17T20:11:19Z","closed_at":null,"merged_at":null,"merge_commit_sha":"f2a19a7b23fa985e0d2ba2ecbd05d42e18bca41a","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/benesch/cockroach/pulls/1/commits","review_comments_url":"https://api.github.com/repos/benesch/cockroach/pulls/1/comments","review_comment_url":"https://api.github.com/repos/benesch/cockroach/pulls/comments{/number}","comments_url":"https://api.github.com/repos/benesch/cockroach/issues/1/comments","statuses_url":"https://api.github.com/repos/benesch/cockroach/statuses/42d811fa32603fd6a74a2eebb44c06c1f1031fa0","head":{"label":"benesch:system-jobs","ref":"system-jobs","sha":"42d811fa32603fd6a74a2eebb44c06c1f1031fa0","user":{"login":"benesch","id":882976,"avatar_url":"https://avatars.githubusercontent.com/u/882976?v=3","gravatar_id":"","url":"https://api.github.com/users/benesch","html_url":"https://github.com/benesch","followers_url":"https://api.github.com/users/benesch/followers","following_url":"https://api.github.com/users/benesch/following{/other_user}","gists_url":"https://api.github.com/users/benesch/gists{/gist_id}","starred_url":"https://api.github.com/users/benesch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/benesch/subscriptions","organizations_url":"https://api.github.com/users/benesch/orgs","repos_url":"https://api.github.com/users/benesch/repos","events_url":"https://api.github.com/users/benesch/events{/privacy}","received_events_url":"https://api.github.com/users/benesch/received_events","type":"User","site_admin":false},"repo":{"id":78508291,"name":"cockroach","full_name":"benesch/cockroach","owner":{"login":"benesch","id":882976,"avatar_url":"https://avatars.githubusercontent.com/u/882976?v=3","gravatar_id":"","url":"https://api.github.com/users/benesch","html_url":"https://github.com/benesch","followers_url":"https://api.github.com/users/benesch/followers","following_url":"https://api.github.com/users/benesch/following{/other_user}","gists_url":"https://api.github.com/users/benesch/gists{/gist_id}","starred_url":"https://api.github.com/users/benesch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/benesch/subscriptions","organizations_url":"https://api.github.com/users/benesch/orgs","repos_url":"https://api.github.com/users/benesch/repos","events_url":"https://api.github.com/users/benesch/events{/privacy}","received_events_url":"https://api.github.com/users/benesch/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/benesch/cockroach","description":"A Scalable, Survivable, Strongly-Consistent SQL Database","fork":true,"url":"https://api.github.com/repos/benesch/cockroach","forks_url":"https://api.github.com/repos/benesch/cockroach/forks","keys_url":"https://api.github.com/repos/benesch/cockroach/keys{/key_id}","collaborators_url":"https://api.github.com/repos/benesch/cockroach/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/benesch/cockroach/teams","hooks_url":"https://api.github.com/repos/benesch/cockroach/hooks","issue_events_url":"https://api.github.com/repos/benesch/cockroach/issues/events{/number}","events_url":"https://api.github.com/repos/benesch/cockroach/events","assignees_url":"https://api.github.com/repos/benesch/cockroach/assignees{/user}","branches_url":"https://api.github.com/repos/benesch/cockroach/branches{/branch}","tags_url":"https://api.github.com/repos/benesch/cockroach/tags","blobs_url":"https://api.github.com/repos/benesch/cockroach/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/benesch/cockroach/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/benesch/cockroach/git/refs{/sha}","trees_url":"https://api.github.com/repos/benesch/cockroach/git/trees{/sha}","statuses_url":"https://api.github.com/repos/benesch/cockroach/statuses/{sha}","languages_url":"https://api.github.com/repos/benesch/cockroach/languages","stargazers_url":"https://api.github.com/repos/benesch/cockroach/stargazers","contributors_url":"https://api.github.com/repos/benesch/cockroach/contributors","subscribers_url":"https://api.github.com/repos/benesch/cockroach/subscribers","subscription_url":"https://api.github.com/repos/benesch/cockroach/subscription","commits_url":"https://api.github.com/repos/benesch/cockroach/commits{/sha}","git_commits_url":"https://api.github.com/repos/benesch/cockroach/git/commits{/sha}","comments_url":"https://api.github.com/repos/benesch/cockroach/comments{/number}","issue_comment_url":"https://api.github.com/repos/benesch/cockroach/issues/comments{/number}","contents_url":"https://api.github.com/repos/benesch/cockroach/contents/{+path}","compare_url":"https://api.github.com/repos/benesch/cockroach/compare/{base}...{head}","merges_url":"https://api.github.com/repos/benesch/cockroach/merges","archive_url":"https://api.github.com/repos/benesch/cockroach/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/benesch/cockroach/downloads","issues_url":"https://api.github.com/repos/benesch/cockroach/issues{/number}","pulls_url":"https://api.github.com/repos/benesch/cockroach/pulls{/number}","milestones_url":"https://api.github.com/repos/benesch/cockroach/milestones{/number}","notifications_url":"https://api.github.com/repos/benesch/cockroach/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/benesch/cockroach/labels{/name}","releases_url":"https://api.github.com/repos/benesch/cockroach/releases{/id}","deployments_url":"https://api.github.com/repos/benesch/cockroach/deployments","created_at":"2017-01-10T07:21:31Z","updated_at":"2017-01-10T07:21:39Z","pushed_at":"2017-02-17T19:06:12Z","git_url":"git://github.com/benesch/cockroach.git","ssh_url":"git@github.com:benesch/cockroach.git","clone_url":"https://github.com/benesch/cockroach.git","svn_url":"https://github.com/benesch/cockroach","homepage":"https://www.cockroachlabs.com","size":247811,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"benesch:master","ref":"master","sha":"7947ae3c5b477b0847ef8887c361c1a6f3fa41a7","user":{"login":"benesch","id":882976,"avatar_url":"https://avatars.githubusercontent.com/u/882976?v=3","gravatar_id":"","url":"https://api.github.com/users/benesch","html_url":"https://github.com/benesch","followers_url":"https://api.github.com/users/benesch/followers","following_url":"https://api.github.com/users/benesch/following{/other_user}","gists_url":"https://api.github.com/users/benesch/gists{/gist_id}","starred_url":"https://api.github.com/users/benesch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/benesch/subscriptions","organizations_url":"https://api.github.com/users/benesch/orgs","repos_url":"https://api.github.com/users/benesch/repos","events_url":"https://api.github.com/users/benesch/events{/privacy}","received_events_url":"https://api.github.com/users/benesch/received_events","type":"User","site_admin":false},"repo":{"id":78508291,"name":"cockroach","full_name":"benesch/cockroach","owner":{"login":"benesch","id":882976,"avatar_url":"https://avatars.githubusercontent.com/u/882976?v=3","gravatar_id":"","url":"https://api.github.com/users/benesch","html_url":"https://github.com/benesch","followers_url":"https://api.github.com/users/benesch/followers","following_url":"https://api.github.com/users/benesch/following{/other_user}","gists_url":"https://api.github.com/users/benesch/gists{/gist_id}","starred_url":"https://api.github.com/users/benesch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/benesch/subscriptions","organizations_url":"https://api.github.com/users/benesch/orgs","repos_url":"https://api.github.com/users/benesch/repos","events_url":"https://api.github.com/users/benesch/events{/privacy}","received_events_url":"https://api.github.com/users/benesch/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/benesch/cockroach","description":"A Scalable, Survivable, Strongly-Consistent SQL Database","fork":true,"url":"https://api.github.com/repos/benesch/cockroach","forks_url":"https://api.github.com/repos/benesch/cockroach/forks","keys_url":"https://api.github.com/repos/benesch/cockroach/keys{/key_id}","collaborators_url":"https://api.github.com/repos/benesch/cockroach/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/benesch/cockroach/teams","hooks_url":"https://api.github.com/repos/benesch/cockroach/hooks","issue_events_url":"https://api.github.com/repos/benesch/cockroach/issues/events{/number}","events_url":"https://api.github.com/repos/benesch/cockroach/events","assignees_url":"https://api.github.com/repos/benesch/cockroach/assignees{/user}","branches_url":"https://api.github.com/repos/benesch/cockroach/branches{/branch}","tags_url":"https://api.github.com/repos/benesch/cockroach/tags","blobs_url":"https://api.github.com/repos/benesch/cockroach/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/benesch/cockroach/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/benesch/cockroach/git/refs{/sha}","trees_url":"https://api.github.com/repos/benesch/cockroach/git/trees{/sha}","statuses_url":"https://api.github.com/repos/benesch/cockroach/statuses/{sha}","languages_url":"https://api.github.com/repos/benesch/cockroach/languages","stargazers_url":"https://api.github.com/repos/benesch/cockroach/stargazers","contributors_url":"https://api.github.com/repos/benesch/cockroach/contributors","subscribers_url":"https://api.github.com/repos/benesch/cockroach/subscribers","subscription_url":"https://api.github.com/repos/benesch/cockroach/subscription","commits_url":"https://api.github.com/repos/benesch/cockroach/commits{/sha}","git_commits_url":"https://api.github.com/repos/benesch/cockroach/git/commits{/sha}","comments_url":"https://api.github.com/repos/benesch/cockroach/comments{/number}","issue_comment_url":"https://api.github.com/repos/benesch/cockroach/issues/comments{/number}","contents_url":"https://api.github.com/repos/benesch/cockroach/contents/{+path}","compare_url":"https://api.github.com/repos/benesch/cockroach/compare/{base}...{head}","merges_url":"https://api.github.com/repos/benesch/cockroach/merges","archive_url":"https://api.github.com/repos/benesch/cockroach/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/benesch/cockroach/downloads","issues_url":"https://api.github.com/repos/benesch/cockroach/issues{/number}","pulls_url":"https://api.github.com/repos/benesch/cockroach/pulls{/number}","milestones_url":"https://api.github.com/repos/benesch/cockroach/milestones{/number}","notifications_url":"https://api.github.com/repos/benesch/cockroach/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/benesch/cockroach/labels{/name}","releases_url":"https://api.github.com/repos/benesch/cockroach/releases{/id}","deployments_url":"https://api.github.com/repos/benesch/cockroach/deployments","created_at":"2017-01-10T07:21:31Z","updated_at":"2017-01-10T07:21:39Z","pushed_at":"2017-02-17T19:06:12Z","git_url":"git://github.com/benesch/cockroach.git","ssh_url":"git@github.com:benesch/cockroach.git","clone_url":"https://github.com/benesch/cockroach.git","svn_url":"https://github.com/benesch/cockroach","homepage":"https://www.cockroachlabs.com","size":247811,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/benesch/cockroach/pulls/1"},"html":{"href":"https://github.com/benesch/cockroach/pull/1"},"issue":{"href":"https://api.github.com/repos/benesch/cockroach/issues/1"},"comments":{"href":"https://api.github.com/repos/benesch/cockroach/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/benesch/cockroach/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/benesch/cockroach/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/benesch/cockroach/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/benesch/cockroach/statuses/42d811fa32603fd6a74a2eebb44c06c1f1031fa0"}}}}
{"actor":{"display_login":"danhhz"}}