{"id":20077208,"url":"https://github.com/boostorg/redis","last_synced_at":"2025-05-16T15:07:54.754Z","repository":{"id":40590559,"uuid":"222791664","full_name":"boostorg/redis","owner":"boostorg","description":"An async redis client designed for performance and scalability","archived":false,"fork":false,"pushed_at":"2025-05-12T11:49:55.000Z","size":4239,"stargazers_count":250,"open_issues_count":28,"forks_count":39,"subscribers_count":12,"default_branch":"develop","last_synced_at":"2025-05-12T12:43:28.645Z","etag":null,"topics":["asio","async","redis-client"],"latest_commit_sha":null,"homepage":"https://www.boost.org/doc/libs/develop/libs/redis/doc/html/index.html","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/boostorg.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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-11-19T21:19:02.000Z","updated_at":"2025-05-12T11:49:59.000Z","dependencies_parsed_at":"2023-02-18T15:31:08.618Z","dependency_job_id":"c51d26eb-b11a-4372-8c3f-6eb443c7d738","html_url":"https://github.com/boostorg/redis","commit_stats":null,"previous_names":[],"tags_count":26,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boostorg%2Fredis","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boostorg%2Fredis/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boostorg%2Fredis/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boostorg%2Fredis/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/boostorg","download_url":"https://codeload.github.com/boostorg/redis/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254553958,"owners_count":22090417,"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":["asio","async","redis-client"],"created_at":"2024-11-13T15:06:34.682Z","updated_at":"2025-05-16T15:07:49.734Z","avatar_url":"https://github.com/boostorg.png","language":"C++","funding_links":[],"categories":["C++"],"sub_categories":[],"readme":"# Boost.Redis\n\nBoost.Redis is a high-level [Redis](https://redis.io/) client library built on top of\n[Boost.Asio](https://www.boost.org/doc/libs/release/doc/html/boost_asio.html)\nthat implements the Redis protocol\n[RESP3](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md).\nThe requirements for using Boost.Redis are\n\n* Boost 1.84 or higher.\n* C++17 or higher.\n* Redis 6 or higher (must support RESP3).\n* GCC (11, 12), Clang (11, 13, 14) and Visual Studio (16 2019, 17 2022).\n* Have basic-level knowledge about [Redis](https://redis.io/docs/)\n  and [Boost.Asio](https://www.boost.org/doc/libs/1_82_0/doc/html/boost_asio/overview.html).\n\nTo use the library it is necessary to include\n\n```cpp\n#include \u003cboost/redis/src.hpp\u003e\n```\n\nin no more than one source file in your applications. To build the\nexamples and tests with cmake run\n\n```cpp\n# Linux\n$ BOOST_ROOT=/opt/boost_1_84_0 cmake -S \u003csource-dir\u003e -B \u003cbinary-dir\u003e\n\n# Windows \n$ cmake -G \"Visual Studio 17 2022\" -A x64 -B bin64 -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake\n```\n\nFor more details see https://github.com/boostorg/cmake.\n\n\u003ca name=\"connection\"\u003e\u003c/a\u003e\n## Connection\n\nThe code below uses a short-lived connection to\n[ping](https://redis.io/commands/ping/) the Redis server \n\n```cpp\nauto co_main(config const\u0026 cfg) -\u003e net::awaitable\u003cvoid\u003e\n{\n   auto conn = std::make_shared\u003cconnection\u003e(co_await net::this_coro::executor);\n   conn-\u003easync_run(cfg, {}, net::consign(net::detached, conn));\n\n   // A request containing only a ping command.\n   request req;\n   req.push(\"PING\", \"Hello world\");\n\n   // Response object.\n   response\u003cstd::string\u003e resp;\n\n   // Executes the request.\n   co_await conn-\u003easync_exec(req, resp);\n   conn-\u003ecancel();\n\n   std::cout \u003c\u003c \"PING: \" \u003c\u003c std::get\u003c0\u003e(resp).value() \u003c\u003c std::endl;\n}\n```\n\nThe roles played by the `async_run` and `async_exec` functions are\n\n* `async_exec`: Execute the commands contained in the\n  request and store the individual responses in the `resp` object. Can\n  be called from multiple places in your code concurrently.\n* `async_run`: Resolve, connect, ssl-handshake,\n  resp3-handshake, health-checks, reconnection and coordinate low-level\n  read and write operations (among other things).\n\n### Server pushes\n\nRedis servers can also send a variety of pushes to the client, some of\nthem are\n\n* [Pubsub](https://redis.io/docs/manual/pubsub/)\n* [Keyspace notification](https://redis.io/docs/manual/keyspace-notifications/)\n* [Client-side caching](https://redis.io/docs/manual/client-side-caching/)\n\nThe connection class supports server pushes by means of the\n`boost::redis::connection::async_receive` function, which can be\ncalled in the same connection that is being used to execute commands.\nThe coroutine below shows how to used it\n\n```cpp\nauto\nreceiver(std::shared_ptr\u003cconnection\u003e conn) -\u003e net::awaitable\u003cvoid\u003e\n{\n   request req;\n   req.push(\"SUBSCRIBE\", \"channel\");\n\n   generic_response resp;\n   conn-\u003eset_receive_response(resp);\n\n   // Loop while reconnection is enabled\n   while (conn-\u003ewill_reconnect()) {\n\n      // Reconnect to channels.\n      co_await conn-\u003easync_exec(req, ignore);\n\n      // Loop reading Redis pushes.\n      for (;;) {\n         error_code ec;\n         co_await conn-\u003easync_receive(resp, net::redirect_error(net::use_awaitable, ec));\n         if (ec)\n            break; // Connection lost, break so we can reconnect to channels.\n\n         // Use the response resp in some way and then clear it.\n         ...\n\n         consume_one(resp);\n      }\n   }\n}\n\n```\n\n\u003ca name=\"requests\"\u003e\u003c/a\u003e\n## Requests\n\nRedis requests are composed of one or more commands (in the\nRedis documentation they are called\n[pipelines](https://redis.io/topics/pipelining)). For example\n\n```cpp\n// Some example containers.\nstd::list\u003cstd::string\u003e list {...};\nstd::map\u003cstd::string, mystruct\u003e map { ...};\n\n// The request can contain multiple commands.\nrequest req;\n\n// Command with variable length of arguments.\nreq.push(\"SET\", \"key\", \"some value\", \"EX\", \"2\");\n\n// Pushes a list.\nreq.push_range(\"SUBSCRIBE\", list);\n\n// Same as above but as an iterator range.\nreq.push_range(\"SUBSCRIBE\", std::cbegin(list), std::cend(list));\n\n// Pushes a map.\nreq.push_range(\"HSET\", \"key\", map);\n```\n\nSending a request to Redis is performed with `boost::redis::connection::async_exec` as already stated.  The\n`boost::redis::request::config` object inside the request dictates how\nthe `boost::redis::connection` the request is handled in some\nsituations. The reader is advised to read it carefully.\n\n\u003ca name=\"responses\"\u003e\u003c/a\u003e\n## Responses\n\nBoost.Redis uses the following strategy to deal with Redis responses\n\n* `boost::redis::request` used for requests whose number of commands are not dynamic.\n* `boost::redis::generic_response` used when the size is dynamic.\n\nFor example, the request below has three commands\n\n```cpp\nrequest req;\nreq.push(\"PING\");\nreq.push(\"INCR\", \"key\");\nreq.push(\"QUIT\");\n```\n\nand therefore its response will also contain three elements which can\nbe read in the following reponse object\n\n```cpp\nresponse\u003cstd::string, int, std::string\u003e\n```\n\nThe response behaves as a tuple and must\nhave as many elements as the request has commands (exceptions below).\nIt is also necessary that each tuple element is capable of storing the\nresponse to the command it refers to, otherwise an error will occur.\nTo ignore responses to individual commands in the request use the tag\n`boost::redis::ignore_t`, for example\n\n```cpp\n// Ignore the second and last responses.\nresponse\u003cstd::string, ignore_t, std::string, ignore_t\u003e\n```\n\nThe following table provides the resp3-types returned by some Redis\ncommands\n\nCommand  | RESP3 type                          | Documentation\n---------|-------------------------------------|--------------\nlpush    | Number                              | https://redis.io/commands/lpush\nlrange   | Array                               | https://redis.io/commands/lrange\nset      | Simple-string, null or blob-string  | https://redis.io/commands/set\nget      | Blob-string                         | https://redis.io/commands/get\nsmembers | Set                                 | https://redis.io/commands/smembers\nhgetall  | Map                                 | https://redis.io/commands/hgetall\n\nTo map these RESP3 types into a C++ data structure use the table below\n\nRESP3 type     | Possible C++ type                                            | Type\n---------------|--------------------------------------------------------------|------------------\nSimple-string  | `std::string`                                              | Simple\nSimple-error   | `std::string`                                              | Simple\nBlob-string    | `std::string`, `std::vector`                               | Simple\nBlob-error     | `std::string`, `std::vector`                               | Simple\nNumber         | `long long`, `int`, `std::size_t`, `std::string`           | Simple\nDouble         | `double`, `std::string`                                    | Simple\nNull           | `std::optional\u003cT\u003e`                                         | Simple\nArray          | `std::vector`, `std::list`, `std::array`, `std::deque`     | Aggregate\nMap            | `std::vector`, `std::map`, `std::unordered_map`            | Aggregate\nSet            | `std::vector`, `std::set`, `std::unordered_set`            | Aggregate\nPush           | `std::vector`, `std::map`, `std::unordered_map`            | Aggregate\n\nFor example, the response to the request\n\n```cpp\nrequest req;\nreq.push(\"HELLO\", 3);\nreq.push_range(\"RPUSH\", \"key1\", vec);\nreq.push_range(\"HSET\", \"key2\", map);\nreq.push(\"LRANGE\", \"key3\", 0, -1);\nreq.push(\"HGETALL\", \"key4\");\nreq.push(\"QUIT\");\n\n```\n\ncan be read in the response object below\n\n```cpp\nresponse\u003c\n   redis::ignore_t,  // hello\n   int,              // rpush\n   int,              // hset\n   std::vector\u003cT\u003e,   // lrange\n   std::map\u003cU, V\u003e,   // hgetall\n   std::string       // quit\n\u003e resp;\n```\n\nThen, to execute the request and read the response use `async_exec` as\nshown below\n\n```cpp\nco_await conn-\u003easync_exec(req, resp);\n```\n\nIf the intention is to ignore responses altogether use `ignore`\n\n```cpp\n// Ignores the response\nco_await conn-\u003easync_exec(req, ignore);\n```\n\nResponses that contain nested aggregates or heterogeneous data\ntypes will be given special treatment later in [The general case](#the-general-case).  As\nof this writing, not all RESP3 types are used by the Redis server,\nwhich means in practice users will be concerned with a reduced\nsubset of the RESP3 specification.\n\n### Pushes\n\nCommands that have no response like\n\n* `\"SUBSCRIBE\"`\n* `\"PSUBSCRIBE\"`\n* `\"UNSUBSCRIBE\"`\n\nmust **NOT** be included in the response tuple. For example, the request below\n\n```cpp\nrequest req;\nreq.push(\"PING\");\nreq.push(\"SUBSCRIBE\", \"channel\");\nreq.push(\"QUIT\");\n```\n\nmust be read in the response object `response\u003cstd::string, std::string\u003e`.\n\n### Null\n\nIt is not uncommon for apps to access keys that do not exist or that\nhave already expired in the Redis server, to deal with these usecases\nwrap the type with an `std::optional` as shown below\n\n```cpp\nresponse\u003c\n   std::optional\u003cA\u003e,\n   std::optional\u003cB\u003e,\n   ...\n   \u003e resp;\n```\n\nEverything else stays the same.\n\n### Transactions\n\nTo read responses to transactions we must first observe that Redis\nwill queue the transaction commands and send their individual\nresponses as elements of an array, the array is itself the response to\nthe `EXEC` command.  For example, to read the response to this request\n\n```cpp\nreq.push(\"MULTI\");\nreq.push(\"GET\", \"key1\");\nreq.push(\"LRANGE\", \"key2\", 0, -1);\nreq.push(\"HGETALL\", \"key3\");\nreq.push(\"EXEC\");\n```\n\nuse the following response type\n\n```cpp\nusing boost::redis::ignore;\n\n\nresponse\u003c\n   ignore_t,  // multi\n   ignore_t,  // QUEUED\n   ignore_t,  // QUEUED\n   ignore_t,  // QUEUED\n   response\u003c\n      std::optional\u003cstd::string\u003e, // get\n      std::optional\u003cstd::vector\u003cstd::string\u003e\u003e, // lrange\n      std::optional\u003cstd::map\u003cstd::string, std::string\u003e\u003e // hgetall\n   \u003e // exec\n\u003e resp;\n```\n\nFor a complete example see cpp20_containers.cpp.\n\n\u003ca name=\"the-general-case\"\u003e\u003c/a\u003e\n\n### The general case\n\nThere are cases where responses to Redis\ncommands won't fit in the model presented above, some examples are\n\n* Commands (like `set`) whose responses don't have a fixed\n  RESP3 type. Expecting an `int` and receiving a blob-string\n  results in an error.\n* RESP3 aggregates that contain nested aggregates can't be read in STL containers.\n* Transactions with a dynamic number of commands can't be read in a `response`.\n\nTo deal with these cases Boost.Redis provides the `boost::redis::resp3::node` type\nabstraction, that is the most general form of an element in a\nresponse, be it a simple RESP3 type or the element of an aggregate. It\nis defined like this\n\n```cpp\ntemplate \u003cclass String\u003e\nstruct basic_node {\n   // The RESP3 type of the data in this node.\n   type data_type;\n\n   // The number of elements of an aggregate (or 1 for simple data).\n   std::size_t aggregate_size;\n\n   // The depth of this node in the response tree.\n   std::size_t depth;\n\n   // The actual data. For aggregate types this is always empty.\n   String value;\n};\n```\n\nAny response to a Redis command can be received in a\n`boost::redis::generic_response`.  The vector can be seen as a\npre-order view of the response tree.  Using it is not different than\nusing other types\n\n```cpp\n// Receives any RESP3 simple or aggregate data type.\nboost::redis::generic_response resp;\nco_await conn-\u003easync_exec(req, resp);\n```\n\nFor example, suppose we want to retrieve a hash data structure\nfrom Redis with `HGETALL`, some of the options are\n\n* `boost::redis::generic_response`: Works always.\n* `std::vector\u003cstd::string\u003e`: Efficient and flat, all elements as string.\n* `std::map\u003cstd::string, std::string\u003e`: Efficient if you need the data as a `std::map`.\n* `std::map\u003cU, V\u003e`: Efficient if you are storing serialized data. Avoids temporaries and requires `boost_redis_from_bulk` for `U` and `V`.\n\nIn addition to the above users can also use unordered versions of the\ncontainers. The same reasoning applies to sets e.g. `SMEMBERS`\nand other data structures in general.\n\n\u003ca name=\"serialization\"\u003e\u003c/a\u003e\n## Serialization\n\nBoost.Redis supports serialization of user defined types by means of\nthe following customization points\n\n```cpp\n\n// Serialize.\nvoid boost_redis_to_bulk(std::string\u0026 to, mystruct const\u0026 obj);\n\n// Deserialize\nvoid boost_redis_from_bulk(mystruct\u0026 u, node_view const\u0026 node, boost::system::error_code\u0026)\n```\n\nThese functions are accessed over ADL and therefore they must be\nimported in the global namespace by the user.  In the\n[Examples](#examples) section the reader can find examples showing how\nto serialize using json and [protobuf](https://protobuf.dev/).\n\n\u003ca name=\"examples\"\u003e\u003c/a\u003e\n## Examples\n\nThe examples below show how to use the features discussed so far\n\n* cpp20_intro.cpp: Does not use awaitable operators.\n* cpp20_intro_tls.cpp: Communicates over TLS.\n* cpp20_containers.cpp: Shows how to send and receive STL containers and how to use transactions.\n* cpp20_json.cpp: Shows how to serialize types using Boost.Json.\n* cpp20_protobuf.cpp: Shows how to serialize types using protobuf.\n* cpp20_resolve_with_sentinel.cpp: Shows how to resolve a master address using sentinels.\n* cpp20_subscriber.cpp: Shows how to implement pubsub with reconnection re-subscription.\n* cpp20_echo_server.cpp: A simple TCP echo server.\n* cpp20_chat_room.cpp: A command line chat built on Redis pubsub.\n* cpp17_intro.cpp: Uses callbacks and requires C++17.\n* cpp17_intro_sync.cpp: Runs `async_run` in a separate thread and performs synchronous calls to `async_exec`.\n\nThe main function used in some async examples has been factored out in\nthe main.cpp file.\n\n## Echo server benchmark\n\nThis document benchmarks the performance of TCP echo servers I\nimplemented in different languages using different Redis clients.  The\nmain motivations for choosing an echo server are\n\n   * Simple to implement and does not require expertise level in most languages.\n   * I/O bound: Echo servers have very low CPU consumption in general\n     and  therefore are excellent to  measure how a program handles concurrent requests.\n   * It simulates very well a typical backend in regard to concurrency.\n\nI also imposed some constraints on the implementations\n\n   * It should be simple enough and not require writing too much code.\n   * Favor the use standard idioms and avoid optimizations that require expert level.\n   * Avoid the use of complex things like connection and thread pool.\n\nTo reproduce these results run one of the echo-server programs in one\nterminal and the\n[echo-server-client](https://github.com/boostorg/redis/blob/42880e788bec6020dd018194075a211ad9f339e8/benchmarks/cpp/asio/echo_server_client.cpp)\nin another.\n\n### Without Redis\n\nFirst I tested a pure TCP echo server, i.e. one that sends the messages\ndirectly to the client without interacting with Redis. The result can\nbe seen below\n\n![](https://boostorg.github.io/redis/tcp-echo-direct.png)\n\nThe tests were performed with a 1000 concurrent TCP connections on the\nlocalhost where latency is 0.07ms on average on my machine. On higher\nlatency networks the difference among libraries is expected to\ndecrease. \n\n   * I expected Libuv to have similar performance to Asio and Tokio.\n   * I did expect nodejs to come a little behind given it is is\n     javascript code. Otherwise I did expect it to have similar\n     performance to libuv since it is the framework behind it.\n   * Go did surprise me: faster than nodejs and libuv!\n\nThe code used in the benchmarks can be found at\n\n   * [Asio](https://github.com/boostorg/redis/blob/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/benchmarks/cpp/asio/echo_server_direct.cpp): A variation of [this](https://github.com/chriskohlhoff/asio/blob/4915cfd8a1653c157a1480162ae5601318553eb8/asio/src/examples/cpp20/coroutines/echo_server.cpp) Asio example.\n   * [Libuv](https://github.com/boostorg/redis/tree/835a1decf477b09317f391eddd0727213cdbe12b/benchmarks/c/libuv): Taken from [here](https://github.com/libuv/libuv/blob/06948c6ee502862524f233af4e2c3e4ca876f5f6/docs/code/tcp-echo-server/main.c) Libuv example .\n   * [Tokio](https://github.com/boostorg/redis/tree/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/benchmarks/rust/echo_server_direct): Taken from [here](https://docs.rs/tokio/latest/tokio/).\n   * [Nodejs](https://github.com/boostorg/redis/tree/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/benchmarks/nodejs/echo_server_direct)\n   * [Go](https://github.com/boostorg/redis/blob/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/benchmarks/go/echo_server_direct.go)\n\n### With Redis\n\nThis is similar to the echo server described above but messages are\nechoed by Redis and not by the echo-server itself, which acts\nas a proxy between the client and the Redis server. The results\ncan be seen below\n\n![](https://boostorg.github.io/redis/tcp-echo-over-redis.png)\n\nThe tests were performed on a network where latency is 35ms on\naverage, otherwise it uses the same number of TCP connections\nas the previous example.\n\nAs the reader can see, the Libuv and the Rust test are not depicted\nin the graph, the reasons are\n\n   * [redis-rs](https://github.com/redis-rs/redis-rs): This client\n     comes so far behind that it can't even be represented together\n     with the other benchmarks without making them look insignificant.\n     I don't know for sure why it is so slow, I suppose it has\n     something to do with its lack of automatic\n     [pipelining](https://redis.io/docs/manual/pipelining/) support.\n     In fact, the more TCP connections I launch the worse its\n     performance gets.\n\n   * Libuv: I left it out because it would require me writing to much\n     c code. More specifically, I would have to use hiredis and\n     implement support for pipelines manually.\n\nThe code used in the benchmarks can be found at\n\n   * [Boost.Redis](https://github.com/boostorg/redis): [code](https://github.com/boostorg/redis/blob/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/examples/echo_server.cpp)\n   * [node-redis](https://github.com/redis/node-redis): [code](https://github.com/boostorg/redis/tree/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/benchmarks/nodejs/echo_server_over_redis)\n   * [go-redis](https://github.com/go-redis/redis): [code](https://github.com/boostorg/redis/blob/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/benchmarks/go/echo_server_over_redis.go)\n\n### Conclusion\n\nRedis clients have to support automatic pipelining to have competitive performance. For updates to this document follow https://github.com/boostorg/redis.\n\n## Comparison\n\nThe main reason for why I started writing Boost.Redis was to have a client\ncompatible with the Asio asynchronous model. As I made progresses I could\nalso address what I considered weaknesses in other libraries.  Due to\ntime constraints I won't be able to give a detailed comparison with\neach client listed in the\n[official](https://redis.io/docs/clients/#cpp) list,\ninstead I will focus on the most popular C++ client on github in number of\nstars, namely\n\n* https://github.com/sewenew/redis-plus-plus\n\n### Boost.Redis vs Redis-plus-plus\n\nBefore we start it is important to mention some of the things\nredis-plus-plus does not support\n\n* The latest version of the communication protocol RESP3. Without that it is impossible to support some important Redis features like client side caching, among other things.\n* Coroutines.\n* Reading responses directly in user data structures to avoid creating temporaries.\n* Error handling with support for error-code.\n* Cancellation.\n\nThe remaining points will be addressed individually.  Let us first\nhave a look at what sending a command a pipeline and a transaction\nlook like\n\n```cpp\nauto redis = Redis(\"tcp://127.0.0.1:6379\");\n\n// Send commands\nredis.set(\"key\", \"val\");\nauto val = redis.get(\"key\"); // val is of type OptionalString.\nif (val)\n    std::cout \u003c\u003c *val \u003c\u003c std::endl;\n\n// Sending pipelines\nauto pipe = redis.pipeline();\nauto pipe_replies = pipe.set(\"key\", \"value\")\n                        .get(\"key\")\n                        .rename(\"key\", \"new-key\")\n                        .rpush(\"list\", {\"a\", \"b\", \"c\"})\n                        .lrange(\"list\", 0, -1)\n                        .exec();\n\n// Parse reply with reply type and index.\nauto set_cmd_result = pipe_replies.get\u003cbool\u003e(0);\n// ...\n\n// Sending a transaction\nauto tx = redis.transaction();\nauto tx_replies = tx.incr(\"num0\")\n                    .incr(\"num1\")\n                    .mget({\"num0\", \"num1\"})\n                    .exec();\n\nauto incr_result0 = tx_replies.get\u003clong long\u003e(0);\n// ...\n```\n\nSome of the problems with this API are\n\n* Heterogeneous treatment of commands, pipelines and transaction. This makes auto-pipelining impossible.\n* Any Api that sends individual commands has a very restricted scope of usability and should be avoided for performance reasons.\n* The API imposes exceptions on users, no error-code overload is provided.\n* No way to reuse the buffer for new calls to e.g. redis.get in order to avoid further dynamic memory allocations.\n* Error handling of resolve and connection not clear.\n\nAccording to the documentation, pipelines in redis-plus-plus have\nthe following characteristics\n\n\u003e NOTE: By default, creating a Pipeline object is NOT cheap, since\n\u003e it creates a new connection.\n\nThis is clearly a downside in the API as pipelines should be the\ndefault way of communicating and not an exception, paying such a\nhigh price for each pipeline imposes a severe cost in performance.\nTransactions also suffer from the very same problem.\n\n\u003e NOTE: Creating a Transaction object is NOT cheap, since it\n\u003e creates a new connection.\n\nIn Boost.Redis there is no difference between sending one command, a\npipeline or a transaction because requests are decoupled\nfrom the IO objects.\n\n\u003e redis-plus-plus also supports async interface, however, async\n\u003e support for Transaction and Subscriber is still on the way.\n\u003e \n\u003e The async interface depends on third-party event library, and so\n\u003e far, only libuv is supported.\n\nAsync code in redis-plus-plus looks like the following\n\n```cpp\nauto async_redis = AsyncRedis(opts, pool_opts);\n\nFuture\u003cstring\u003e ping_res = async_redis.ping();\n\ncout \u003c\u003c ping_res.get() \u003c\u003c endl;\n```\nAs the reader can see, the async interface is based on futures\nwhich is also known to have a bad performance.  The biggest\nproblem however with this async design is that it makes it\nimpossible to write asynchronous programs correctly since it\nstarts an async operation on every command sent instead of\nenqueueing a message and triggering a write when it can be sent.\nIt is also not clear how are pipelines realised with this design\n(if at all).\n\n\u003ca name=\"api-reference\"\u003e\u003c/a\u003e\n## Reference\n\nThe [High-Level](#high-level-api) page documents all public types.\n\n## Acknowledgement\n\nAcknowledgement to people that helped shape Boost.Redis\n\n* Richard Hodges ([madmongo1](https://github.com/madmongo1)): For very helpful support with Asio, the design of asynchronous programs, etc.\n* Vinícius dos Santos Oliveira ([vinipsmaker](https://github.com/vinipsmaker)): For useful discussion about how Boost.Redis consumes buffers in the read operation.\n* Petr Dannhofer ([Eddie-cz](https://github.com/Eddie-cz)): For helping me understand how the `AUTH` and `HELLO` command can influence each other.\n* Mohammad Nejati ([ashtum](https://github.com/ashtum)): For pointing out scenarios where calls to `async_exec` should fail when the connection is lost.\n* Klemens Morgenstern ([klemens-morgenstern](https://github.com/klemens-morgenstern)): For useful discussion about timeouts, cancellation, synchronous interfaces and general help with Asio.\n* Vinnie Falco ([vinniefalco](https://github.com/vinniefalco)): For general suggestions about how to improve the code and the documentation.\n* Bram Veldhoen ([bveldhoen](https://github.com/bveldhoen)): For contributing a Redis-streams example.\n\nAlso many thanks to all individuals that participated in the Boost\nreview\n\n* Zach Laine: https://lists.boost.org/Archives/boost/2023/01/253883.php\n* Vinnie Falco: https://lists.boost.org/Archives/boost/2023/01/253886.php\n* Christian Mazakas: https://lists.boost.org/Archives/boost/2023/01/253900.php\n* Ruben Perez: https://lists.boost.org/Archives/boost/2023/01/253915.php\n* Dmitry Arkhipov: https://lists.boost.org/Archives/boost/2023/01/253925.php\n* Alan de Freitas: https://lists.boost.org/Archives/boost/2023/01/253927.php\n* Mohammad Nejati: https://lists.boost.org/Archives/boost/2023/01/253929.php\n* Sam Hartsfield: https://lists.boost.org/Archives/boost/2023/01/253931.php\n* Miguel Portilla: https://lists.boost.org/Archives/boost/2023/01/253935.php\n* Robert A.H. Leahy: https://lists.boost.org/Archives/boost/2023/01/253928.php\n\nThe Reviews can be found at:\nhttps://lists.boost.org/Archives/boost/2023/01/date.php. The thread\nwith the ACCEPT from the review manager can be found here:\nhttps://lists.boost.org/Archives/boost/2023/01/253944.php.\n\n## Changelog\n\n### Boost 1.88\n\n* (Issue [233](https://github.com/boostorg/redis/issues/233))\n  To deal with keys that might not exits in the Redis server, the\n  library supports `std::optional`, for example\n  `response\u003cstd::optional\u003cstd::vector\u003cstd::string\u003e\u003e\u003e`. In some cases\n  however, such as the [MGET](https://redis.io/docs/latest/commands/mget/) command,\n  each element in the vector might be non exiting, now it is possible\n  to specify a response as `response\u003cstd::optional\u003cstd::vector\u003cstd::optional\u003cstd::string\u003e\u003e\u003e\u003e`.\n\n* (Issue [225](https://github.com/boostorg/redis/issues/225))\n  Use `deferred` as the connection default completion token.\n\n* (Issue [128](https://github.com/boostorg/redis/issues/128))\n  Adds a new `async_exec` overload that allows passing response\n  adapters. This makes it possible to receive Redis responses directly\n  in custom data structures thereby avoiding uncessary data copying.\n  Thanks to Ruben Perez (@anarthal) for implementing this feature.\n\n* There are also other multiple small improvements in this release,\n  users can refer to the git history for more details.\n\n### Boost 1.87\n\n* (Issue [205](https://github.com/boostorg/redis/issues/205))\n  Improves reaction time to disconnection by using `wait_for_one_error`\n  instead of `wait_for_all`. The function `connection::async_run` was\n  also changed to return EOF to the user when that error is received\n  from the server. That is a breaking change.\n\n* (Issue [210](https://github.com/boostorg/redis/issues/210))\n  Fixes the adapter of empty nested reposponses.\n\n* (Issues [211](https://github.com/boostorg/redis/issues/211) and [212](https://github.com/boostorg/redis/issues/212))\n  Fixes the reconnect loop that would hang under certain conditions,\n  see the linked issues for more details.\n\n* (Issue [219](https://github.com/boostorg/redis/issues/219))\n  Changes the default log level from `disabled` to `debug`.\n\n### Boost 1.85\n\n* (Issue [170](https://github.com/boostorg/redis/issues/170))\n  Under load and on low-latency networks it is possible to start\n  receiving responses before the write operation completed and while\n  the request is still marked as staged and not written. This messes\n  up with the heuristics that classifies responses as unsolicied or\n  not.\n\n* (Issue [168](https://github.com/boostorg/redis/issues/168)).\n  Provides a way of passing a custom SSL context to the connection.\n  The design here differs from that of Boost.Beast and Boost.MySql\n  since in Boost.Redis the connection owns the context instead of only\n  storing a reference to a user provided one. This is ok so because\n  apps need only one connection for their entire application, which\n  makes the overhead of one ssl-context per connection negligible.\n\n* (Issue [181](https://github.com/boostorg/redis/issues/181)).\n  See a detailed description of this bug in\n  [this](https://github.com/boostorg/redis/issues/181#issuecomment-1913346983)\n  comment.\n\n* (Issue [182](https://github.com/boostorg/redis/issues/182)).\n  Sets `\"default\"` as the default value of `config::username`. This\n  makes it simpler to use the `requirepass` configuration in Redis.\n\n* (Issue [189](https://github.com/boostorg/redis/issues/189)).\n  Fixes narrowing conversion by using `std::size_t` instead of\n  `std::uint64_t` for the sizes of bulks and aggregates. The code\n  relies now on `std::from_chars` returning an error if a value\n  greater than 32 is received on platforms on which the size\n  of `std::size_t` is 32.\n\n\n### Boost 1.84 (First release in Boost)\n\n* Deprecates the `async_receive` overload that takes a response. Users\n  should now first call `set_receive_response` to avoid constantly and\n  unnecessarily setting the same response.\n\n* Uses `std::function` to type erase the response adapter. This change\n  should not influence users in any way but allowed important\n  simplification in the connections internals. This resulted in\n  massive performance improvement.\n\n* The connection has a new member `get_usage()` that returns the\n  connection usage information, such as number of bytes written,\n  received etc.\n\n* There are massive performance improvements in the consuming of\n  server pushes which are now communicated with an `asio::channel` and\n  therefore can be buffered which avoids blocking the socket read-loop.\n  Batch reads are also supported by means of `channel.try_send` and\n  buffered messages can be consumed synchronously with\n  `connection::receive`. The function `boost::redis::cancel_one` has\n  been added to simplify processing multiple server pushes contained\n  in the same `generic_response`.  *IMPORTANT*: These changes may\n  result in more than one push in the response when\n  `connection::async_receive` resumes. The user must therefore be\n  careful when calling `resp.clear()`: either ensure that all message\n  have been processed or just use `consume_one`.\n\n### v1.4.2 (incorporates changes to conform the boost review and more)\n\n* Adds `boost::redis::config::database_index` to make it possible to\n  choose a database before starting running commands e.g. after an\n  automatic reconnection.\n\n* Massive performance improvement. One of my tests went from\n  140k req/s to 390k/s. This was possible after a parser\n  simplification that reduced the number of reschedules and buffer\n  rotations.\n\n* Adds Redis stream example.\n\n* Renames the project to Boost.Redis and moves the code into namespace\n  `boost::redis`.\n\n* As pointed out in the reviews the `to_bulk` and `from_bulk` names were too\n  generic for ADL customization points. They gained the prefix `boost_redis_`.\n\n* Moves `boost::redis::resp3::request` to `boost::redis::request`.\n\n* Adds new typedef `boost::redis::response` that should be used instead of\n  `std::tuple`.\n\n* Adds new typedef `boost::redis::generic_response` that should be used instead\n  of `std::vector\u003cresp3::node\u003cstd::string\u003e\u003e`.\n\n* Renames `redis::ignore` to `redis::ignore_t`.\n\n* Changes `async_exec` to receive a `redis::response` instead of an adapter,\n  namely, instead of passing `adapt(resp)` users should pass `resp` directly.\n\n* Introduces `boost::redis::adapter::result` to store responses to commands\n  including possible resp3 errors without losing the error diagnostic part. To\n  access values now use `std::get\u003cN\u003e(resp).value()` instead of\n  `std::get\u003cN\u003e(resp)`.\n\n* Implements full-duplex communication. Before these changes the connection\n  would wait for a response to arrive before sending the next one. Now requests\n  are continuously coalesced and written to the socket. `request::coalesce`\n  became unnecessary and was removed. I could measure significative performance\n  gains with these changes.\n\n* Improves serialization examples using Boost.Describe to serialize to JSON and protobuf. See\n  cpp20_json.cpp and cpp20_protobuf.cpp for more details.\n\n* Upgrades to Boost 1.81.0.\n\n* Fixes build with libc++.\n\n* Adds high-level functionality to the connection classes. For\n  example, `boost::redis::connection::async_run` will automatically\n  resolve, connect, reconnect and perform health checks.\n\n### v1.4.0-1\n\n* Renames `retry_on_connection_lost` to `cancel_if_unresponded`.  (v1.4.1)\n* Removes dependency on Boost.Hana, `boost::string_view`, Boost.Variant2 and Boost.Spirit.\n* Fixes build and setup CI on windows.\n\n### v1.3.0-1\n\n* Upgrades to Boost 1.80.0\n\n* Removes automatic sending of the `HELLO` command. This can't be\n  implemented properly without bloating the connection class. It is\n  now a user responsibility to send HELLO. Requests that contain it have\n  priority over other requests and will be moved to the front of the\n  queue, see `aedis::request::config` \n\n* Automatic name resolving and connecting have been removed from\n  `aedis::connection::async_run`. Users have to do this step manually\n  now. The reason for this change is that having them built-in doesn't\n  offer enough flexibility that is need for boost users.\n\n* Removes healthy checks and idle timeout. This functionality must now\n  be implemented by users, see the examples. This is\n  part of making Aedis useful to a larger audience and suitable for\n  the Boost review process.\n\n* The `aedis::connection` is now using a typeddef to a\n  `net::ip::tcp::socket` and  `aedis::ssl::connection` to\n  `net::ssl::stream\u003cnet::ip::tcp::socket\u003e`.  Users that need to use\n  other stream type must now specialize `aedis::basic_connection`.\n\n* Adds a low level example of async code.\n\n### v1.2.0\n\n* `aedis::adapt` supports now tuples created with `std::tie`.\n  `aedis::ignore` is now an alias to the type of `std::ignore`.\n\n* Provides allocator support for the internal queue used in the\n  `aedis::connection` class.\n\n* Changes the behaviour of `async_run` to complete with success if\n  asio::error::eof is received. This makes it easier to  write\n  composed operations with awaitable operators.\n\n* Adds allocator support in the `aedis::request` (a\n  contribution from Klemens Morgenstern).\n\n* Renames `aedis::request::push_range2` to `push_range`. The\n  suffix 2 was used for disambiguation. Klemens fixed it with SFINAE.\n\n* Renames `fail_on_connection_lost` to\n  `aedis::request::config::cancel_on_connection_lost`. Now, it will\n  only cause connections to be canceled when `async_run` completes.\n\n* Introduces `aedis::request::config::cancel_if_not_connected` which will\n  cause a request to be canceled if `async_exec` is called before a\n  connection has been established.\n\n* Introduces new request flag `aedis::request::config::retry` that if\n  set to true will cause the request to not be canceled when it was\n  sent to Redis but remained unresponded after `async_run` completed.\n  It provides a way to avoid executing commands twice.\n\n* Removes the `aedis::connection::async_run` overload that takes\n  request and adapter as parameters.\n\n* Changes the way `aedis::adapt()` behaves with\n  `std::vector\u003caedis::resp3::node\u003cT\u003e\u003e`. Receiving RESP3 simple errors,\n  blob errors or null won't causes an error but will be treated as\n  normal response.  It is the user responsibility to check the content\n  in the vector.\n\n* Fixes a bug in `connection::cancel(operation::exec)`. Now this\n  call will only cancel non-written requests.\n\n* Implements per-operation implicit cancellation support for\n  `aedis::connection::async_exec`. The following call will `co_await (conn.async_exec(...) || timer.async_wait(...))`\n  will cancel the request as long as it has not been written.\n\n* Changes `aedis::connection::async_run` completion signature to\n  `f(error_code)`. This is how is was in the past, the second\n  parameter was not helpful.\n\n* Renames `operation::receive_push` to `aedis::operation::receive`.\n\n### v1.1.0-1\n\n* Removes `coalesce_requests` from the `aedis::connection::config`, it\n  became a request property now, see `aedis::request::config::coalesce`.\n\n* Removes `max_read_size` from the `aedis::connection::config`. The maximum\n  read size can be specified now as a parameter of the\n  `aedis::adapt()` function.\n\n* Removes `aedis::sync` class, see intro_sync.cpp for how to perform\n  synchronous and thread safe calls. This is possible in Boost. 1.80\n  only as it requires `boost::asio::deferred`. \n\n* Moves from `boost::optional` to `std::optional`. This is part of\n  moving to C++17.\n\n* Changes the behaviour of the second `aedis::connection::async_run` overload\n  so that it always returns an error when the connection is lost.\n\n* Adds TLS support, see intro_tls.cpp.\n\n* Adds an example that shows how to resolve addresses over sentinels,\n  see subscriber_sentinel.cpp.\n\n* Adds a `aedis::connection::timeouts::resp3_handshake_timeout`. This is\n  timeout used to send the `HELLO` command.\n\n* Adds `aedis::endpoint` where in addition to host and port, users can\n  optionally provide username, password and the expected server role\n  (see `aedis::error::unexpected_server_role`).\n\n* `aedis::connection::async_run` checks whether the server role received in\n  the hello command is equal to the expected server role specified in\n  `aedis::endpoint`. To skip this check let the role variable empty.\n\n* Removes reconnect functionality from `aedis::connection`. It\n  is possible in simple reconnection strategies but bloats the class\n  in more complex scenarios, for example, with sentinel,\n  authentication and TLS. This is trivial to implement in a separate\n  coroutine. As a result the `enum event` and `async_receive_event`\n  have been removed from the class too.\n\n* Fixes a bug in `connection::async_receive_push` that prevented\n  passing any response adapter other that `adapt(std::vector\u003cnode\u003e)`.\n\n* Changes the behaviour of `aedis::adapt()` that caused RESP3 errors\n  to be ignored. One consequence of it is that `connection::async_run`\n  would not exit with failure in servers that required authentication.\n\n* Changes the behaviour of `connection::async_run` that would cause it\n  to complete with success when an error in the\n  `connection::async_exec` occurred.\n\n* Ports the buildsystem from autotools to CMake.\n\n### v1.0.0\n\n* Adds experimental cmake support for windows users.\n\n* Adds new class `aedis::sync` that wraps an `aedis::connection` in\n  a thread-safe and synchronous API.  All free functions from the\n  `sync.hpp` are now member functions of `aedis::sync`.\n\n* Split `aedis::connection::async_receive_event` in two functions, one\n  to receive events and another for server side pushes, see\n  `aedis::connection::async_receive_push`.\n\n* Removes collision between `aedis::adapter::adapt` and\n  `aedis::adapt`.\n\n* Adds `connection::operation` enum to replace `cancel_*` member\n  functions with a single cancel function that gets the operations\n  that should be cancelled as argument.\n\n* Bugfix: a bug on reconnect from a state where the `connection` object\n  had unsent commands. It could cause `async_exec` to never\n  complete under certain conditions.\n\n* Bugfix: Documentation of `adapt()` functions were missing from\n  Doxygen.\n\n### v0.3.0\n\n* Adds `experimental::exec` and `receive_event` functions to offer a\n  thread safe and synchronous way of executing requests across\n  threads. See `intro_sync.cpp` and `subscriber_sync.cpp` for\n  examples.\n\n* `connection::async_read_push` was renamed to `async_receive_event`.\n\n* `connection::async_receive_event` is now being used to communicate\n  internal events to the user, such as resolve, connect, push etc. For\n  examples see cpp20_subscriber.cpp and `connection::event`.\n\n* The `aedis` directory has been moved to `include` to look more\n  similar to Boost libraries. Users should now replace `-I/aedis-path`\n  with `-I/aedis-path/include` in the compiler flags.\n\n* The `AUTH` and `HELLO` commands are now sent automatically. This change was\n  necessary to implement reconnection. The username and password\n  used in `AUTH` should be provided by the user on\n  `connection::config`.\n\n* Adds support for reconnection. See `connection::enable_reconnect`.\n\n* Fixes a bug in the `connection::async_run(host, port)` overload\n  that was causing crashes on reconnection.\n\n* Fixes the executor usage in the connection class. Before these\n  changes it was imposing `any_io_executor` on users.\n\n* `connection::async_receiver_event` is not cancelled anymore when\n  `connection::async_run` exits. This change makes user code simpler.\n\n* `connection::async_exec` with host and port overload has been\n  removed. Use the other `connection::async_run` overload.\n\n* The host and port parameters from `connection::async_run` have been\n  move to `connection::config` to better support authentication and\n  failover.\n\n* Many simplifications in the `chat_room` example.\n\n* Fixes build in clang the compilers and makes some improvements in\n  the documentation.\n\n### v0.2.0-1\n\n* Fixes a bug that happens on very high load. (v0.2.1) \n* Major rewrite of the high-level API. There is no more need to use the low-level API anymore.\n* No more callbacks: Sending requests follows the ASIO asynchronous model.\n* Support for reconnection: Pending requests are not canceled when a connection is lost and are re-sent when a new one is established.\n* The library is not sending HELLO-3 on user behalf anymore. This is important to support AUTH properly.\n\n### v0.1.0-2\n\n* Adds reconnect coroutine in the `echo_server` example. (v0.1.2)\n* Corrects `client::async_wait_for_data` with `make_parallel_group` to launch operation. (v0.1.2)\n* Improvements in the documentation. (v0.1.2)\n* Avoids dynamic memory allocation in the client class after reconnection. (v0.1.2)\n* Improves the documentation and adds some features to the high-level client. (v.0.1.1)\n* Improvements in the design and documentation.\n\n### v0.0.1\n\n* First release to collect design feedback.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fboostorg%2Fredis","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fboostorg%2Fredis","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fboostorg%2Fredis/lists"}