{"action":"created","comment":{"url":"https://api.github.com/repos/cockroachdb/cockroach/pulls/comments/42674100","id":42674100,"diff_hunk":"@@ -0,0 +1,540 @@\n+// Copyright 2015 The Cockroach Authors.\n+//\n+// Licensed under the Apache License, Version 2.0 (the \"License\");\n+// you may not use this file except in compliance with the License.\n+// You may obtain a copy of the License at\n+//\n+//     http://www.apache.org/licenses/LICENSE-2.0\n+//\n+// Unless required by applicable law or agreed to in writing, software\n+// distributed under the License is distributed on an \"AS IS\" BASIS,\n+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n+// implied. See the License for the specific language governing\n+// permissions and limitations under the License. See the AUTHORS file\n+// for names of contributors.\n+//\n+// Author: Peter Mattis (peter@cockroachlabs.com)\n+\n+package sql\n+\n+import (\n+\t\"bytes\"\n+\t\"errors\"\n+\t\"fmt\"\n+\t\"math/rand\"\n+\t\"sort\"\n+\t\"sync\"\n+\t\"time\"\n+\n+\t\"github.com/cockroachdb/cockroach/client\"\n+\t\"github.com/cockroachdb/cockroach/sql/parser\"\n+\t\"github.com/cockroachdb/cockroach/util\"\n+\t\"github.com/cockroachdb/cockroach/util/hlc\"\n+\t\"github.com/cockroachdb/cockroach/util/log\"\n+\t\"github.com/cockroachdb/cockroach/util/retry\"\n+)\n+\n+// TODO(pmattis): Periodically renew leases for tables that were used recently and\n+// for which the lease will expire soon.\n+\n+const (\n+\tleaseDuration    = int64(5 * time.Minute)\n+\tminLeaseDuration = int64(time.Minute)\n+)\n+\n+const (\n+\tleaseDescIDRowIndex = iota\n+\tleaseVersionRowIndex\n+\tleaseExpirationRowIndex\n+\tleaseNodeIDRowIndex\n+)\n+\n+var (\n+\tleaseValTypes          []parser.Datum\n+\tleaseColIDToRowIndex   map[ColumnID]int\n+\terrLeaseVersionChanged = errors.New(\"lease version changed\")\n+)\n+\n+func init() {\n+\tcolumns := [...]string{\n+\t\tleaseDescIDRowIndex:     \"descID\",\n+\t\tleaseVersionRowIndex:    \"version\",\n+\t\tleaseExpirationRowIndex: \"expiration\",\n+\t\tleaseNodeIDRowIndex:     \"nodeID\",\n+\t}\n+\tleaseColIDToRowIndex = make(map[ColumnID]int, len(columns))\n+\tfor i, name := range columns {\n+\t\tcol, err := LeaseTable.FindColumnByName(name)\n+\t\tif err != nil {\n+\t\t\tlog.Fatal(err)\n+\t\t}\n+\t\tif col.ID == 0 {\n+\t\t\t// This shouldn't happen, just being paranoid.\n+\t\t\tlog.Fatalf(\"column %s invalid\", col.Name)\n+\t\t}\n+\t\tleaseColIDToRowIndex[col.ID] = i\n+\t}\n+\n+\tvar err error\n+\tleaseValTypes, err = makeKeyVals(&LeaseTable, LeaseTable.PrimaryIndex.ColumnIDs)\n+\tif err != nil {\n+\t\tlog.Fatal(err)\n+\t}\n+}\n+\n+func nanosToTime(nanos int64) time.Time {\n+\treturn time.Unix(nanos/1e9, nanos%1e9)\n+}\n+\n+func makeLeaseKey(nodeID uint32, descID ID, version uint32, expiration int64) []byte {\n+\tindex := LeaseTable.PrimaryIndex\n+\trowVals := make([]parser.Datum, len(index.ColumnIDs))\n+\trowVals[leaseDescIDRowIndex] = parser.DInt(descID)\n+\trowVals[leaseExpirationRowIndex] = parser.DTimestamp{Time: nanosToTime(expiration)}\n+\trowVals[leaseNodeIDRowIndex] = parser.DInt(nodeID)\n+\trowVals[leaseVersionRowIndex] = parser.DInt(version)\n+\n+\tprefix := MakeIndexKeyPrefix(LeaseTable.ID, index.ID)\n+\tk, _, err := encodeIndexKey(index.ColumnIDs, leaseColIDToRowIndex, rowVals, prefix)\n+\tif err != nil {\n+\t\tpanic(err) // Should never happen, we're careful to only encode supported datum types.\n+\t}\n+\treturn k\n+}\n+\n+func makeLeaseScanKeys(descID ID, version uint32, expiration int64) ([]byte, []byte) {\n+\tindex := LeaseTable.PrimaryIndex\n+\n+\tprefix := MakeIndexKeyPrefix(LeaseTable.ID, index.ID)\n+\tstartKey := append([]byte(nil), prefix...)\n+\tstartKey, err := encodeTableKey(startKey, parser.DInt(descID))\n+\tif err != nil {\n+\t\tpanic(err) // Should never happen, we're careful to only encode supported datum types.\n+\t}\n+\tstartKey, err = encodeTableKey(startKey, parser.DInt(version))\n+\tif err != nil {\n+\t\tpanic(err) // Should never happen, we're careful to only encode supported datum types.\n+\t}\n+\tstartKey, err = encodeTableKey(startKey, parser.DTimestamp{Time: nanosToTime(expiration)})\n+\tif err != nil {\n+\t\tpanic(err) // Should never happen, we're careful to only encode supported datum types.\n+\t}\n+\n+\tendKey := prefix\n+\tendKey, err = encodeTableKey(endKey, parser.DInt(descID))\n+\tif err != nil {\n+\t\tpanic(err) // Should never happen, we're careful to only encode supported datum types.\n+\t}\n+\tendKey, err = encodeTableKey(endKey, parser.DInt(version)+1)\n+\tif err != nil {\n+\t\tpanic(err) // Should never happen, we're careful to only encode supported datum types.\n+\t}\n+\treturn startKey, endKey\n+}\n+\n+// LeaseState holds the state for a lease. Exported only for testing.\n+type LeaseState struct {\n+\tTableDescriptor\n+\texpiration int64\n+\trefcount   int\n+}\n+\n+func (s *LeaseState) String() string {\n+\treturn fmt.Sprintf(\"%d:%d\", s.Version, s.expiration)\n+}\n+\n+// Expiration returns the expiration time of the lease.\n+func (s *LeaseState) Expiration() time.Time {\n+\treturn nanosToTime(s.expiration)\n+}\n+\n+// Refcount returns the reference count of the lease.\n+func (s *LeaseState) Refcount() int {\n+\treturn s.refcount\n+}\n+\n+// LeaseStore implements the operations for acquiring and releasing leases and\n+// publishing a new version of a descriptor. Exported only for testing.\n+type LeaseStore struct {\n+\tdb     client.DB\n+\tclock  *hlc.Clock\n+\tnodeID uint32\n+}\n+\n+// jitteredLeaseDuration returns a randomly jittered duration from the interval\n+// [0.75 * leaseDuration, 1.25 * leaseDuration].\n+func (s LeaseStore) jitteredLeaseDuration() int64 {\n+\treturn int64(float64(leaseDuration) * (0.75 + 0.5*rand.Float64()))\n+}\n+\n+// Acquire a lease on the most recent version of a table descriptor.\n+func (s LeaseStore) Acquire(tableID ID, minVersion uint32) (*LeaseState, error) {\n+\tlease := &LeaseState{}\n+\tlease.expiration = s.clock.Now().WallTime + s.jitteredLeaseDuration()\n+\tdescKey := MakeDescMetadataKey(tableID)\n+\n+\terr := s.db.Txn(func(txn *client.Txn) error {\n+\t\tif err := txn.GetProto(descKey, &lease.TableDescriptor); err != nil {\n+\t\t\treturn err\n+\t\t}\n+\t\tif err := lease.Validate(); err != nil {\n+\t\t\treturn err\n+\t\t}\n+\t\tif lease.Version < minVersion {\n+\t\t\treturn fmt.Errorf(\"version %d of table %d does not exist yet\", minVersion, tableID)\n+\t\t}\n+\t\t// Add an entry to the lease table.\n+\t\tleaseKey := makeLeaseKey(s.nodeID, lease.ID, lease.Version, lease.expiration)\n+\t\tif log.V(3) {\n+\t\t\tlog.Infof(\"acquire: %s\", prettyKey(leaseKey, 0))\n+\t\t}\n+\t\tb := &client.Batch{}\n+\t\tb.Put(leaseKey, nil)\n+\t\t// TODO(pmattis): Setting the system DB trigger is currently necessary\n+\t\t// because the lease table resides in the system DB span. Perhaps it\n+\t\t// shouldn't.\n+\t\ttxn.SetSystemDBTrigger()\n+\t\treturn txn.CommitInBatch(b)\n+\t})\n+\treturn lease, err\n+}\n+\n+// Release a previously acquired table descriptor lease.\n+func (s LeaseStore) Release(lease *LeaseState) error {\n+\tleaseKey := makeLeaseKey(s.nodeID, lease.ID, lease.Version, lease.expiration)\n+\tif log.V(3) {\n+\t\tlog.Infof(\"release: %s\", prettyKey(leaseKey, 0))\n+\t}\n+\treturn s.db.Txn(func(txn *client.Txn) error {\n+\t\tb := &client.Batch{}\n+\t\tb.Del(leaseKey)\n+\t\ttxn.SetSystemDBTrigger()\n+\t\treturn txn.CommitInBatch(b)\n+\t})\n+}\n+\n+// Publish a new version of a table descriptor. The update closure may be\n+// called multiple times if retries occur: make sure it does not have side\n+// effects.\n+func (s LeaseStore) Publish(tableID ID, update func(*TableDescriptor) error) error {\n+\tdesc := &TableDescriptor{}\n+\tdescKey := MakeDescMetadataKey(tableID)\n+\n+\tretryOpts := retry.Options{\n+\t\tInitialBackoff: 20 * time.Millisecond,\n+\t\tMaxBackoff:     2 * time.Second,\n+\t\tMultiplier:     2,\n+\t}\n+\n+\tfor r := retry.Start(retryOpts); r.Next(); {\n+\t\t// Get the current version of the table descriptor non-transactionally.\n+\t\t//\n+\t\t// TODO(pmattis): Do an inconsistent read here?\n+\t\tif err := s.db.GetProto(descKey, desc); err != nil {\n+\t\t\treturn err\n+\t\t}\n+\t\t// Check to see if there are any leases that still exist on the previous\n+\t\t// version of the descriptor.\n+\t\tnow := s.clock.Now()\n+\t\tcount, err := s.countLeases(desc.ID, desc.Version-1, now.WallTime)\n+\t\tif err != nil {\n+\t\t\treturn err\n+\t\t}\n+\t\tif count != 0 {\n+\t\t\tlog.Infof(\"publish (count leases): descID=%d version=%d count=%d\", desc.ID, desc.Version-1, count)\n+\t\t\tcontinue\n+\t\t}\n+\n+\t\t// At this point, desc.Version is the only version of the descriptor that\n+\t\t// has leases outstanding. Lease acquisition (see acquire()) maintains the\n+\t\t// invariant that no new leases for desc.Version-1 will be granted once\n+\t\t// desc.Version exists.\n+\t\texpectedVersion := desc.Version\n+\t\terr = s.db.Txn(func(txn *client.Txn) error {\n+\t\t\t// Re-read the current version of the table descriptor, this time\n+\t\t\t// transactionally.\n+\t\t\tif err := txn.GetProto(descKey, desc); err != nil {\n+\t\t\t\treturn err\n+\t\t\t}\n+\t\t\tif expectedVersion != desc.Version {\n+\t\t\t\t// The version changed out from under us. Someone else must be\n+\t\t\t\t// performing a schema change operation.\n+\t\t\t\tif log.V(3) {\n+\t\t\t\t\tlog.Infof(\"publish (version changed): %d != %d\", expectedVersion, desc.Version)\n+\t\t\t\t}\n+\t\t\t\treturn errLeaseVersionChanged\n+\t\t\t}\n+\n+\t\t\t// Update the descriptor.\n+\t\t\tif err := update(desc); err != nil {\n+\t\t\t\treturn err\n+\t\t\t}\n+\n+\t\t\t// Bump the version and modification time.\n+\t\t\tdesc.Version = desc.Version + 1\n+\t\t\tdesc.ModificationTime = now\n+\t\t\tif log.V(3) {\n+\t\t\t\tlog.Infof(\"publish: descID=%d version=%d mtime=%s\", desc.ID, desc.Version, now.GoTime())\n+\t\t\t}\n+\n+\t\t\t// Write the updated descriptor.\n+\t\t\tb := &client.Batch{}\n+\t\t\tb.Put(descKey, desc)\n+\t\t\ttxn.SetSystemDBTrigger()\n+\t\t\treturn txn.CommitInBatch(b)\n+\t\t})\n+\n+\t\tif err != errLeaseVersionChanged {\n+\t\t\treturn err\n+\t\t}\n+\t}\n+\n+\tpanic(\"not reached\")\n+}\n+\n+// countLeases returns the number of unexpired leases for a particular version\n+// of a descriptor.\n+func (s LeaseStore) countLeases(descID ID, version uint32, expiration int64) (int, error) {","path":"sql/lease.go","position":297,"original_position":297,"commit_id":"c72d9f55b8de4b214b709e4c2c74ced5221e4f54","original_commit_id":"c72d9f55b8de4b214b709e4c2c74ced5221e4f54","user":{"login":"petermattis","id":6201335,"avatar_url":"https://avatars.githubusercontent.com/u/6201335?v=3","gravatar_id":"","url":"https://api.github.com/users/petermattis","html_url":"https://github.com/petermattis","followers_url":"https://api.github.com/users/petermattis/followers","following_url":"https://api.github.com/users/petermattis/following{/other_user}","gists_url":"https://api.github.com/users/petermattis/gists{/gist_id}","starred_url":"https://api.github.com/users/petermattis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/petermattis/subscriptions","organizations_url":"https://api.github.com/users/petermattis/orgs","repos_url":"https://api.github.com/users/petermattis/repos","events_url":"https://api.github.com/users/petermattis/events{/privacy}","received_events_url":"https://api.github.com/users/petermattis/received_events","type":"User","site_admin":false},"body":"True. This isn't used during testing, but I do log the value. With `hasActiveLease` I could change the scan below to limit to a single key. ","created_at":"2015-10-21T20:00:05Z","updated_at":"2015-10-21T20:00:05Z","html_url":"https://github.com/cockroachdb/cockroach/pull/2872#discussion_r42674100","pull_request_url":"https://api.github.com/repos/cockroachdb/cockroach/pulls/2872","_links":{"self":{"href":"https://api.github.com/repos/cockroachdb/cockroach/pulls/comments/42674100"},"html":{"href":"https://github.com/cockroachdb/cockroach/pull/2872#discussion_r42674100"},"pull_request":{"href":"https://api.github.com/repos/cockroachdb/cockroach/pulls/2872"}}},"pull_request":{"url":"https://api.github.com/repos/cockroachdb/cockroach/pulls/2872","id":48351207,"html_url":"https://github.com/cockroachdb/cockroach/pull/2872","diff_url":"https://github.com/cockroachdb/cockroach/pull/2872.diff","patch_url":"https://github.com/cockroachdb/cockroach/pull/2872.patch","issue_url":"https://api.github.com/repos/cockroachdb/cockroach/issues/2872","number":2872,"state":"open","locked":false,"title":"Initial work on leaseManager.","user":{"login":"petermattis","id":6201335,"avatar_url":"https://avatars.githubusercontent.com/u/6201335?v=3","gravatar_id":"","url":"https://api.github.com/users/petermattis","html_url":"https://github.com/petermattis","followers_url":"https://api.github.com/users/petermattis/followers","following_url":"https://api.github.com/users/petermattis/following{/other_user}","gists_url":"https://api.github.com/users/petermattis/gists{/gist_id}","starred_url":"https://api.github.com/users/petermattis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/petermattis/subscriptions","organizations_url":"https://api.github.com/users/petermattis/orgs","repos_url":"https://api.github.com/users/petermattis/repos","events_url":"https://api.github.com/users/petermattis/events{/privacy}","received_events_url":"https://api.github.com/users/petermattis/received_events","type":"User","site_admin":false},"body":"Added the system.lease table. Added TableDescriptor.Version and\ninitialize to 1 on table creation.\n\nAdded leaseManager and associated components. Support lease acquisition\nand releasing along with local ref counts. Support publication of new\ntable descriptor versions.","created_at":"2015-10-21T15:56:54Z","updated_at":"2015-10-21T20:00:05Z","closed_at":null,"merged_at":null,"merge_commit_sha":"ee804c9e633a05d6fb2a4cf55c3cb84fcb5f07d7","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/cockroachdb/cockroach/pulls/2872/commits","review_comments_url":"https://api.github.com/repos/cockroachdb/cockroach/pulls/2872/comments","review_comment_url":"https://api.github.com/repos/cockroachdb/cockroach/pulls/comments{/number}","comments_url":"https://api.github.com/repos/cockroachdb/cockroach/issues/2872/comments","statuses_url":"https://api.github.com/repos/cockroachdb/cockroach/statuses/c72d9f55b8de4b214b709e4c2c74ced5221e4f54","head":{"label":"cockroachdb:pmattis/sql-descriptor-lease","ref":"pmattis/sql-descriptor-lease","sha":"c72d9f55b8de4b214b709e4c2c74ced5221e4f54","user":{"login":"cockroachdb","id":6748139,"avatar_url":"https://avatars.githubusercontent.com/u/6748139?v=3","gravatar_id":"","url":"https://api.github.com/users/cockroachdb","html_url":"https://github.com/cockroachdb","followers_url":"https://api.github.com/users/cockroachdb/followers","following_url":"https://api.github.com/users/cockroachdb/following{/other_user}","gists_url":"https://api.github.com/users/cockroachdb/gists{/gist_id}","starred_url":"https://api.github.com/users/cockroachdb/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cockroachdb/subscriptions","organizations_url":"https://api.github.com/users/cockroachdb/orgs","repos_url":"https://api.github.com/users/cockroachdb/repos","events_url":"https://api.github.com/users/cockroachdb/events{/privacy}","received_events_url":"https://api.github.com/users/cockroachdb/received_events","type":"Organization","site_admin":false},"repo":{"id":16563587,"name":"cockroach","full_name":"cockroachdb/cockroach","owner":{"login":"cockroachdb","id":6748139,"avatar_url":"https://avatars.githubusercontent.com/u/6748139?v=3","gravatar_id":"","url":"https://api.github.com/users/cockroachdb","html_url":"https://github.com/cockroachdb","followers_url":"https://api.github.com/users/cockroachdb/followers","following_url":"https://api.github.com/users/cockroachdb/following{/other_user}","gists_url":"https://api.github.com/users/cockroachdb/gists{/gist_id}","starred_url":"https://api.github.com/users/cockroachdb/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cockroachdb/subscriptions","organizations_url":"https://api.github.com/users/cockroachdb/orgs","repos_url":"https://api.github.com/users/cockroachdb/repos","events_url":"https://api.github.com/users/cockroachdb/events{/privacy}","received_events_url":"https://api.github.com/users/cockroachdb/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/cockroachdb/cockroach","description":"A Scalable, Geo-Replicated, Transactional Datastore","fork":false,"url":"https://api.github.com/repos/cockroachdb/cockroach","forks_url":"https://api.github.com/repos/cockroachdb/cockroach/forks","keys_url":"https://api.github.com/repos/cockroachdb/cockroach/keys{/key_id}","collaborators_url":"https://api.github.com/repos/cockroachdb/cockroach/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/cockroachdb/cockroach/teams","hooks_url":"https://api.github.com/repos/cockroachdb/cockroach/hooks","issue_events_url":"https://api.github.com/repos/cockroachdb/cockroach/issues/events{/number}","events_url":"https://api.github.com/repos/cockroachdb/cockroach/events","assignees_url":"https://api.github.com/repos/cockroachdb/cockroach/assignees{/user}","branches_url":"https://api.github.com/repos/cockroachdb/cockroach/branches{/branch}","tags_url":"https://api.github.com/repos/cockroachdb/cockroach/tags","blobs_url":"https://api.github.com/repos/cockroachdb/cockroach/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/cockroachdb/cockroach/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/cockroachdb/cockroach/git/refs{/sha}","trees_url":"https://api.github.com/repos/cockroachdb/cockroach/git/trees{/sha}","statuses_url":"https://api.github.com/repos/cockroachdb/cockroach/statuses/{sha}","languages_url":"https://api.github.com/repos/cockroachdb/cockroach/languages","stargazers_url":"https://api.github.com/repos/cockroachdb/cockroach/stargazers","contributors_url":"https://api.github.com/repos/cockroachdb/cockroach/contributors","subscribers_url":"https://api.github.com/repos/cockroachdb/cockroach/subscribers","subscription_url":"https://api.github.com/repos/cockroachdb/cockroach/subscription","commits_url":"https://api.github.com/repos/cockroachdb/cockroach/commits{/sha}","git_commits_url":"https://api.github.com/repos/cockroachdb/cockroach/git/commits{/sha}","comments_url":"https://api.github.com/repos/cockroachdb/cockroach/comments{/number}","issue_comment_url":"https://api.github.com/repos/cockroachdb/cockroach/issues/comments{/number}","contents_url":"https://api.github.com/repos/cockroachdb/cockroach/contents/{+path}","compare_url":"https://api.github.com/repos/cockroachdb/cockroach/compare/{base}...{head}","merges_url":"https://api.github.com/repos/cockroachdb/cockroach/merges","archive_url":"https://api.github.com/repos/cockroachdb/cockroach/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/cockroachdb/cockroach/downloads","issues_url":"https://api.github.com/repos/cockroachdb/cockroach/issues{/number}","pulls_url":"https://api.github.com/repos/cockroachdb/cockroach/pulls{/number}","milestones_url":"https://api.github.com/repos/cockroachdb/cockroach/milestones{/number}","notifications_url":"https://api.github.com/repos/cockroachdb/cockroach/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/cockroachdb/cockroach/labels{/name}","releases_url":"https://api.github.com/repos/cockroachdb/cockroach/releases{/id}","created_at":"2014-02-06T00:18:47Z","updated_at":"2015-10-21T08:54:22Z","pushed_at":"2015-10-21T19:47:45Z","git_url":"git://github.com/cockroachdb/cockroach.git","ssh_url":"git@github.com:cockroachdb/cockroach.git","clone_url":"https://github.com/cockroachdb/cockroach.git","svn_url":"https://github.com/cockroachdb/cockroach","homepage":null,"size":68792,"stargazers_count":5473,"watchers_count":5473,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":461,"mirror_url":null,"open_issues_count":207,"forks":461,"open_issues":207,"watchers":5473,"default_branch":"master"}},"base":{"label":"cockroachdb:master","ref":"master","sha":"9515804b66f4cfc125675aa45909559565ffbf40","user":{"login":"cockroachdb","id":6748139,"avatar_url":"https://avatars.githubusercontent.com/u/6748139?v=3","gravatar_id":"","url":"https://api.github.com/users/cockroachdb","html_url":"https://github.com/cockroachdb","followers_url":"https://api.github.com/users/cockroachdb/followers","following_url":"https://api.github.com/users/cockroachdb/following{/other_user}","gists_url":"https://api.github.com/users/cockroachdb/gists{/gist_id}","starred_url":"https://api.github.com/users/cockroachdb/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cockroachdb/subscriptions","organizations_url":"https://api.github.com/users/cockroachdb/orgs","repos_url":"https://api.github.com/users/cockroachdb/repos","events_url":"https://api.github.com/users/cockroachdb/events{/privacy}","received_events_url":"https://api.github.com/users/cockroachdb/received_events","type":"Organization","site_admin":false},"repo":{"id":16563587,"name":"cockroach","full_name":"cockroachdb/cockroach","owner":{"login":"cockroachdb","id":6748139,"avatar_url":"https://avatars.githubusercontent.com/u/6748139?v=3","gravatar_id":"","url":"https://api.github.com/users/cockroachdb","html_url":"https://github.com/cockroachdb","followers_url":"https://api.github.com/users/cockroachdb/followers","following_url":"https://api.github.com/users/cockroachdb/following{/other_user}","gists_url":"https://api.github.com/users/cockroachdb/gists{/gist_id}","starred_url":"https://api.github.com/users/cockroachdb/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cockroachdb/subscriptions","organizations_url":"https://api.github.com/users/cockroachdb/orgs","repos_url":"https://api.github.com/users/cockroachdb/repos","events_url":"https://api.github.com/users/cockroachdb/events{/privacy}","received_events_url":"https://api.github.com/users/cockroachdb/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/cockroachdb/cockroach","description":"A Scalable, Geo-Replicated, Transactional Datastore","fork":false,"url":"https://api.github.com/repos/cockroachdb/cockroach","forks_url":"https://api.github.com/repos/cockroachdb/cockroach/forks","keys_url":"https://api.github.com/repos/cockroachdb/cockroach/keys{/key_id}","collaborators_url":"https://api.github.com/repos/cockroachdb/cockroach/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/cockroachdb/cockroach/teams","hooks_url":"https://api.github.com/repos/cockroachdb/cockroach/hooks","issue_events_url":"https://api.github.com/repos/cockroachdb/cockroach/issues/events{/number}","events_url":"https://api.github.com/repos/cockroachdb/cockroach/events","assignees_url":"https://api.github.com/repos/cockroachdb/cockroach/assignees{/user}","branches_url":"https://api.github.com/repos/cockroachdb/cockroach/branches{/branch}","tags_url":"https://api.github.com/repos/cockroachdb/cockroach/tags","blobs_url":"https://api.github.com/repos/cockroachdb/cockroach/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/cockroachdb/cockroach/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/cockroachdb/cockroach/git/refs{/sha}","trees_url":"https://api.github.com/repos/cockroachdb/cockroach/git/trees{/sha}","statuses_url":"https://api.github.com/repos/cockroachdb/cockroach/statuses/{sha}","languages_url":"https://api.github.com/repos/cockroachdb/cockroach/languages","stargazers_url":"https://api.github.com/repos/cockroachdb/cockroach/stargazers","contributors_url":"https://api.github.com/repos/cockroachdb/cockroach/contributors","subscribers_url":"https://api.github.com/repos/cockroachdb/cockroach/subscribers","subscription_url":"https://api.github.com/repos/cockroachdb/cockroach/subscription","commits_url":"https://api.github.com/repos/cockroachdb/cockroach/commits{/sha}","git_commits_url":"https://api.github.com/repos/cockroachdb/cockroach/git/commits{/sha}","comments_url":"https://api.github.com/repos/cockroachdb/cockroach/comments{/number}","issue_comment_url":"https://api.github.com/repos/cockroachdb/cockroach/issues/comments{/number}","contents_url":"https://api.github.com/repos/cockroachdb/cockroach/contents/{+path}","compare_url":"https://api.github.com/repos/cockroachdb/cockroach/compare/{base}...{head}","merges_url":"https://api.github.com/repos/cockroachdb/cockroach/merges","archive_url":"https://api.github.com/repos/cockroachdb/cockroach/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/cockroachdb/cockroach/downloads","issues_url":"https://api.github.com/repos/cockroachdb/cockroach/issues{/number}","pulls_url":"https://api.github.com/repos/cockroachdb/cockroach/pulls{/number}","milestones_url":"https://api.github.com/repos/cockroachdb/cockroach/milestones{/number}","notifications_url":"https://api.github.com/repos/cockroachdb/cockroach/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/cockroachdb/cockroach/labels{/name}","releases_url":"https://api.github.com/repos/cockroachdb/cockroach/releases{/id}","created_at":"2014-02-06T00:18:47Z","updated_at":"2015-10-21T08:54:22Z","pushed_at":"2015-10-21T19:47:45Z","git_url":"git://github.com/cockroachdb/cockroach.git","ssh_url":"git@github.com:cockroachdb/cockroach.git","clone_url":"https://github.com/cockroachdb/cockroach.git","svn_url":"https://github.com/cockroachdb/cockroach","homepage":null,"size":68792,"stargazers_count":5473,"watchers_count":5473,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":461,"mirror_url":null,"open_issues_count":207,"forks":461,"open_issues":207,"watchers":5473,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/cockroachdb/cockroach/pulls/2872"},"html":{"href":"https://github.com/cockroachdb/cockroach/pull/2872"},"issue":{"href":"https://api.github.com/repos/cockroachdb/cockroach/issues/2872"},"comments":{"href":"https://api.github.com/repos/cockroachdb/cockroach/issues/2872/comments"},"review_comments":{"href":"https://api.github.com/repos/cockroachdb/cockroach/pulls/2872/comments"},"review_comment":{"href":"https://api.github.com/repos/cockroachdb/cockroach/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/cockroachdb/cockroach/pulls/2872/commits"},"statuses":{"href":"https://api.github.com/repos/cockroachdb/cockroach/statuses/c72d9f55b8de4b214b709e4c2c74ced5221e4f54"}}}}