Skip to content

Commit 1c2587f

Browse files
authored
Update documents (#384)
* WIP: Doc * more docs * Update documents * More doc update
1 parent 7ce0ef8 commit 1c2587f

18 files changed

Lines changed: 236 additions & 78 deletions

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ Features
3030
* [Custom/separate quorum size for commit & leader election](docs/custom_quorum_size.md)
3131
* [Asynchronous replication](docs/async_replication.md)
3232
* [SSL/TLS support](docs/enabling_ssl.md)
33-
33+
* [Parallel Log Appending](docs/parallel_log_appending.md)
34+
* [Custom Commit Policy](docs/custom_commit_policy.md)
3435

3536
How to Build
3637
------------
@@ -111,9 +112,9 @@ Please refer to [tests/bench](./tests/bench).
111112

112113
Supported Platforms
113114
-------------------
114-
* Ubuntu (tested on 14.04, 16.04, and 18.04)
115+
* Ubuntu (tested on 14.04 -- 20.04)
115116
* Centos (tested on 7)
116-
* OSX (tested on 10.13 and 10.14)
117+
* OSX (tested on 10.13 -- 12.3)
117118
* Windows (built using MSVC 2019, not thoroughly tested)
118119

119120

@@ -130,7 +131,7 @@ Contact
130131

131132
License Information
132133
--------------------
133-
Copyright 2017-2019 eBay Inc.
134+
Copyright 2017-present eBay Inc.
134135

135136
Author/Developer: Jung-Sang Ahn
136137

docs/async_replication.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
Asynchronous Replication
22
------------------------
33

4-
The original Raft's commit happens after reaching quorum, which means that network communication is always involved in users' operation path.
4+
The original Raft's commit happens after reaching the quorum, which means that network communication is always involved in users' operation paths.
55

6-
However, there are some loosened cases that we want to achieve low latency by sacrificing consistency and resolving conflicts manually. Then waiting for the acknowledges from a majority of servers is a waste of time.
6+
However, there are some loosened cases where we want to achieve low latency by sacrificing consistency and resolving conflicts manually. Then waiting for the acknowledgment from a majority of servers is a waste of time.
77

88
To support such cases, we provide `async_replication_` flag in [`cluster_config`](../include/libnuraft/cluster_config.hxx). If that flag is set, `append_entries()` API immediately returns with the result of `state_machine::pre_commit()`, and replication is done in background later.
99

10-
Below diagram shows the overall flow. You can compare it with [original sequence](replication_flow.md):
10+
The below diagram shows the overall flow. You can compare it with [original sequence](replication_flow.md):
1111
```
1212
User Leader Follower(s)
1313
| | |
@@ -32,8 +32,8 @@ To enable asynchronous replication, `state_machine::pre_commit()` function shoul
3232
In synchronous replication mode, we provide another option: `async_handler` in [`raft_params`](../include/libnuraft/raft_params.hxx). Here are the differences between asynchronous replication mode and synchronous replication with `async_handler` mode:
3333

3434
* Synchronous replication with `async_handler` mode:
35-
* The actual execution in state machine happens after reaching consensus.
36-
* Although `append_entries()` API returns immediately, the given data is not committed yet. The result of `commit()` will be set later, by invoking user-defined handler as a notification.
35+
* The actual execution in the state machine happens after reaching consensus.
36+
* Although `append_entries()` API returns immediately, the given data is not committed yet. The result of `commit()` will be set later by invoking a user-defined handler as a notification.
3737
* Asynchronous replication mode:
38-
* The actual execution in state machine happens before replication.
39-
* `append_entries()` API returns immediately, which already contains the result of state machine execution. There is no later notification.
38+
* The actual execution in the state machine happens before replication.
39+
* `append_entries()` API returns immediately, which already contains the result of state machine execution. Hence, there will be no later notification.

docs/basic_operations.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ Basic Operations
55

66
Initializing Raft Server
77
------------------------
8-
First of all, you need to define your own [log store](../include/libnuraft/log_store.hxx), [state machine](../include/libnuraft/state_machine.hxx), and [state manager](../include/libnuraft/state_mgr.hxx) (optionally [debugging logger](../include/libnuraft/logger.hxx)):
8+
First of all, you should implement your own [log store](../include/libnuraft/log_store.hxx), [state machine](../include/libnuraft/state_machine.hxx), and [state manager](../include/libnuraft/state_mgr.hxx) (optionally [debugging logger](../include/libnuraft/logger.hxx)):
99
```C++
1010
ptr<logger> my_logger;
1111
ptr<state_machine> my_state_machine;
1212
ptr<state_mgr> my_state_manager;
1313
```
14-
Log store will not be passed at initialization time, but will be loaded through `load_log_store()` API in state manager later. So you need properly implement that function.
14+
Log store will not be passed at the initialization time but will be loaded by `load_log_store()` API in the state manager later. So you need properly implement that function.
1515

1616
After that, set your [Asio](../include/libnuraft/asio_service_options.hxx) and [Raft](../include/libnuraft/raft_params.hxx) options:
1717
```C++
@@ -37,17 +37,17 @@ if (server->is_initialized()) {
3737

3838
### What Is Happening on Raft Initialization? ###
3939

40-
Once you initialize Raft server, it will invoke below APIs from your custom modules:
40+
Once you initialize the Raft server, it will invoke the below APIs from your custom modules:
4141

4242
* `state_mgr::load_log_store()`
4343
* This function should return your log store instance.
4444
* `state_mgr::load_config()`
45-
* This function should return the last committed Raft cluster config, that contains the membership info.
46-
* At the very first launch, you can return a cluster config that contains the server itself only. After adding server the cluster config will change, and you should make it durable (if necessary).
45+
* This function should return the last committed Raft cluster config containing the membership info.
46+
* At the very first launch, you can return a cluster config containing the server itself only. After adding servers, the cluster config will change, and you should make it durable (if necessary).
4747
* `state_mgr::read_state()`
4848
* This function should return the last [server state](../include/libnuraft/srv_state.hxx), that contains term and voting info.
4949
* `state_machine::last_commit_index()`
50-
* You should make the last committed log number durable (if necessary), and return it here. Otherwise, Raft server attempts to do catch-up from the beginning.
50+
* You should make the last committed log number durable (if necessary), and return it here. Otherwise, the Raft server attempts to do the log replaying from the beginning.
5151
* `state_machine::last_snapshot()`
5252
* You should make the last snapshot durable (if necessary), and return the handle of it here.
5353

@@ -58,7 +58,7 @@ You can simply use [Launcher](../include/libnuraft/launcher.hxx)'s shutdown API:
5858
```C++
5959
bool success = launcher.shutdown();
6060
```
61-
This API is a blocking call, so that the server termination is guaranteed once the function returns `true`.
61+
This API is a blocking call, so the server termination is guaranteed once the function returns `true`.
6262

6363
Adding Server -- Organizing a Group
6464
---
@@ -73,8 +73,8 @@ server->add_srv( server_to_add );
7373
```
7474
Note:
7575
* `add_srv()` API is an asynchronous task, thus need to check the result using `get_srv_config()` API.
76-
* The server to be added should be running and empty at the time you add server.
77-
* If the log of leader has been compacted (i.e., the smallest log number is greater than 1), leader will transfer snapshot first. Before receiving snapshot is done, the server is officially not the member of Raft group. In the meantime, you also cannot add other servers concurrently.
76+
* The server to be added should be running at the time you add the server.
77+
* If the leader's logs have already been compacted (i.e., the smallest log number is greater than 1), the leader will transfer a snapshot first. Before receiving the snapshot is done, the server is officially not a member of the Raft group. In the meantime, you also cannot add other servers concurrently.
7878

7979
Removing Server
8080
---
@@ -85,7 +85,7 @@ server->remove_srv( server_id_to_remove );
8585
```
8686
The same as `add_srv()` API, `remove_srv()` is also an asynchronous task so that you need to check the result by using `get_srv_config()` API.
8787
88-
The server to be removed should be running at the time you remove server. Otherwise, the leader will attempt to communicate with it a few times, and then force remove it.
88+
The server to be removed should be running at the time you remove the server. Otherwise, the leader will attempt to communicate with it a few times and then force remove it.
8989
9090
Appending Log -- Replication
9191
---

docs/custom_commit_policy.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
Custom Commit Policy
2+
====================
3+
4+
In addition to the basic quorum-based consensus, NuRaft provides 1) full consensus mode and 2) selective quorum.
5+
6+
7+
Full Consensus Mode
8+
-------------------
9+
The leader commits a log only when all healthy members have the log, in order to achieve strong consistency; once a log is committed, the latest data can be read from any members.
10+
11+
However, stronger consistency worsens availability, and full consensus mode is the extreme case. If there is at least one unreachable member, the entire protocol stops. To avoid such a bad availability, full consensus mode dynamically excludes *unhealthy* members from the quorum. If there is any node not responding longer than `response_limit_` (in [`raft_server::limits`](../include/libnuraft/raft_server.hxx)) multiplied by heartbeat period, NuRaft automatically regards it as *unhealthy*. The full consensus is achieved among healthy members only. Unhealthy members become healthy as soon as they respond to the leader's message.
12+
13+
If the number of unhealthy members becomes a majority, then the leader will not be able to commit logs, the same as the basic quorum-based consensus.
14+
15+
16+
Selective Quorum
17+
----------------
18+
[State machine](../include/libnuraft/state_machine.hxx) interface provides `adjust_commit_index` API for selective quorum. This API is called for each commit decision, with `adjust_commit_index_params`. This parameter contains the list of <peer ID, its last log index> pairs, along with the current commit index and the new commit index determined by NuRaft. This API will return the new log index to commit.
19+
20+
With the given information, we can pin some servers in the quorum so as to make them always have the latest committed log. For example, let's assume we have 5 servers, and their ID and last log index are as follows:
21+
```
22+
{{1, 10}, {2, 9}, {3, 10}, {4, 10}, {5, 8}}
23+
```
24+
In such a case, with the original Raft algorithm, the commit index should be `10`, as `{1, 3, 4}` can form a quorum.
25+
26+
However, if we want to make server 2 always have the latest committed log, server 2 should be pinned in the quorum for commit. So quorum can be either `{1, 2, 3}` or `{1, 2, 4}` in this example, and the commit index number should be `9`. We can inform such a decision to NuRaft by letting `adjust_commit_index` return `9`. In this case, the `append_entries` request for log `10` will be pending until log `10` is committed, i.e., until server 2 receives log `10`.
27+
28+
Similar to full consensus mode, pinning a fixed set of servers in the quorum will sacrifice availability. Users who implement the selective quorum are responsible for tuning and its trade-off.

