You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/async_replication.md
+7-7Lines changed: 7 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,13 +1,13 @@
1
1
Asynchronous Replication
2
2
------------------------
3
3
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.
5
5
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.
7
7
8
8
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.
9
9
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):
11
11
```
12
12
User Leader Follower(s)
13
13
| | |
@@ -32,8 +32,8 @@ To enable asynchronous replication, `state_machine::pre_commit()` function shoul
32
32
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:
33
33
34
34
* 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.
37
37
* 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.
Copy file name to clipboardExpand all lines: docs/basic_operations.md
+10-10Lines changed: 10 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -5,13 +5,13 @@ Basic Operations
5
5
6
6
Initializing Raft Server
7
7
------------------------
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)):
9
9
```C++
10
10
ptr<logger> my_logger;
11
11
ptr<state_machine> my_state_machine;
12
12
ptr<state_mgr> my_state_manager;
13
13
```
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.
15
15
16
16
After that, set your [Asio](../include/libnuraft/asio_service_options.hxx) and [Raft](../include/libnuraft/raft_params.hxx) options:
17
17
```C++
@@ -37,17 +37,17 @@ if (server->is_initialized()) {
37
37
38
38
### What Is Happening on Raft Initialization? ###
39
39
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:
41
41
42
42
*`state_mgr::load_log_store()`
43
43
* This function should return your log store instance.
44
44
*`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).
47
47
*`state_mgr::read_state()`
48
48
* This function should return the last [server state](../include/libnuraft/srv_state.hxx), that contains term and voting info.
49
49
*`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.
51
51
*`state_machine::last_snapshot()`
52
52
* You should make the last snapshot durable (if necessary), and return the handle of it here.
53
53
@@ -58,7 +58,7 @@ You can simply use [Launcher](../include/libnuraft/launcher.hxx)'s shutdown API:
58
58
```C++
59
59
bool success = launcher.shutdown();
60
60
```
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`.
*`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.
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.
87
87
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.
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.
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.
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.
Copy file name to clipboardExpand all lines: docs/custom_quorum_size.md
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -5,7 +5,7 @@ The motivation comes from [Flexible Paxos](https://fpaxos.github.io/) paper:
5
5
6
6
* Howard et al., [Flexible Paxos: Quorum Intersection Revisited](https://arxiv.org/pdf/1608.06696v1.pdf), 2016.
7
7
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.
9
9
10
10
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).
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.
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_`:
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.
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"`:
[`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.
5
5
6
6
You can use `alloc()` API to allocate memory:
7
7
```C++
8
8
ptr<buffer> b = buffer::alloc( size_to_allocate );
9
9
```
10
10
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:
12
12
```C++
13
13
void* ptr = (void*)b->data();
14
14
```
15
15
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:
17
17
```C++
18
18
size_t current_position = b->pos();
19
19
...
20
20
b->pos( new_position );
21
21
```
22
22
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:
24
24
```C++
25
25
void* ptr_begin = (void*)b->data_begin();
26
26
```
27
27
28
28
Buffer Serializer
29
29
---
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.
31
31
32
32
Instead, you can use [`buffer_serializer`](../include/libnuraft/buffer_serializer.hxx):
33
33
```C++
@@ -37,4 +37,4 @@ buffer_serializer s(b);
37
37
```
38
38
`buffer_serializer` allows concurrent access to the same `buffer` instance.
39
39
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