Skip to content

Commit

Permalink
docs: update readme about client-side caching
Browse files Browse the repository at this point in the history
  • Loading branch information
rueian authored May 16, 2024
1 parent e90cf77 commit d6aeb96
Showing 1 changed file with 27 additions and 27 deletions.
54 changes: 27 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ A fast Golang Redis client that does auto pipelining and supports server-assiste
* [Server-assisted client-side caching](#server-assisted-client-side-caching)
* [Generic Object Mapping with client-side caching](./om)
* [Cache-Aside pattern with client-side caching](./rueidisaside)
* [Distributed Locks with client side caching](./rueidislock)
* [Distributed Locks with client-side caching](./rueidislock)
* [Helpers for writing tests with rueidis mock](./mock)
* [OpenTelemetry integration](./rueidisotel)
* [Hooks and other integrations](./rueidishook)
Expand Down Expand Up @@ -49,11 +49,11 @@ func main() {
}
```

Checkout more examples: [Command Response Cheatsheet](https://github.com/redis/rueidis#command-response-cheatsheet)
Check out more examples: [Command Response Cheatsheet](https://github.com/redis/rueidis#command-response-cheatsheet)

## Developer Friendly Command Builder

`client.B()` is the builder entrypoint to construct a redis command:
`client.B()` is the builder entry point to construct a redis command:

![Developer friendly command builder](https://user-images.githubusercontent.com/2727535/209358313-39000aee-eaa4-42e1-9748-0d3836c1264f.gif)\
<sub>_Recorded by @FZambia [Improving Centrifugo Redis Engine throughput and allocation efficiency with Rueidis Go library
Expand All @@ -63,7 +63,7 @@ Once a command is built, use either `client.Do()` or `client.DoMulti()` to send

**You ❗️SHOULD NOT❗️ reuse the command to another `client.Do()` or `client.DoMulti()` call because it has been recycled to the underlying `sync.Pool` by default.**

To reuse a command, use `Pin()` after `Build()` and it will prevent the command from being recycled.
To reuse a command, use `Pin()` after `Build()` and it will prevent the command from being recycled.


## [Pipelining](https://redis.io/docs/manual/pipelining/)
Expand All @@ -89,7 +89,7 @@ func BenchmarkPipelining(b *testing.B, client rueidis.Client) {

### Benchmark comparison with go-redis v9

Comparing to go-redis, Rueidis has higher throughput across 1, 8, and 64 parallelism settings.
Compared to go-redis, Rueidis has higher throughput across 1, 8, and 64 parallelism settings.

It is even able to achieve **~14x** throughput over go-redis in a local benchmark of Macbook Pro 16" M1 Pro 2021. (see `parallelism(64)-key(16)-value(64)-10`)

Expand Down Expand Up @@ -126,7 +126,7 @@ client.DoMultiCache(ctx,
rueidis.CT(client.B().Get().Key("k2").Cache(), 2*time.Minute))
```

Cached responses will be invalidated either when being notified by redis servers or when their client side TTLs are reached.
Cached responses, including Redis Nils, will be invalidated either when being notified by redis servers or when their client-side TTLs are reached. See https://github.com/redis/rueidis/issues/534 for more details.

### Benchmark

Expand All @@ -136,15 +136,15 @@ Server-assisted client-side caching can dramatically boost latencies and through

Benchmark source code: https://github.com/rueian/rueidis-benchmark

### Client Side Caching Helpers
### Client-Side Caching Helpers

Use `CacheTTL()` to check the remaining client side TTL in seconds:
Use `CacheTTL()` to check the remaining client-side TTL in seconds:

```golang
client.DoCache(ctx, client.B().Get().Key("k1").Cache(), time.Minute).CacheTTL() == 60
```

Use `IsCacheHit()` to verify that if the response came from the client side memory:
Use `IsCacheHit()` to verify if the response came from the client-side memory:

```golang
client.DoCache(ctx, client.B().Get().Key("k1").Cache(), time.Minute).IsCacheHit() == true
Expand All @@ -154,12 +154,12 @@ If the OpenTelemetry is enabled by the `rueidisotel.NewClient(option)`, then the
* rueidis_do_cache_miss
* rueidis_do_cache_hits

### MGET/JSON.MGET Client Side Caching Helpers
### MGET/JSON.MGET Client-Side Caching Helpers

`rueidis.MGetCache` and `rueidis.JsonMGetCache` are handy helpers fetching multiple keys across different slots through the client side caching.
`rueidis.MGetCache` and `rueidis.JsonMGetCache` are handy helpers fetching multiple keys across different slots through the client-side caching.
They will first group keys by slot to build `MGET` or `JSON.MGET` commands respectively and then send requests with only cache missed keys to redis nodes.

### Broadcast Mode Client Side Caching
### Broadcast Mode Client-Side Caching

Although the default is opt-in mode, you can use broadcast mode by specifying your prefixes in `ClientOption.ClientTrackingOptions`:

Expand All @@ -178,7 +178,7 @@ client.DoCache(ctx, client.B().Get().Key("prefix1:1").Cache(), time.Minute).IsCa
Please make sure that commands passed to `DoCache()` and `DoMultiCache()` are covered by your prefixes.
Otherwise, their client-side cache will not be invalidated by redis.

### Client Side Caching with Cache Aside Pattern
### Client-Side Caching with Cache Aside Pattern

Cache-Aside is a widely used caching strategy.
[rueidisaside](https://github.com/redis/rueidis/blob/main/rueidisaside/README.md) can help you cache data into your client-side cache backed by Redis. For example:
Expand All @@ -202,9 +202,9 @@ val, err := client.Get(context.Background(), time.Minute, "mykey", func(ctx cont

Please refer to the full example at [rueidisaside](https://github.com/redis/rueidis/blob/main/rueidisaside/README.md).

### Disable Client Side Caching
### Disable Client-Side Caching

Some Redis provider doesn't support client-side caching, ex. Google Cloud Memorystore.
Some Redis providers don't support client-side caching, ex. Google Cloud Memorystore.
You can disable client-side caching by setting `ClientOption.DisableCache` to `true`.
This will also fall back `client.DoCache()` and `client.DoMultiCache()` to `client.Do()` and `client.DoMulti()`.

Expand Down Expand Up @@ -233,13 +233,13 @@ err = client.Receive(context.Background(), client.B().Subscribe().Channel("ch1",
The provided handler will be called with the received message.

It is important to note that `client.Receive()` will keep blocking until returning a value in the following cases:
1. return `nil` when received any unsubscribe/punsubscribe message related to the provided `subscribe` command.
1. return `nil` when receiving any unsubscribe/punsubscribe message related to the provided `subscribe` command.
2. return `rueidis.ErrClosing` when the client is closed manually.
3. return `ctx.Err()` when the `ctx` is done.
4. return non-nil `err` when the provided `subscribe` command fails.

While the `client.Receive()` call is blocking, the `Client` is still able to accept other concurrent requests,
and they are sharing the same tcp connection. If your message handler may take some time to complete, it is recommended
and they are sharing the same TCP connection. If your message handler may take some time to complete, it is recommended
to use the `client.Receive()` inside a `client.Dedicated()` for not blocking other concurrent requests.

### Alternative PubSub Hooks
Expand Down Expand Up @@ -294,7 +294,7 @@ c, cancel := client.Dedicate()
defer cancel()

c.Do(ctx, c.B().Watch().Key("k1", "k2").Build())
// do the rest CAS operations with the `client` who occupies a connection
// do the rest CAS operations with the `client` who occupies a connection
```

However, occupying a connection is not good in terms of throughput. It is better to use [Lua script](#lua-script) to perform
Expand All @@ -315,7 +315,7 @@ list, err := script.Exec(ctx, client, []string{"k1", "k2"}, []string{"a1", "a2"}
## Streaming Read

`client.DoStream()` and `client.DoMultiStream()` can be used to send large redis responses to an `io.Writer`
directly without allocating them in the memory. They work by first sending commands to a dedicated connection acquired from a pool,
directly without allocating them to the memory. They work by first sending commands to a dedicated connection acquired from a pool,
then directly copying the response values to the given `io.Writer`, and finally recycling the connection.

```go
Expand Down Expand Up @@ -381,7 +381,7 @@ client, err := rueidis.NewClient(rueidis.ClientOption{

You can use `ParseURL` or `MustParseURL` to construct a `ClientOption`.

The provided url must be started with either `redis://`, `rediss://` or `unix://`.
The provided URL must be started with either `redis://`, `rediss://` or `unix://`.

Currently supported url parameters are `db`, `dial_timeout`, `write_timeout`, `addr`, `protocol`, `client_cache`, `client_name`, `max_retries`, and `master_set`.

Expand All @@ -400,22 +400,22 @@ client, err = rueidis.NewClient(rueidis.MustParseURL("redis://127.0.0.1:26379/0?
If you want to construct commands that are absent from the command builder, you can use `client.B().Arbitrary()`:

```golang
// This will result into [ANY CMD k1 k2 a1 a2]
// This will result in [ANY CMD k1 k2 a1 a2]
client.B().Arbitrary("ANY", "CMD").Keys("k1", "k2").Args("a1", "a2").Build()
```

## Working with JSON, Raw `[]byte`, and Vector Similarity Search

The command builder treats all the parameters as Redis strings, which are binary safe. This means that users can store `[]byte`
directly into Redis without conversion. And the `rueidis.BinaryString` helper can convert `[]byte` to `string` without copy. For example:
directly into Redis without conversion. And the `rueidis.BinaryString` helper can convert `[]byte` to `string` without copying. For example:

```golang
client.B().Set().Key("b").Value(rueidis.BinaryString([]byte{...})).Build()
```

Treating all the parameters as Redis strings also means that the command builder doesn't do any quoting, conversion automatically for users.

When working with RedisJSON, users frequently need to prepare JSON string in Redis string. And `rueidis.JSON` can help:
When working with RedisJSON, users frequently need to prepare JSON strings in Redis strings. And `rueidis.JSON` can help:

```golang
client.B().JsonSet().Key("j").Path("$.myStrField").Value(rueidis.JSON("str")).Build()
Expand All @@ -434,10 +434,10 @@ n, resp, err := client.Do(ctx, cmd).AsFtSearch()

## Command Response Cheatsheet

While the command builder is developer friendly, the response parser is a little unfriendly. Developers must know what
While the command builder is developer-friendly, the response parser is a little unfriendly. Developers must know what
type of Redis response will be returned from the server beforehand and which parser they should use. Otherwise, it panics.

It is hard to remember what type of message will be returned and which parsing to used. So, here are some common examples:
It is hard to remember what type of message will be returned and which parsing to use. So, here are some common examples:

```golang
// GET
Expand Down Expand Up @@ -518,7 +518,7 @@ for _, user := range users {

### !!!!!! DO NOT DO THIS !!!!!!

Please make sure that all values in the result have same JSON structure.
Please make sure that all values in the result have the same JSON structures.

```golang
// Set a pure string value
Expand All @@ -531,7 +531,7 @@ users := make([]*User, 0)
if err := rueidis.DecodeSliceOfJSON(client.Do(ctx, client.B().Mget().Key("user1").Build()), &users); err != nil {
return err
}
// -> Error: invalid character 'u' looking for beginning of value
// -> Error: invalid character 'u' looking for the beginning of the value
// in this case, use client.Do(ctx, client.B().Mget().Key("user1").Build()).AsStrSlice()
```

Expand Down

0 comments on commit d6aeb96

Please sign in to comment.