Skip to content

Commit 5ac6798

Browse files
committed
Merge branch 'main' into DOC-5048
2 parents bad4c70 + 8587667 commit 5ac6798

File tree

10 files changed

+748
-159
lines changed

10 files changed

+748
-159
lines changed

content/develop/clients/jedis/produsage.md

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,62 @@ title: Production usage
1515
weight: 6
1616
---
1717

18-
The following sections explain how to handle situations that may occur
19-
in your production environment.
18+
This guide offers recommendations to get the best reliability and
19+
performance in your production environment.
20+
21+
## Checklist
22+
23+
Each item in the checklist below links to the section
24+
for a recommendation. Use the checklist icons to record your
25+
progress in implementing the recommendations.
26+
27+
{{< checklist "prodlist" >}}
28+
{{< checklist-item "#connection-pooling" >}}Connection pooling{{< /checklist-item >}}
29+
{{< checklist-item "#client-side-caching" >}}Client-side caching{{< /checklist-item >}}
30+
{{< checklist-item "#timeouts" >}}Timeouts{{< /checklist-item >}}
31+
{{< checklist-item "#health-checks" >}}Health checks{{< /checklist-item >}}
32+
{{< checklist-item "#exception-handling" >}}Exception handling{{< /checklist-item >}}
33+
{{< checklist-item "#dns-cache-and-redis" >}}DNS cache and Redis{{< /checklist-item >}}
34+
{{< /checklist >}}
35+
36+
## Recommendations
37+
38+
The sections below offer recommendations for your production environment. Some
39+
of them may not apply to your particular use case.
40+
41+
### Connection pooling
42+
43+
Example code often opens a connection at the start, demonstrates a feature,
44+
and then closes the connection at the end. However, production code
45+
typically uses connections many times intermittently. Repeatedly opening
46+
and closing connections has a performance overhead.
47+
48+
Use [connection pooling]({{< relref "/develop/clients/pools-and-muxing" >}})
49+
to avoid the overhead of opening and closing connections without having to
50+
write your own code to cache and reuse open connections. See
51+
[Connect with a connection pool]({{< relref "/develop/clients/jedis/connect#connect-with-a-connection-pool" >}})
52+
to learn how to use this technique with Jedis.
53+
54+
### Client-side caching
55+
56+
[Client-side caching]({{< relref "/develop/clients/client-side-caching" >}})
57+
involves storing the results from read-only commands in a local cache. If the
58+
same command is executed again later, the results can be obtained from the cache,
59+
without contacting the server. This improves command execution time on the client,
60+
while also reducing network traffic and server load. See
61+
[Connect using client-side caching]({{< relref "/develop/clients/jedis/connect#connect-using-client-side-caching" >}})
62+
for more information and example code.
2063

2164
### Timeouts
2265

23-
To set a timeout for a connection, use the `JedisPooled` or `JedisPool` constructor with the `timeout` parameter, or use `JedisClientConfig` with the `socketTimeout` and `connectionTimeout` parameters:
66+
If a network or server error occurs while your code is opening a
67+
connection or issuing a command, it can end up hanging indefinitely.
68+
You can prevent this from happening by setting timeouts for socket
69+
reads and writes and for opening connections.
70+
71+
To set a timeout for a connection, use the `JedisPooled` or `JedisPool` constructor with the `timeout` parameter, or use `JedisClientConfig` with the `socketTimeout` and `connectionTimeout` parameters.
72+
(The socket timeout is the maximum time allowed for reading or writing data while executing a
73+
command. The connection timeout is the maximum time allowed for establishing a new connection.)
2474

2575
```java
2676
HostAndPort hostAndPort = new HostAndPort("localhost", 6379);
@@ -34,9 +84,33 @@ JedisPooled jedisWithTimeout = new JedisPooled(hostAndPort,
3484
);
3585
```
3686

87+
### Health checks
88+
89+
If your code doesn't access the Redis server continuously then it
90+
might be useful to make a "health check" periodically (perhaps once
91+
every few seconds). You can do this using a simple
92+
[`PING`]({{< relref "/commands/ping" >}}) command:
93+
94+
```java
95+
try (Jedis jedis = jedisPool.getResource()) {
96+
if (! "PONG".equals(jedis.ping())) {
97+
// Report problem.
98+
}
99+
}
100+
```
101+
102+
Health checks help to detect problems as soon as possible without
103+
waiting for a user to report them.
104+
37105
### Exception handling
38106

