Skip to content

Commit 7a4ab73

Browse files
authored
Merge branch 'master' into rs-connect-refcount-cycle
2 parents 0d9ac95 + 05c1fbe commit 7a4ab73

62 files changed

Lines changed: 6612 additions & 195 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/actions/run-tests/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ runs:
315315
with:
316316
name: pytest-results-redis_${{inputs.redis-version}}-python_${{inputs.python-version}}-parser_${{env.PARSER_BACKEND}}-el_${{inputs.event-loop}}-config_${{inputs.test-config}}
317317
path: |
318-
*-results.xml
318+
junit-results/*-results.xml
319319
prof/**
320320
profile_output*
321321
if-no-files-found: error

.github/wordlist.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,14 @@ docstrings
122122
eg
123123
enums
124124
exc
125+
fieldset
126+
fieldsets
125127
firsttimersonly
126128
fo
127129
formatter
128130
genindex
129131
gmail
132+
himport
130133
hiredis
131134
http
132135
idx
@@ -162,6 +165,7 @@ parsers
162165
performant
163166
pmessage
164167
png
168+
positionally
165169
pre
166170
psubscribe
167171
pubsub

.github/workflows/integration.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ env:
3131
# for example after 8.2.1 is published, 8.2 image contains 8.2.1 content
3232
CURRENT_REDIS_VERSION: '8.8.0'
3333
REDIS_VERSION_CUSTOM_MAP: >-
34-
8.10:unstable-29211243717-debian
34+
8.10:8.10.0
3535
3636
jobs:
3737
dependency-audit:
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
name: Nightly test metrics
2+
3+
# Once a day, run the redis-py standalone test suite (master, default
4+
# client configuration, modules included) against a Redis server built
5+
# from redis' unstable branch, and report per-test durations to Grafana
6+
# for performance regression tracking.
7+
8+
on:
9+
schedule:
10+
- cron: '30 3 * * *' # nightly, staggered from the 01:00 CI run
11+
workflow_dispatch:
12+
13+
concurrency:
14+
group: nightly-metrics
15+
cancel-in-progress: false
16+
17+
permissions:
18+
contents: read # to fetch code (actions/checkout)
19+
20+
jobs:
21+
nightly-metrics:
22+
name: Redis unstable; test metrics
23+
runs-on: ubuntu-latest
24+
timeout-minutes: 60
25+
steps:
26+
# Secrets can't be used in `if:` conditions, so a first step checks
27+
# the token and later steps are gated on its output. Without the
28+
# secret the job prints a warning and skips everything.
29+
- name: Check for the metrics token
30+
id: gate
31+
env:
32+
OTEL_TOKEN: ${{ secrets.OTEL_CI_VISIBILITY_TOKEN }}
33+
run: |
34+
if [ -z "$OTEL_TOKEN" ]; then
35+
echo "::warning::The OTEL_CI_VISIBILITY_TOKEN secret is not set; skipping the nightly metrics run."
36+
echo "run=false" >> "$GITHUB_OUTPUT"
37+
else
38+
echo "run=true" >> "$GITHUB_OUTPUT"
39+
fi
40+
41+
- uses: actions/checkout@v7
42+
if: steps.gate.outputs.run == 'true'
43+
44+
- name: Resolve latest unstable Redis image
45+
id: unstable-image
46+
if: steps.gate.outputs.run == 'true'
47+
run: |
48+
set -euo pipefail
49+
# Pick the newest tag client-side instead of relying on the API's
50+
# ordering parameter.
51+
tag=$(curl -sf "https://hub.docker.com/v2/repositories/redislabs/client-libs-test/tags?page_size=100&name=unstable-" \
52+
| jq -r '.results | max_by(.last_updated) | .name // empty')
53+
if [ -z "$tag" ]; then
54+
echo "::error::Could not resolve an unstable client-libs-test image tag from Docker Hub"
55+
exit 1
56+
fi
57+
echo "Using image tag: $tag"
58+
echo "tag=$tag" >> "$GITHUB_OUTPUT"
59+
60+
- name: Run tests
61+
if: steps.gate.outputs.run == 'true'
62+
uses: ./.github/actions/run-tests
63+
with:
64+
python-version: '3.12'
65+
parser-backend: 'plain'
66+
# The server under test is the unstable image resolved above;
67+
# redis-version only drives run-tests' setup branching and the
68+
# artifact name, and it must be numeric.
69+
redis-version: '8.10'
70+
client-libs-test-image: ${{ steps.unstable-image.outputs.tag }}
71+
# One config, so each test reports exactly one duration per night.
72+
test-config: 'default-legacy_responses-standalone'
73+
74+
- name: Report test metrics
75+
# Report even when tests fail, but not when the run was cancelled.
76+
if: ${{ !cancelled() && steps.gate.outputs.run == 'true' }}
77+
uses: redis-developer/cae-otel-ci-visibility@v4
78+
with:
79+
junit-xml-folder: 'junit-results'
80+
otlp-endpoint: 'https://otlp-gateway-prod-us-central-0.grafana.net/otlp/v1/metrics'
81+
otlp-headers: 'Authorization=Basic ${{ secrets.OTEL_CI_VISIBILITY_TOKEN }}'
82+
server-version: 'unstable'

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ coverage.xml
1919
.coverage*
2020
prof
2121
profile_output*
22+
junit-results/
2223
docker/stunnel/keys
2324
/dockers/*/node-*/*
2425
/dockers/*/tls/*

README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,66 @@ This is useful when:
221221

222222
For the complete failover configuration options and examples, see the [Multi-database client docs](https://redis.readthedocs.io/en/latest/multi_database.html).
223223

224+
### Bulk hash ingestion (HIMPORT)
225+
226+
Redis 8.10 adds the `HIMPORT` command family for loading many hashes that share
227+
the same set of field names: register the field names once with `himport_prepare`,
228+
then create each hash by sending only its values. Keys written this way are regular
229+
hashes — every hash command works on them.
230+
231+
``` python
232+
r.himport_prepare("users", ["name", "email", "age"])
233+
r.himport_set("user:1", "users", ["alice", "alice@example.com", "25"])
234+
r.himport_set("user:2", "users", ["bob", "bob@example.com", "30"])
235+
r.himport_discard("users") # => 1
236+
```
237+
238+
Values pair positionally with the prepared fields. Hash enumeration order
239+
(`HGETALL`, `HKEYS`) is not guaranteed to match the prepare order.
240+
241+
**Fieldsets are connection state.** A prepared fieldset lives in the server-side
242+
session of the physical connection that prepared it: it is invisible to other
243+
connections and destroyed by a disconnect or `RESET`. redis-py handles this for you —
244+
`himport_prepare` records the fieldset in a client-level registry, and the `PREPARE`
245+
is applied lazily on whatever pooled connection serves each `himport_set` (and
246+
re-applied automatically after a reconnect, `RESET`, or Sentinel/cluster failover).
247+
You declare each fieldset once per client with `himport_prepare`; there is no
248+
constructor argument for it.
249+
250+
For the highest ingestion throughput, send the `PREPARE` and its `SET`s in one
251+
pipeline — a single batch always executes on one connection:
252+
253+
``` python
254+
with r.pipeline(transaction=False) as pipe:
255+
pipe.himport_prepare("users", ["name", "email", "age"])
256+
for uid, row in rows:
257+
pipe.himport_set(f"user:{uid}", "users", row)
258+
pipe.execute()
259+
```
260+
261+
The automatic re-prepare applies to direct calls only, not to commands inside
262+
`pipeline`/`transaction` blocks: a batched `himport_set` relies on the single
263+
pre-flight `PREPARE` in that batch.
264+
265+
With `RedisCluster`, `himport_prepare` / `himport_discard` / `himport_discard_all`
266+
update the client's shared, cluster-wide registry and return immediately — like the
267+
standalone API, they perform no server I/O of their own. The server-side `PREPARE`
268+
(and, after a discard, `DISCARD`) is applied lazily on each node's connection the
269+
next time it serves an `himport_set`, and re-applied after reconnects or failover;
270+
`himport_set` itself routes by the key's hash slot. A discard is therefore not
271+
removed from every server session at once: each connection drops the fieldset on its
272+
next `himport_set` (or on disconnect). With Sentinel, call
273+
`himport_prepare` on the long-lived client returned by `master_for(...)`; the fieldset
274+
survives failover automatically. HIMPORT is not supported on the multi-database
275+
(Active-Active) client.
276+
277+
The async client mirrors this exactly:
278+
279+
``` python
280+
await r.himport_prepare("users", ["name", "email", "age"])
281+
await r.himport_set("user:1", "users", ["alice", "alice@example.com", "25"])
282+
```
283+
224284
---------------------------------------------
225285

226286
### Author

docs/examples.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ Examples
1515
examples/timeseries_examples
1616
examples/redis-stream-example
1717
examples/opentelemetry_api_examples
18+
examples/hash_examples

0 commit comments

Comments
 (0)