Skip to content

Commit 22fa42b

Browse files
syntonyzelucamilanesio
authored andcommitted
Update design proposal to abstract broker and Ref-DB
The implementation of the message broker and the Ref-DB should be agnostic of the arbitrary choice of Kafka and Zookeper. This change updates the documentation with a proposal that aims to abstract over those specific choices and allow a dynamic binding of different implementations instead. Feature: Issue 10828 Change-Id: I026d48bd6c36bf512c119445b9a0d638f805e25b
1 parent 38b549c commit 22fa42b

File tree

9 files changed

+97
-44
lines changed

9 files changed

+97
-44
lines changed

DESIGN.md

Lines changed: 87 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -257,8 +257,18 @@ be replicated transparently across sites.
257257
The multi-site solution described here depends upon the combined use of different
258258
components:
259259

260-
- **multi-site plugin**: Enables the replication of Gerrit _indexes_, _caches_,
261-
and _stream events_ across sites.
260+
- **multi-site libModule**: exports interfaces as DynamicItems to plug in specific
261+
implementation of `Brokers` and `Global Ref-DB` plugins.
262+
263+
- **broker plugin**: an implementation of the broker interface, which enables the
264+
replication of Gerrit _indexes_, _caches_, and _stream events_ across sites.
265+
When no specific implementation is provided, then the [Broker Noop implementation](#broker-noop-implementation)
266+
then libModule interfaces are mapped to internal no-ops implementations.
267+
268+
- **Global Ref-DB plugin**: an implementation of the Global Ref-DB interface,
269+
which enables the detection of out-of-sync refs across gerrit sites.
270+
When no specific implementation is provided, then the [Global Ref-DB Noop implementation](#global-ref-db-noop-implementation)
271+
then libModule interfaces are mapped to internal no-ops implementations.
262272

263273
- **replication plugin**: enables the replication of the _Git repositories_ across
264274
sites.
@@ -277,10 +287,78 @@ The interactions between these components are illustrated in the following diagr
277287

278288
## Implementation Details
279289

280-
### Message brokers
281-
The multi-site plugin adopts an event-sourcing pattern and is based on an
282-
external message broker. The current implementation uses Apache Kafka.
283-
It is, however, potentially extensible to others, like RabbitMQ or NATS.
290+
### Multi-site libModule
291+
As mentioned earlier there are different components behind the overarching architecture
292+
of this solution of a distributed multi-site gerrit installation, each one fulfilling
293+
a specific goal. However, whilst the goal of each component is well-defined, the
294+
mechanics on how each single component achieves that goal is not: the choice of which
295+
specific message broker or which Ref-DB to use can depend on different factors,
296+
such as scalability, maintainability, business standards and costs, to name a few.
297+
298+
For this reason the multi-site component is designed to be explicitly agnostic to
299+
specific choices of brokers and Global Ref-DB implementations, and it does
300+
not care how they, specifically, fulfill their task.
301+
302+
Instead, this component takes on only two responsibilities:
303+
304+
* Wrapping the GitRepositoryManager so that every interaction with git can be
305+
verified by the Global Ref-DB plugin.
306+
307+
* Exposing DynamicItem bindings onto which concrete _Broker_ and a _Global Ref-DB_
308+
plugins can register their specific implementations.
309+
When no such plugins are installed, then the initial binding points to no-ops.
310+
311+
* Detect out-of-sync refs across multiple gerrit sites:
312+
Each change attempting to mutate a ref will be checked against the Ref-DB to
313+
guarantee that each node has an up-to-date view of the repository state.
314+
315+
### Message brokers plugin
316+
Each gerrit node in the cluster needs to be informed and inform all other nodes
317+
about fundamental events, such as indexing of new changes, cache evictions and
318+
stream events. This component will provide a specific pub/sub broker implementation
319+
that is able to do so.
320+
321+
When provided, the message broker plugin will override the dynamicItem binding exposed
322+
by the multi-site module with a specific implementation, such as Kafka, RabbitMQ, NATS, etc.
323+
324+
#### Broker Noop implementation
325+
The default `Noop` implementation provided by the `Multi-site` libModule does nothing
326+
upon publishing and producing events. This is useful for setting up a test environment
327+
and allows multi-site library to be installed independently from any additional
328+
plugins or the existence of a specific broker installation.
329+
The Noop implementation can also be useful when there is no need for coordination
330+
with remote nodes, since it avoids maintaining an external broker altogether:
331+
for example, using the multi-site plugin purely for the purpose of replicating the Git
332+
repository to a disaster-recovery site and nothing else.
333+
334+
### Global Ref-DB plugin
335+
Whilst the replication plugin allows the propagation of the Git repositories across
336+
sites and the broker plugin provides a mechanism to propagate events, the Global
337+
Ref-DB ensures correct alignment of refs of the multi-site nodes.
338+
339+
It is the responsibility of this plugin to store atomically key/pairs of refs in
340+
order to allow the libModule to detect out-of-sync refs across multi sites.
341+
(aka split brain). This is achieved by storing the most recent `sha` for each
342+
specific mutable `refs`, by the usage of some sort of atomic _Compare and Set_ operation.
343+
344+
We mentioned earlier the [CAP theorem](https://en.wikipedia.org/wiki/CAP_theorem),
345+
which in a nutshell states that a distributed system can only provide two of these
346+
three properties: _Consistency_, _Availability_ and _Partition tolerance_: the Global
347+
Ref-DB helps achieving _Consistency_ and _Partition tolerance_ (thus sacrificing
348+
Availability).
349+
350+
See [Prevent split brain thanks to Global Ref-DB](#prevent-split-brain-thanks-to-global-ref-db)
351+
For a thorough example on this.
352+
353+
When provided, the Global Ref-DB plugin will override the dynamicItem binding
354+
exposed by the multi-site module with a specific implementation, such as Zoekeeper,
355+
etcd, MySQL, Mongo, etc.
356+
357+
#### Global Ref-DB Noop implementation
358+
The default `Noop` implementation provided by the `Multi-site` libModule accepts
359+
any refs without checking for consistency. This is useful for setting up a test environment
360+
and allows multi-site library to be installed independently from any additional
361+
plugins or the existence of a specific Ref-DB installation.
284362

285363
### Eventual consistency on Git, indexes, caches, and stream events
286364

@@ -408,22 +486,10 @@ detail below.
408486

409487
**NOTE**: The two options are not exclusive.
410488

411-
#### Introduce a `DfsRefDatabase`
412-
413-
An implementation of the out-of-sync detection logic could be based on a central
414-
coordinator holding the _last known status_ of a _mutable ref_ (immutable refs won't
415-
have to be stored here). This would be, essentially, a DFS base `RefDatabase` or `DfsRefDatabase`.
416-
417-
This component would:
418-
419-
- Contain a subset of the local `RefDatabase` data:
420-
- Store only _mutable _ `refs`
421-
- Keep only the most recent `sha` for each specific `ref`
422-
- Require that atomic _Compare and Set_ operations can be performed on a
423-
key -> value storage. For example, it could be implemented using `Zookeeper`. (One implementation
424-
was done by Dave Borowitz some years ago.)
489+
#### Prevent split brain thanks to Global Ref-DB
425490

426-
This interaction is illustrated in the diagram below:
491+
The above scenario can be prevented by using an implementation of the Global Ref-DB
492+
interface, which will operate as follows:
427493

428494
![Split Brain Prevented](images/git-replication-split-brain-detected.png)
429495

@@ -469,23 +535,10 @@ sent the request to the Ref-DB but before persisting this request into its `git`
469535
able to differentiate the type of traffic and, thus, is forced always to use the
470536
RW site, even though the operation is RO.
471537

472-
- **Support for different brokers**: Currently, the multi-site plugin supports Kafka.
473-
More brokers need to be supported in a fashion similar to the
474-
[ITS-* plugins framework](https://gerrit-review.googlesource.com/admin/repos/q/filter:plugins%252Fits).
475-
Explicit references to Kafka must be removed from the multi-site plugin. Other plugins may contribute
476-
implementations to the broker extension point.
477-
478-
- **Split the publishing and subscribing**: Create two separate
479-
plugins. Combine the generation of the events into the current kafka-
480-
events plugin. The multi-site plugin will focus on
481-
consumption of, and sorting of, the replication issues.
482-
483538
## Step-2: Move to multi-site Stage #8.
484539

485540
- Auto-reconfigure HAProxy rules based on the projects sharding policy
486541

487542
- Serve RW/RW traffic based on the project name/ref-name.
488543

489544
- Balance traffic with "locally-aware" policies based on historical data
490-
491-
- Preventing split-brain in case of temporary sites isolation
32.1 KB
Loading
249 Bytes
Loading

src/.DS_Store

6 KB
Binary file not shown.

src/main/.DS_Store

6 KB
Binary file not shown.

src/main/resources/.DS_Store

6 KB
Binary file not shown.
6 KB
Binary file not shown.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<mxfile modified="2019-05-09T13:38:37.123Z" host="www.draw.io" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36" etag="JKQWieSjNJp7nwGUZ6iM" version="10.6.7" type="google"><diagram name="Page-1" id="5f0bae14-7c28-e335-631c-24af17079c00">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</diagram></mxfile>
2+
<mxfile modified="2019-06-07T17:30:04.207Z" host="www.draw.io" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36" etag="zx6JNhjWm9CAjpC8XxAK" version="10.7.5" type="google"><diagram id="aLS-0ix4CCXe6ffUliya" name="Page-1">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</diagram></mxfile>

0 commit comments

Comments
 (0)