Skip to content

Commit 25fd74a

Browse files
adam-fowlerheckj
andauthored
subscription documentation (#164)
* Add pipelining article, update index and README Signed-off-by: Adam Fowler <[email protected]> * Subscriptions Signed-off-by: Adam Fowler <[email protected]> * Subscription article Signed-off-by: Adam Fowler <[email protected]> * Update Sources/Valkey/Documentation.docc/Pubsub.md Co-authored-by: Joseph Heck <[email protected]> Signed-off-by: Adam Fowler <[email protected]> * Apply suggestions from code review Co-authored-by: Joseph Heck <[email protected]> Signed-off-by: Adam Fowler <[email protected]> * Update Sources/Valkey/Documentation.docc/Pubsub.md Co-authored-by: Joseph Heck <[email protected]> Signed-off-by: Adam Fowler <[email protected]> * Update Sources/Valkey/Documentation.docc/Pubsub.md Co-authored-by: Joseph Heck <[email protected]> Signed-off-by: Adam Fowler <[email protected]> * Update Sources/Valkey/Documentation.docc/Pubsub.md Co-authored-by: Joseph Heck <[email protected]> Signed-off-by: Adam Fowler <[email protected]> * Client functions are only available on ValkeyConnection Signed-off-by: Adam Fowler <[email protected]> * fixes for API changes Signed-off-by: Adam Fowler <[email protected]> --------- Signed-off-by: Adam Fowler <[email protected]> Co-authored-by: Joseph Heck <[email protected]>
1 parent 53b5822 commit 25fd74a

File tree

3 files changed

+114
-5
lines changed

3 files changed

+114
-5
lines changed

Sources/Valkey/Documentation.docc/Pipelining.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Send multiple commands at once without waiting for the response of each command.
66

77
Valkey pipelining is a technique for improving performance by issuing multiple commands at once without waiting for the response to each individual command. Pipelining not only reduces the latency cost of waiting for the result of each command it also reduces the cost to the server as it reduces I/O costs. Multiple commands can be read with a single syscall, and multiple results are delivered with a single syscall.
88

9-
## Implementation
9+
### Implementation
1010

1111
In valkey-swift each command has its own type conforming to the protocol ``ValkeyCommand``. This type is initialized with the parameters of the command and has an `associatedtype` ``ValkeyCommand/Response`` which is the expected response type of the command. The ``ValkeyClient/execute(_:)->(_,_)`` command takes a parameter pack of types conforming to ``ValkeyCommand`` and returns a parameter pack containing the results holding the corresponding responses of each command.
1212

@@ -22,18 +22,18 @@ if let result = try getResult.get().map({ String(buffer: $0) }) {
2222
}
2323
```
2424

25-
## Pipelining and Concurrency
25+
### Pipelining and Concurrency
2626

2727
Being able to have multiple requests in transit on a single connection means we can have multiple tasks use that connection concurrently. Each request is added to a queue and as each response comes back the first request on the queue is popped off and given the response. By using a single connection across multiple tasks you can reduce the number of connections to your database.
2828

2929
```swift
3030
try await client.withConnection { connection in
3131
try await withThrowingTaskGroup(of: Void.self) { group in
3232
group.addTask {
33-
_ = try await connection.lpush("fooList", elements: ["bar"])
33+
try await connection.lpush("fooList", elements: ["bar"])
3434
}
3535
group.addTask {
36-
_ = try await connection.rpush("fooList2", elements: ["baz"])
36+
try await connection.rpush("fooList2", elements: ["baz"])
3737
}
3838
try await group.waitForAll()
3939
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Subscriptions
2+
3+
Implementing Pub/Sub using valkey-swift
4+
5+
## Overview
6+
7+
Valkey provides publish and subscribe (Pub/Sub) messaging support using the `PUBLISH`, `SUBSCRIBE` and `UNSUBSCRIBE` commands. It has the concept of a channel that a client can both publish to and subscribe to. The server sends any messages published to a channel to clients subscribed to that channel. Valkey channels are not persisted, for instance if a message is published to a channel that has no subscribers, that message is lost.
8+
9+
### Publishing
10+
11+
Valkey has one function for publishing to a channel ``ValkeyClientProtocol/publish(channel:message:)``. As a member function of ``ValkeyClientProtocol``, it is available from the types that conform to it which include ``ValkeyConnection``, ``ValkeyClient`` and ``ValkeyClusterClient``.
12+
13+
```swift
14+
try await valkeyClient.publish(channel: "channel1", message: "Hello, World!")
15+
```
16+
17+
### Subscribing
18+
19+
Use ``ValkeyConnection/subscribe(to:isolation:process:)-(String...,_,_)`` to subscribe to a single or multiple channels and receive every message published to the channel via an AsyncSequence. When you exit the closure provided, the Valkey client sends the relevant `UNSUBSCRIBE` messages.
20+
21+
```swift
22+
try await valkeyClient.withConnection { connection in
23+
try await connection.subscribe(to: ["channel1", "channel2"]) { subscription in
24+
for try await item in subscription {
25+
// a subscription item includes the channel the message was published on
26+
// as well as the message
27+
print(item.channel)
28+
print(item.message)
29+
}
30+
}
31+
}
32+
```
33+
34+
Valkey-swift uses the RESP3 protocol, which allows for commands to be run on the same connection as subsciption. This allows you to call `SET` with the same connection as the subscription, as the next example illustrates:
35+
36+
```swift
37+
try await connection.subscribe(to: ["channel1"]) { subscription in
38+
for try await entry in subscription {
39+
try await connection.set("channel1/last", value: entry.message)
40+
}
41+
}
42+
```
43+
44+
### Patterns
45+
46+
Valkey allows you to use glob style patterns to subscribe to a range of channels. These are available with the function ``ValkeyConnection/psubscribe(to:isolation:process:)-([String],_,_)``. This is formatted in a similar manner to normal subscriptions.
47+
48+
```swift
49+
try await connection.subscribe(to: ["channel*"]) { subscription in
50+
for try await entry in subscription {
51+
let channel = "\(entry.channel)/last"
52+
try await connection.set(channel, value: entry.message)
53+
}
54+
}
55+
```
56+
57+
The code above receives all messages sent to channels prefixed with the string "channel".
58+
59+
More can be found out about Valkey pub/sub in the [Valkey documentation](https://valkey.io/topics/pubsub/).
60+
61+
### Support for Client Side Caching
62+
63+
Client side caching is a way to improve performance of a service using Valkey. Caching the values of specific keys locally avoids putting pressure on your Valkey server unnecessarily. For a client side cache to work, you need to know when to invalidate the local data. Valkey provides two different ways to do this, both using pub/sub:
64+
65+
1) The server remembers the keys a connection has accessed and publishes events to the invalidation channel whenever those keys are modified.
66+
2) Broadcasting, where the server doesn't keep a record of what keys have been accessed. The client provides a prefix of the keys they are interested in and the server sends events to the invalidation channel whenever any key that matches that prefix is modified.
67+
68+
#### Enabling Tracking
69+
70+
Connections start without invalidation tracking enabled. To enable receiving invalidation events on a connection, call ``ValkeyConnection/clientTracking(status:clientId:prefixes:bcast:optin:optout:noloop:)``. For example:
71+
72+
```swift
73+
try await connection.clientTracking(status: .on)
74+
```
75+
76+
This tells the server to use the first invalidation method where it remembers the keys accessed by a connection. If you would like to track changes to all keys with a prefix, use:
77+
78+
```swift
79+
try await connection.clientTracking(
80+
status: .on,
81+
prefixes: ["object:"],
82+
bcast: true
83+
)
84+
```
85+
86+
#### Subscribing to Invalidation Events
87+
88+
Once tracking is enabled you can subscribe to invalidation events using ``ValkeyConnection/subscribeKeyInvalidations(process:)``. The AsyncSequence passed to the `process` closure is a list of keys that have been invalidated.
89+
90+
```swift
91+
try await connection.subscribeKeyInvalidations { keys in
92+
for try await key in keys {
93+
myCache.invalidate(key)
94+
}
95+
}
96+
```
97+
98+
#### Redirecting Invalidation Events
99+
100+
With RESP3 it is possible to perform data operations and receive the invalidation events on the same connection, but Valkey client tracking also allows you to redirect invalidation events to another connection. Given that the Valkey client uses a persistent connection pool, it is preferable to use a single connection for receiving invalidation messages to implement a system wide cache.
101+
102+
For this to work you need to know the id of the connection that is subscribed to the key invalidation events. Get the connection id using ``ValkeyConnection/clientId()`` and use it when you set up tracking.
103+
104+
```swift
105+
try await connection.clientTracking(status: .on, clientId: id)
106+
```
107+
108+
More can be found out about Valkey client side caching in the [Valkey documentation](https://valkey.io/topics/client-side-caching/).

Sources/Valkey/Documentation.docc/index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Valkey-swift is a swift based client for Valkey, the high-performance key/value
88

99
### Setup
1010

11-
``ValkeyClient`` and ``ValkeyClusterClient`` use a connection pool that requires a background root task to run all the maintenance work required to establish connections and maintain the cluster state. You can either run it using a Task group
11+
``ValkeyClient`` and ``ValkeyClusterClient`` use a connection pool that requires a background root task to run all the maintenance work required to establish connections and maintain the cluster state. You can either run them using a Task group
1212

1313
```swift
1414
let valkeyClient = ValkeyClient(.hostname("localhost", port: 6379), logger: logger)
@@ -45,6 +45,7 @@ try await valkeyClient.withConnection { connection in
4545
### Articles
4646

4747
- <doc:Pipelining>
48+
- <doc:Pubsub>
4849
- <doc:Transactions>
4950

5051
### Client

0 commit comments

Comments
 (0)