Commit Graph
12651 Commits
Author SHA1 Message Date
debing.sun 5ff81f68a3 Fix XPENDING reply schema for empty reply (#14129)
When the PEL is empty, the reply of `XPENDING` without `start` option
will be:
```
1) (integer) 0
2) (nil)
3) (nil)
4) (nil)
```

It is not an empty array, so we need to create an individual reply
schema for it.
2025-07-01 17:35:09 +08:00
itayTzivanddebing.sun 64ae81d37c New config: lazyexpire-nested-arbitrary-keys (#14149)
In this PR we added hidden config - `lazyexpire-nested-arbitrary-keys`,
which can take:
* yes - the default. produce and propagate lazy-expire DELs as usual.
* no - avoid lazy-expire from commands that touch arbitrary keys (such
as SCAN, RANDOMKEY), if generated within a transactions (MULTI/EXEC,
LUA). This ensures such commands won't induce CROSSSLOT on remote proxy,
as happened in when replicating one db into another (possibly sharded
differently).
Since the issue is relevant only in replicated servers (RE's replica-of
mode or CRDT) - it was added to the core as a hidden config.

Please note that this config will always apply to read-only commands
(see EXPIRE_FORCE_DELETE_EXPIRED flag).
Since write commands may require key expiration to operate correctly.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2025-07-01 15:28:13 +08:00
Oran Agra 96930663b4 Make Active defrag big list test much faster (#14157)
it aims to create listpacks of 500k, but did that with 5 insertions of
100k each, instead do that in one insertion, reducing the need for
listpack gradual growth, and reducing the number of commands we send.
apparently there are some stalls reading the replies of the commands,
specifically in GH actions, reducing the number of commands seems to
eliminate that.
2025-06-30 16:56:17 +03:00
wclmxxsandYuan Wang ca6145b18c Reduce the main thread blocking in clients cron (#13900)
The main thread needs to check clients in every cron iteration. During
this check, the corresponding I/O threads must not operate on these
clients to avoid data-race. As a result, the main thread is blocked
until the I/O threads finish processing and are suspended, allowing the
main thread to proceed with client checks.

Since the main thread's resources are more valuable than those of I/O
threads in Redis, this blocking behavior should be avoided. To address
this, the I/O threads check during their cron whether any of their
maintained clients need to be inspected by the main thread. If so, the
I/O threads send those clients to the main thread for processing, then
the main thread runs cron jobs for these clients.

In addition, an always-active client might not be in thread->clients, so
before processing the client’s command, we also check whether the client
has skipped running its cron job for over 1 second. If it has, we run
the cron job for the client.

The main thread does not need to actively pause the IO threads, thus
avoiding potential blocking behavior, fixes
https://github.com/redis/redis/issues/13885

Besides, this approach also can let all clients run cron task in a
second, but before, we pause IO threads in multiple batches when there
are more than 8 IO threads, that may cause some clients are not be
processed in a second.

---------

Co-authored-by: Yuan Wang <yuan.wang@redis.com>
2025-06-30 09:37:17 +08:00
Yi DengandYuan Wang 531b82df22 Fix replication lseek check (#14135)
In `sendBulkToSlave`, the `lseek()` call used to position the RDB file
descriptor before reading the next data chunk was not checked for
errors.
If the `lseek()` system call were to fail, the file descriptor would
remain at an incorrect position. The subsequent `read()` would then
fetch the wrong data, leading to a corrupted RDB stream being sent to
the replica. This could cause the replication to fail or result in data
inconsistency.
This patch introduces a check for the `lseek()` return value. On
failure, it logs a detailed warning and aborts the replication by
freeing the client, mirroring the existing error handling for `read()`
and `write()` calls within the same function. This improves the
robustness of the RDB transfer process.

---------

Co-authored-by: Yuan Wang <wangyuancode@163.com>
2025-06-27 09:07:33 +08:00
Salvatore Sanfilippo 8948a5d2b2 [Vector Sets] IN operator for string/string operands (#14122)
This PR introduces "IN" overloading for strings in Vector Sets VSIM
FILTER expressions.
Now it is possible to do something like:

    "foo" IN "foobar"

IN continues to work as usually if the second operand is an array,
checking for membership of the left operand.

Ping @rowantrollope that requested this feature. I'm evaluating if to
add glob matching functionalities via the `=~` operator but I need to do
an optimization round in our glob matching function probably. Glob
matching can be slower, at the same time the complexity of the greedy
search in the graph remains unchanged, so it may be a good idea to have
it.

Case insensitive search will be likely not be added however, since this
would require handling unicode that is kinda outside the scope of Redis
filters. The user is still able to perform `"foo" in "foobar" || "FOO"
in "foobar"` at least.
2025-06-26 10:13:54 +08:00
h.o.t. neglected a25f0a715e Fix generate-commands-json.py script (#14098) (#14111)
Close https://github.com/redis/redis/issues/14098
This is to handle some optional command docs fields, when the commands
belong to group module.
2025-06-26 09:47:00 +08:00
a744411f27 Optimize Expiry Check in scanCallback() Using kvobj (#14140)
The current `scanCallback()` implementation performs expiry checks like
this:

```c
robj kobj;
sds keyname = kvobjGetKey(kv);
initStaticStringObject(kobj, keyname);
expireIfNeeded(db, &kobj, kv, 0);
```

This pattern introduces unnecessary temporary stack allocation for robj
and additional memory traffic, confirmed by topdown analysis + perf

```
sudo ./toplev.py --pid $(pgrep redis-server) --level 2 --run-sample -- sleep 30
# 5.01-full-perf on Intel(R) Xeon(R) Platinum 8488C [spr/sapphire_rapids]
BE               Backend_Bound               % Slots                       49.6  
BE/Mem           Backend_Bound.Memory_Bound  % Slots                       34.3  <==
	This metric represents fraction of slots the Memory
	subsystem within the Backend was a bottleneck...
```


From perf record -g sampling (hot path in scanCallback):

kvobjGetKey() + initStaticStringObject() account for ~12% frontend +
backend bound stalls


These cycles are avoidable when kvobj is already available.

This PR extends expireIfNeeded to support key is none (using kvobj) , in
order to avoid sds key copy + temporary robj on scanCallback when it's
not needed.

## Benchmarks

By running 
```
redis-benchmarks-spec-client-runner --tests-regexp ".*scan.*" --flushall_on_every_test_start --flushall_on_every_test_end  --cpuset_start_pos 2 --override-memtier-test-time 30 --benchmark_local_install --override-test-runs 3 --db_server_port <...> --db_server_password <...> --db_server_host <...>
```

We see 

Test Name | Metric | baseline redis Wed Jun 25
(4313d7ff23) | comparison redis
(3bb00b3a977fe131b189f3112dc1bf48284a9bcf) | Δ (%)
-- | -- | -- | -- | --
generic-scan-count-500 | Ops/sec | 25126 | 26726 | 6.4%
generic-scan-cursor-count-5000 | Ops/sec | 1416.18 | 1446 | 2.1%
generic-scan-count-500 | p99 | 55.039 | 50.687 | 8.6%
generic-scan-cursor-count-5000 | p99 | 1448.62 | 1064.959 | 36.0%

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
Co-authored-by: Yuan Wang <yuan.wang@redis.com>
2025-06-25 20:32:23 +08:00
Yuan Wang 4313d7ff23 Stabilize tests for IO threading (#14138)
- tests/unit/maxmemory.tcl
If multithreaded, we need to let IO threads have chance to reply output
buffer, to avoid next commands causing eviction. After eviction is
performed,
the next command becomes ready immediately in IO threads, and now we
enqueue
the client to be processed in main thread’s beforeSleep without
notification.
However, invalidation messages generated by eviction may not have been
fully
delivered by that time. As a result, executing the command in
beforeSleep of
the event loop (running eviction) can cause additional keys to be
evicted.
```
Expected '73' to be between to '200' and '300' (context: type source line 473 file 
redis/tests/unit/maxmemory.tcl cmd {assert_range [r dbsize] 200 300} proc ::test)
```
the reason why CI doesn't find this issue is that we skill this test
`tsan:skip` as below
`start_server {tags {"maxmemory external:skip tsan:skip"}} `,so remove
this tag.

- tests/integration/aof.tcl
Because IO and the main thread are working in better parallelism without
notification,
the main thread may haven't write AOF buffer into file, but the IO
thread just writes
the reply, so the clients receive the reply before AOF file is changed.
We should use `appendfsync always` policy to make the command is written
into
AOF file when receiving reply.
```
Expected '0' to be equal to '54' (context: type source line 249 file
redis/tests/integration/aof.tcl cmd {assert_equal $before $after} proc ::test)
```

#13969 makes these scenarios easy to appear.
2025-06-25 15:36:40 +08:00
Ozan Tezcan 03816c15f7 Fix short read of hfe key that causes exit() on replica (#14143)
If replica detects broken connection while reading min expiration time
of hfe key, it calls exit().
Fixed it to handle the error gracefully without calling exit. 

To reproduce the issue, the short-read test was modified to generate
many small hfe keys, increasing the likelihood of a connection drop
while reading min expiration time:

```tcl
for {set k 0} {$k < 50000} {incr k} {
  for {set i 0} {$i < 1} {incr i} {
    r hsetex "$k hfe_small" EX [expr {int(rand()*10)}] FIELDS 1 [string repeat A [expr {int(rand()*10)}]] 0[string repeat A [expr {int(rand()*10)}]]
  }
}
```

We can't have the test use only hfe keys, so a few were added alongside
other data. I couldn't reproduce the issue this way but with the test's
randomization, it should hit this scenario in one of the runs.
2025-06-23 07:41:30 +03:00
Stav-Levi 51239f75d0 Record the time a replica attempts to connect with master (#13990)
Merge fork counters with https://github.com/redis/redis/pull/12957
repl_current_sync_attempts - Total number of attempts to connect to a
master since the last time we disconnected from a good connection (or a
configuration change). any number greater than 1 (even if the link is
currently up), indicates an issue.
repl_total_sync_attempts - Number of times in current configuration, the
replica attempted to sync to a master. (dosent reset on master
reconnect.)
repl_total_disconnect_time - Total cumulative time we've been
disconnected as a replica, visible when the link is up too.
master_link_up_since_seconds - Number of seconds since the link is down,
just maintain symmetry with master_link_down_since_seconds.
2025-06-22 09:19:26 +03:00
Filipe Oliveira (Redis)anddebing.sun 2e1a17c26c Optimize quicklistCompare with optional string2ll caching. (#14131)
### Summary

This pull request improves the performance of quicklistCompare and
lpCompare by avoiding repeated calls to string2ll when comparing many
quicklist/listpack entries against the same string value. The
optimization targets use cases like LREM, LPOS, LINSERT, and ZRANK where
comparisons are made repeatedly in a loop.

By caching the result of string2ll during a single command execution, we
avoid re-parsing the same input string thousands of times—resulting in
up to **30% higher throughput and up to 25% lower p50 latency** in LREM
LINSERT benchmarks, and **5% higher throughput** in ZRANK (listpack)
command.

### Changes

- Updated quicklistCompare and lpCompare to accept two optional
parameters:
  - `long long *cached_val`
  - `int *cached_valid`
- If caching parameters are provided, string2ll is invoked only once and
its result is reused across comparisons.
- listTypeEqual was updated to forward these parameters.
- Commands such as LREM, LPOS, LINSERT, and ZRANK now use this
optimization.
- All internal tests and usage of quicklistCompare/lpCompare were
updated accordingly.

### Behavior

- If cached_valid is NULL, quicklistCompare/lpCompare behaves as before
(no caching).
- If cached_valid is non-NULL:
  - 0 means uninitialized: string2ll is attempted.
  - 1 means valid: cached_val is used.
  - -1 means invalid: string2ll previously failed and is skipped.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2025-06-21 10:28:51 +08:00
yzc-yzc 117424f85c Fix negative offset issue for ZRANGEBY[SCORE|LEX] command (#14043)
Fix #13952

This PR ensures that ZRANGE_SCORE/LEX command with a negative offset
will return empty.
2025-06-20 13:51:52 +08:00
yzc-yzcanddebing.sun 61fa8bb06f Record peak memory time (#14067)
resolve #14049

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2025-06-20 13:49:20 +08:00
YaacovHazan ce6edd289a Check length of AOF file name in redis-check-aof (CVE-2025-27151) (#14139)
Ensure that the length of the input file name does not exceed PATH_MAX
2025-06-19 11:25:32 +03:00
Yuan Wang a95b94b3dd Fix command arity check in IO threads (#14134)
We need to check the command arity in IO threads, if it is not correct,
we should reset it, as we may do memory prefetching according to the
`iolookedcmd`. Accessing `argv` using the key positions returned by
`getKeysFromCommand` is unsafe and must be avoided for invalid commands.

This bug starts to have an impact after #14017
2025-06-18 22:29:13 +08:00
Filipe Oliveira (Redis) 35dbfc4ba8 Improve SCAN performance by only performing expiration checks on DBs with volatile keys (#14121)
This PR optimizes scanGenericCommand by moving type filtering and
expiration checks from post-processing (Step 3) to the scan callback,
eliminating expensive `lookupKeyReadWithFlags()` calls and adding an
optimization to skip expiration checks via dict lookup given we can now
check the expiration with expiry flag in the kvobj (due to the move to
the scanCallback).

Profiling data (https://pprof.me/1ac456b6d1a46b2184a5e2ef314aa0a2)
showed that scanGenericCommand accounted for 2.8% of total CPU time and
was a notable hotspot during SCAN-heavy workloads.

## Key optimizations:
1. **Type filtering moved to scanCallback**: Uses existing `kvobj *kv`
instead of expensive lookups
2. **Expiration check optimization**: Skips `expireIfNeeded()` calls
when database has no volatile keys
3. **Better cache locality**: Processes filtering during iteration
rather than post-processing
2025-06-18 21:37:27 +08:00
Salvatore Sanfilippo f6d1fd08f9 Vset tests improvements (#14089)
This changes improve a bit the Vector Sets tests:

* DB9 is used instead of the target DB. After a successful test the DB
is left empty.
* If the replica is not available, the replication tests are skipped
without errors but just a warning.
* Other refactoring stuff.
2025-06-18 10:23:22 +08:00
lerman25 a8cde7d19c Fix alpine missing __STRING #define (#14133)
Alpine Linux doesn't provide the  __STRING macro, causing build failure:
Adding conditional define the macro if not already available.
2025-06-17 21:50:35 +08:00
Moti Cohen 90178712f6 Optimize COPY, RENAME and RESTORE commands with TTL (#14088)
* DEL optimized to call `kvstoreDictDelete(db->expires ...)` only when
O(1) `kvobjGetExpire() != -1`
* Combines the two funcitons `dbAdd()` and `setExpire()` into a single 
efficient call: `dbAdd(..., long long expire)`. This optimization
eliminates an extra lookup
and also avoid reallocating a new object to store the TTL.

**Benchmarking RESTORE command with TTL:**
```
memtier_benchmark --command "RESTORE bla 1000000 \"\\x00\\x03bbb\\x0c\\x00\\x8e\\x85\\xaf\\x9f\\x0e'#\\x00\" REPLACE" --command-key-pattern=P --data-size=1 --pipeline=1000 --key-maximum=10000000 -c 5 -t 20 --hide-histogram --requests=100000
```
**Results Summary (Average of 3 Runs):**

Metric | unstable | optimize-setExpire | Δ (Improvement)
-- | -- | -- | --
Throughput (ops/sec) | 1,614,176 | 1,737,198 | +7.6%
P50 Latency (ms) | 61.25 | 57.30 | –6.5%
P95 Latency (ms) | 64.77 | 59.99 | –7.4%
P99 Latency (ms) | 73.73 | 70.83 | –3.9%
Max Latency (ms) | 87.55 | 85.84 | –2.0%
2025-06-16 15:11:48 +03:00
94aebb7324 Add config base to vector-sets and hnsw thread config (#14082)
This PR introduces the initial configuration infrastructure for
vector-sets, along with a new option:
`vset-force-single-threaded-execution`. When enabled, it applies the
`NOTHREAD` flag to VSIM and disables the `CAS` option for VADD, thereby
enforcing single-threaded execution.
Note: This mode is not optimized for single-threaded performance.

---------

Co-authored-by: GuyAv46 <47632673+GuyAv46@users.noreply.github.com>
Co-authored-by: debing.sun <debing.sun@redis.com>
2025-06-16 10:06:43 +08:00
Filipe Oliveira (Redis) 66b3d2d98e Add 2K software prefetch to improve BITCOUNT performance (#14103)
Adds a software prefetch with a 2K stride to the scalar popcount loop in
redisPopcount().
Prefetching improved BITCOUNT throughput by up to 41.6%, reduced p50
latency by up to 43.9%, and significantly lowered L3 memory stalls,
confirming effective mitigation of memory-bound bottlenecks, with no
negative impact on L1/L2 usage or cache pollution (confirmed with HW
counters).

Note: The 2K stride was the best starting from 128,256,512,1024,2048,4096.
4K gave the same outcome so it's best to avoid larger strides without reason.
2025-06-13 16:59:34 +08:00
abaed0d54c Reduce the overhead from malloc usable (#14074)
Currently, in the zmalloc and zfree family functions, we rely on
`je_malloc_usable_size()` to obtain the usable size of a pointer for
memory statistics or to return it to the caller. However, this function
is relatively expensive, as it involves locking and rbtree lookups
within jemalloc. Reducing the frequency of these calls can yield
significant performance improvements.

---------

Co-authored-by: oranagra <oran@redislabs.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-12 22:31:26 +08:00
Alexander Dobrzhansky b1d202fb23 Bump rust version for INSTALL_RUST_TOOLCHAIN flag (#14118)
Updates the rust version that is getting installed if
`INSTALL_RUST_TOOLCHAIN` is defined
2025-06-12 11:50:24 +03:00
Salvatore Sanfilippoanddebing.sun 27dd3b71ce Vector Sets fixes against corrupted data in absence of checksum verification (#14102)
Vector Sets deserialization was not designed to resist corrupted data,
assuming that a good checksum would mean everything is fine. However
Redis allows the user to specify extra protection via a specific
configuration option.

This commit makes the implementation more resistant, at the cost of some
slowdown. This also fixes a serialization bug that is unrelated (and has
no memory corruption effects) about the lack of the worst index /
distance serialization, that could lower the quality of a graph after
links are replaced. I'll address the serialization issues in a new PR
that will focus on that aspect alone (already work in progress).

The net result is that loading vector sets is, when the serialization of
worst index/distance is missing (always, for now) 100% slower, that is 2
times the loading time we had before. Instead when the info will be
added it will be just 10/15% slower, that is, just making the new sanity
checks.

It may be worth to export to modules if advanced sanity check if needed
or not. Anyway most of the slowdown in this patch comes from having to
recompute the worst neighbor, since duplicated and non reciprocal links
detection was heavy optimized with probabilistic algorithms.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2025-06-10 21:55:09 +08:00
alonre24 2ba81b7095 Bump search version to 8.1.00 (#14110) 2025-06-08 16:24:52 +03:00
Eran Hadad 38a8894b50 Update TS, JSON and Bloom Modules to 8.1.00 (#14109) 2025-06-08 16:24:19 +03:00
debing.sun 2467eff59a Fix db->expires can't be defragged due to incorrect comparison in the expires stage (#14092)
This bug was introduced by https://github.com/redis/redis/issues/13814

When defragmenting `db->expires`, if the process exits early and
`db->expires` was modified in the meantime (e.g., FLUSHDB), we need to
check whether the previously defragmented expires is still the same as
the current one when resuming. If they differ, we should abort the
current defragmentation of expires.

However, in https://github.com/redis/redis/issues/13814, I made a
mistake by using `db->keys` and `db->expires`, as expires will never be
defragged.
2025-06-05 21:52:33 +08:00
carlosfu f646d2324b [sentinel] add tilt total count in sentinel info (#13907)
it will be a good idea to add total_tilt in info command to show total
tilt count, in order to help admin to know sentinel tilt condition.
2025-06-05 11:41:53 +08:00
70a079db5e Improve multithreaded performance with memory prefetching (#14017)
This PR is based on: https://github.com/valkey-io/valkey/pull/861

> ### Memory Access Amortization
> (Designed and implemented by [dan
touitou](https://github.com/touitou-dan))
> 
> Memory Access Amortization (MAA) is a technique designed to optimize
the performance of dynamic data structures by reducing the impact of
memory access latency. It is applicable when multiple operations need to
be executed concurrently. The principle behind it is that for certain
dynamic data structures, executing operations in a batch is more
efficient than executing each one separately.
> 
> Rather than executing operations sequentially, this approach
interleaves the execution of all operations. This is done in such a way
that whenever a memory access is required during an operation, the
program prefetches the necessary memory and transitions to another
operation. This ensures that when one operation is blocked awaiting
memory access, other memory accesses are executed in parallel, thereby
reducing the average access latency.
> 
> We applied this method in the development of dictPrefetch, which takes
as parameters a vector of keys and dictionaries. It ensures that all
memory addresses required to execute dictionary operations for these
keys are loaded into the L1-L3 caches when executing commands.
Essentially, dictPrefetch is an interleaved execution of dictFind for
all the keys.

### Implementation of Redis
When the main thread processes clients with ready-to-execute commands
(i.e., clients for which the IO thread has parsed the commands), a batch
of up to 16 commands is created. Initially, the command's argv, which
were allocated by the IO thread, is prefetched to the main thread's L1
cache. Subsequently, all the dict entries and values required for the
commands are prefetched from the dictionary before the command
execution.

#### Memory prefetching for main hash table
As shown in the picture, after https://github.com/redis/redis/pull/13806
, we unify key value and the dict uses no_value optimization, so the
memory prefetching has 4 steps:

1. prefetch the bucket of the hash table
2. prefetch the entry associated with the given key's hash
3. prefetch the kv object of the entry
4. prefetch the value data of the kv object

we also need to handle the case that the dict entry is the pointer of kv
object, just skip step 3.

MAA can improves single-threaded memory access efficiency by
interleaving the execution of multiple independent operations, allowing
memory-level parallelism and better CPU utilization. Its key point is
batch-wise interleaved execution. Split a batch of independent
operations (such as multiple key lookups) into multiple state machines,
and interleave their progress within a single thread to hide the memory
access latency of individual requests.

The difference between serial execution and interleaved execution:
**naive serial execution**
```
key1: step1 → wait → step2 → wait → done
key2: step1 → wait → step2 → wait → done
```
**interleaved execution**
```
key1: step1   → step2   → done
key2:   step1 → step2   → done
key3:     step1 → step2 → done
         ↑ While waiting for key1’s memory, progress key2/key3
```

#### New configuration
This PR involves a new configuration `prefetch-batch-max-size`, but we
think it is a low level optimization, so we hide this config:
When multiple commands are parsed by the I/O threads and ready for
execution, we take advantage of knowing the next set of commands and
prefetch their required dictionary entries in a batch. This reduces
memory access costs. The optimal batch size depends on the specific
workflow of the user. The default batch size is 16, which can be
modified using the 'prefetch-batch-max-size' config.
When the config is set to 0, prefetching is disabled.

---------

Co-authored-by: Uri Yagelnik <uriy@amazon.com>
Co-authored-by: Ozan Tezcan <ozantezcan@gmail.com>
2025-06-05 08:57:43 +08:00
Slavomir Kaslev b7c6755b1b Add thread sanitizer run to daily CI (#13964)
Add thread sanitizer run to daily CI.

Few tests are skipped in tsan runs for two reasons:
* Stack trace producing tests (oom, `unit/moduleapi/crash`, etc) are
tagged `tsan:skip` because redis calls `backtrace()` in signal handler
which turns out to be signal-unsafe since it might allocate memory (e.g.
glibc 2.39 does it through a call to `_dl_map_object_deps()`).
* Few tests become flaky with thread sanitizer builds and don't finish
in expected deadlines because of the additional tsan overhead. Instead
of skipping those tests, this can improved in the future by allowing
more iterations when waiting for tsan builds.

Deadlock detection is disabled for now because of tsan limitation where
max 64 locks can be taken at once.

There is one outstanding (false-positive?) race in jemalloc which is
suppressed in `tsan.sup`.

Fix few races thread sanitizer reported having to do with writes from
signal handlers. Since in multi-threaded setting signal handlers might
be called on any thread (modulo pthread_sigmask) while the main thread
is running, `volatile sig_atomic_t` type is not sufficient and atomics
are used instead.
2025-06-02 10:13:23 +03:00
Ozan Tezcan 7f60945bc6 Fix short read issue that causes exit() on replica (#14085)
When `repl-diskless-load` is enabled on a replica, and it is in the
process of loading an RDB file, a broken connection detected by the main
channel may trigger a call to rioAbort(). This sets a flag to cause the
rdb channel to fail on the next rioRead() call, allowing it to perform
necessary cleanup.

However, there are specific scenarios where the error is checked using
rioGetReadError(), which does not account for the RIO_ABORT flag (see
[source](https://github.com/redis/redis/blob/79b37ff53548ee4d0fca287cb37a2eaf84c519a7/src/rdb.c#L3098)).
As a result, the error goes undetected. The code then proceeds to
validate a module type, fails to find a match, and calls
rdbReportCorruptRDB() which logs the following error and exits the
process:

```
The RDB file contains module data I can't load: no matching module type '_________'
```

To fix this issue, the RIO_ABORT flag has been removed. Now, rioAbort()
sets both read and write error flags, so that subsequent operations and
error checks properly detect the failure.

Additional keys were added to the short read test. It reproduces the
issue with this change. We hit that problematic line once per key. My
guess is that with many smaller keys, the likelihood of the connection
being killed at just the right moment increases.
2025-05-28 12:43:59 +03:00
kei-nanandYuan Wang 161326d332 Avoid performing IO on coverage when child exits due to signal handler (#14072)
Compiled Redis with COVERAGE_TEST, while using the fork API encountered
the following issue:
- Forked process calls `RedisModule_ExitFromChild` - child process
starts to report its COW while performing IO operations
- Parent process terminates child process with
`RedisModule_KillForkChild`
- Child process signal handler gets called while an IO operation is
called
- exit() is called because COVERAGE_TEST was on during compilation.
- exit() tries to perform more IO operations in its exit handlers.
- process gets deadlocked

Backtrace snippet:
```
#0  futex_wait (private=0, expected=2, futex_word=0x7e1220000c50) at ../sysdeps/nptl/futex-internal.h:146
#1  __GI___lll_lock_wait_private (futex=0x7e1220000c50) at ./nptl/lowlevellock.c:34
#2  0x00007e1234696429 in __GI__IO_flush_all () at ./libio/genops.c:698
#3  0x00007e123469680d in _IO_cleanup () at ./libio/genops.c:843
#4  0x00007e1234647b74 in __run_exit_handlers (status=status@entry=255, listp=<optimized out>, run_list_atexit=run_list_atexit@entry=true, run_dtors=run_dtors@entry=true) at ./stdlib/exit.c:129
#5  0x00007e1234647bbe in __GI_exit (status=status@entry=255) at ./stdlib/exit.c:138
#6  0x00005ef753264e13 in exitFromChild (retcode=255) at /home/jonathan/CLionProjects/redis/src/server.c:263
#7  sigKillChildHandler (sig=<optimized out>) at /home/jonathan/CLionProjects/redis/src/server.c:6794
#8  <signal handler called>
#9  0x00007e1234685b94 in _IO_fgets (buf=buf@entry=0x7e122dafdd90 "KSM:", ' ' <repeats 19 times>, "0 kB\n", n=n@entry=1024, fp=fp@entry=0x7e1220000b70) at ./libio/iofgets.c:47
#10 0x00005ef75326c5e0 in fgets (__stream=<optimized out>, __n=<optimized out>, __s=<optimized out>, __s=<optimized out>, __n=<optimized out>, __stream=<optimized out>) at /usr/include/x86_64-linux-gnu/bits/stdio2.h:200
#11 zmalloc_get_smap_bytes_by_field (field=0x5ef7534c42fd "Private_Dirty:", pid=<optimized out>) at /home/jonathan/CLionProjects/redis/src/zmalloc.c:928
#12 0x00005ef75338ab1f in zmalloc_get_private_dirty (pid=-1) at /home/jonathan/CLionProjects/redis/src/zmalloc.c:978
#13 sendChildInfoGeneric (info_type=CHILD_INFO_TYPE_MODULE_COW_SIZE, keys=0, progress=-1, pname=0x5ef7534c95b2 "Module fork") at /home/jonathan/CLionProjects/redis/src/childinfo.c:71
#14 0x00005ef75337962c in sendChildCowInfo (pname=0x5ef7534c95b2 "Module fork", info_type=CHILD_INFO_TYPE_MODULE_COW_SIZE) at /home/jonathan/CLionProjects/redis/src/server.c:6895
#15 RM_ExitFromChild (retcode=0) at /home/jonathan/CLionProjects/redis/src/module.c:11468
```

Change is to make the exit() _exit() calls conditional based on a
parameter to exitFromChild function.
The signal handler should exit without io operations since it doesn't
know its history.(If we were in the middle of IO operations before it
was called)

---------

Co-authored-by: Yuan Wang <wangyuancode@163.com>
2025-05-28 16:27:52 +08:00
Moti Cohen 79b37ff535 Fix RESTORE with TTL (#14071)
restoreCommand() creates a key-value object (kv) with a TTL in two steps.
During the second step, setExpire() may reallocate the kv object. To ensure
correct behavior, kv must be updated after this call, as it might be used later
in the function.
2025-05-28 08:02:10 +03:00
Salvatore Sanfilippo 0ac822e154 Implement WITHATTRIBS for VSIM. (#14065)
Hi, as described, this implements WITHATTRIBS, a feature requested by a
few users, and indeed needed.
This was requested the first time by @rowantrollope but I was not sure
how to make it work with RESP2 and RESP3 in a clean way, hopefully
that's it.

The patch includes tests and documentation updates.
2025-05-27 22:12:48 +08:00
debing.sun bb23eb0b01 Fix incorrect server.cronloops update in defragWhileBlocked() causing timer to run twice as fast (#14081)
This bug was introduced in
[#13814](https://github.com/redis/redis/issues/13814), and was found by
@guybe7.
It incorrectly moved the update of `server.cronloops` from
`whileBlockedCron()` to `activeDefragTimeProc()`,
causing the cron-based timers to effectively run twice as fast when
active defrag is enabled.
As a result, memory statistics are not updated during blocked
operations.
The repair parts from https://github.com/redis/redis/pull/13995, because
it needs to be backport, so use a separate pr repair it.
2025-05-27 17:14:06 +08:00
guybe7 6349a7c4f9 Add GETRANGE tests with negative indices (#13950)
Inspired by https://github.com/redis/redis/pull/12272
2025-05-27 09:41:28 +08:00
debing.sunandmadolson e93b44560c Resolve bounds checks on cluster_legacy.c (#13970)
Based on https://github.com/valkey-io/valkey/pull/1463 and
https://github.com/valkey-io/valkey/pull/1481

In the failure of fully
CI(https://github.com/redis/redis/actions/runs/14595343452/job/40979173087?pr=13965)
in version 7.0 we are getting a number of errors like:
```
array subscript ‘clusterMsg[0]’ is partly outside array bounds of ‘unsigned char[2272]’
```

Which is basically GCC telling us that we have an object which is longer
than the underlying storage of the allocation. We actually do this a
lot, but GCC is generally not aware of how big the underlying allocation
is, so it doesn't throw this error. We are specifically getting this
error because the msgBlock can be of variable length depending on the
type of message, but GCC assumes it's the longest one possible. The
solution I went with here was make the message type optional, so that it
wasn't included in the size. I think this also makes some sense, since
it's really just a helper for us to easily cast the object around.

This compilation warning only occurs in version 7.2, because in [this
PR](https://github.com/redis/redis/pull/13073), we started passing
`-flto` to `CFLAGS` by default. It seems that in this case, GCC is
unable to detect such warnings. However, this change is not present in
version 7.2.
So, to reproduce this compilation warning in versions after 7.2, we can
pass `OPTIMIZATION=-O2` manually.

---------

Co-authored-by: madolson <34459052+madolson@users.noreply.github.com>
2025-05-26 11:52:06 +03:00
Salvatore Sanfilippo 22ebb06eb3 LOLWUT for Redis 8. (#14048)
# Add LOLWUT 8: TAPE MARK I - Computer Poetry Generation

This PR introduces LOLWUT 8, implementing Nanni Balestrini's
groundbreaking TAPE MARK I algorithm from 1962 - one of the first
experiments in computer-generated poetry.

## Background

TAPE MARK I, created by Italian poet Nanni Balestrini and published in
Almanacco Letterario Bompiani (1962), represents a [pioneering moment in
computational creativity](https://en.wikipedia.org/wiki/Digital_poetry).
Using an IBM 7090 mainframe, Balestrini developed an algorithm that
combines verses from three different literary sources:

1. **Diary of Hiroshima** by Michihito Hachiya
2. **The Mystery of the Elevator** by Paul Goldwin  
3. **Tao Te Ching** by Lao Tse

The algorithm selects and arranges verses based on metrical
compatibility rules and ensures alternation between different literary
sources, creating unique poetic combinations with each execution.

## Implementation

This LOLWUT command faithfully reproduces Balestrini's original
algorithm.
The main difference is that the default output is in English, and not in
Italian. However it should be noted that Balestrini used three poems
that were not in Italian anyway, so the translation process was already
part of it. In the English versions, sometimes I operated minimal
changes in order to preserve either the metric, or to make sure that the
sentence stands on its own (like adding "it" before expands rapidly).

## Cultural Significance

TAPE MARK I predates most computational art experiments and demonstrates
the early intersection of literature, technology, and algorithmic
creativity. This implementation honors that pioneering work while making
it accessible to a modern audience through Redis's LOLWUT tradition.

Each execution generates a unique poem, just as Balestrini intended.

Trivia: the original code, running on an IBM 7090, used six minutes to
generate each verse :D

**IMPORTANT** This commit should be back-ported to Redis 8.
2025-05-26 09:27:45 +03:00
Vitah Linanddebing.sun 35e15962b5 Add length check before content comparison in equalStringObjects (#14062)
### Issue 

Previously, even when only string equality needed to be determined, the
comparison logic still performed unnecessary `memcmp()` calls to check
string ordering, even if the lengths were not equal.

### Change
This PR add length check before content comparison in
`equalStringObjects` function.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2025-05-24 11:47:13 +08:00
Yuan Wang 7998a2a05f To reduce memory churn during client list transfer (#14068)
When we transfer clients between IO thread and main thread, the creation
and destruction of list nodes also consume some CPU. In this commit, we
reuse list nodes to avoid this issue.
2025-05-23 19:21:29 +08:00
Hüseyin Açacak 645858d518 Add size_t cast for RM_call() in module tests (#14061)
This PR addresses a potential misalignment issue when using `va_args`.
Without this fix,
[argument](https://github.com/redis/redis/blob/9a9aa921bcd2e65e5619600961b0447c864833b3/src/module.c#L6249-L6264)
values may occasionally become incorrect due to stack alignment
inconsistencies.
2025-05-23 10:10:11 +08:00
Yuan Wang 99d30654e8 Let IO threads free argv and rewrite objects (#13968)
Some objects that are allocated in the IO thread, we should let IO
thread free them, so we can avoid memory arena contention and also
reduce the load of the main thread.

These objects include:
- client argv objects
- the rewrite objects that are only `OBJ_ENCODING_RAW` encoding strings,
since only the type object is usually allocated by IO threads.

For the implementation, if the client is assigned to IO threads, we will
create a `deferred_objects` array of size 32. We will put objects into
the `deferred_objects` when main thread wants to free above objects,
and finally they are be freed by IO threads.
2025-05-23 09:09:58 +08:00
Vitah Lin d592cb7409 Cleanup redundant declaration of kvstoreDictFetchValue() (#14066) 2025-05-22 20:54:37 +08:00
debing.sunandBinbin ba88a7fbb6 Fix crash when freeing newly created node when nodeIp2String fail (#14055)
This PR is a reference from
https://github.com/valkey-io/valkey/pull/1535

In the process of handling the
failure(https://github.com/redis/redis/pull/14024) of ARM64 CI with TLS,
found in a slow environment, the nodes might frequently disconnect and
trigger this assertion.

---------

Co-authored-by: Binbin <binloveplay1314@qq.com>
2025-05-21 10:04:40 +08:00
Moti Cohen 8bd50a3b35 Refine and optimize dbSetValue() (#14040)
Optimize and simplify dbSetValue() code into one place in case not required to keepTTL.
2025-05-20 13:23:54 +03:00
Salvatore Sanfilippo 871d4c4004 Test: check always for memory leaks on MacOS. (#14060)
When running the Redis test on MacOS, the test detects that the
operating system is able to use "leaks" to test for memory leaks and
executes this check after every server spinned is terminated.

While we have the ability to run the test in environments able to detect
memory issues, the fact it is possible to check for leaks at every run
baasically for free is very valuable, and allows to fix leaks
immediately in your laptop before submitting a PR.

However, the feature avoided to run leaks when no test was run: this
check was added in the early stage of Redis, when all the tests were
like:

server {
   test { ... }
}

So the check counts for the number of tests ran, and if no test is
executed, no leaks detection is performed. However now we have certain
tests that are in the form:

test {
    server { ... }
}

For instance just loading a corrupted RDB or alike. In this case, the
leaks test is not executed. This commit removes the check so that the
leaks test is always executed.
2025-05-20 17:46:56 +08:00
Mincho Paskalevanddebing.sun 8dfb823c51 Implement DIFF, DIFF1, ANDOR and ONE for BITOP (#13898)
This PR adds 4 new operators to the `BITOP` command - `DIFF`, `DIFF1`,
`ANDOR` and `ONE`. They enable redis clients to atomically do
non-trivial logical operations that are useful for checking membership
of a bitmap against a group of bitmaps.
 
* **DIFF**
    `BITOP DIFF dest srckey1 srckey2 [key...]`

    **Description**
DIFF(*X*, *A1*, *A2*, *...*, *AN*) = *X* ∧ ¬(*A1* ∨ *A2* ∨ *...* ∨
*AN*), i.e the set bits of *X* that are not set in any of *A1*, *A2*,
*…*, *AN*

    **NOTE**
    Command expects at least 2 source keys.

* **DIFF1**
    `BITOP DIFF1 dest srckey1 srckey2 [key...]`

    **Description**
DIFF1(*X*, *A1*, *A2*, *...*, *AN*) = ¬*X* ∧ (*A1* ∨ *A2* ∨ *...* ∨
*AN*), i.e the bits set in one or more of *A1*, *A2*, *…*, *AN* that are
not set in *X*

    **NOTE**
    Command expects at least 2 source keys.

* **ANDOR**
    `BITOP ANDOR dest srckey1 srckey2 [key...]`

    **Description**
ANDOR(*X*, *A1*, *A2*, *...*, *AN*) = *X* ∧ (*A1* ∨ *A2* ∨ *...* ∨
*AN*), i.e the set bits of X that are also set in *A1*, *A2*, *…*, *AN*

    **NOTE**
    Command expects at least 2 source keys.

* **ONE**
    `BITOP ONE dest key [key...]`

    **Description**
    ONE(*A1*, *A2*, *...*, *AN*) = *X*, where 
if *X[i]* is the *i*-th bit of *X* then *X[i] = 1* if and only if there
is m such that *A_m[i] = 1* and *An[i] = 0* for all *n != m*, i.e bit
*X[i]* is set only if it set in exactly one of *A1*, *A2*, *...*, *AN*

**Return value**
As in all other `BITOP` operators return value for all the new ones is
the number bytes of the longest key.

EDIT:
Besides adding the new commands couple more changes were made:
- Added AVX2 path for more optimized computation of the BITOP operations
(including the new ones)
- Removed the hard limit of max 16 source keys for the fast path to be
used - now no matter the number of keys we can enter the fast path given
keys are long enough.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2025-05-20 10:45:50 +03:00
Mincho Paskalev 391e3452ca Optimize hash and zset with lpBatchAppend (#13981)
# Description

Use lpBatchAppend/Insert instead of multiple calls to lpAppend/Insert in
hash and zset

HSET Improvement 2.2%
ZADD Improvement 3.3%
2025-05-20 10:44:46 +03:00
Hyeon Sung 9a9aa921bc extract duplicated AOF list formatting logic into helper function (#14012)
Separated the repeated logic for iterating and formatting AOF info from
both
history and incremental AOF lists into a new helper function named 
appendAofInfoFromList. This improves code readability, reduces
duplication,
and makes the getAofManifestAsString function cleaner and easier to
maintain.

No changes in behavior were introduced.
2025-05-20 11:08:03 +08:00