{"id":16678438,"url":"https://github.com/royaltm/node-zmq-raft","last_synced_at":"2025-03-21T18:32:05.186Z","repository":{"id":51495469,"uuid":"72183715","full_name":"royaltm/node-zmq-raft","owner":"royaltm","description":"An opinionated Raft implementation powered by ØMQ","archived":false,"fork":false,"pushed_at":"2023-11-22T00:31:00.000Z","size":727,"stargazers_count":25,"open_issues_count":0,"forks_count":4,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-04-23T20:43:56.640Z","etag":null,"topics":["raft","raft-server","state-machine","zmq","zmq-raft"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/royaltm.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-10-28T07:19:39.000Z","updated_at":"2023-09-08T13:00:46.000Z","dependencies_parsed_at":"2023-11-22T02:00:58.104Z","dependency_job_id":"f84cf79b-274a-43ae-8b45-d9eb1af46873","html_url":"https://github.com/royaltm/node-zmq-raft","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/royaltm%2Fnode-zmq-raft","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/royaltm%2Fnode-zmq-raft/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/royaltm%2Fnode-zmq-raft/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/royaltm%2Fnode-zmq-raft/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/royaltm","download_url":"https://codeload.github.com/royaltm/node-zmq-raft/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244848784,"owners_count":20520586,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["raft","raft-server","state-machine","zmq","zmq-raft"],"created_at":"2024-10-12T13:29:17.395Z","updated_at":"2025-03-21T18:32:04.833Z","avatar_url":"https://github.com/royaltm.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"ØMQ Raft for nodejs\n===================\n\nAn opinionated implementation of [Raft](https://raft.github.io/#implementations) consensus algorithm powered by ØMQ.\n\nThe ØMQ part is in the core of this implementation and is not replaceable.\nState machine and some other parts (Log, Snapshot, Persistence) can be easily replaced though.\n\nComponents\n----------\n\n```\n                             +---------+\n                             | Clients |\n                             +---------+\n                              /|\\   /|\\\n                             / | \\ / | \\\n                             +---------+\n                             | Cluster |\n                             +---------+\n                                \\ | /\n                                 \\|/\n   +-------------+         +-------------+         +----------------+\n   | Persistence | ======= | Raft * peer | ======= | Log + Snapshot |\n   +-------------+         +-------------+         +----------------+\n                                 / \\\n                                /   \\\n                               /     \\\n                              /       \\\n                             /         \\\n                            /           \\\n              +---------------+        +---------+\n              | State machine | ------ | Clients |\n              +---------------+        +---------+\n```\n### Cluster\n\nA group of well known and interconnected Raft peers.\n\n\n### Raft peer\n\nA Raft peer is a server with a single ZMQ_ROUTER type socket for connections incoming from clients and its peers.\n\n\n### Persistence\n\nACID storage for the Raft's state:\n\n- the current term,\n- the last voted for,\n- the cluster configuration.\n\nThe `RaftPersistence` implementation is file based.\n\n\n### Log + Snapshot\n\nACID storage for:\n\n- log entries\n- a snapshot\n\n`FileLog` and `SnapshotFile` implementations are file based.\n\nSee also: [ACID](ACID.md).\n\n\n### State machine\n\nThe state machine is opaque to zmq-raft.\nDevelopers should implement it following [`StateMachineBase`](lib/api/state_machine_base.js) class api.\n\n\n### Clients\n\nClients can connect directly to any Raft peer server for:\n\n- Retrieving the cluster configuration (cluster discovery) with REQUEST_CONFIG RPC.\n- Retrieving the latest Raft state and log meta-data with REQUEST_LOG_INFO RPC.\n- Retrieving log entries with REQUEST_ENTRIES RPC.\n- Uploading state updates with REQUEST_UPDATE RPC.\n- Requesting cluster configuration changes with CONFIG_UPDATE RPC.\n- Custom requests provided by the state machine implementation.\n\nClients can also connect to the state machine directly depending on its implementation.\n\nClient tools:\n\n* [`ZmqRaftPeerClient`](lib/client/zmq_raft_peer_client.js) implements the single-peer client RPC protocol.\n* [`ZmqRaftClient`](lib/client/zmq_raft_client.js) implements an easy to use cluster-aware client, w/ peer failover, configuration auto-discovery etc.\n\n\nBroadcasting State Machine (BSM)\n--------------------------------\n\nThis repository provides one implementation of the (state opaque) proxy state machine: `BroadcastStateMachine`.\n\nBSM opens a ZMQ_PUB socket (when its peer is a Raft LEADER) and broadcasts applied log entries.\nClients should query zmq-raft (with REQEUST_URL rpc) for the broadcasting PUB URL.\n\nState machine changes will be fan out to any number of clients in real time.\n\nWhen clients miss some entries they have to query zmq-raft for missing entries with REQUEST_ENTRIES RPC.\n\nClient tools:\n\n* [`ZmqRaftPeerSub`](lib/client/zmq_raft_peer_sub.js) implements the single-peer BSM-based log entries stream reader on top of `ZmqRaftPeerClient`.\n* [`ZmqRaftSubscriber`](lib/client/zmq_raft_subscriber.js) implements an easy to use cluster-aware BSM-based stream R/W client, w/ peer failover, configuration auto-discovery etc.\n\n\nHier\n----\n\nThe protocol and some implementation details are presented here: [PROTO](PROTO.md) and [RAFT](RAFT.md).\n\nPublic main classes:\n\nAssuming:\n\n```\nconst raft = require('zmq-raft');\n```\n\nServer components:\n\n- [`raft.server.ZmqRaft`](lib/server/raft.js)\n- [`raft.server.FileLog`](lib/server/filelog.js)\n- [`raft.common.SnaphotFile`](lib/common/snapshotfile.js)\n- [`raft.server.RaftPersistence`](lib/server/raft_persistence.js)\n- [`raft.server.BroadcastStateMachine`](lib/server/broadcast_state_machine.js)\n\nPublic base API for building applications:\n\n- [`raft.api.PersistenceBase`](lib/api/persistence_base.js)\n- [`raft.api.StateMachineBase`](lib/api/state_machine_base.js)\n\nClient components:\n\n- [`raft.client.ZmqRaftPeerClient`](lib/client/zmq_raft_peer_client.js)\n- [`raft.client.ZmqRaftPeerSub`](lib/client/zmq_raft_peer_sub.js)\n- [`raft.client.ZmqRaftClient`](lib/client/zmq_raft_client.js)\n- [`raft.client.ZmqRaftSubscriber`](lib/client/zmq_raft_subscriber.js)\n\nData parsers:\n\n- [`raft.common.LogEntry`](lib/common/log_entry.js)\n- [`common.SnapshotChunk`](lib/common/snapshot_chunk.js)\n\nPublic intermediate common classes for building implementations:\n\n- [`raft.common.ClusterConfiguration`](lib/common/cluster_configuration.js)\n- [`raft.common.IndexFile`](lib/common/indexfile.js)\n- [`raft.common.ReadyEmitter`](lib/common/readyemitter.js)\n- [`raft.common.FilePersistence`](lib/common/file_persistence.js)\n- [`raft.common.StateMachineWriter`](lib/common/state_machine_writer.js)\n- [`raft.client.ZmqProtocolSocket`](lib/client/zmq_protocol_socket.js)\n- [`raft.server.ZmqRpcSocket`](lib/server/zmq_rpc_socket.js)\n\nProtocol components:\n\n- [`raft.protocol.FramesProtocol`](lib/protocol/frames_protocol.js)\n- [`raft.protocol.Protocols`](lib/protocol/index.js)\n\nHelper utilities:\n\n- [`raft.protocol`](lib/protocol/index.js): communication protocol frames for FramesProtocol.\n- [`raft.common.constants`](lib/common/constants.js): important defaults.\n- [`raft.utils.id`](lib/utils/id.js): unique ID utilities.\n- [`raft.utils.helpers`](lib/utils/helpers.js): various helper functions.\n- [`raft.utils.fsutil`](lib/utils/fsutil.js): file utilities.\n\n\nUse cases\n---------\n\nThe author envisions ØMQ Raft Server usage in two basic scenarios:\n\n1. Scenario - in-process RAFT peer.\n\nIn this scenario, each application instance embeds a single RAFT peer instance. All application instances are run on different machines and form a RAFT cluster.\n\nThis scenario plays best if the number of server instances is uneven and predictable. Adding new server instances will require updating the RAFT configuration. This scenario provides the best state update latency.\n\n* The application embeds the RAFT peer, e.g. using [`raft.server.builder`](lib/server/builder.js).\n* The application then implements its own state machine by extending [`raft.api.StateMachineBase`](lib/api/state_machine_base.js) and providing it to the builder's `factory` option. The State Machine instance receives the committed log entries and can update itself or a local database file.\n* The application utilizes the [`raft.client.ZmqRaftClient`](lib/client/zmq_raft_client.js#L409) class to update RAFT state with `requestUpdate` method.\n\n2. Scenario - external RAFT peer.\n\nIn this scenario, the RAFT peers run as stand-alone processes e.g. using `bin/zmq-raft.js` on different machines. The RAFT peer isntances form a RAFT cluster, and applications can access it remotely through a network. The number of application instances can differ from the number of RAFT peers. The applications can come and go dynamically without impacting the RAFT configuration. The number of RAFT peers is changeable through cluster configuration change protocol.\n\nIt is the most flexible scenario, but the state update latency may be slightly worse.\n\n* The RAFT peer servers run as standalone processes and implement the [`raft.server.BroadcastStateMachine`](lib/server/broadcast_state_machine.js) state machine.\n* The application utilizes the [`raft.client.ZmqRaftSubscriber`](lib/client/zmq_raft_subscriber.js) to receive both the committed log entries and update the RAFT cluster via standard Node.JS `Duplex` stream interface.\n\n\nHow To\n------\n\nBuilding a Raft server requires to assemble component class instances for:\n\n- The Raft Persistence.\n- Log + snapshot.\n- The state machine.\n- The `ZmqRaft` server.\n\nThe simplest way is to use [`raft.server.builder`](lib/server/builder.js) that provides convenient defaults for all the necessary components.\n\nThis will create a single peer raft server listening on `tcp://127.0.0.1:8047` with data stored in `/tmp/raft` directory:\n\n```js\nconst raft = require('zmq-raft');\nraft.server.builder.build({data: {path: '/tmp/raft'}}).then(zmqRaft =\u003e {\n  console.log('server alive and ready at: %s', zmqRaft.url);\n});\n```\n\nThe following example will create a raft server instance for the first peer in a cluster with the `BroadcastStateMachine` as its state machine:\n```js\nraft.server.builder.build({\n  id: \"my1\",\n  secret: \"\",\n  peers: [\n    {id: \"my1\", url: \"tcp://127.0.0.1:8047\"},\n    {id: \"my2\", url: \"tcp://127.0.0.1:8147\"},\n    {id: \"my3\", url: \"tcp://127.0.0.1:8247\"}\n  ],\n  data: {\n    path: \"/path/to/raft/data\"\n  },\n  router: {\n    /* optional */\n    bind: \"tcp://*:8047\"\n  },\n  broadcast: {\n    /* required for broadcast state */\n    url: \"tcp://127.0.0.1:8048\",\n    /* optional */\n    bind: \"tcp://*:8048\"\n  }\n}).then(zmqRaft =\u003e { /* ... */ });\n```\n\nTo provide a custom state machine override `factory.state` function in `builder.build` options:\n\n```js\nraft.server.builder.build({\n  /* ... */\n  factory: {\n    state: (options) =\u003e new MyStateMachine(options);\n  }\n})\n```\n\nProvide your own listeners for events on the `ZmqRaft` instance instead of the default ones or disable them:\nThe listeners are attached early just after `ZmqRaft` is being initialized.\n\n```js\nraft.server.builder.build({\n  /* ... */\n  listeners: {\n    error: (err) =\u003e {\n      console.warn(err.stack);\n    },\n    state: (state, currentTerm) =\u003e {\n      console.warn('state: %s term: %s', state, currentTerm);\n    },\n    close: null /* pass null to prevent initializing default listeners */\n  }\n})\n```\n\n\n### Quick Start Guide.\n\nFor testing, or to quickly setup the 0MQ Raft server with the Broadcasting State Machine use `bin/zmq-raft.js`:\n\n```\n  Usage: zmq-raft [options] [id]\n\n  start zmq-raft cluster peer using provided config and optional id\n\n  Options:\n\n    -V, --version        output the version number\n    -c, --config \u003cfile\u003e  config file (default: config\\default.hjson)\n    -b, --bind \u003curl\u003e     router bind url\n    -p, --pub \u003curl\u003e      broadcast state machine url\n    -w, --www \u003curl\u003e      webmonitor url\n    --ns [namespace]     raft config root namespace (default: raft)\n    -h, --help           output usage information\n```\n\ne.g.:\n\n```\nexport DEBUG=*\nbin/zmq-raft.js -c config/example.hjson 1 \u0026\nbin/zmq-raft.js -c config/example.hjson 2 \u0026\nbin/zmq-raft.js -c config/example.hjson 3 \u0026\n```\n\nYou can direct your web browser to the webmonitor of any of the started peers:\n\n- http://localhost:8050\n- http://localhost:8150\n- http://localhost:8250\n\nTo experiment with our cluster, let's spawn another terminal window and enter the `cli` with:\n\n```\nDEBUG=* npm run cli\n```\n\n1. Now, from the `cli`, let's connect to the cluster with: `.connect 127.0.0.1:8047`.\n2. Let's subscribe to the BSM from another console with: `.subscribe 127.0.0.1:8047`.\n3. We can now flood the cluster with some updates using: `.start some_data`. You will see the updates being populated to the subscribers.\n4. To stop flooding, enter `.stop`.\n5. To read the whole log, type: `.read`.\n6. To get the current log information, type: `.info`.\n7. Type `.help` for more commands.\n\n\n### Cluster membership changes.\n\n#### Adding new peers to the cluster.\n\n1. Let's start the new peer (preferably from a new terminal window):\n\n```\nDEBUG=* bin/zmq-raft.js -c config/example.hjson \\\n  --bind \"tcp://*:8347\" \\\n  --pub tcp://127.0.0.1:8348 \\\n  --www http://localhost:8350 4\n```\n\nWe've added some arguments that are missing in the `example.hjson` file, so the peer can setup itself properly. On production, those options should've been added to the new peer's unique configuration file.\n\nThe important part is that the new peer MUST NOT be included in the `peers` collection of the configuration file.\n\nThe new peer `4` will connect itself to the cluster as a client and fetch the current log data. Then it changes its RAFT status to `CLIENT` and opens its ROUTER socket listening for messages.\n\n2. We will send a `ConfigUpdate RPC` to the cluster to update the peer membership for the new peer `4`. From another terminal window:\n\n```\nDEBUG=* bin/zr-config.js -c config/example.hjson -a tcp://127.0.0.1:8347/4\n```\n\nIn addition to a bunch of debug messages you should also see:\n\n```\nRequesting configuration change with ...some request id...:\n  tcp://127.0.0.1:8047/1\n  tcp://127.0.0.1:8147/2\n  tcp://127.0.0.1:8247/3\n  tcp://127.0.0.1:8347/4 (added)\n\nCluster joined configuration changed at index ...some index....\nCluster final configuration changed at index ...some index...:\n  tcp://127.0.0.1:8047/1 (leader)\n  tcp://127.0.0.1:8147/2\n  tcp://127.0.0.1:8247/3\n  tcp://127.0.0.1:8347/4\n```\n\nThe `(leader)` may appear beside a different row.\n\nIf you check out the terminal where the new peer was started, you may notice that the peer has changed its status to the `FOLLOWER`.\n\nThe web monitor should have also picked up the membership change and there should appear a new row for the new peer: `4`.\n\nFrom the `cli` you may check the peers' status with the `.peer` command:\n\n```\n\u003e .peers\nCluster peers:\n1: tcp://127.0.0.1:8047\n2: tcp://127.0.0.1:8147\n3: tcp://127.0.0.1:8247\n4: tcp://127.0.0.1:8347\n```\n\nThe leader, if elected, will be highlighted.\n\nYou can further experiment with killing and restarting peers and observing the leader election process, e.g. while flooding the cluster with updates.\n\n\n#### Removing peers from the cluster.\n\nNow to remove the peer `4` from the cluster:\n\n```\nDEBUG=* bin/zr-config.js -c config/example.hjson -d tcp://127.0.0.1:8347/4\n```\n\nAfter the peer has been successfully removed, if it wasn't a leader during the configuration update it will most probably become a CANDIDATE. It happens when the removed peer isn't updated with the final `Cnew` configuration. This is ok, because cluster members will ignore voting requests from non-member peers. For more information on membership changes read [here](RAFT.md).\n\n\n### Interpreting log entries.\n\nThe log entry format is described [here](PROTO.md). There are several API methods that provide entries as raw data Buffers. In those instances [`raft.common.LogEntry`](lib/common/log_entry.js) API can be used to interpret each entry.\n\nPlease note that not all log entries contain state data. Log entries are also created for cluster configuration changes and checkpointing (see [RAFT](RAFT.md)).\n\nHere is an example on how to interpret log entries when extending [`raft.api.StateMachineBase`](lib/api/state_machine_base.js):\n\n```js\nclass MyStateMachine extends raft.api.StateMachineBase {\n    constructor() {\n      super();\n      initMyStateMachine().then(() =\u003e {\n        this[Symbol.for('setReady')]();\n      });\n    }\n\n    applyEntries(logEntries, nextIndex, currentTerm, snapshot) {\n      for (let [index, item] of logEntries.entries()) {\n          let entry = raft.common.LogEntry.bufferToLogEntry(item, nextIndex + index);\n          console.log(\"log entry: log-index=%s term=%s\", entry.logIndex, entry.readEntryTerm());\n          if (entry.isStateEntry) {\n            console.log(\"this is state entry:\");\n            //  user data of the log entry\n            let data = entry.readEntryData();\n            // ... do something with entry data\n          } else if (entry.isConfigEntry) {\n            console.log(\"this is config entry\");\n          } else {\n            console.log(\"this is checkpoint entry\");\n          }\n      }\n      return super.applyEntries(logEntries, nextIndex, currentTerm, snapshot);\n    }\n}\n```\n\nAn example of the \"pulse\" event listener of the `raft.client.ZmqRaftPeerSub` client:\n\n```js\nlet sub = new raft.client.ZmqRaftPeerSub(/* ... */);\nsub.on('pulse', (lastIndex, currentTerm, logEntries) =\u003e {\n  let nextIndex = lastIndex - entries.length + 1;\n  for (let [index, item] of logEntries.entries()) {\n      let entry = raft.common.LogEntry.bufferToLogEntry(item, nextIndex + index);\n      // ... do something with entry\n  }\n});\n```\n\nThe `receiver` callback argument to `requestEntries` method of [`ZmqRaftClient`](lib/client/zmq_raft_client.js#L465) and [`ZmqRaftPeerClient`](lib/client/zmq_raft_peer_client.js#L383) also receives log entries as an array of raw Buffer chunks, that can be interpreted the same way.\n\nThe following:\n\n* [`ZmqRaftSubscriber`](lib/client/zmq_raft_subscriber.js),\n* [`RequestEntriesStream`](lib/client/zmq_raft_peer_client.js#L597) returned from [`requestEntriesStream`](lib/client/zmq_raft_client.js#L557),\n* [`ForeverEntriesStream`](lib/client/zmq_raft_peer_sub.js#L404) returned from [`foreverEntriesStream`](lib/client/zmq_raft_peer_sub.js#L336)\n\nare all object streams that yield instances of either [`common.LogEntry`](lib/common/log_entry.js) or [`common.SnapshotChunk`](lib/common/snapshot_chunk.js).\n\n```js\nlet snapshot;\n\nstream.on('data', obj =\u003e {\n  console.log(\"received entry or chunk with index: %d\", obj.logIndex);\n  if (obj.isLogEntry \u0026\u0026 obj.isStateEntry) {\n    let data = obj.readEntryData();\n    // ... do something with a state log entry\n  }\n  else if (obj.isSnapshotChunk) {\n    // ... do something with a snapshot chunk\n    // obj.snapshotByteOffset - the byte offset of this chunk\n    // obj.snapshotTotalLength - the total snapshot size in bytes\n    // obj.logTerm - the snapshot log term\n    // obj.length - the snapshot chunk length\n    // any other Buffer instance method can be also called on obj\n    if (obj.isFirstChunk) {\n      snapshot = createNewSnapshot(obj.snapshotTotalLength, obj.logIndex, obj.logTerm);\n    }\n    // this is just an exmample, use stream piping instead or implement backpressure with pause/resume\n    snapshot.write(obj, obj.snapshotByteOffset);\n    if (obj.isLastChunk) {\n      snapshot.commit();\n    }\n  }\n});\n```\n\n\n### Updating state.\n\nThe state of the cluster can be updated using the [Request Update RPC](PROTO.md#requestupdate-rpc).\n\nDepending on chosen scenario, use either a [`ZmqRaftClient#requestUpdate`](lib/client/zmq_raft_client.js#L409) method or use the [`stream.Writable`](https://nodejs.org/dist/latest-v18.x/docs/api/stream.html#writable-streams) side of the [`raft.client.ZmqRaftSubscriber`](lib/client/zmq_raft_subscriber.js) `Duplex` API.\n\nScenario [1](#use-cases):\n\n```js\nconst stateMachine = new MyStateMachine(/* ... */);\nconst raftPeer = raft.server.builder.build({\n  // ...\n  factory: {\n    state: (_) =\u003e stateMachine\n  }\n})\n// ...\nconst seedPeers = [\"tcp://raft-host-1.local:8047\", \"tcp://raft-host-2.local:8047\", \"tcp://raft-host-3.local:8047\"];\n// seed peers are only here for initial discovery, ZmqRaftClient retrieves the actual peer list\n// from any RAFT server it connects to initially\nconst client = new raft.client.ZmqRaftClient(seedPeers, {\n                                  secret: mySecret, lazy: true, heartbeat: 5000});\n// keep the client instance through the lifetime of your application,\n// so it can keep track of the cluster membership changes\n\nasync function requestUpdate(txData) {\n  const serializedTxData = Buffer.from(JSON.stringify(txData));\n  const requestId = raft.utils.id.genIdent();\n  const logIndex = await client.requestUpdate(requestId, serializedTxData);\n  console.log('state updated with log tx: %d, request-id: %s', logIndex, requestId);\n  return logIndex;\n} \n\n// sometimes your application may want to check if the current data is \"fresh\" by comparing the\n// last log index applied to the state machine with the log index of the last known update committed.\nstateMachine.lastUpdateLogIndex = 0;\n\nrequestUpdate({foo: 1}).then(index =\u003e {\n  if (index \u003e stateMachine.lastUpdateLogIndex) {\n    stateMachine.lastUpdateLogIndex = index;\n  }\n  notifyIsFresh(stateMachine.lastApplied \u003e= stateMachine.lastUpdateLogIndex);\n});\n\n// somewhere in your state machine implementation\nclass MyStateMachine extends raft.api.StateMachineBase {\n    //...\n    applyEntries(logEntries, nextIndex, currentTerm, snapshot) {\n      // ...\n      let res = super.applyEntries(logEntries, nextIndex, currentTerm, snapshot);\n      notifyIsFresh(this.lastApplied \u003e= this.lastUpdateLogIndex);\n      return res;\n    }\n}\n```\n\nScenario [2](#use-cases):\n\n```js\nconst seedPeers = [\"tcp://raft-host-1.local:8047\", \"tcp://raft-host-2.local:8047\", \"tcp://raft-host-3.local:8047\"];\nconst sub = new raft.client.ZmqRaftSubscriber(seedPeers, {\n                              secret: mySecret, lastIndex: localLastIndex||0});\n// keep the sub instance through the lifetime of your application\n\nconst bufferToUpdateRequest = raft.common.LogEntry.UpdateRequest.bufferToUpdateRequest;\n\nfunction requestUpdate(txData) {\n  const serializedTxData = Buffer.from(JSON.stringify(txData));\n  const requestId = raft.utils.id.genIdent();\n  const updateRequest = bufferToUpdateRequest(serializedTxData, requestId);\n  notifyIsFresh(false);\n  return sub.write(updateRequest);\n} \n\n// ...\nif (requestUpdate(txData)) {\n  console.log(\"no backpressure\");\n  nextIteration();\n}\nelse {\n  console.log(\"backpressured\");\n  sub.once('drain', () =\u003e nextIteration());\n}\n\nsub.on('data', obj =\u003e {\n  // ...\n  notifyIsFresh(obj.logIndex \u003e= sub.lastUpdateLogIndex);\n});\n```\n\nDetermining whether your local view of the distributed data is fresh is especially important when dealing with non-idempotent updates (e.g. increasing values, appending strings). It may also help optimize the user interface: the application may want to postpone displaying changes unless all local updates are committed and visible in the local state machine.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froyaltm%2Fnode-zmq-raft","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Froyaltm%2Fnode-zmq-raft","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froyaltm%2Fnode-zmq-raft/lists"}