docs/custom_metadata.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
Custom Metadata for Each Message
2+
--------------------------------
3+
4+
NuRaft provides an ability to ship custom metadata for each message and response that can be used for your own verification. When you set [`asio_options`](../include/libnuraft/asio_service_options.hxx), there are a few options for it:
5+
```C++
6+
asio_service::options asio_opt;
7+
asio_opt.write_req_meta_ = my_write_req_meta;
8+
asio_opt.read_req_meta_ = my_read_req_meta;
9+
asio_opt.write_resp_meta_ = my_write_resp_meta;
10+
asio_opt.read_resp_meta_ = my_read_resp_meta;
11+
```
12+
13+
* `write_req_meta_`: return custom metadata to be shipped with the Raft message.
14+
```c++
15+
std::string my_write_req_meta(const asio_service::meta_cb_params& params) {
16+
return "my req metadata";
17+
}
18+
```
19+
20+
* `read_req_meta_`: read the custom metadata shipped with each Raft message, and return a boolean flag to decide to accept the message.
21+
```c++
22+
bool my_read_req_meta(const asio_service::meta_cb_params& params,
23+
const std::string& meta) {
24+
return (meta == "my req metadata");
25+
}
26+
```
27+
28+
* `write_resp_meta_`: return custom metadata to be shipped with the Raft message response.
29+
```c++
30+
std::string my_write_resp_meta(const asio_service::meta_cb_params& params) {
31+
return "my resp metadata";
32+
}
33+
```
34+
35+
* `read_resp_meta_`: read the custom metadata shipped with the Raft message response, and return a boolean flag to decide to accept the response.
36+
```c++
37+
bool my_read_resp_meta(const asio_service::meta_cb_params& params,
38+
const std::string& meta) {
39+
return (meta == "my resp metadata");
40+
}
41+
```
42+
43+
If `read_req_meta_` or `read_resp_meta_` returns `false`, the message or response is discarded immediately; thus it behaves the same as if it is not received.