39-
The Jedis Exception Hierarchy is rooted on `JedisException`, which implements `RuntimeException`, and are therefore all unchecked exceptions.
107+
Redis handles many errors using return values from commands, but there
108+
are also situations where exceptions can be thrown. In production code,
109+
you should handle exceptions as they occur.
110+
111+
The Jedis exception hierarchy is rooted on `JedisException`, which implements
112+
`RuntimeException`. All exceptions in the hierarchy are therefore unchecked
113+
exceptions.
40114

41115
```
42116
JedisException

content/develop/clients/jedis/vecsearch.md

Lines changed: 148 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ of their meaning.
3131
In the example below, we use the [HuggingFace](https://huggingface.co/) model
3232
[`all-mpnet-base-v2`](https://huggingface.co/sentence-transformers/all-mpnet-base-v2)
3333
to generate the vector embeddings to store and index with Redis Query Engine.
34+
The code is first demonstrated for hash documents with a
35+
separate section to explain the
36+
[differences with JSON documents](#differences-with-json-documents).
3437

3538
## Initialize
3639

@@ -75,6 +78,7 @@ import java.nio.ByteBuffer;
7578
import java.nio.ByteOrder;
7679
import java.util.Map;
7780
import java.util.List;
81+
import org.json.JSONObject;
7882

7983
// Tokenizer to generate the vector embeddings.
8084
import ai.djl.huggingface.tokenizers.HuggingFaceTokenizer;
@@ -185,8 +189,9 @@ as shown below to create the embedding that represents the `content` field.
185189
The `getIds()` method that follows `encode()` obtains the vector
186190
of `long` values which we then convert to a `float` array stored as a `byte`
187191
string using our helper method. Use the `byte` string representation when you are
188-
indexing hash objects (as we are here), but use the default list of `float` for
189-
JSON objects. Note that when we set the `embedding` field, we must use an overload
192+
indexing hash objects (as we are here), but use an array of `float` for
193+
JSON objects (see [Differences with JSON objects](#differences-with-json-documents)
194+
below). Note that when we set the `embedding` field, we must use an overload
190195
of `hset()` that requires `byte` arrays for each of the key, the field name, and
191196
the value, which is why we include the `getBytes()` calls on the strings.
192197

@@ -281,6 +286,147 @@ For this model, the text *"That is a happy dog"*
281286
is the result judged to be most similar in meaning to the query text
282287
*"That is a happy person"*.
283288

289+
## Differences with JSON documents
290+
291+
Indexing JSON documents is similar to hash indexing, but there are some
292+
important differences. JSON allows much richer data modeling with nested fields, so
293+
you must supply a [path]({{< relref "/develop/data-types/json/path" >}}) in the schema
294+
to identify each field you want to index. However, you can declare a short alias for each
295+
of these paths (using the `as()` option) to avoid typing it in full for
296+
every query. Also, you must specify `IndexDataType.JSON` when you create the index.
297+
298+
The code below shows these differences, but the index is otherwise very similar to
299+
the one created previously for hashes:
300+
301+
```java
302+
SchemaField[] jsonSchema = {
303+
TextField.of("$.content").as("content"),
304+
TagField.of("$.genre").as("genre"),
305+
VectorField.builder()
306+
.fieldName("$.embedding").as("embedding")
307+
.algorithm(VectorAlgorithm.HNSW)
308+
.attributes(
309+
Map.of(
310+
"TYPE", "FLOAT32",
311+
"DIM", 768,
312+
"DISTANCE_METRIC", "L2"
313+
)
314+
)
315+
.build()
316+
};
317+
318+
jedis.ftCreate("vector_json_idx",
319+
FTCreateParams.createParams()
320+
.addPrefix("jdoc:")
321+
.on(IndexDataType.JSON),
322+
jsonSchema
323+
);
324+
```
325+
326+
An important difference with JSON indexing is that the vectors are
327+
specified using arrays of `float` instead of binary strings. This requires
328+
a modified version of the `longsToFloatsByteString()` method
329+
used previously:
330+
331+
```java
332+
public static float[] longArrayToFloatArray(long[] input) {
333+
float[] floats = new float[input.length];
334+
for (int i = 0; i < input.length; i++) {
335+
floats[i] = input[i];
336+
}
337+
return floats;
338+
}
339+
```
340+
341+
Use [`jsonSet()`]({{< relref "/commands/json.set" >}}) to add the data
342+
instead of [`hset()`]({{< relref "/commands/hset" >}}). Use instances
343+
of `JSONObject` to supply the data instead of `Map`, as you would for
344+
hash objects.
345+
346+
```java
347+
String jSentence1 = "That is a very happy person";
348+
349+
JSONObject jdoc1 = new JSONObject()
350+
.put("content", jSentence1)
351+
.put("genre", "persons")
352+
.put(
353+
"embedding",
354+
longArrayToFloatArray(
355+
sentenceTokenizer.encode(jSentence1).getIds()
356+
)
357+
);
358+
359+
jedis.jsonSet("jdoc:1", Path2.ROOT_PATH, jdoc1);
360+
361+
String jSentence2 = "That is a happy dog";
362+
363+
JSONObject jdoc2 = new JSONObject()
364+
.put("content", jSentence2)
365+
.put("genre", "pets")
366+
.put(
367+
"embedding",
368+
longArrayToFloatArray(
369+
sentenceTokenizer.encode(jSentence2).getIds()
370+
)
371+
);
372+
373+
jedis.jsonSet("jdoc:2", Path2.ROOT_PATH, jdoc2);
374+
375+
String jSentence3 = "Today is a sunny day";
376+
377+
JSONObject jdoc3 = new JSONObject()
378+
.put("content", jSentence3)
379+
.put("genre", "weather")
380+
.put(
381+
"embedding",
382+
longArrayToFloatArray(
383+
sentenceTokenizer.encode(jSentence3).getIds()
384+
)
385+
);
386+
387+
jedis.jsonSet("jdoc:3", Path2.ROOT_PATH, jdoc3);
388+
```
389+
390+
The query is almost identical to the one for the hash documents. This
391+
demonstrates how the right choice of aliases for the JSON paths can
392+
save you having to write complex queries. An important thing to notice
393+
is that the vector parameter for the query is still specified as a
394+
binary string (using the `longsToFloatsByteString()` method), even though
395+
the data for the `embedding` field of the JSON was specified as an array.
396+
397+
```java
398+
String jSentence = "That is a happy person";
399+
400+
int jK = 3;
401+
Query jq = new Query("*=>[KNN $K @embedding $BLOB AS distance]").
402+
returnFields("content", "distance").
403+
addParam("K", jK).
404+
addParam(
405+
"BLOB",
406+
longsToFloatsByteString(
407+
sentenceTokenizer.encode(jSentence).getIds()
408+
)
409+
)
410+
.setSortBy("distance", true)
411+
.dialect(2);
412+
413+
// Execute the query
414+
List<Document> jDocs = jedis
415+
.ftSearch("vector_json_idx", jq)
416+
.getDocuments();
417+
418+
```
419+
420+
Apart from the `jdoc:` prefixes for the keys, the result from the JSON
421+
query is the same as for hash:
422+
423+
```
424+
Results:
425+
ID: jdoc:2, Distance: 1411344, Content: That is a happy dog
426+
ID: jdoc:1, Distance: 9301635, Content: That is a very happy person
427+
ID: jdoc:3, Distance: 67178800, Content: Today is a sunny day
428+
```
429+
284430
## Learn more
285431

286432
See

content/develop/clients/lettuce/produsage.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,31 @@ In some cases, the defaults are based on environment-specific settings (e.g., op
2929
For more details on setting specific timeouts, see the [Lettuce reference guide](https://redis.github.io/lettuce/).
3030
{{% /alert %}}
3131

32+
### Prerequisites
33+
34+
To set TCP-level timeouts, you need to ensure you have one of [Netty Native Transports](https://netty.io/wiki/native-transports.html) installed. The most common one is `netty-transport-native-epoll`, which is used for Linux systems. You can add it to your project by including the following dependency in your `pom.xml` file:
35+
36+
```xml
37+
<dependency>
38+
<groupId>io.netty</groupId>
39+
<artifactId>netty-transport-native-epoll</artifactId>
40+
<version>${netty.version}</version> <!-- e.g., 4.1.118.Final -->
41+
<classifier>linux-x86_64</classifier>
42+
</dependency>
43+
```
44+
45+
Once you have the native transport dependency, you can verify that by using the following code:
46+
47+
```java
48+
logger.info("Lettuce epool is available: {}", EpollProvider.isAvailable());
49+
```
50+
51+
If the snippet above returns `false`, you need to enable debugging logging for `io.lettuce.core` and `io.netty` to see why the native transport is not available.
52+
53+
For more information on using Netty Native Transport, see the [Lettuce reference guide](https://redis.github.io/lettuce/advanced-usage/#native-transports).
54+
55+
### Setting timeouts
56+
3257
Below is an example of setting socket-level timeouts. The `TCP_USER_TIMEOUT` setting is useful for scenarios where the server stops responding without acknowledging the last request, while the `KEEPALIVE` setting is good for detecting dead connections where there is no traffic between the client and the server.
3358

3459
```java

0 commit comments

Comments
 (0)