{"id":13563940,"url":"https://github.com/tidwall/uhaha","last_synced_at":"2025-05-15T07:04:54.845Z","repository":{"id":45149110,"uuid":"177012937","full_name":"tidwall/uhaha","owner":"tidwall","description":"High Availability Raft Framework for Go","archived":false,"fork":false,"pushed_at":"2025-01-19T20:01:57.000Z","size":377,"stargazers_count":618,"open_issues_count":3,"forks_count":35,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-04-14T11:12:20.572Z","etag":null,"topics":["fault-tolerant","framework","high-availability","raft"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tidwall.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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,"zenodo":null}},"created_at":"2019-03-21T19:42:24.000Z","updated_at":"2025-03-11T07:38:47.000Z","dependencies_parsed_at":"2024-01-14T06:50:10.778Z","dependency_job_id":"6262fc62-87c7-4647-895e-08b334467937","html_url":"https://github.com/tidwall/uhaha","commit_stats":null,"previous_names":[],"tags_count":43,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tidwall%2Fuhaha","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tidwall%2Fuhaha/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tidwall%2Fuhaha/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tidwall%2Fuhaha/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tidwall","download_url":"https://codeload.github.com/tidwall/uhaha/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254292039,"owners_count":22046426,"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":["fault-tolerant","framework","high-availability","raft"],"created_at":"2024-08-01T13:01:24.758Z","updated_at":"2025-05-15T07:04:49.827Z","avatar_url":"https://github.com/tidwall.png","language":"Go","funding_links":[],"categories":["Go","Networking, Distributed, Microservices \u0026 Cloud - Tools \u0026 Services"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n\t\u003cimg src=\"logo.png\" border=0 width=500 alt=\"uhaha\"\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://godoc.org/github.com/tidwall/uhaha\"\u003e\u003cimg src=\"https://img.shields.io/badge/api-reference-blue.svg?style=flat-square\" alt=\"GoDoc\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003eHigh Availabilty Framework for Happy Data\u003c/p\u003e\n\nUhaha is a framework for building highly available Raft-based data applications in Go. \nThis is basically an upgrade to the [Finn](https://github.com/tidwall/finn) project, but has an updated API, better security features (TLS and auth passwords), \ncustomizable services, deterministic time, recalculable random numbers, simpler snapshots, a smaller network footprint, and more.\nUnder the hood it utilizes [hashicorp/raft](https://github.com/hashicorp/raft), [tidwall/redcon](https://github.com/tidwall/redcon), and [syndtr/goleveldb](https://github.com/syndtr/goleveldb).\n\n## Features\n\n- Simple API for quickly creating a custom Raft-based application.\n- Deterministic monotonic time that does not drift and stays in sync with the internet.\n- APIs for building custom services such as HTTP and gRPC.\n  Supports the Redis protocol by default, so most Redis client library will work with Uhaha.\n- [TLS](#tls) and [Auth password](#auth-password) support.\n- Multiple examples to help jumpstart integration, including\n  a [Key-value DB](https://github.com/tidwall/uhaha/tree/master/examples/kvdb), \n  a [Timeseries DB](https://github.com/tidwall/uhaha/tree/master/examples/timeseries), \n  and a [Ticket Service](https://github.com/tidwall/uhaha/tree/master/examples/ticket).\n\n## Example\n\nBelow a simple example of a service for monotonically increasing tickets. \n\n```go\npackage main\n\nimport \"github.com/tidwall/uhaha\"\n\ntype data struct {\n\tTicket int64\n}\n\nfunc main() {\n\t// Set up a uhaha configuration\n\tvar conf uhaha.Config\n\t\n\t// Give the application a name. All servers in the cluster should use the\n\t// same name.\n\tconf.Name = \"ticket\"\n\t\n\t// Set the initial data. This is state of the data when first server in the \n\t// cluster starts for the first time ever.\n\tconf.InitialData = new(data)\n\n\t// Since we are not holding onto much data we can used the built-in JSON\n\t// snapshot system. You just need to make sure all the important fields in\n\t// the data are exportable (capitalized) to JSON. In this case there is\n\t// only the one field \"Ticket\".\n\tconf.UseJSONSnapshots = true\n\t\n\t// Add a command that will change the value of a Ticket. \n\tconf.AddWriteCommand(\"ticket\", cmdTICKET)\n\n\t// Finally, hand off all processing to uhaha.\n\tuhaha.Main(conf)\n}\n\n// TICKET\n// help: returns a new ticket that has a value that is at least one greater\n// than the previous TICKET call.\nfunc cmdTICKET(m uhaha.Machine, args []string) (interface{}, error) {\n\t// The the current data from the machine\n\tdata := m.Data().(*data)\n\n\t// Increment the ticket\n\tdata.Ticket++\n\n\t// Return the new ticket to caller\n\treturn data.Ticket, nil\n}\n```\n\n### Building\n\nUsing the source file from the examples directory, we'll build an application\nnamed \"ticket\"\n\n```\ngo build -o ticket examples/ticket/main.go\n```\n\n### Running\n\nIt's ideal to have three, five, or seven nodes in your cluster.\n\nLet's create the first node.\n\n```\n./ticket -n 1 -a :11001\n```\n\nThis will create a node named 1 and bind the address to :11001\n\nNow let's create two more nodes and add them to the cluster.\n\n```\n./ticket -n 2 -a :11002 -j :11001\n./ticket -n 3 -a :11003 -j :11001\n```\n\nNow we have a fault-tolerant three node cluster up and running.\n\n### Using\n\nYou can use any Redis compatible client, such as the redis-cli, telnet, \nor netcat.\n\nI'll use the redis-cli in the example below.\n\nConnect to the leader. This will probably be the first node you created.\n\n```\nredis-cli -p 11001\n```\n\nSend the server a TICKET command and receive the first ticket.\n\n```\n\u003e TICKET\n\"1\"\n```\n\nFrom here on every TICKET command will guarentee to generate a value larger\nthan the previous TICKET command.\n\n```\n\u003e TICKET\n\"2\"\n\u003e TICKET\n\"3\"\n\u003e TICKET\n\"4\"\n\u003e TICKET\n\"5\"\n```\n\n\n## Built-in Commands\n\nThere are a number built-in commands for managing and monitor the cluster.\n\n```sh\nVERSION                                 # show the application version\nMACHINE                                 # show information about the state machine\nRAFT LEADER                             # show the address of the current raft leader\nRAFT INFO [pattern]                     # show information about the raft server and cluster\nRAFT SERVER LIST                        # show all servers in cluster\nRAFT SERVER ADD id address              # add a server to cluster\nRAFT SERVER REMOVE id                   # remove a server from the cluster\nRAFT SNAPSHOT NOW                       # make a snapshot of the data\nRAFT SNAPSHOT LIST                      # show a list of all snapshots on server\nRAFT SNAPSHOT FILE id                   # show the file path of a snapshot on server\nRAFT SNAPSHOT READ id [RANGE start end] # download all or part of a snapshot\n```\n\nAnd also some client commands.\n\n```sh\nQUIT                                    # close the client connection\nPING                                    # ping the server\nECHO [message]                          # echo a message to the server\nAUTH password                           # authenticate with a password\n```\n\n## Network and security considerations (TLS and Auth password)\n\nBy default a single Uhaha instance is bound to the local `127.0.0.1` IP address. Thus nothing outside that machine, including other servers in the cluster or machines on the same local network will be able communicate with this instance. \n\n### Network security\n\nTo open up the service you will need to provide an IP address that can be reached from the outside.\nFor example, let's say you want to set up three servers on a local `10.0.0.0` network.\n\nOn server 1:\n\n```sh\n./ticket -n 1 -a 10.0.0.1:11001\n```\n\nOn server 2:\n\n```sh\n./ticket -n 2 -a 10.0.0.2:11001 -j 10.0.0.1:11001\n```\n\nOn server 3:\n\n```sh\n./ticket -n 3 -a 10.0.0.3:11001 -j 10.0.0.1:11001\n```\n\nNow you have a Raft cluster running on three distinct servers in the same local network. This may be enough for applications that only require a [network security policy](https://en.wikipedia.org/wiki/Network_security). Basically any server on the local network can access the cluster.\n\n### Auth password\n\nIf you want to lock down the cluster further you can provide a secret auth, which is more or less a password that the cluster and client will need to communicate with each other.\n\n```sh\n./ticket -n 1 -a 10.0.0.1:11001 --auth my-secret\n```\n\nAll the servers will need to be started with the same auth.\n\n```sh\n./ticket -n 2 -a 10.0.0.2:11001 --auth my-secret -j 10.0.0.1:11001\n```\n\n```sh\n./ticket -n 2 -a 10.0.0.3:11001 --auth my-secret -j 10.0.0.1:11001\n```\n\nThe client will also need the same auth to talk with cluster. All redis clients support an auth password, such as:\n\n```sh\nredis-cli -h 10.0.0.1 -p 11001 -a my-secret\n```\n\nThis may be enough if you keep all your machines on the same private network, but you don't want all machines or applications to have unfettered access to the cluster.\n\n### TLS\n\nFinally you can use TLS, which I recommend along with an auth password.\n\nIn this example a custom cert and key are created using the [`mkcert`](https://github.com/FiloSottile/mkcert) tool.\n\n```sh\nmkcert uhaha-example\n# produces uhaha-example.pem, uhaha-example-key.pem, and a rootCA.pem\n```\n\nThen create a cluster using the cert \u0026 key files. Along with an auth.\n\n```sh\n./ticket -n 1 -a 10.0.0.1:11001 --tls-cert uhaha-example.pem --tls-key uhaha-example-key.pem --auth my-secret\n```\n\n```sh\n./ticket -n 2 -a 10.0.0.2:11001 --tls-cert uhaha-example.pem --tls-key uhaha-example-key.pem --auth my-secret -j 10.0.0.1:11001\n```\n\n```sh\n./ticket -n 2 -a 10.0.0.3:11001 --tls-cert uhaha-example.pem --tls-key uhaha-example-key.pem --auth my-secret -j 10.0.0.1:11001\n```\n\nNow you can connect to the server from a client that has the `rootCA.pem`. \nYou can find the location of your rootCA.pem file in the running `ls \"$(mkcert -CAROOT)/rootCA.pem\"`.\n\n```sh\nredis-cli -h 10.0.0.1 -p 11001 --tls --cacert rootCA.pem -a my-secret\n```\n\n## Command-line options\n\nBelow are all of the command line options.\n\n```\nUsage: my-uhaha-app [-n id] [-a addr] [options]\n\nBasic options:\n  -v               : display version\n  -h               : display help, this screen\n  -a addr          : bind to address  (default: 127.0.0.1:11001)\n  -n id            : node ID  (default: 1)\n  -d dir           : data directory  (default: data)\n  -j addr          : leader address of a cluster to join\n  -l level         : log level  (default: info) [debug,verb,info,warn,silent]\n\nSecurity options:\n  --tls-cert path  : path to TLS certificate\n  --tls-key path   : path to TLS private key\n  --auth auth      : cluster authorization, shared by all servers and clients\n\nNetworking options:\n  --advertise addr : advertise address  (default: network bound address)\n\nAdvanced options:\n  --nosync         : turn off syncing data to disk after every write. This leads\n                     to faster write operations but opens up the chance for data\n                     loss due to catastrophic events such as power failure.\n  --openreads      : allow followers to process read commands, but with the\n                     possibility of returning stale data.\n  --localtime      : have the raft machine time synchronized with the local\n                     server rather than the public internet. This will run the\n                     risk of time shifts when the local server time is\n                     drastically changed during live operation.\n  --restore path   : restore a raft machine from a snapshot file. This will\n                     start a brand new single-node cluster using the snapshot as\n                     initial data. The other nodes must be re-joined. This\n                     operation is ignored when a data directory already exists.\n                     Cannot be used with -j flag.\n```\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftidwall%2Fuhaha","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftidwall%2Fuhaha","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftidwall%2Fuhaha/lists"}