| :linkattrs: | |
| = Gerrit Code Review - System Design | |
| == Objective | |
| Gerrit is a web based code review system, facilitating online code | |
| reviews for projects using the Git version control system. | |
| Gerrit makes reviews easier by showing changes in a side-by-side | |
| display, and allowing inline/file comments to be added by any reviewer. | |
| Gerrit simplifies Git based project maintainership by permitting | |
| any authorized user to submit changes to the master Git repository, | |
| rather than requiring all approved changes to be merged in by | |
| hand by the project maintainer. This functionality enables a more | |
| centralized usage of Git. | |
| == Background | |
| Git is a distributed version control system, wherein each repository | |
| is assumed to be owned/maintained by a single user. There are no | |
| inherent security controls built into Git, so the ability to read | |
| from or write to a repository is controlled entirely by the host's | |
| filesystem or network access controls. | |
| The objective of Gerrit is to facilitate Git development by larger | |
| teams: it provides a means to enforce organizational policies around | |
| code submissions, eg. "all code must be reviewed by another | |
| developer", "all code shall pass tests". It achieves this by | |
| * providing fine-grained (per-branch, per-repository, inheriting) | |
| access controls, which allow a Gerrit admin to delegate permissions | |
| to different team(-lead)s. | |
| * facilitate code review: Gerrit offers a web view of pending code | |
| changes, that allows for easy reading and commenting by humans. The | |
| web view can offer data coming out of automated QA processes (eg. | |
| CI). The permission system also includes fine grained control of who | |
| can approve pending changes for submission to further facilitate | |
| delegation of code ownership. | |
| == Overview | |
| Developers create one or more changes on their local desktop system, | |
| then upload them for review to Gerrit using the standard `git push` | |
| command line program, or any GUI which can invoke `git push` on behalf | |
| of the user. Authentication and data transfer are handled through SSH | |
| and HTTPS. Uploads are protected by the authentication, | |
| confidentiality and integrity offered by the transport (SSH, HTTPS). | |
| Each Git commit created on the client desktop system is converted into | |
| a unique change record which can be reviewed independently. | |
| A summary of each newly uploaded change is automatically emailed | |
| to reviewers, so they receive a direct hyperlink to review the | |
| change on the web. Reviewer email addresses can be specified on the | |
| `git push` command line, but typically reviewers are added in the web | |
| interface. | |
| Reviewers use the web interface to read the side-by-side or unified | |
| diff of a change, and insert draft inline/file comments where | |
| appropriate. A draft comment is visible only to the reviewer, until | |
| they publish those comments. Published comments are automatically | |
| emailed to the change author by Gerrit, and are CC'd to all other | |
| reviewers who have already commented on the change. | |
| Reviewers can score the change ("vote"), indicating whether they feel the | |
| change is ready for inclusion in the project, needs more work, or | |
| should be rejected outright. These scores provide direct feedback to | |
| Gerrit's change submit function. | |
| After a change has been scored positively by reviewers, Gerrit enables | |
| a submit button on the web interface. Authorized users can push the | |
| submit button to have the change enter the project repository. The | |
| user pressing the submit button does not need to be the author of the | |
| change. | |
| == Infrastructure | |
| End-user web browsers make HTTP requests directly to Gerrit's | |
| HTTP server. As nearly all of the Gerrit user interface is implemented | |
| in a JavaScript based web app, the majority of these requests are | |
| transmitting compressed JSON payloads, with all HTML being generated | |
| within the browser. | |
| Gerrit's HTTP server side component is implemented as a standard Java | |
| servlet, and thus runs within any link:install-j2ee.html[J2EE servlet | |
| container]. The standard install will run inside Jetty, which is | |
| included in the binary. | |
| End-user uploads are performed over SSH or HTTP, so Gerrit's servlets | |
| also start up a background thread to receive SSH connections through | |
| an independent SSH port. SSH clients communicate directly with this | |
| port, bypassing the HTTP server used by browsers. | |
| User authentication is handled by identity realms. Gerrit supports the | |
| following types of authentication: | |
| * OpenID (see link:http://openid.net/developers/specs/[OpenID Specifications,role=external,window=_blank]) | |
| * OAuth2 | |
| * LDAP | |
| * Google accounts (on googlesource.com) | |
| * SAML | |
| * Kerberos | |
| * 3rd party SSO | |
| === NoteDb | |
| Server side data storage for Gerrit is broken down into two different | |
| categories: | |
| * Git repository data | |
| * Gerrit metadata | |
| The Git repository data is the Git object database used to store | |
| already submitted revisions, as well as all uploaded (proposed) | |
| changes. Gerrit uses the standard Git repository format, and | |
| therefore requires direct filesystem access to the repositories. | |
| All repository data is stored in the filesystem and accessed through | |
| the JGit library. Repository data can be stored on remote servers | |
| accessible through NFS or SMB, but the remote directory must | |
| be mounted on the Gerrit server as part of the local filesystem | |
| namespace. Remote filesystems are likely to perform worse than | |
| local ones, due to Git disk IO behavior not being optimized for | |
| remote access. | |
| The Gerrit metadata contains a summary of the available changes, all | |
| comments (published and drafts), and individual user account | |
| information. | |
| Gerrit metadata is also stored in Git, with the commits marking the | |
| historical state of metadata. Data is stored in the trees associated | |
| with the commits, typically using Git config file or JSON as the base | |
| format. For metadata, there are 3 types of data: changes, accounts and | |
| groups. | |
| Accounts are stored in a special Git repository `All-Users`. | |
| Accounts can be grouped in groups. Gerrit has a built-in group system, | |
| but can also interface to external group system (eg. Google groups, | |
| LDAP). The built-in groups are stored in `All-Users`. | |
| Draft comments are stored in `All-Users` too. | |
| Permissions are stored in Git, in a branch `refs/meta/config` for the | |
| repository. Repository configuration (including permissions) supports | |
| single inheritance, with the `All-Projects` repository containing | |
| site-wide defaults. | |
| Code review metadata is stored in Git, alongside the code under | |
| review. Metadata includes change status, votes, comments. This review | |
| metadata is stored in NoteDb along with the submitted code and code | |
| under review. Hence, the review history can be exported with `git | |
| clone --mirror` by anyone with sufficient permissions. | |
| == Permissions | |
| Permissions are specified on branch names, and given to groups. For | |
| example, | |
| ``` | |
| [access "refs/heads/stable/*"] | |
| push = group Release-Engineers | |
| ``` | |
| this provides a rule, granting Release-Engineers push permission for | |
| stable branches. | |
| There are fundamentally two types of permissions: | |
| * Write permissions (who can vote, push, submit etc.) | |
| * Read permissions (who can see data) | |
| Read permissions need special treatment across Gerrit, because Gerrit | |
| should only surface data (including repository existence) if a user | |
| has read permission. This means that | |
| * The git wire protocol support must omit references from | |
| advertisement if the user lacks read permissions | |
| * Uploads through the git wire protocol must refuse commits that are | |
| based on SHA-1s for data that the user can't see. | |
| * Tags are only visible if their commits are visible to user through a | |
| non-tag reference. | |
| Metadata (eg. OAuth credentials) is also stored in Git. Existing | |
| endpoints must refuse creating branches or changes that expose these | |
| metadata or allow changes to them. | |
| === Indexing | |
| Almost all data is stored as Git, but Git only supports fast lookup by | |
| SHA-1 or by ref (branch) name. Therefore Gerrit also has an indexing | |
| system (powered by Lucene by default) for other types of queries. | |
| There are 4 indices: | |
| * Project index - find repositories by name, parent project, etc. | |
| * Account index - find accounts by name, email, etc. | |
| * Group index - find groups by name, owner, description etc. | |
| * Change index - find changes by file, status, modification date etc. | |
| The base entities are characterized by SHA-1s. Storing the | |
| characterizing SHA-1s allows detection of stale index entries. | |
| == Plug-in architecture | |
| Gerrit has a plug-in architecture. Plugins can be installed by | |
| dropping them into $site_directory/plugins, or at runtime through | |
| plugin SSH commands, or the plugin REST API. | |
| === Backend plugins | |
| At runtime, code can be loaded from a `.jar` file. This code can hook | |
| into predefined extension points. A common use of plugins is to have | |
| Gerrit interoperate with site-specific tools, such as CI-systems or | |
| issue trackers. | |
| // list some notable extension points, and notable plugins | |
| // link to plugin development | |
| Some backend plugins expose the JVM for scripting use (eg. Groovy, | |
| Scala), so plugins can be written without having to setup a Java | |
| development environment. | |
| // Luca to expand: how do script plugins load their scripts? | |
| === Frontend plugins | |
| The UI can be extended using Frontend plugins. This is useful for | |
| changing the look & feel of Gerrit, but it can also be used to surface | |
| data from systems that aren't integrated with the Gerrit backend, eg. | |
| CI systems or code coverage providers. | |
| // FE team to write a bit more: | |
| // * how to load ? | |
| // * XSRF, CORS ? | |
| == Internationalization and Localization | |
| As a source code review system for open source projects, where the | |
| commonly preferred language for communication is typically English, | |
| Gerrit does not make internationalization or localization a priority. | |
| The majority of Gerrit's users will be writing change descriptions | |
| and comments in English, and therefore an English user interface | |
| is usable by the target user base. | |
| == Accessibility Considerations | |
| // UI team to rewrite this. | |
| Whenever possible Gerrit displays raw text rather than image icons, | |
| so screen readers should still be able to provide useful information | |
| to blind persons accessing Gerrit sites. | |
| Standard HTML hyperlinks are used rather than HTML div or span tags | |
| with click listeners. This provides two benefits to the end-user. | |
| The first benefit is that screen readers are optimized to locating | |
| standard hyperlink anchors and presenting them to the end-user as | |
| a navigation action. The second benefit is that users can use | |
| the 'open in new tab/window' feature of their browser whenever | |
| they choose. | |
| When possible, Gerrit uses the ARIA properties on DOM widgets to | |
| provide hints to screen readers. | |
| == Browser Compatibility | |
| Gerrit requires a JavaScript enabled browser. | |
| // UI team to add section on minimum browser requirements. | |
| As Gerrit is a pure JavaScript application on the client side, with | |
| no server side rendering fallbacks, the browser must support modern | |
| JavaScript semantics in order to access the Gerrit web application. | |
| Dumb clients such as `lynx`, `wget`, `curl`, or even many search engine | |
| spiders are not able to access Gerrit content. | |
| All of the content stored within Gerrit is also available through | |
| other means, such as gitweb or the `git://` protocol. Any existing | |
| search engine crawlers can index the server-side HTML served by a code | |
| browser, and thus can index the majority of the changes which might | |
| appear in Gerrit. Therefore the lack of support for most search engine | |
| crawlers is a non-issue for most Gerrit deployments. | |
| == Product Integration | |
| Gerrit optionally surfaces links to HTML pages in a code browser. The | |
| links are configurable, and Gerrit comes with a built-in code browser, | |
| called Gitiles. | |
| Gerrit integrates with some types of corporate single-sign-on (SSO) | |
| solutions, typically by having the SSO authentication be performed | |
| in a reverse proxy web server and then blindly trusting that all | |
| incoming connections have been authenticated by that reverse proxy. | |
| When configured to use this form of authentication, Gerrit does | |
| not integrate with OpenID providers. | |
| When installing Gerrit, administrators may optionally include an | |
| HTML header or footer snippet which may include user tracking code, | |
| such as that used by Google Analytics. This is a per-instance | |
| configuration that must be done by hand, and is not supported | |
| out of the box. Other site trackers instead of Google Analytics | |
| can be used, as the administrator can supply any HTML/JavaScript | |
| they choose. | |
| Gerrit does not integrate with any Google service, or any other | |
| services other than those listed above. | |
| Plugins (see above) can be used to drive product integrations from the | |
| Gerrit side. Products that support Gerrit explicitly can use the REST | |
| API or the SSH API to contact Gerrit. | |
| == Privacy Considerations | |
| Gerrit stores the following information per user account: | |
| * Full Name | |
| * Preferred Email Address | |
| The full name and preferred email address fields are shown to any | |
| site visitor viewing a page containing a change uploaded by the | |
| account owner, or containing a published comment written by the | |
| account owner. | |
| Showing the full name and preferred email is approximately the same | |
| risk as the `From` header of an email posted to a public mailing | |
| list that maintains archives, and Gerrit treats these fields in | |
| much the same way that a mailing list archive might handle them. | |
| Users who don't want to expose this information should either not | |
| participate in a Gerrit based online community, or open a new email | |
| address dedicated for this use. | |
| As the Gerrit UI data is only available through XSRF protected | |
| JSON-RPC calls, "screen-scraping" for email addresses is difficult, | |
| but not impossible. It is unlikely a spammer will go through the | |
| effort required to code a custom scraping application necessary | |
| to cull email addresses from published Gerrit comments. In most | |
| cases these same addresses would be more easily obtained from the | |
| project's mailing list archives. | |
| The user's name and email address is stored unencrypted in the | |
| link:config-accounts.html#all-users[All-Users] repository. | |
| == Spam and Abuse Considerations | |
| There is no spam protection for the Git protocol upload path. | |
| Uploading a change successfully requires a pre-existing account, and a | |
| lot of up-front effort. | |
| Gerrit makes no attempt to detect spam changes or comments in the web | |
| UI. To post and publish a comment a client must sign in and then use | |
| the XSRF protected JSON-RPC interface to publish the draft on an | |
| existing change record. | |
| Absence of SPAM handling is based upon the idea that Gerrit caters to | |
| a niche audience, and will therefore be unattractive to spammers. In | |
| addition, it is not a factor for corporate, on-premise deployments. | |
| == Scalability | |
| Gerrit supports the Git wire protocol, and an API (one API for HTTP, | |
| and one for SSH). | |
| The git wire protocol does a client/server negotiation to avoid | |
| sending too much data. This negotiation occupies a CPU, so the number | |
| of concurrent push/fetch operations should be capped by the number of | |
| CPUs. | |
| Clients on slow network connections may be network bound rather than | |
| server side CPU bound, in which case a core may be effectively shared | |
| with another user. Possible core sharing due to network bottlenecks | |
| generally holds true for network connections running below 10 MiB/sec. | |
| Deployments for large, distributed companies can replicate Git data to | |
| read-only replicas to offload fetch traffic. The read-only replicas | |
| should also serve this data using Gerrit to ensure that permissions | |
| are obeyed. | |
| The API serves requests of varying costs. Requests that originate in | |
| the UI can block productivity, so care has been taken to optimize | |
| these for latency, using the following techniques: | |
| * Async calls: the UI becomes responsive before some UI elements | |
| finished loading | |
| * Caching: metadata is stored in Git, which is relatively expensive to | |
| access. This is sped up by multiple caches. Metadata entities are | |
| stored in Git, and can therefore be seen as immutable values keyed | |
| by SHA-1, which is very amenable to caching. All SHA-1 keyed caches | |
| can be persisted on local disk. | |
| The size (memory, disk) of these caches should be adapted to the | |
| instance size (number of users, size and quantity of repositories) | |
| for optimal performance. | |
| Git does not impose fundamental limits (eg. number of files per | |
| change) on data. To ensure stability, Gerrit configures a number of | |
| default limits for these. | |
| // add a link to the default settings. | |
| === Scaling team size | |
| A team of size N has N^2 possible interactions. As a result, features | |
| that expose interactions with activities of other team members has a | |
| quadratic cost in aggregate. The following features scale poorly with | |
| large team sizes: | |
| * the change screen shows conflicting changes by default. This data is | |
| cached, but updates to pending changes cause cache misses. For a | |
| single change, the amount of work is proportional to the number of | |
| pending changes, so in aggregate, the cost of this feature is | |
| quadratic in the team size. | |
| * the change screen shows if a change is mergeable to the target | |
| branch. If the target branch moves quickly (large developer team), | |
| this causes cache misses. In aggregate, the cost of this feature is | |
| also quadratic. | |
| Both features should be turned off for repositories that involve 1000s | |
| of developers. | |
| === Browser performance | |
| // say something about browser performance tuning. | |
| === Real life numbers | |
| Gerrit is designed for very large projects, both open source and | |
| proprietary commercial projects. For a single Gerrit process, the | |
| following limits are known to work: | |
| .Observed maximums | |
| [options="header"] | |
| |====================================================== | |
| |Parameter | Maximum | Deployment | |
| |Projects | 50,000 | gerrithub.io | |
| |Contributors | 150,000 | eclipse.org | |
| |Bytes/repo | 100G | Qualcomm internal | |
| |Changes/repo | 300k | Qualcomm internal | |
| |Revisions/Change | 300 | Qualcomm internal | |
| |Reviewers/Change | 87 | Qualcomm internal | |
| |====================================================== | |
| // find some numbers for these stats: | |
| // |Files/repo | ? | | |
| // |Files/Change | ? | | |
| // |Comments/Change | ? | | |
| // |max QPS/CPU | ? | | |
| Google runs a horizontally scaled deployment. We have seen the | |
| following per-JVM maximums: | |
| .Observed maximums (googlesource.com) | |
| [options="header"] | |
| |====================================================== | |
| |Parameter | Maximum | Deployment | |
| |Files/repo | 500,000 | chromium-review | |
| |Bytes/repo | 12G | chromium-review | |
| |Changes/repo | 500k | chromium-review | |
| |Revisions/Change | 1900 | chromium-review | |
| |Files/Change | 10,000| android-review | |
| |Comments/Change | 1,200 | chromium-review | |
| |====================================================== | |
| == Redundancy & Reliability | |
| Gerrit is structured as a single JVM process, reading and writing to a | |
| single file system. If there are hardware failures in the machine | |
| running the JVM, or the storage holding the repositories, there is no | |
| recourse; on failure, errors will be returned to the client. | |
| Deployments needing more stringent uptime guarantees can use | |
| replication/multi-master setup, which ensures availability and | |
| geographical distribution, at the cost of slower write actions. | |
| // TODO: link. | |
| === Backups | |
| Using the standard replication plugin, Gerrit can be configured | |
| to replicate changes made to the local Git repositories over any | |
| standard Git transports. After the plugin is installed, remote | |
| destinations can be configured in `'$site_path'/etc/replication.conf` | |
| to send copies of all changes over SSH to other servers, or to the | |
| Amazon S3 blob storage service. | |
| == Logging Plan | |
| Gerrit stores Apache style HTTPD logs, as well as ERROR/INFO messages | |
| from the Java logger, under `$site_dir/logs/`. | |
| Published comments contain a publication date, so users can judge | |
| when the comment was posted and decide if it was "recent" or not. | |
| Only the timestamp is stored in the database, the IP address of | |
| the comment author is not stored. | |
| Changes uploaded over the SSH daemon from `git push` have the | |
| standard Git reflog updated with the date and time that the upload | |
| occurred, and the Gerrit account identity of who did the upload. | |
| Changes submitted and merged into a branch also update the | |
| Git reflog. These logs are available only to the Gerrit site | |
| administrator, and they are not replicated through the automatic | |
| replication noted earlier. These logs are primarily recorded for an | |
| "oh s**t" moment where the administrator has to rewind data. In most | |
| installations they are a waste of disk space. Future versions of | |
| JGit may allow disabling these logs, and Gerrit may take advantage | |
| of that feature to stop writing these logs. | |
| A web server positioned in front of Gerrit (such as a reverse proxy) | |
| or the hosting servlet container may record access logs, and these | |
| logs may be mined for usage information. This is outside of the | |
| scope of Gerrit. | |
| GERRIT | |
| ------ | |
| Part of link:index.html[Gerrit Code Review] | |
| SEARCHBOX | |
| --------- |