Skip to content

DOC-5064 improvements to Jedis production usage advice #1400

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
104 changes: 100 additions & 4 deletions content/develop/clients/jedis/produsage.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,63 @@ title: Production usage
weight: 6
---

The following sections explain how to handle situations that may occur
in your production environment.
This guide offers recommendations to get the best reliability and
performance in your production environment.

## Checklist

Each item in the checklist below links to the section
for a recommendation. Use the checklist icons to record your
progress in implementing the recommendations.

{{< checklist "prodlist" >}}
{{< checklist-item "#connection-pooling" >}}Connection pooling{{< /checklist-item >}}
{{< checklist-item "#client-side-caching" >}}Client-side caching{{< /checklist-item >}}
{{< checklist-item "#timeouts" >}}Timeouts{{< /checklist-item >}}
{{< checklist-item "#health-checks" >}}Health checks{{< /checklist-item >}}
{{< checklist-item "#tcp-keepalive" >}}TCP keepalive{{< /checklist-item >}}
{{< checklist-item "#exception-handling" >}}Exception handling{{< /checklist-item >}}
{{< checklist-item "#dns-cache-and-redis" >}}DNS cache and Redis{{< /checklist-item >}}
{{< /checklist >}}

## Recommendations

The sections below offer recommendations for your production environment. Some
of them may not apply to your particular use case.

### Connection pooling

Example code often opens a connection at the start, demonstrates a feature,
and then closes the connection at the end. However, production code
typically uses connections many times intermittently. Repeatedly opening
and closing connections has a performance overhead.

Use [connection pooling]({{< relref "/develop/clients/pools-and-muxing" >}})
to avoid the overhead of opening and closing connections without having to
write your own code to cache and reuse open connections. See
[Connect with a connection pool]({{< relref "/develop/clients/jedis/connect#connect-with-a-connection-pool" >}})
to learn how to use this technique with Jedis.

### Client-side caching

[Client-side caching]({{< relref "/develop/clients/client-side-caching" >}})
involves storing the results from read-only commands in a local cache. If the
same command is executed again later, the results can be obtained from the cache,
without contacting the server. This improves command execution time on the client,
while also reducing network traffic and server load. See
[Connect using client-side caching]({{< relref "/develop/clients/jedis/connect#connect-using-client-side-caching" >}})
for more information and example code.

### Timeouts

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:
If a network or server error occurs while your code is opening a
connection or issuing a command, it can end up hanging indefinitely.
You can prevent this from happening by setting timeouts for socket
reads and writes and for opening connections.

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.
(The socket timeout is the maximum time allowed for reading or writing data while executing a
command. The connection timeout is the maximum time allowed for establishing a new connection.)

```java
HostAndPort hostAndPort = new HostAndPort("localhost", 6379);
Expand All @@ -34,9 +85,54 @@ JedisPooled jedisWithTimeout = new JedisPooled(hostAndPort,
);
```

### Health checks

If your code doesn't access the Redis server continuously then it
might be useful to make a "health check" periodically (perhaps once
every few seconds). You can do this using a simple
[`PING`]({{< relref "/commands/ping" >}}) command:

```java
try (Jedis jedis = jedisPool.getResource()) {
if (! "PONG".equals(jedis.ping())) {
// Report problem.
}
}
```

Health checks help to detect problems as soon as possible without
waiting for a user to report them.

### TCP keepalive

[TCP keepalive](https://en.wikipedia.org/wiki/Keepalive) is a technique
where TCP packets are periodically sent on an otherwise idle connection
to check that it is still working. You can enable TCP keepalive for a
connection using an option on the connection config builder:

```java
JedisClientConfig clientConfig = DefaultJedisClientConfig.builder()
.connectionTimeoutMillis(2000)
.socketTimeoutMillis(2000)
.tcpKeepAlive(true)
.build();

JedisPool pool = new JedisPool(poolConfig, "redis-host", clientConfig);
```

TCP keepalive can be especially useful to detect when unused connections
in a [connection pool](#connection-pooling) have been dropped due to
inactivity.

### Exception handling

The Jedis Exception Hierarchy is rooted on `JedisException`, which implements `RuntimeException`, and are therefore all unchecked exceptions.
Redis handles many errors using return values from commands, but there
are also situations where exceptions can be thrown. In production code,
you should handle exceptions as they occur.

The Jedis exception hierarchy is rooted on `JedisException`, which implements
`RuntimeException`. All exceptions in the hierarchy are therefore unchecked
exceptions.

```
JedisException
Expand Down
13 changes: 13 additions & 0 deletions layouts/shortcodes/checklist-item.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<li>
<select onchange="clChange('prodlist')">
<option value="R">&#x274C;</option>
<option value="G">&#9989;</option>
<option value="A">&#x1F50D;</option>
<option value="X">&#x2205;</option>
</select>
{{- if index .Params 0 -}}
<a href="{{ index .Params 0 }}">{{ .Inner }}</a>
{{- else -}}
{{ .Inner }}
{{- end -}}
</li>
85 changes: 85 additions & 0 deletions layouts/shortcodes/checklist.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
{{ $formId := index .Params 0 }}
<form id="{{ $formId }}">
<ul style="list-style-type: none;padding-left: 0px;">
{{ .Inner }}
</ul>
<label for="gcount">&#9989; = </label>
<output name="gcount" id="gcount">0</output>/<output name="gtotal"></output>,
<label for="rcount">&#x274C; = </label>
<output name="rcount" id="rcount">0</output>/<output name="rtotal"></output>,
<label for="acount">&#x1F50D; = </label>
<output name="acount" id="acount">0</output>/<output name="atotal"></output>
<br/>
(<label for="xcount">&#x2205; = </label>
<output name="xcount" id="xcount">0</output>/<output name="xtotal"></output>)
</form>
<script>
document.addEventListener('DOMContentLoaded', () => {
let itemString = localStorage.getItem("{{ $formId }}");

if (itemString !== "") {
setCLItemsFromString("{{ $formId }}", itemString);
} else {
clChange("{{ $formId }}");
}
});


function getStringFromCLItems(formId) {
let result = "";

let form = document.getElementById(formId);

let listItems = form.getElementsByTagName("li");

for (let elem of listItems) {
let menu = elem.getElementsByTagName("select")[0];
result += menu.value;
}

return result;
}


function setCLItemsFromString(formId, clString) {
let counts = {R: 0, G: 0, A: 0, X:0};

let form = document.getElementById(formId);
let listItems = form.getElementsByTagName("li");

if (clString.length < listItems.length) {
clString = clString.padEnd(listItems.length, "R");
} else if (clString.length > listItems.length) {
clString = clString.substring(0, listItems.length);
}

for (let i = 0; i < clString.length; i++) {
let char = clString.charAt(i);
counts[char]++;
let menu = listItems[i].getElementsByTagName("select")[0];
menu.value = char;
}

form.elements["rcount"].value = counts["R"];
form.elements["gcount"].value = counts["G"];
form.elements["acount"].value = counts["A"];
form.elements["xcount"].value = counts["X"];


let numClItems = listItems.length - counts["X"];

form.elements["rtotal"].value = numClItems;
form.elements["gtotal"].value = numClItems;
form.elements["atotal"].value = numClItems;
form.elements["xtotal"].value = counts["X"];

let itemChoices = getStringFromCLItems("{{ $formId }}");
localStorage.setItem("{{ $formId }}", itemChoices);
}


function clChange(formId) {
let itemChoices = getStringFromCLItems(formId);
setCLItemsFromString(formId, itemChoices);
}
</script>