docs/custom_quorum_size.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ The motivation comes from [Flexible Paxos](https://fpaxos.github.io/) paper:
55

66
* Howard et al., [Flexible Paxos: Quorum Intersection Revisited](https://arxiv.org/pdf/1608.06696v1.pdf), 2016.
77

8-
The basic idea is that as long as there is at least one overlapping node between the quorum for commit and the quorum for leader election, the entire group is safe. For example, let's say Qc and Qe represent the size of quorum for commit and leader election, respectively. If we have 5 servers, the set of {Qc, Qe} pairs {1, 5}, {2, 4}, {3, 3} (original algorithm), {4, 2}, and {5, 1} provides the same level of safety. Note that availability will be sacrificed as the value of |Qc - Qe| increases.
8+
The basic idea is that as long as there is at least one overlapping node between the quorum for commit and the quorum for leader election, the entire group is safe. For example, let's say Qc and Qe represent the size of quorum for the commit and leader election, respectively. If we have 5 servers, the set of {Qc, Qe} pairs {1, 5}, {2, 4}, {3, 3} (original algorithm), {4, 2}, and {5, 1} provides the same level of safety. Note that availability will be sacrificed as the value of |Qc - Qe| increases.
99

1010
For custom quorum size, we provide two parameters: `custom_commit_quorum_size_` and `custom_election_quorum_size_` in [`raft_params`](../include/libnuraft/raft_params.hxx).
1111

@@ -17,7 +17,7 @@ params.custom_election_quorum_size_ = 4;
1717

1818
The default value of both parameters is 0, which follows the original algorithm.
1919

20-
Those parameters are dynamically adjustable; you can change the size of quorum without shutting down Raft server:
20+
Those parameters are dynamically adjustable; you can change the size of quorums without shutting down the Raft server:
2121

2222
```C++
2323
raft_params params = server->get_current_params();
@@ -26,4 +26,4 @@ params.custom_election_quorum_size_ = 4;
2626
server->update_params(params);
2727
```
2828
29-
Note that it is also possible to set those quorum sizes without intersection: {2, 2} out of 5 servers for example. In such case, data loss or log diverging is inevitable and resolving those problems is your responsibility.
29+
Note that it is also possible to set those quorum sizes without intersection: {2, 2} out of 5 servers, for example. In such cases, data loss or log diverging is inevitable, and resolving those problems is your responsibility.

docs/custom_resolver.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
Custom Resolver
2+
---------------
3+
4+
You can attach your custom resolver that can be used when establishing new connections. When you set [`asio_options`](../include/libnuraft/asio_service_options.hxx), there is an option named `custom_resolver_`:
5+
```C++
6+
std::function< void( const std::string&,
7+
const std::string&,
8+
asio_service_custom_resolver_response ) > custom_resolver_;
9+
```
10+
The first parameter contains the host name to resolve, and the second has the port number. The third parameter is a callback function to be invoked when your resolver finishes its job.
11+
12+
The type of the callback function is as follows:
13+
```c++
14+
using asio_service_custom_resolver_response =
15+
std::function< void(const std::string&, const std::string&, std::error_code) >;
16+
```
17+
You need to pass the resolved IP address (or the host name that the default resolver can understand) to the first parameter and the port number to the second one. The third parameter should contain an error code if resolving the host name fails.
18+
19+
This is a very simple example of resolving `"my_custom_localhost"`:
20+
```c++
21+
asio_service::options asio_opt;
22+
asio_opt.custom_resolver_ =
23+
[](const std::string& host,
24+
const std::string& port,
25+
asio_service_custom_resolver_response when_done) {
26+
if (host == "my_custom_localhost") {
27+
when_done("127.0.0.1", port, std::error_code());
28+
}
29+
// Otherwise: pass through. The default resolver will do the rest.
30+
when_done(host, port, std::error_code());
31+
};
32+
```

docs/dealing_with_buffer.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
11
Dealing with Buffer
22
---
33

4-
[`buffer`](../include/libnuraft/buffer.hxx) class instance just points to a raw memory blob. At the beginning of each memory blob, a few bytes are reserved for metadata: 4 bytes if buffer size is less than 32KB and 8 bytes otherwise. User data section starts right after that, thus you should write your data starting from there.
4+
[`buffer`](../include/libnuraft/buffer.hxx) class instance just points to a raw memory blob. At the beginning of each memory blob, a few bytes are reserved for metadata: 4 bytes if the buffer size is less than 32KB and 8 bytes otherwise. The user data section starts right after that; thus you should write your data starting from there.
55

66
You can use `alloc()` API to allocate memory:
77
```C++
88
ptr<buffer> b = buffer::alloc( size_to_allocate );
99
```
1010

11-
You can get the starting point of user section by using `data()` API:
11+
You can get the starting point of the user section by using `data()` API:
1212
```C++
1313
void* ptr = (void*)b->data();
1414
```
1515

16-
Each buffer instance has a cursor pointing to its current internal position (initially 0), you can get or set the position:
16+
Each buffer instance has a cursor pointing to its current internal position (initially 0); you can get or set the position:
1717
```C++
1818
size_t current_position = b->pos();
1919
...
2020
b->pos( new_position );
2121
```
2222
23-
If you want to get the starting point of user section regardless of the current position, use `data_begin()` API:
23+
If you want to get the starting point of the user section regardless of the current position, use `data_begin()` API:
2424
```C++
2525
void* ptr_begin = (void*)b->data_begin();
2626
```
2727

2828
Buffer Serializer
2929
---
30-
`buffer` itself has a few APIs to get and put data, but we do not recommend using those APIs, since they change the internal position of the buffer, which blocks concurrent reads and also is mistake-prone if you forget to reset the position.
30+
`buffer` itself has a few APIs to get and put data, but we do not recommend using those APIs since they change the internal position of the buffer, which blocks concurrent reads and also is mistake-prone if you forget to reset the position.
3131

3232
Instead, you can use [`buffer_serializer`](../include/libnuraft/buffer_serializer.hxx):
3333
```C++
@@ -37,4 +37,4 @@ buffer_serializer s(b);
3737
```
3838
`buffer_serializer` allows concurrent access to the same `buffer` instance.
3939
40-
We provide endianness options, and it is little endian by default.
40+
We provide endianness options, and it is a little endian by default.

0 commit comments

Comments
 (0)