Compare commits

...
46 Commits
Author SHA1 Message Date
YaacovHazan 5fe3e74a38 Redis 8.0 M01 (#13538)
CI / test-ubuntu-latest (push) Has been cancelled
CI / test-sanitizer-address (push) Has been cancelled
CI / build-debian-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos-jemalloc (push) Has been cancelled
CI / build-old-chain-jemalloc (push) Has been cancelled
External Server Tests / test-external-standalone (push) Has been cancelled
External Server Tests / test-external-cluster (push) Has been cancelled
External Server Tests / test-external-nodebug (push) Has been cancelled
Spellcheck / Spellcheck (push) Has been cancelled
### New Features in binary distributions

- 7 new data structures: JSON, Time series, Bloom filter, Cuckoo filter,
Count-min sketch, Top-k, t-digest
- Redis scalable query engine (including vector search)

### Potentially breaking changes

- #12272 `GETRANGE` returns an empty bulk when the negative end index is
out of range
- #12395 Optimize `SCAN` command when matching data type

### Bug fixes

- #13510 Fix `RM_RdbLoad` to enable AOF after RDB loading is completed
- #13489 `ACL CAT` - return module commands
- #13476 Fix a race condition in the `cache_memory` of `functionsLibCtx`
- #13473 Fix incorrect lag due to trimming stream via `XTRIM` command
- #13338 Fix incorrect lag field in `XINFO` when tombstone is after the
`last_id` of the consume group
- #13470 On `HDEL` of last field - update the global hash field
expiration data structure
- #13465 Cluster: Pass extensions to node if extension processing is
handled by it
- #13443 Cluster: Ensure validity of myself when loading cluster config
- #13422 Cluster: Fix `CLUSTER SHARDS` command returns empty array

### Modules API

- #13509 New API calls: `RM_DefragAllocRaw`, `RM_DefragFreeRaw`, and
`RM_RegisterDefragCallbacks` - defrag API to allocate and free raw
memory

### Performance and resource utilization improvements

- #13503 Avoid overhead of comparison function pointer calls in listpack
`lpFind`
- #13505 Optimize `STRING` datatype write commands
- #13499 Optimize `SMEMBERS` command
- #13494 Optimize `GEO*` commands reply
- #13490 Optimize `HELLO` command
- #13488 Optimize client query buffer
- #12395 Optimize `SCAN` command when matching data type
- #13529 Optimize `LREM`, `LPOS`, `LINSERT`, and `LINDEX` commands
- #13516 Optimize `LRANGE` and other commands that perform several
writes to client buffers per call
- #13431 Avoid `used_memory` contention when updating from multiple
threads

### Other general improvements

- #13495 Reply `-LOADING` on replica while flushing the db

### CLI tools

- #13411 redis-cli: Fix wrong `dbnum` showed after the client
reconnected

### Notes

- No backward compatibility for replication or persistence.
- Additional distributions, upgrade paths, features, and improvements
will be introduced in upcoming pre-releases.
- With the GA release of 8.0 we will deprecate Redis Stack.
2024-09-12 11:52:28 +03:00
YaacovHazan 4955375ec7 Redis 8.0 M01 2024-09-12 10:23:36 +03:00
YaacovHazan 406a365f44 Bundle modules for Redis 8.0 M01 2024-09-11 16:40:16 +03:00
debing.sunandoranagra 2dd4cca363 Increment kvstore's non_empty_dicts only on first insert (#13528)
Found by @oranagra 

Currently, when the size of dict becomes 1, we do not check whether
`delta` is positive or negative.
As a result, `non_empty_dicts` is still incremented when the size of
dict changes from 2 to 1.
We should only increment `non_empty_dicts` when `delta` is positive, as
this indicates the first time an element is inserted into the dict.

---------

Co-authored-by: oranagra <oran@redislabs.com>
2024-09-11 09:36:01 +08:00
Filipe Oliveira (Redis) bcae770819 Optimize LREM, LPOS, LINSERT, LINDEX: Avoid N-1 sdslen() calls on listTypeEqual (#13529)
This is a very easy optimization, that avoids duplicate computation of
the object length for LREM, LPOS, LINSERT na LINDEX.

We can see that sdslen takes 7.7% of the total CPU cycles of the
benchmarks.

Function Stack | CPU Time: Total | CPU Time: Self | Module | Function
(Full) | Source File | Start Address
-- | -- | -- | -- | -- | -- | --
listTypeEqual | 15.50% | 2.346s | redis-server | listTypeEqual |
t_list.c | 0x845dd
sdslen | 7.70% | 2.300s | redis-server | sdslen | sds.h | 0x845e4

Preliminary data showcases 4% improvement in the achievable ops/sec of
LPOS in string elements, and 2% in int elements.
2024-09-10 20:26:36 +08:00
YaacovHazanandYaacovHazan bf802b0764 Add the option to build Redis with modules (#13524)
A new BUILD_WITH_MODULES flag was added to the Makefile to control
building the module directory.

The new module directory includes a general Makefile that iterates
over each module, fetch a specific version, and build it.

Co-authored-by: YaacovHazan <yaacov.hazan@redislabs.com>
2024-09-09 15:47:02 +03:00
Ozan Tezcan ac03e3721d Fix flaky replication tests (#13518)
#13495 introduced a change to reply -LOADING while flushing existing db on a replica. Some of our tests are
 sensitive to this change and do no expect -LOADING reply.

Fixing a couple of tests that fail time to time.
2024-09-08 12:54:01 +03:00
Filipe Oliveira (Redis)anddebing.sun 31227f4faf Optimize client type check on reply hot code paths (#13516)
## Proposed improvement

This PR introduces the static inlined function `clientTypeIsSlave` which
is doing only 1 condition check vs 3 checks of `getClientType`, and also
uses the `unlikely` to tell the compiler that the most common outcome is
for the client not to be a slave.
Preliminary data show 3% improvement on the achievable ops/sec on the
specific LRANGE benchmark. After running the entire suite we see up to
5% improvement in 2 tests.
https://github.com/redis/redis/pull/13516#issuecomment-2331326052

## Context

This optimization efforts comes from analyzing the profile info from the
[memtier_benchmark-1key-list-1K-elements-lrange-all-elements](https://github.com/redis/redis-benchmarks-specification/blob/main/redis_benchmarks_specification/test-suites/memtier_benchmark-1key-list-1K-elements-lrange-all-elements.yml)
benchmark.
 
By going over it, we can see that `getClientType` consumes 2% of the cpu
time, strictly to check if the client is a slave (
https://github.com/redis/redis/blob/unstable/src/networking.c#L397 , and
https://github.com/redis/redis/blob/unstable/src/networking.c#L1254 )


Function | CPU Time: Total | CPU Time: Self | Module | Function (Full)
-- | -- | -- | -- | --
_addReplyToBufferOrList->getClientType | 1.20% | 0.728s | redis-server |
getClientType
clientHasPendingReplies->getClientType | 0.80% | 0.482s | redis-server |
getClientType

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2024-09-06 10:24:30 +08:00
Max Malekzadeh f6f11f3ef1 Remove outdated "Try Redis" link in README.md (#13498) 2024-09-05 22:04:49 +08:00
Moti Cohen 569584d463 HFE - Simplify logic of HGETALL command (#13425) 2024-09-05 12:48:44 +03:00
ea3e8b79a1 Introduce reusable query buffer for client reads (#13488)
This PR is based on the commits from PR
https://github.com/valkey-io/valkey/pull/258,
https://github.com/valkey-io/valkey/pull/593,
https://github.com/valkey-io/valkey/pull/639

This PR optimizes client query buffer handling in Redis by introducing
a reusable query buffer that is used by default for client reads. This
reduces memory usage by ~20KB per client by avoiding allocations for
most clients using short (<16KB) complete commands. For larger or
partial commands, the client still gets its own private buffer.

The primary changes are:

* Adding a reusable query buffer `thread_shared_qb` that clients use by
default.
* Modifying client querybuf initialization and reset logic.
* Freeing idle client query buffers when empty to allow reuse of the
reusable query buffer.
* Master client query buffers are kept private as their contents need to
be preserved for replication stream.
* When nested commands is executed, only the first user uses the reuse
buffer, and subsequent users will still use the private buffer.

In addition to the memory savings, this change shows a 3% improvement in
latency and throughput when running with 1000 active clients.

The memory reduction may also help reduce the need to evict clients when
reaching max memory limit, as the query buffer is the main memory
consumer per client.

This PR is different from https://github.com/valkey-io/valkey/pull/258
1. When a client is in the mid of requiring a reused buffer and
returning it, regardless of whether the query buffer has changed
(expanded), we do not update the reused query buffer in the middle, but
return the reused query buffer (expanded or with data remaining) or
reset it at the end.
2. Adding a new thread variable `thread_shared_qb_used` to avoid
multiple clients requiring the reusable query buffer at the same time.

---------

Signed-off-by: Uri Yagelnik <uriy@amazon.com>
Signed-off-by: Madelyn Olson <matolson@amazon.com>
Co-authored-by: Uri Yagelnik <uriy@amazon.com>
Co-authored-by: Madelyn Olson <madelyneolson@gmail.com>
Co-authored-by: oranagra <oran@redislabs.com>
2024-09-04 19:10:40 +08:00
debing.sun 74609d44cd Fix set with invalid length causes smembers to hang (#13515)
After https://github.com/redis/redis/pull/13499, If the length set by
`addReplySetLen()` does not match the actual number of elements in the
reply, it will cause protocol broken and result in the client hanging.
2024-09-04 17:35:46 +08:00
Ozan Tezcan ea05c6ac47 Fix RM_RdbLoad() to enable AOF after loading is completed (#13510)
RM_RdbLoad() disables AOF temporarily while loading RDB.
Later, it does not enable it back as it checks AOF state (disabled by then) 
rather than AOF config parameter.

Added a change to restart AOF according to config parameter.
2024-09-04 11:11:04 +03:00
Filipe Oliveira (Redis)anddebing.sun 05aed4cab9 Optimize SET/INCR*/DECR*/SETRANGE/APPEND by reducing duplicate computation (#13505)
- Avoid addReplyLongLong (which converts back to string) the value we
already have as a robj, by using addReplyProto + addReply
- Avoid doing dbFind Twice for the same dictEntry on
INCR*/DECR*/SETRANGE/APPEND commands.
- Avoid multiple sdslen calls with the same input on setrangeCommand and
appendCommand
- Introduce setKeyWithDictEntry, which is like setKey(), but accepts an
optional dictEntry input: Avoids the second dictFind in SET command

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2024-09-04 14:51:21 +08:00
debing.sunandOzan Tezcan de7f2f87f7 Avoid overhead of comparision function pointer calls in lpFind() (#13503)
In #13279 (found by @filipecosta90), for custom lookups, we introduce a
comparison function for `lpFind()` to compare entry, but it also
introduces some overhead.

To avoid the overhead of function pointer calls:
1. Extract the lpFindCb() method into a lpFindCbInternal() method that
is easier to inline.
2. Use unlikely to annotate the comparison method, as can only success
once.

---------

Co-authored-by: Ozan Tezcan <ozantezcan@gmail.com>
2024-09-03 22:53:13 +08:00
Filipe Oliveira (Redis) fb8755a636 changed addReplyHumanLongDouble to addReplyDouble in georadiusGeneric and geoposCommand (#13494)
# Summary 

- Addresses https://github.com/redis/redis/issues/11565
- Measured improvements of 30% and 37% on the simple use-case (GEOSEARCH
and GEOPOS) (check
https://github.com/redis/redis/pull/13494#issuecomment-2313668934), and
of 66% on a dataset with >60M datapoints and pipeline 10 benchmark.
2024-09-03 20:54:20 +08:00
Meir Shpilraien (Spielrein) d3d94ccf2e Added new defrag API to allocate and free raw memory. (#13509)
All the defrag allocations API expects to get a value and replace it, leaving the old value untouchable.
In some cases a value might be shared between multiple keys, in such cases we can not simply replace
it when the defrag callback is called.

To allow support such use cases, the PR adds two new API's to the defrag API:

1. `RM_DefragAllocRaw` - allocate memory base on a given size.
2. `RM_DefragFreeRaw` - Free the given pointer.

Those API's avoid using tcache so they operate just like `RM_DefragAlloc` but allows the user to split
the allocation and the memory free operations into two stages and control when those happen.

In addition the PR adds new API to allow the module to receive notifications when defrag start and end: `RM_RegisterDefragCallbacks`
Those callbacks are the same as `RM_RegisterDefragFunc` but promised to be called and the start
and the end of the defrag process.
2024-09-03 15:03:19 +03:00
Filipe Oliveira (Redis)anddebing.sun 00a8e72cfc Created specific SMEMBERS command logic which avoids sinterGenericCommand, and minimizes processing and memory overhead (#13499)
This PR introduces a dedicated implementation for the SMEMBERS command
that avoids using the more generalized sinterGenericCommand function.
By tailoring the logic specifically for SMEMBERS, we reduce unnecessary
processing and memory overheads that were previously incurred by
handling more complex cases like set intersections.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2024-09-03 18:32:43 +08:00
Filipe Oliveira (Redis) a31b516e25 Optimize the HELLO command reply (#13490)
# Overall improvement

TBD ( current is approximately 6% on the achievable ops/sec), coming
from:

- In case of no module we can skip 1.3% CPU cycles on dict Iterator
creation/deletion
- Use addReplyBulkCBuffer instead of addReplyBulkCString to avoid
runtime strlen overhead within HELLO reply on string constants.

## Optimization 1: In case of no module we can skip 1.3% CPU cycles on
dict Iterator creation/deletion.

## Optimization 2: Use addReplyBulkCBuffer instead of
addReplyBulkCString to avoid runtime strlen overhead within HELLO reply
on string constants.
2024-09-03 17:27:35 +08:00
cyy-tagandyingyin.chen c77b8f45e9 Fixed variable parameter formatting issues in serverPanic function (#13504)
Currently aeApiPoll panic does not record error code information. Added
variable parameter formatting to _serverPanic to fix the issue

---------

Co-authored-by: yingyin.chen <15816602944@163.com>
2024-09-03 15:51:46 +08:00
Ozan Tezcan a7afd1d2b2 Reply LOADING on replica while flushing the db (#13495)
On a full sync, replica starts discarding existing db. If the existing 
db is huge and flush is happening synchronously, replica may become 
unresponsive. 

Adding a change to yield back to event loop while flushing db on 
a replica. Replica will reply -LOADING in this case. Note that while 
replica is loading the new rdb, it may get an error and start flushing
the partial db. This step may take a long time as well. Similarly, 
replica will reply -LOADING in this case. 

To call processEventsWhileBlocked() and reply -LOADING, we need to do:
- Set connSetReadHandler() null not to process further data from the master
- Set server.loading flag
- Call blockingOperationStarts()

rdbload() already does these steps and calls processEventsWhileBlocked()
while loading the rdb. Added a new call rdbLoadWithEmptyFunc() which 
accepts callback to flush db before loading rdb or when an error 
happens while loading. 

For diskless replication, doing something similar and calling emptyData()
after setting required flags.

Additional changes:
- Allow `appendonly` config change during loading. 
 Config can be changed while loading data on startup or on replication 
 when slave is loading RDB. We allow config change command to update 
 `server.aof_enabled` and then lazily apply config change after loading
 operation is completed.
 
 - Added a test for `replica-lazy-flush` config
2024-09-03 09:48:44 +03:00
Oran Agra 3fcddfb61f testsuite --dump-logs works on servers started before the test (#13500)
so far ./runtest --dump-logs used work for servers started within the
test proc.
now it'll also work on servers started outside the test proc scope.
the downside is that these logs can be huge if they served many tests
and not just the failing one.
but for some rare failures, we rather have that than nothing.
this feature isn't enabled y default, but is used by our GH actions.
2024-08-29 07:27:23 +01:00
CoolThi 3c9f5954b5 Remove variable expired in expireSlaveKeys() to prevent confusing the compiler (#13299)
This change prevents missed optimization for some compilers:
https://godbolt.org/z/W66h86E13 (the reduced intermediate form in
optimization).
2024-08-28 21:52:23 +08:00
Raz Monsonego 3b1b1d1486 MOD-7645: Return module commands in ACL CAT (#13489)
Currently, module commands are not returned for the `ACL CAT <category>`
command, but skipped instead. Since now modules can add ACL categories
they should no longer be skipped.
2024-08-26 19:25:22 +01:00
paoloredis 60f22ca830 Add redis_docs_sync workflow (#13426)
This workflow executes on releases. It invokes the `redis_docs_sync`
workflow on `redis/docs`.
2024-08-21 09:41:14 +01:00
Zihao Linanddebing.sun 6ceadfb580 Improve GETRANGE command behavior (#12272)
Fixed the issue about GETRANGE and SUBSTR command
return unexpected result caused by the `start` and `end` out of
definition range of string.

---
## break change
Before this PR, when negative `end` was out of range (i.e., end <
-strlen), we would fix it to 0 to get the substring, which also resulted
in the first character still being returned for this kind of out of
range.
After this PR, we ensure that `GETRANGE` returns an empty bulk when the
negative end index is out of range.

Closes #11738

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2024-08-20 12:34:43 +08:00
judeng 7f0a7f0a69 improve performance for scan command when matching data type (#12395)
Move the TYPE filtering to the scan callback so that avoided the
`lookupKey` operation. This is the follow-up to #12209 . In this thread
we introduced two breaking changes:
1. we will not attempt to do lazy expire (delete) a key that was
filtered by not matching the TYPE (like we already do for MATCH
pattern).
2. when the specified key TYPE filter is an unknown type, server will
reply a error immediately instead of doing a full scan that comes back
empty handed.
2024-08-20 10:47:51 +08:00
Meir Shpilraien (Spielrein) 3264deb24e Avoid used_memory contention when update from multiple threads. (#13431)
The PR attempt to avoid contention on the `used_memory` global variable
when allocate or free memory from multiple threads at the same time.

Each time a thread is allocating or releasing a memory, it needs to
update the `used_memory` global variable. This update might cause a
contention when done aggressively from multiple threads.

### The solution

Instead of having a single global variable that need to be updated from
multiple thread. We create an array of used_memory, each entry in the
array is updated by a single thread and the main thread summarizes all
the values to accumulate the memory usage.

This solution, though reduces the contention between threads on updating
the `used_memory` global variable, it adds work to the main thread that
need to summarize all the entries at the `used_memory` array. To avoid
increasing the work done by the main thread by too much, we limit the
size of the used memory array to 16. This means that up to 16 threads
can run without any contention between them. If there are more than 16
threads, we will reuse entries on the used_memory array, in this case we
might still have contention between threads, but it will be much less
significant.

Notice, that in order to really avoid contention, the entries in the
`used_memory` array must reside on different cache lines. To achieve
that we create a struct with padding such that its size will be exactly
cache_line size. In addition we make sure the address of the
`used_memory` array will be aligned to cache_line size.

### Benchmark

Some benchmark shows improvement (up to 15%):

| Test Case |Baseline unstable (median obs. +- std.dev)|Comparison
test_used_memory_per_thread_array (median obs. +- std.dev)|% change
(higher-better)| Note |

|-------------------------------------------------------------------------------|------------------------------------------|--------------------------------------------------------------------:|------------------------|------------------------------------|
|memtier_benchmark-1key-list-100-elements-lrange-all-elements | 92657 +-
2.0% (2 datapoints) | 101445|9.5% |IMPROVEMENT |
|memtier_benchmark-1key-list-1K-elements-lrange-all-elements | 14965 +-
1.3% (2 datapoints) | 16296|8.9% |IMPROVEMENT |
|memtier_benchmark-1key-set-10-elements-smembers-pipeline-10 | 431019 +-
5.2% (2 datapoints) | 461039|7.0% |waterline=5.2%. IMPROVEMENT |
|memtier_benchmark-1key-set-100-elements-smembers | 74367 +- 0.0% (2
datapoints) | 80190|7.8% |IMPROVEMENT |
|memtier_benchmark-1key-set-1K-elements-smembers | 11730 +- 0.4% (2
datapoints) | 13519|15.3% |IMPROVEMENT |


Full results:

| Test Case |Baseline unstable (median obs. +- std.dev)|Comparison
test_used_memory_per_thread_array (median obs. +- std.dev)|% change
(higher-better)| Note |

|-------------------------------------------------------------------------------|------------------------------------------|--------------------------------------------------------------------:|------------------------|------------------------------------|
|memtier_benchmark-10Mkeys-load-hash-5-fields-with-1000B-values | 88613
+- 1.0% (2 datapoints) | 88688|0.1% |No Change |

|memtier_benchmark-10Mkeys-load-hash-5-fields-with-1000B-values-pipeline-10
| 124786 +- 1.2% (2 datapoints) | 123671|-0.9% |No Change |
|memtier_benchmark-10Mkeys-load-hash-5-fields-with-100B-values | 122460
+- 1.4% (2 datapoints) | 122990|0.4% |No Change |

|memtier_benchmark-10Mkeys-load-hash-5-fields-with-100B-values-pipeline-10
| 333384 +- 5.1% (2 datapoints) | 319221|-4.2% |waterline=5.1%.
potential REGRESSION|
|memtier_benchmark-10Mkeys-load-hash-5-fields-with-10B-values | 137354
+- 0.3% (2 datapoints) | 138759|1.0% |No Change |

|memtier_benchmark-10Mkeys-load-hash-5-fields-with-10B-values-pipeline-10
| 401261 +- 4.3% (2 datapoints) | 398524|-0.7% |No Change |
|memtier_benchmark-1Mkeys-100B-expire-use-case | 179058 +- 0.4% (2
datapoints) | 180114|0.6% |No Change |
|memtier_benchmark-1Mkeys-10B-expire-use-case | 180390 +- 0.2% (2
datapoints) | 180401|0.0% |No Change |
|memtier_benchmark-1Mkeys-1KiB-expire-use-case | 175993 +- 0.7% (2
datapoints) | 175147|-0.5% |No Change |
|memtier_benchmark-1Mkeys-4KiB-expire-use-case | 165771 +- 0.0% (2
datapoints) | 164434|-0.8% |No Change |
|memtier_benchmark-1Mkeys-bitmap-getbit-pipeline-10 | 931339 +- 2.1% (2
datapoints) | 929487|-0.2% |No Change |
|memtier_benchmark-1Mkeys-generic-exists-pipeline-10 | 999462 +- 0.4% (2
datapoints) | 963226|-3.6% |potential REGRESSION |
|memtier_benchmark-1Mkeys-generic-expire-pipeline-10 | 905333 +- 1.4% (2
datapoints) | 896673|-1.0% |No Change |
|memtier_benchmark-1Mkeys-generic-expireat-pipeline-10 | 885015 +- 1.0%
(2 datapoints) | 865010|-2.3% |No Change |
|memtier_benchmark-1Mkeys-generic-pexpire-pipeline-10 | 897115 +- 1.2%
(2 datapoints) | 887544|-1.1% |No Change |
|memtier_benchmark-1Mkeys-generic-scan-pipeline-10 | 451103 +- 3.2% (2
datapoints) | 465571|3.2% |potential IMPROVEMENT |
|memtier_benchmark-1Mkeys-generic-touch-pipeline-10 | 996809 +- 0.6% (2
datapoints) | 984478|-1.2% |No Change |
|memtier_benchmark-1Mkeys-generic-ttl-pipeline-10 | 979570 +- 1.7% (2
datapoints) | 958752|-2.1% |No Change |

|memtier_benchmark-1Mkeys-hash-hget-hgetall-hkeys-hvals-with-100B-values
| 180888 +- 0.5% (2 datapoints) | 182295|0.8% |No Change |

|memtier_benchmark-1Mkeys-hash-hmget-5-fields-with-100B-values-pipeline-10
| 717881 +- 1.0% (2 datapoints) | 724814|1.0% |No Change |
|memtier_benchmark-1Mkeys-hash-transactions-multi-exec-pipeline-20 |
1055447 +- 0.4% (2 datapoints) | 1065836|1.0% |No Change |
|memtier_benchmark-1Mkeys-lhash-hexists | 164332 +- 0.1% (2 datapoints)
| 163636|-0.4% |No Change |
|memtier_benchmark-1Mkeys-lhash-hincbry | 171674 +- 0.3% (2 datapoints)
| 172737|0.6% |No Change |
|memtier_benchmark-1Mkeys-list-lpop-rpop-with-100B-values | 180904 +-
1.1% (2 datapoints) | 179467|-0.8% |No Change |
|memtier_benchmark-1Mkeys-list-lpop-rpop-with-10B-values | 181746 +-
0.8% (2 datapoints) | 182416|0.4% |No Change |
|memtier_benchmark-1Mkeys-list-lpop-rpop-with-1KiB-values | 182004 +-
0.7% (2 datapoints) | 180237|-1.0% |No Change |
|memtier_benchmark-1Mkeys-load-hash-5-fields-with-1000B-values | 105191
+- 0.9% (2 datapoints) | 105058|-0.1% |No Change |

|memtier_benchmark-1Mkeys-load-hash-5-fields-with-1000B-values-pipeline-10
| 150683 +- 0.9% (2 datapoints) | 153597|1.9% |No Change |
|memtier_benchmark-1Mkeys-load-hash-hmset-5-fields-with-1000B-values |
104122 +- 0.7% (2 datapoints) | 105236|1.1% |No Change |
|memtier_benchmark-1Mkeys-load-list-with-100B-values | 149770 +- 0.9% (2
datapoints) | 150510|0.5% |No Change |
|memtier_benchmark-1Mkeys-load-list-with-10B-values | 165537 +- 1.9% (2
datapoints) | 164329|-0.7% |No Change |
|memtier_benchmark-1Mkeys-load-list-with-1KiB-values | 113315 +- 0.5% (2
datapoints) | 114110|0.7% |No Change |
|memtier_benchmark-1Mkeys-load-stream-1-fields-with-100B-values | 131201
+- 0.7% (2 datapoints) | 129545|-1.3% |No Change |

|memtier_benchmark-1Mkeys-load-stream-1-fields-with-100B-values-pipeline-10
| 352891 +- 2.8% (2 datapoints) | 348338|-1.3% |No Change |
|memtier_benchmark-1Mkeys-load-stream-5-fields-with-100B-values | 104386
+- 0.7% (2 datapoints) | 105796|1.4% |No Change |

|memtier_benchmark-1Mkeys-load-stream-5-fields-with-100B-values-pipeline-10
| 227593 +- 5.5% (2 datapoints) | 218783|-3.9% |waterline=5.5%.
potential REGRESSION|
|memtier_benchmark-1Mkeys-load-string-with-100B-values | 167552 +- 0.2%
(2 datapoints) | 170282|1.6% |No Change |
|memtier_benchmark-1Mkeys-load-string-with-100B-values-pipeline-10 |
646888 +- 0.5% (2 datapoints) | 639680|-1.1% |No Change |
|memtier_benchmark-1Mkeys-load-string-with-10B-values | 174891 +- 0.7%
(2 datapoints) | 174382|-0.3% |No Change |
|memtier_benchmark-1Mkeys-load-string-with-10B-values-pipeline-10 |
749988 +- 5.1% (2 datapoints) | 769986|2.7% |waterline=5.1%. No Change |
|memtier_benchmark-1Mkeys-load-string-with-1KiB-values | 155929 +- 0.1%
(2 datapoints) | 156387|0.3% |No Change |
|memtier_benchmark-1Mkeys-load-zset-with-10-elements-double-score |
92241 +- 0.2% (2 datapoints) | 92189|-0.1% |No Change |
|memtier_benchmark-1Mkeys-load-zset-with-10-elements-int-score | 114328
+- 1.3% (2 datapoints) | 113154|-1.0% |No Change |
|memtier_benchmark-1Mkeys-string-get-100B | 180685 +- 0.2% (2
datapoints) | 180359|-0.2% |No Change |
|memtier_benchmark-1Mkeys-string-get-100B-pipeline-10 | 991291 +- 3.1%
(2 datapoints) | 1020086|2.9% |No Change |
|memtier_benchmark-1Mkeys-string-get-10B | 181183 +- 0.3% (2 datapoints)
| 177868|-1.8% |No Change |
|memtier_benchmark-1Mkeys-string-get-10B-pipeline-10 | 1032554 +- 0.8%
(2 datapoints) | 1023120|-0.9% |No Change |
|memtier_benchmark-1Mkeys-string-get-1KiB | 180479 +- 0.9% (2
datapoints) | 182215|1.0% |No Change |
|memtier_benchmark-1Mkeys-string-get-1KiB-pipeline-10 | 979286 +- 0.9%
(2 datapoints) | 989888|1.1% |No Change |
|memtier_benchmark-1Mkeys-string-mget-1KiB | 121950 +- 0.4% (2
datapoints) | 120996|-0.8% |No Change |
|memtier_benchmark-1key-geo-60M-elements-geodist | 179404 +- 1.0% (2
datapoints) | 181232|1.0% |No Change |
|memtier_benchmark-1key-geo-60M-elements-geodist-pipeline-10 | 1023797
+- 0.5% (2 datapoints) | 1014980|-0.9% |No Change |
|memtier_benchmark-1key-geo-60M-elements-geohash | 180808 +- 1.2% (2
datapoints) | 180606|-0.1% |No Change |
|memtier_benchmark-1key-geo-60M-elements-geohash-pipeline-10 | 1056458
+- 1.6% (2 datapoints) | 1040050|-1.6% |No Change |
|memtier_benchmark-1key-geo-60M-elements-geopos | 181808 +- 0.2% (2
datapoints) | 175945|-3.2% |potential REGRESSION |
|memtier_benchmark-1key-geo-60M-elements-geopos-pipeline-10 | 1038180 +-
3.4% (2 datapoints) | 1033005|-0.5% |No Change |
|memtier_benchmark-1key-geo-60M-elements-geosearch-fromlonlat | 142614
+- 0.3% (2 datapoints) | 144259|1.2% |No Change |
|memtier_benchmark-1key-geo-60M-elements-geosearch-fromlonlat-bybox |
141008 +- 0.4% (2 datapoints) | 139602|-1.0% |No Change |

|memtier_benchmark-1key-geo-60M-elements-geosearch-fromlonlat-pipeline-10
| 560698 +- 0.8% (2 datapoints) | 548806|-2.1% |No Change |
|memtier_benchmark-1key-list-10-elements-lrange-all-elements | 166132 +-
0.9% (2 datapoints) | 170259|2.5% |No Change |
|memtier_benchmark-1key-list-100-elements-lrange-all-elements | 92657 +-
2.0% (2 datapoints) | 101445|9.5% |IMPROVEMENT |
|memtier_benchmark-1key-list-1K-elements-lrange-all-elements | 14965 +-
1.3% (2 datapoints) | 16296|8.9% |IMPROVEMENT |
|memtier_benchmark-1key-pfadd-4KB-values-pipeline-10 | 264156 +- 0.2% (2
datapoints) | 262582|-0.6% |No Change |
|memtier_benchmark-1key-set-10-elements-smembers | 138916 +- 1.7% (2
datapoints) | 138016|-0.6% |No Change |
|memtier_benchmark-1key-set-10-elements-smembers-pipeline-10 | 431019 +-
5.2% (2 datapoints) | 461039|7.0% |waterline=5.2%. IMPROVEMENT |
|memtier_benchmark-1key-set-10-elements-smismember | 173545 +- 1.1% (2
datapoints) | 173488|-0.0% |No Change |
|memtier_benchmark-1key-set-100-elements-smembers | 74367 +- 0.0% (2
datapoints) | 80190|7.8% |IMPROVEMENT |
|memtier_benchmark-1key-set-100-elements-smismember | 155682 +- 1.6% (2
datapoints) | 151367|-2.8% |No Change |
|memtier_benchmark-1key-set-1K-elements-smembers | 11730 +- 0.4% (2
datapoints) | 13519|15.3% |IMPROVEMENT |
|memtier_benchmark-1key-set-200K-elements-sadd-constant | 181070 +- 1.1%
(2 datapoints) | 180214|-0.5% |No Change |
|memtier_benchmark-1key-set-2M-elements-sadd-increasing | 166364 +- 0.1%
(2 datapoints) | 166944|0.3% |No Change |
|memtier_benchmark-1key-zincrby-1M-elements-pipeline-1 | 46071 +- 0.6%
(2 datapoints) | 44979|-2.4% |No Change |
|memtier_benchmark-1key-zrank-1M-elements-pipeline-1 | 48429 +- 0.4% (2
datapoints) | 49265|1.7% |No Change |
|memtier_benchmark-1key-zrem-5M-elements-pipeline-1 | 48528 +- 0.4% (2
datapoints) | 48869|0.7% |No Change |
|memtier_benchmark-1key-zrevrangebyscore-256K-elements-pipeline-1 |
100580 +- 1.5% (2 datapoints) | 101782|1.2% |No Change |
|memtier_benchmark-1key-zrevrank-1M-elements-pipeline-1 | 48621 +- 2.0%
(2 datapoints) | 48473|-0.3% |No Change |
|memtier_benchmark-1key-zset-10-elements-zrange-all-elements | 83485 +-
0.6% (2 datapoints) | 83095|-0.5% |No Change |

|memtier_benchmark-1key-zset-10-elements-zrange-all-elements-long-scores
| 118673 +- 0.8% (2 datapoints) | 118006|-0.6% |No Change |
|memtier_benchmark-1key-zset-100-elements-zrange-all-elements | 19009 +-
1.1% (2 datapoints) | 19293|1.5% |No Change |
|memtier_benchmark-1key-zset-100-elements-zrangebyscore-all-elements |
18957 +- 0.5% (2 datapoints) | 19419|2.4% |No Change |

|memtier_benchmark-1key-zset-100-elements-zrangebyscore-all-elements-long-scores|
171693 +- 0.5% (2 datapoints) | 172432|0.4% |No Change |
|memtier_benchmark-1key-zset-1K-elements-zrange-all-elements | 3566 +-
0.6% (2 datapoints) | 3672|3.0% |No Change |
|memtier_benchmark-1key-zset-1M-elements-zcard-pipeline-10 | 1067713 +-
0.4% (2 datapoints) | 1071550|0.4% |No Change |
|memtier_benchmark-1key-zset-1M-elements-zrevrange-5-elements | 169195
+- 0.7% (2 datapoints) | 169620|0.3% |No Change |
|memtier_benchmark-1key-zset-1M-elements-zscore-pipeline-10 | 914338 +-
0.2% (2 datapoints) | 905540|-1.0% |No Change |
|memtier_benchmark-2keys-lua-eval-hset-expire | 88346 +- 1.7% (2
datapoints) | 87259|-1.2% |No Change |
|memtier_benchmark-2keys-lua-evalsha-hset-expire | 103273 +- 1.2% (2
datapoints) | 102393|-0.9% |No Change |
|memtier_benchmark-2keys-set-10-100-elements-sdiff | 15418 +- 10.9%
UNSTABLE (2 datapoints) | 14369|-6.8% |UNSTABLE (very high variance) |
|memtier_benchmark-2keys-set-10-100-elements-sinter | 83601 +- 3.6% (2
datapoints) | 82508|-1.3% |No Change |
|memtier_benchmark-2keys-set-10-100-elements-sunion | 14942 +- 11.2%
UNSTABLE (2 datapoints) | 14001|-6.3% |UNSTABLE (very high variance) |
|memtier_benchmark-2keys-stream-5-entries-xread-all-entries | 75938 +-
0.4% (2 datapoints) | 76565|0.8% |No Change |
|memtier_benchmark-2keys-stream-5-entries-xread-all-entries-pipeline-10
| 120781 +- 1.1% (2 datapoints) | 119142|-1.4% |No Change |
2024-08-19 13:22:16 +03:00
debing.sun 6c6489280c Fix a race condition issue in the cache_memory of functionsLibCtx (#13476)
This is a missing of the PR https://github.com/redis/redis/pull/13383.
We will call `functionsLibCtxClear()` in bio, so we shouldn't touch
`curr_functions_lib_ctx` in it.
2024-08-19 10:11:45 +08:00
debing.sun 2b88db90aa Fix incorrect lag due to trimming stream via XTRIM command (#13473)
## Describe
When using the `XTRIM` command to trim a stream, it does not update the
maximal tombstone (`max_deleted_entry_id`). This leads to an issue where
the lag calculation incorrectly assumes that there are no tombstones
after the consumer group's last_id, resulting in an inaccurate lag.

The reason XTRIM doesn't need to update the maximal tombstone is that it
always trims from the beginning of the stream. This means that it
consistently changes the position of the first entry, leading to the
following scenarios:

1) First entry trimmed after maximal tombstone:
If the first entry is trimmed to a position after the maximal tombstone,
all tombstones will be before the first entry, so they won't affect the
consumer group's lag.

2) First entry trimmed before maximal tombstone:
If the first entry is trimmed to a position before the maximal
tombstone, the maximal tombstone will not be updated.

## Solution
Therefore, this PR optimizes the lag calculation by ensuring that when
both the consumer group's last_id and the maximal tombstone are behind
the first entry, the consumer group's lag is always equal to the number
of remaining elements in the stream.

Supplement to PR https://github.com/redis/redis/pull/13338
2024-08-16 23:13:31 +08:00
debing.sun b94b714f81 Fix error message for XREAD command with wrong parameter (#13474)
Fixed a missing from #13117.
When the number of streams is incorrect, the error message for `XREAD`
needs to include the '+' symbol.
2024-08-14 21:40:43 +08:00
Moti Cohen 806459f481 On HDEL last field with expiry, update global HFE DS (#13470)
Hash field expiration is optimized to avoid frequent update global HFE DS for
each field deletion. Eventually active-expiration will run and update or remove
the hash from global HFE DS gracefully. Nevertheless, statistic "subexpiry"
might reflect wrong number of hashes with HFE to the user if HDEL deletes
the last field with expiration in hash (yet there are more fields without expiration).

Following this change, if HDEL the last field with expiration in the hash then
take care to remove the hash from global HFE DS as well.
2024-08-11 16:39:03 +03:00
LuMingYinDetectanddebing.sun 3a08819f51 Fix some memory leaks in redis-cli (#13258)
Fix memory leak related to variable slot_nodes  in the
clusterManagerFixSlotsCoverage() function.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2024-08-08 10:51:33 +08:00
debing.sunandHarkrishn Patro 6f0ddc9d92 Pass extensions to node if extension processing is handled by it (#13465)
This PR is based on the commits from PR
https://github.com/valkey-io/valkey/pull/52.
Ref: https://github.com/redis/redis/pull/12760
Close https://github.com/redis/redis/issues/13401
This PR will replace https://github.com/redis/redis/pull/13449

Fixes compatibilty of Redis cluster (7.2 - extensions enabled by
default) with older Redis cluster (< 7.0 - extensions not handled) .

With some of the extensions enabled by default in 7.2 version, new nodes
running 7.2 and above start sending out larger clusterbus message
payload including the ping extensions. This caused an incompatibility
with node running engine versions < 7.0. Old nodes (< 7.0) would receive
the payload from new nodes (> 7.2) would observe a payload length
(totlen) > (estlen) and would perform an early exit and won't process
the message.

This fix does the following things:
1. Always set `CLUSTERMSG_FLAG0_EXT_DATA`, because during the meet
phase, we do not know whether the connected node supports ext data, we
need to make sure that it knows and send back its ext data if it has.
2. If another node does not support ext data, we will not send it ext
data to avoid the handshake failure due to the incorrect payload length.

Note: A successful `PING`/`PONG` is required as a sender for a given
node to be marked as `CLUSTERMSG_FLAG0_EXT_DATA` and then extensions
message
will be sent to it. This could cause a slight delay in receiving the
extensions message(s).

---------

Signed-off-by: Harkrishn Patro <harkrisp@amazon.com>
Co-authored-by: Harkrishn Patro <harkrisp@amazon.com>

---------

Signed-off-by: Harkrishn Patro <harkrisp@amazon.com>
Co-authored-by: Harkrishn Patro <harkrisp@amazon.com>
2024-08-08 10:48:03 +08:00
731f2dc5c7 Make some commets more friendly (#13319)
Close  #13316

---------

Co-authored-by: Anuragkillswitch <70265851+Anuragkillswitch@users.noreply.github.com>
Co-authored-by: debing.sun <debing.sun@redis.com>
2024-08-07 01:20:06 +08:00
debing.sun bf643a63c8 Ensure validity of myself as master or replica when loading cluster config (#13443)
First, we need to ensure that `curmaster` in
`clusterUpdateSlotsConfigWith()` is not NULL in the line
https://github.com/redis/redis/blob/82f00f5179720c8cee6cd650763d184ba943be92/src/cluster_legacy.c#L2320
otherwise, it will crash in the
https://github.com/redis/redis/blob/82f00f5179720c8cee6cd650763d184ba943be92/src/cluster_legacy.c#L2395

So when loading cluster node config, we need to ensure that the
following conditions are met:
1. A node must be at least one of the master or replica.
2. If a node is a replica, its master can't be NULL.
2024-08-06 20:40:46 +08:00
YaacovHazan e4ddc34463 Keep cluster shards command implementation generic (#13440)
Make the clusterCommandShards function use only cluster API functions
instead of accessing cluster implementation details.
This way the cluster API implementation doesn't have to have intimate
knowledge of the command reply format, and doesn't need to interact with
the client directly (the addReply function family).
The PR has two commits, one moves the function from cluster_legacy.c to
cluster.c, and the other modifies it's implementation.


**better merge without squashing.**
2024-08-05 11:08:48 +03:00
Josh Hershberg 6d5d754119 Make cluster shards cmd implementation generic
This and the previous commit make the cluster
shards command a generic implementation instead of a
specific implementation for each cluster API implementation.
This commit (a) adds functions to the cluster API
and (b) modifies the cluster shards cmd implementation
to use cluster API functions instead of directly
accessing the legacy clustering implementation.

Signed-off-by: Josh Hershberg <yehoshua@redis.com>
2024-08-05 10:31:40 +03:00
Josh Hershberg e3e631f394 Prep to make cluster shards cmd generic
This and the next following commit makes the cluster
shards command a generic implementation instead of a
specific implementation for each cluster API implementation.
This commit simply moves the cluster shards implementation
from cluster_legacy.c to cluster.c without changing the
implementation at all. The reason for doing so was to
help with reviewing the changes in the diff.

Signed-off-by: Josh Hershberg <yehoshua@redis.com>
2024-08-05 10:27:03 +03:00
Zhongxian Pananddebing.sun 6263823e54 Replace bit shift with __builtin_ctzll in HyperLogLog (#13218)
## Replace bit shift with `__builtin_ctzll` in HyperLogLog

Builtin function `__builtin_ctzll` is more effective than bit shift even
though "in the average case there are high probabilities to find a 1
after a few iterations" mentioned in the source file comment.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2024-08-05 10:51:43 +08:00
Moti Cohen 4dd8b1faa9 Fix HTTL/HPTTL to be NONDETERMINISTIC_OUTPUT (#13461)
H[P]TTL should be marked as NONDETERMINISTIC_OUTPUT just like [P]TTL.
2024-08-04 17:42:50 +03:00
Vitah Linanddebing.sun 8038eb3147 Fix wrong dbnum showed in redis-cli after client reconnected (#13411)
When the server restarts while the CLI is connecting, the reconnection
does not automatically select the previous db.
This may lead users to believe they are still in the previous db, in
fact, they are in db0.

This PR will automatically reset the current dbnum and `cliSelect()`
again when reconnecting.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
2024-08-03 12:06:02 +08:00
c8ef 89742a95db Fix typo in hyperloglog.c (#13458) 2024-08-02 07:49:52 +08:00
debing.sun 60e9e630bd Fix CLUSTER SHARDS command returns empty array (#13422)
Close https://github.com/redis/redis/issues/13414

When the cluster's master node fails and is switched to another node,
the first node in the shard node list (the old master) is no longer
valid.
Add a new method clusterGetMasterFromShard() to obtain the current
master.
2024-08-02 07:22:13 +08:00
debing.sunandoranagra e750c619b2 Fix some test failures caused by key being deleted due to premature expiration (#13453)
1. Fix fuzzer test failure when the key was deleted due to expiration
before sending random traffic for the key.

After HFE, when all fields in a hash are expired, the hash might be
deleted due to expiration.

If the key was expired in the mid of `RESTORE` command and sending rand
trafic, `fuzzer` test will fail in the following code because the 'TYPE
key' will return `none` and then throw an exception because it cannot be
found in `$commands`

https://github.com/redis/redis/blob/94b9072e44af0bae8cfe2de5cfa4af7b8e399759/tests/support/util.tcl#L712-L713

This PR adds a `None` check for the reply of `KEY TYPE` command and adds
a print of `err` to avoid false positives in the future.

failed CI:
https://github.com/redis/redis/actions/runs/10127334121/job/28004985388

2. Fix the issue where key was deleted due to expiration before the
`scan.scan_key` command could execute, caused by premature enabling of
`set-active-expire`.

failed CI:
https://github.com/redis/redis/actions/runs/10153722715/job/28077610552

---------

Co-authored-by: oranagra <oran@redislabs.com>
2024-07-31 08:15:39 +08:00
debing.sun 93fb83b4cb Fix incorrect lag field in XINFO when tombstone is after the last_id of consume group (#13338)
Fix #13337

Ths PR fixes fixed two bugs that caused lag calculation errors.
1. When the latest tombstone is before the first entry, the tombstone
may stil be after the last id of consume group.
2. When a tombstone is after the last id of consume group, the group's
counter will be invalid, we should caculate the entries_read by using
estimates.
2024-07-30 22:31:31 +08:00
71 changed files with 1750 additions and 420 deletions
+24
View File
@@ -0,0 +1,24 @@
name: redis_docs_sync
on:
release:
types: [published]
jobs:
redis_docs_sync:
if: github.repository == 'redis/redis'
runs-on: ubuntu-latest
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.DOCS_APP_ID }}
private-key: ${{ secrets.DOCS_APP_PRIVATE_KEY }}
- name: Invoke workflow on redis/docs
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
RELEASE_NAME: ${{ github.event.release.tag_name }}
run: |
gh workflow run -R redis/docs redis_docs_sync.yaml -f release="${RELEASE_NAME}"
+75 -11
View File
@@ -1,16 +1,80 @@
Hello! This file is just a placeholder, since this is the "unstable" branch
of Redis, the place where all the development happens.
Redis Community Edition 8.0 release notes
=========================================
There is no release notes for this branch, it gets forked into another branch
every time there is a partial feature freeze in order to eventually create
a new stable release.
===========================================================
8.0-M01 (v7.9.224) Released Thu 12 Sep 2024 10:00:00 IST
===========================================================
Usually "unstable" is stable enough for you to use it in development environments
however you should never use it in production environments. It is possible
to download the latest stable release here:
This is the first Milestone of Redis Community Edition 8.0.
https://download.redis.io/redis-stable.tar.gz
Milestones are non-feature-complete pre-releases. Pre-releases are not suitable for production use.
Once we reach feature-completeness we will release RC1.
More information is available at https://redis.io
### Headlines:
Redis 8.0 introduces new data structures: JSON, time series, and 5 probabilistic data structures (previously available as separate Redis modules) and incorporates Redis scalable query engine (including vector search).
8.0-M01 is available as a Docker image and can be downloaded from [Docker Hub](https://hub.docker.com/_/redis). Additional distributions will be introduced in upcoming pre-releases.
### Supported upgrade paths (by replication or persistence) to 8.0-M01
- From previous Redis versions, without modules
The following upgrade paths (by replication or persistence) to 8.0-M01 are not yet tested and will be introduced in upcoming pre-releases:
- From previous Redis versions with modules (RediSearch, RedisJSON, RedisTimeSeries, RedisBloom)
- From Redis Stack 7.2 or 7.4
### New Features in binary distributions
- 7 new data structures: JSON, Time series, Bloom filter, Cuckoo filter, Count-min sketch, Top-k, t-digest
- Redis scalable query engine (including vector search)
### Potentially breaking changes
- #12272 `GETRANGE` returns an empty bulk when the negative end index is out of range
- #12395 Optimize `SCAN` command when matching data type
### Bug fixes
- #13510 Fix `RM_RdbLoad` to enable AOF after RDB loading is completed
- #13489 `ACL CAT` - return module commands
- #13476 Fix a race condition in the `cache_memory` of `functionsLibCtx`
- #13473 Fix incorrect lag due to trimming stream via `XTRIM` command
- #13338 Fix incorrect lag field in `XINFO` when tombstone is after the `last_id` of the consume group
- #13470 On `HDEL` of last field - update the global hash field expiration data structure
- #13465 Cluster: Pass extensions to node if extension processing is handled by it
- #13443 Cluster: Ensure validity of myself when loading cluster config
- #13422 Cluster: Fix `CLUSTER SHARDS` command returns empty array
### Modules API
- #13509 New API calls: `RM_DefragAllocRaw`, `RM_DefragFreeRaw`, and `RM_RegisterDefragCallbacks` - defrag API to allocate and free raw memory
### Performance and resource utilization improvements
- #13503 Avoid overhead of comparison function pointer calls in listpack `lpFind`
- #13505 Optimize `STRING` datatype write commands
- #13499 Optimize `SMEMBERS` command
- #13494 Optimize `GEO*` commands reply
- #13490 Optimize `HELLO` command
- #13488 Optimize client query buffer
- #12395 Optimize `SCAN` command when matching data type
- #13529 Optimize `LREM`, `LPOS`, `LINSERT`, and `LINDEX` commands
- #13516 Optimize `LRANGE` and other commands that perform several writes to client buffers per call
- #13431 Avoid `used_memory` contention when updating from multiple threads
### Other general improvements
- #13495 Reply `-LOADING` on replica while flushing the db
### CLI tools
- #13411 redis-cli: Fix wrong `dbnum` showed after the client reconnected
### Notes
- No backward compatibility for replication or persistence.
- Additional distributions, upgrade paths, features, and improvements will be introduced in upcoming pre-releases.
- With the GA release of 8.0 we will deprecate Redis Stack.
Happy hacking!
+8 -3
View File
@@ -1,11 +1,16 @@
# Top level makefile, the real shit is at src/Makefile
# Top level makefile, the real stuff is at ./src/Makefile and in ./modules/Makefile
SUBDIRS = src
ifeq ($(BUILD_WITH_MODULES), yes)
SUBDIRS += modules
endif
default: all
.DEFAULT:
cd src && $(MAKE) $@
for dir in $(SUBDIRS); do $(MAKE) -C $$dir $@; done
install:
cd src && $(MAKE) $@
for dir in $(SUBDIRS); do $(MAKE) -C $$dir $@; done
.PHONY: install
-1
View File
@@ -16,7 +16,6 @@ Another good example is to think of Redis as a more complex version of memcached
If you want to know more, this is a list of selected starting points:
* Introduction to Redis data types. https://redis.io/topics/data-types-intro
* Try Redis directly inside your browser. https://try.redis.io
* The full list of Redis commands. https://redis.io/commands
* There is much more inside the official Redis documentation. https://redis.io/documentation
+1 -1
View File
@@ -296,7 +296,7 @@ static int isUnsupportedTerm(void) {
return 0;
}
/* Raw mode: 1960 magic shit. */
/* Raw mode: 1960's magic. */
static int enableRawMode(int fd) {
if (getenv("FAKETTY_WITH_PROMPT") != NULL) {
return 0;
+72
View File
@@ -0,0 +1,72 @@
SUBDIRS = redisjson redistimeseries redisbloom redisearch
define submake
for dir in $(SUBDIRS); do $(MAKE) -C $$dir $(1); done
endef
all: prepare_source
$(call submake,$@)
get_source:
$(call submake,$@)
prepare_source: get_source handle-werrors setup_environment
clean:
$(call submake,$@)
distclean: clean_environment
$(call submake,$@)
pristine:
$(call submake,$@)
install:
$(call submake,$@)
setup_environment: install-rust handle-werrors
clean_environment: uninstall-rust
# Keep all of the Rust stuff in one place
install-rust:
ifeq ($(INSTALL_RUST_TOOLCHAIN),yes)
@RUST_VERSION=1.80.1; \
case "$$(uname -m)" in \
'x86_64') RUST_INSTALLER="rust-$${RUST_VERSION}-x86_64-unknown-linux-gnu"; RUST_SHA256="85e936d5d36970afb80756fa122edcc99bd72a88155f6bdd514f5d27e778e00a" ;; \
'aarch64') RUST_INSTALLER="rust-$${RUST_VERSION}-aarch64-unknown-linux-gnu"; RUST_SHA256="2e89bad7857711a1c11d017ea28fbfeec54076317763901194f8f5decbac1850" ;; \
*) echo >&2 "Unsupported architecture: '$$(uname -m)'"; exit 1 ;; \
esac; \
wget --quiet -O $${RUST_INSTALLER}.tar.xz https://static.rust-lang.org/dist/$${RUST_INSTALLER}.tar.xz; \
echo "$${RUST_SHA256} $${RUST_INSTALLER}.tar.xz" | sha256sum -c --quiet || { echo "Rust standalone installer checksum failed!"; exit 1; }; \
tar -xf $${RUST_INSTALLER}.tar.xz; \
(cd $${RUST_INSTALLER} && ./install.sh); \
rm -rf $${RUST_INSTALLER}
endif
uninstall-rust:
ifeq ($(INSTALL_RUST_TOOLCHAIN),yes)
@if [ -x "/usr/local/lib/rustlib/uninstall.sh" ]; then \
echo "Uninstalling Rust using uninstall.sh script"; \
rm -rf ~/.cargo; \
/usr/local/lib/rustlib/uninstall.sh; \
else \
echo "WARNING: Rust toolchain not found or uninstall script is missing."; \
fi
endif
handle-werrors: get_source
ifeq ($(DISABLE_WERRORS),yes)
@echo "Disabling -Werror for all modules"
@for dir in $(SUBDIRS); do \
echo "Processing $$dir"; \
find $$dir/src -type f \
\( -name "Makefile" \
-o -name "*.mk" \
-o -name "CMakeLists.txt" \) \
-exec sed -i 's/-Werror//g' {} +; \
done
endif
.PHONY: all clean distclean install $(SUBDIRS) setup_environment clean_environment install-rust uninstall-rust handle-werrors
+49
View File
@@ -0,0 +1,49 @@
PREFIX ?= /usr/local
INSTALL_DIR ?= $(DESTDIR)$(PREFIX)/lib/redis/modules
INSTALL ?= install
# This logic *partially* follows the current module build system. It is a bit awkward and
# should be changed if/when the modules' build process is refactored.
ARCH_MAP_x86_64 := x64
ARCH_MAP_i386 := x86
ARCH_MAP_i686 := x86
ARCH_MAP_aarch64 := arm64v8
ARCH_MAP_arm64 := arm64v8
OS := $(shell uname -s | tr '[:upper:]' '[:lower:]')
ARCH := $(ARCH_MAP_$(shell uname -m))
ifeq ($(ARCH),)
$(error Unrecognized CPU architecture $(shell uname -m))
endif
FULL_VARIANT := $(OS)-$(ARCH)-release
# Common rules for all modules, based on per-module configuration
all: $(TARGET_MODULE)
$(TARGET_MODULE): get_source
$(MAKE) -C $(SRC_DIR)
get_source: $(SRC_DIR)/.prepared
$(SRC_DIR)/.prepared:
mkdir -p $(SRC_DIR)
git clone --recursive --depth 1 --branch $(MODULE_VERSION) $(MODULE_REPO) $(SRC_DIR)
touch $@
clean:
-$(MAKE) -C $(SRC_DIR) clean
distclean:
-$(MAKE) -C $(SRC_DIR) distclean
pristine:
-rm -rf $(SRC_DIR)
install: $(TARGET_MODULE)
mkdir -p $(INSTALL_DIR)
$(INSTALL) -m 0755 -D $(TARGET_MODULE) $(INSTALL_DIR)
.PHONY: all clean distclean pristine install
+6
View File
@@ -0,0 +1,6 @@
SRC_DIR = src
MODULE_VERSION = v2.8.2
MODULE_REPO = https://github.com/redisbloom/redisbloom
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/redisbloom.so
include ../common.mk
+7
View File
@@ -0,0 +1,7 @@
SRC_DIR = src
MODULE_VERSION = v7.99.0
MODULE_REPO = https://github.com/redisearch/redisearch
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/coord-oss/redisearch.so
include ../common.mk
+11
View File
@@ -0,0 +1,11 @@
SRC_DIR = src
MODULE_VERSION = v2.8.4
MODULE_REPO = https://github.com/redisjson/redisjson
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/rejson.so
include ../common.mk
$(SRC_DIR)/.cargo_fetched:
cd $(SRC_DIR) && cargo fetch
get_source: $(SRC_DIR)/.cargo_fetched
+6
View File
@@ -0,0 +1,6 @@
SRC_DIR = src
MODULE_VERSION = v1.12.2
MODULE_REPO = https://github.com/redistimeseries/redistimeseries
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/redistimeseries.so
include ../common.mk
-1
View File
@@ -2762,7 +2762,6 @@ void aclCatWithFlags(client *c, dict *commands, uint64_t cflag, int *arraylen) {
while ((de = dictNext(di)) != NULL) {
struct redisCommand *cmd = dictGetVal(de);
if (cmd->flags & CMD_MODULE) continue;
if (cmd->acl_categories & cflag) {
addReplyBulkCBuffer(c, cmd->fullname, sdslen(cmd->fullname));
(*arraylen)++;
+23
View File
@@ -997,6 +997,29 @@ int startAppendOnly(void) {
return C_OK;
}
void startAppendOnlyWithRetry(void) {
unsigned int tries, max_tries = 10;
for (tries = 0; tries < max_tries; ++tries) {
if (startAppendOnly() == C_OK)
break;
serverLog(LL_WARNING, "Failed to enable AOF! Trying it again in one second.");
sleep(1);
}
if (tries == max_tries) {
serverLog(LL_WARNING, "FATAL: AOF can't be turned on. Exiting now.");
exit(1);
}
}
/* Called after "appendonly" config is changed. */
void applyAppendOnlyConfig(void) {
if (!server.aof_enabled && server.aof_state != AOF_OFF) {
stopAppendOnly();
} else if (server.aof_enabled && server.aof_state == AOF_OFF) {
startAppendOnlyWithRetry();
}
}
/* This is a wrapper to the write syscall in order to retry on short writes
* or if the syscall gets interrupted. It could look strange that we retry
* on short writes given that we are writing to a block device: normally if
+124
View File
@@ -783,6 +783,130 @@ unsigned int countKeysInSlot(unsigned int slot) {
return kvstoreDictSize(server.db->keys, slot);
}
/* Add detailed information of a node to the output buffer of the given client. */
void addNodeDetailsToShardReply(client *c, clusterNode *node) {
int reply_count = 0;
char *hostname;
void *node_replylen = addReplyDeferredLen(c);
addReplyBulkCString(c, "id");
addReplyBulkCBuffer(c, clusterNodeGetName(node), CLUSTER_NAMELEN);
reply_count++;
if (clusterNodeTcpPort(node)) {
addReplyBulkCString(c, "port");
addReplyLongLong(c, clusterNodeTcpPort(node));
reply_count++;
}
if (clusterNodeTlsPort(node)) {
addReplyBulkCString(c, "tls-port");
addReplyLongLong(c, clusterNodeTlsPort(node));
reply_count++;
}
addReplyBulkCString(c, "ip");
addReplyBulkCString(c, clusterNodeIp(node));
reply_count++;
addReplyBulkCString(c, "endpoint");
addReplyBulkCString(c, clusterNodePreferredEndpoint(node));
reply_count++;
hostname = clusterNodeHostname(node);
if (hostname != NULL && *hostname != '\0') {
addReplyBulkCString(c, "hostname");
addReplyBulkCString(c, hostname);
reply_count++;
}
long long node_offset;
if (clusterNodeIsMyself(node)) {
node_offset = clusterNodeIsSlave(node) ? replicationGetSlaveOffset() : server.master_repl_offset;
} else {
node_offset = clusterNodeReplOffset(node);
}
addReplyBulkCString(c, "role");
addReplyBulkCString(c, clusterNodeIsSlave(node) ? "replica" : "master");
reply_count++;
addReplyBulkCString(c, "replication-offset");
addReplyLongLong(c, node_offset);
reply_count++;
addReplyBulkCString(c, "health");
const char *health_msg = NULL;
if (clusterNodeIsFailing(node)) {
health_msg = "fail";
} else if (clusterNodeIsSlave(node) && node_offset == 0) {
health_msg = "loading";
} else {
health_msg = "online";
}
addReplyBulkCString(c, health_msg);
reply_count++;
setDeferredMapLen(c, node_replylen, reply_count);
}
static clusterNode *clusterGetMasterFromShard(void *shard_handle) {
clusterNode *n = NULL;
void *node_it = clusterShardHandleGetNodeIterator(shard_handle);
while((n = clusterShardNodeIteratorNext(node_it)) != NULL) {
if (!clusterNodeIsFailing(n)) {
break;
}
}
clusterShardNodeIteratorFree(node_it);
if (!n) return NULL;
return clusterNodeGetMaster(n);
}
/* Add the shard reply of a single shard based off the given primary node. */
void addShardReplyForClusterShards(client *c, void *shard_handle) {
serverAssert(clusterGetShardNodeCount(shard_handle) > 0);
addReplyMapLen(c, 2);
addReplyBulkCString(c, "slots");
/* Use slot_info_pairs from the primary only */
clusterNode *master_node = clusterGetMasterFromShard(shard_handle);
if (master_node && clusterNodeHasSlotInfo(master_node)) {
serverAssert((clusterNodeSlotInfoCount(master_node) % 2) == 0);
addReplyArrayLen(c, clusterNodeSlotInfoCount(master_node));
for (int i = 0; i < clusterNodeSlotInfoCount(master_node); i++)
addReplyLongLong(c, (unsigned long)clusterNodeSlotInfoEntry(master_node, i));
} else {
/* If no slot info pair is provided, the node owns no slots */
addReplyArrayLen(c, 0);
}
addReplyBulkCString(c, "nodes");
addReplyArrayLen(c, clusterGetShardNodeCount(shard_handle));
void *node_it = clusterShardHandleGetNodeIterator(shard_handle);
for (clusterNode *n = clusterShardNodeIteratorNext(node_it); n != NULL; n = clusterShardNodeIteratorNext(node_it)) {
addNodeDetailsToShardReply(c, n);
clusterFreeNodesSlotsInfo(n);
}
clusterShardNodeIteratorFree(node_it);
}
/* Add to the output buffer of the given client, an array of slot (start, end)
* pair owned by the shard, also the primary and set of replica(s) along with
* information about each node. */
void clusterCommandShards(client *c) {
addReplyArrayLen(c, clusterGetShardCount());
/* This call will add slot_info_pairs to all nodes */
clusterGenNodesSlotsInfo(0);
dictIterator *shard_it = clusterGetShardIterator();
for(void *shard_handle = clusterNextShardHandle(shard_it); shard_handle != NULL; shard_handle = clusterNextShardHandle(shard_it)) {
addShardReplyForClusterShards(c, shard_handle);
}
clusterFreeShardIterator(shard_it);
}
void clusterCommandHelp(client *c) {
const char *help[] = {
"COUNTKEYSINSLOT <slot>",
+20 -1
View File
@@ -97,7 +97,7 @@ int clusterManualFailoverTimeLimit(void);
void clusterCommandSlots(client * c);
void clusterCommandMyId(client *c);
void clusterCommandMyShardId(client *c);
void clusterCommandShards(client *c);
sds clusterGenNodeDescription(client *c, clusterNode *node, int tls_primary);
int clusterNodeCoversSlot(clusterNode *n, int slot);
@@ -142,4 +142,23 @@ int isValidAuxString(char *s, unsigned int length);
void migrateCommand(client *c);
void clusterCommand(client *c);
ConnectionType *connTypeOfCluster(void);
void clusterGenNodesSlotsInfo(int filter);
void clusterFreeNodesSlotsInfo(clusterNode *n);
int clusterNodeSlotInfoCount(clusterNode *n);
uint16_t clusterNodeSlotInfoEntry(clusterNode *n, int idx);
int clusterNodeHasSlotInfo(clusterNode *n);
int clusterGetShardCount(void);
void *clusterGetShardIterator(void);
void *clusterNextShardHandle(void *shard_iterator);
void clusterFreeShardIterator(void *shard_iterator);
int clusterGetShardNodeCount(void *shard);
void *clusterShardHandleGetNodeIterator(void *shard);
clusterNode *clusterShardNodeIteratorNext(void *node_iterator);
void clusterShardNodeIteratorFree(void *node_iterator);
clusterNode *clusterShardNodeFirst(void *shard);
int clusterNodeTcpPort(clusterNode *node);
int clusterNodeTlsPort(clusterNode *node);
#endif /* __CLUSTER_H */
+76 -108
View File
@@ -2,8 +2,13 @@
* Copyright (c) 2009-Present, Redis Ltd.
* All rights reserved.
*
* Copyright (c) 2024-present, Valkey contributors.
* All rights reserved.
*
* Licensed under your choice of the Redis Source Available License 2.0
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
*
* Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
*/
/*
@@ -634,6 +639,8 @@ int clusterLoadConfig(char *filename) {
}
/* Config sanity check */
if (server.cluster->myself == NULL) goto fmterr;
if (!(myself->flags & (CLUSTER_NODE_MASTER | CLUSTER_NODE_SLAVE))) goto fmterr;
if (nodeIsSlave(myself) && myself->slaveof == NULL) goto fmterr;
zfree(line);
fclose(fp);
@@ -2577,9 +2584,6 @@ uint32_t writePingExt(clusterMsg *hdr, int gossipcount) {
extensions++;
if (hdr != NULL) {
if (extensions != 0) {
hdr->mflags[0] |= CLUSTERMSG_FLAG0_EXT_DATA;
}
hdr->extensions = htons(extensions);
}
@@ -2769,6 +2773,9 @@ int clusterProcessPacket(clusterLink *link) {
}
sender = getNodeFromLinkAndMsg(link, hdr);
if (sender && (hdr->mflags[0] & CLUSTERMSG_FLAG0_EXT_DATA)) {
sender->flags |= CLUSTER_NODE_EXTENSIONS_SUPPORTED;
}
/* Update the last time we saw any data from this node. We
* use this in order to avoid detecting a timeout from a node that
@@ -3534,6 +3541,8 @@ static void clusterBuildMessageHdr(clusterMsg *hdr, int type, size_t msglen) {
/* Set the message flags. */
if (clusterNodeIsMaster(myself) && server.cluster->mf_end)
hdr->mflags[0] |= CLUSTERMSG_FLAG0_PAUSED;
hdr->mflags[0] |= CLUSTERMSG_FLAG0_EXT_DATA; /* Always make other nodes know that
* this node supports extension data. */
hdr->totlen = htonl(msglen);
}
@@ -3612,7 +3621,9 @@ void clusterSendPing(clusterLink *link, int type) {
* to put inside the packet. */
estlen = sizeof(clusterMsg) - sizeof(union clusterMsgData);
estlen += (sizeof(clusterMsgDataGossip)*(wanted + pfail_wanted));
estlen += writePingExt(NULL, 0);
if (link->node && nodeSupportsExtensions(link->node)) {
estlen += writePingExt(NULL, 0);
}
/* Note: clusterBuildMessageHdr() expects the buffer to be always at least
* sizeof(clusterMsg) or more. */
if (estlen < (int)sizeof(clusterMsg)) estlen = sizeof(clusterMsg);
@@ -3682,7 +3693,9 @@ void clusterSendPing(clusterLink *link, int type) {
/* Compute the actual total length and send! */
uint32_t totlen = 0;
totlen += writePingExt(hdr, gossipcount);
if (link->node && nodeSupportsExtensions(link->node)) {
totlen += writePingExt(hdr, gossipcount);
}
totlen += sizeof(clusterMsg)-sizeof(union clusterMsgData);
totlen += (sizeof(clusterMsgDataGossip)*gossipcount);
serverAssert(gossipcount < USHRT_MAX);
@@ -5582,113 +5595,68 @@ void clusterUpdateSlots(client *c, unsigned char *slots, int del) {
}
}
/* Add detailed information of a node to the output buffer of the given client. */
void addNodeDetailsToShardReply(client *c, clusterNode *node) {
int reply_count = 0;
void *node_replylen = addReplyDeferredLen(c);
addReplyBulkCString(c, "id");
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
reply_count++;
if (node->tcp_port) {
addReplyBulkCString(c, "port");
addReplyLongLong(c, node->tcp_port);
reply_count++;
}
if (node->tls_port) {
addReplyBulkCString(c, "tls-port");
addReplyLongLong(c, node->tls_port);
reply_count++;
}
addReplyBulkCString(c, "ip");
addReplyBulkCString(c, node->ip);
reply_count++;
addReplyBulkCString(c, "endpoint");
addReplyBulkCString(c, clusterNodePreferredEndpoint(node));
reply_count++;
if (sdslen(node->hostname) != 0) {
addReplyBulkCString(c, "hostname");
addReplyBulkCBuffer(c, node->hostname, sdslen(node->hostname));
reply_count++;
}
long long node_offset;
if (node->flags & CLUSTER_NODE_MYSELF) {
node_offset = nodeIsSlave(node) ? replicationGetSlaveOffset() : server.master_repl_offset;
} else {
node_offset = node->repl_offset;
}
addReplyBulkCString(c, "role");
addReplyBulkCString(c, nodeIsSlave(node) ? "replica" : "master");
reply_count++;
addReplyBulkCString(c, "replication-offset");
addReplyLongLong(c, node_offset);
reply_count++;
addReplyBulkCString(c, "health");
const char *health_msg = NULL;
if (nodeFailed(node)) {
health_msg = "fail";
} else if (nodeIsSlave(node) && node_offset == 0) {
health_msg = "loading";
} else {
health_msg = "online";
}
addReplyBulkCString(c, health_msg);
reply_count++;
setDeferredMapLen(c, node_replylen, reply_count);
int clusterGetShardCount(void) {
return dictSize(server.cluster->shards);
}
/* Add the shard reply of a single shard based off the given primary node. */
void addShardReplyForClusterShards(client *c, list *nodes) {
serverAssert(listLength(nodes) > 0);
clusterNode *n = listNodeValue(listFirst(nodes));
addReplyMapLen(c, 2);
addReplyBulkCString(c, "slots");
/* Use slot_info_pairs from the primary only */
n = clusterNodeGetMaster(n);
if (n->slot_info_pairs != NULL) {
serverAssert((n->slot_info_pairs_count % 2) == 0);
addReplyArrayLen(c, n->slot_info_pairs_count);
for (int i = 0; i < n->slot_info_pairs_count; i++)
addReplyLongLong(c, (unsigned long)n->slot_info_pairs[i]);
} else {
/* If no slot info pair is provided, the node owns no slots */
addReplyArrayLen(c, 0);
}
addReplyBulkCString(c, "nodes");
addReplyArrayLen(c, listLength(nodes));
listIter li;
listRewind(nodes, &li);
for (listNode *ln = listNext(&li); ln != NULL; ln = listNext(&li)) {
clusterNode *n = listNodeValue(ln);
addNodeDetailsToShardReply(c, n);
clusterFreeNodesSlotsInfo(n);
}
void *clusterGetShardIterator(void) {
return dictGetSafeIterator(server.cluster->shards);
}
/* Add to the output buffer of the given client, an array of slot (start, end)
* pair owned by the shard, also the primary and set of replica(s) along with
* information about each node. */
void clusterCommandShards(client *c) {
addReplyArrayLen(c, dictSize(server.cluster->shards));
/* This call will add slot_info_pairs to all nodes */
clusterGenNodesSlotsInfo(0);
dictIterator *di = dictGetSafeIterator(server.cluster->shards);
for(dictEntry *de = dictNext(di); de != NULL; de = dictNext(di)) {
addShardReplyForClusterShards(c, dictGetVal(de));
}
dictReleaseIterator(di);
void *clusterNextShardHandle(void *shard_iterator) {
dictEntry *de = dictNext(shard_iterator);
if(de == NULL) return NULL;
return dictGetVal(de);
}
void clusterFreeShardIterator(void *shard_iterator) {
dictReleaseIterator(shard_iterator);
}
int clusterNodeHasSlotInfo(clusterNode *n) {
return n->slot_info_pairs != NULL;
}
int clusterNodeSlotInfoCount(clusterNode *n) {
return n->slot_info_pairs_count;
}
uint16_t clusterNodeSlotInfoEntry(clusterNode *n, int idx) {
return n->slot_info_pairs[idx];
}
int clusterGetShardNodeCount(void *shard) {
return listLength((list*)shard);
}
void *clusterShardHandleGetNodeIterator(void *shard) {
listIter *li = zmalloc(sizeof(listIter));
listRewind((list*)shard, li);
return li;
}
void clusterShardNodeIteratorFree(void *node_iterator) {
zfree(node_iterator);
}
clusterNode *clusterShardNodeIteratorNext(void *node_iterator) {
listNode *item = listNext((listIter*)node_iterator);
if (item == NULL) return NULL;
return listNodeValue(item);
}
clusterNode *clusterShardNodeFirst(void *shard) {
listNode *item = listFirst((list*)shard);
if (item == NULL) return NULL;
return listNodeValue(item);
}
int clusterNodeTcpPort(clusterNode *node) {
return node->tcp_port;
}
int clusterNodeTlsPort(clusterNode *node) {
return node->tls_port;
}
sds genClusterInfoString(void) {
+15
View File
@@ -1,3 +1,16 @@
/*
* Copyright (c) 2009-Present, Redis Ltd.
* All rights reserved.
*
* Copyright (c) 2024-present, Valkey contributors.
* All rights reserved.
*
* Licensed under your choice of the Redis Source Available License 2.0
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
*
* Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
*/
#ifndef CLUSTER_LEGACY_H
#define CLUSTER_LEGACY_H
@@ -51,6 +64,7 @@ typedef struct clusterLink {
#define CLUSTER_NODE_MEET 128 /* Send a MEET message to this node */
#define CLUSTER_NODE_MIGRATE_TO 256 /* Master eligible for replica migration. */
#define CLUSTER_NODE_NOFAILOVER 512 /* Slave will not try to failover. */
#define CLUSTER_NODE_EXTENSIONS_SUPPORTED 1024 /* This node supports extensions. */
#define CLUSTER_NODE_NULL_NAME "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
#define nodeIsSlave(n) ((n)->flags & CLUSTER_NODE_SLAVE)
@@ -59,6 +73,7 @@ typedef struct clusterLink {
#define nodeTimedOut(n) ((n)->flags & CLUSTER_NODE_PFAIL)
#define nodeFailed(n) ((n)->flags & CLUSTER_NODE_FAIL)
#define nodeCantFailover(n) ((n)->flags & CLUSTER_NODE_NOFAILOVER)
#define nodeSupportsExtensions(n) ((n)->flags & CLUSTER_NODE_EXTENSIONS_SUPPORTED)
/* This structure represent elements of node->fail_reports. */
typedef struct clusterNodeFailReport {
+9 -5
View File
@@ -3778,7 +3778,9 @@ struct COMMAND_ARG HPEXPIRETIME_Args[] = {
#ifndef SKIP_CMD_TIPS_TABLE
/* HPTTL tips */
#define HPTTL_Tips NULL
const char *HPTTL_Tips[] = {
"nondeterministic_output",
};
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
@@ -3956,7 +3958,9 @@ struct COMMAND_ARG HSTRLEN_Args[] = {
#ifndef SKIP_CMD_TIPS_TABLE
/* HTTL tips */
#define HTTL_Tips NULL
const char *HTTL_Tips[] = {
"nondeterministic_output",
};
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
@@ -11044,13 +11048,13 @@ struct COMMAND_STRUCT redisCommandTable[] = {
{MAKE_CMD("hpexpire","Set expiry for hash field using relative time to expire (milliseconds)","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIRE_History,0,HPEXPIRE_Tips,0,hpexpireCommand,-6,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HPEXPIRE_Keyspecs,1,NULL,4),.args=HPEXPIRE_Args},
{MAKE_CMD("hpexpireat","Set expiry for hash field using an absolute Unix timestamp (milliseconds)","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIREAT_History,0,HPEXPIREAT_Tips,0,hpexpireatCommand,-6,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HPEXPIREAT_Keyspecs,1,NULL,4),.args=HPEXPIREAT_Args},
{MAKE_CMD("hpexpiretime","Returns the expiration time of a hash field as a Unix timestamp, in msec.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIRETIME_History,0,HPEXPIRETIME_Tips,0,hpexpiretimeCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HPEXPIRETIME_Keyspecs,1,NULL,2),.args=HPEXPIRETIME_Args},
{MAKE_CMD("hpttl","Returns the TTL in milliseconds of a hash field.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPTTL_History,0,HPTTL_Tips,0,hpttlCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HPTTL_Keyspecs,1,NULL,2),.args=HPTTL_Args},
{MAKE_CMD("hpttl","Returns the TTL in milliseconds of a hash field.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPTTL_History,0,HPTTL_Tips,1,hpttlCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HPTTL_Keyspecs,1,NULL,2),.args=HPTTL_Args},
{MAKE_CMD("hrandfield","Returns one or more random fields from a hash.","O(N) where N is the number of fields returned","6.2.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HRANDFIELD_History,0,HRANDFIELD_Tips,1,hrandfieldCommand,-2,CMD_READONLY,ACL_CATEGORY_HASH,HRANDFIELD_Keyspecs,1,NULL,2),.args=HRANDFIELD_Args},
{MAKE_CMD("hscan","Iterates over fields and values of a hash.","O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.","2.8.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSCAN_History,0,HSCAN_Tips,1,hscanCommand,-3,CMD_READONLY,ACL_CATEGORY_HASH,HSCAN_Keyspecs,1,NULL,5),.args=HSCAN_Args},
{MAKE_CMD("hset","Creates or modifies the value of a field in a hash.","O(1) for each field/value pair added, so O(N) to add N field/value pairs when the command is called with multiple field/value pairs.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSET_History,1,HSET_Tips,0,hsetCommand,-4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HSET_Keyspecs,1,NULL,2),.args=HSET_Args},
{MAKE_CMD("hsetnx","Sets the value of a field in a hash only when the field doesn't exist.","O(1)","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSETNX_History,0,HSETNX_Tips,0,hsetnxCommand,4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HSETNX_Keyspecs,1,NULL,3),.args=HSETNX_Args},
{MAKE_CMD("hstrlen","Returns the length of the value of a field.","O(1)","3.2.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSTRLEN_History,0,HSTRLEN_Tips,0,hstrlenCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HSTRLEN_Keyspecs,1,NULL,2),.args=HSTRLEN_Args},
{MAKE_CMD("httl","Returns the TTL in seconds of a hash field.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HTTL_History,0,HTTL_Tips,0,httlCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HTTL_Keyspecs,1,NULL,2),.args=HTTL_Args},
{MAKE_CMD("httl","Returns the TTL in seconds of a hash field.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HTTL_History,0,HTTL_Tips,1,httlCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HTTL_Keyspecs,1,NULL,2),.args=HTTL_Args},
{MAKE_CMD("hvals","Returns all values in a hash.","O(N) where N is the size of the hash.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HVALS_History,0,HVALS_Tips,1,hvalsCommand,2,CMD_READONLY,ACL_CATEGORY_HASH,HVALS_Keyspecs,1,NULL,1),.args=HVALS_Args},
/* hyperloglog */
{MAKE_CMD("pfadd","Adds elements to a HyperLogLog key. Creates the key if it doesn't exist.","O(1) to add every element.","2.8.9",CMD_DOC_NONE,NULL,NULL,"hyperloglog",COMMAND_GROUP_HYPERLOGLOG,PFADD_History,0,PFADD_Tips,0,pfaddCommand,-2,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HYPERLOGLOG,PFADD_Keyspecs,1,NULL,2),.args=PFADD_Args},
@@ -11141,7 +11145,7 @@ struct COMMAND_STRUCT redisCommandTable[] = {
{MAKE_CMD("sintercard","Returns the number of members of the intersect of multiple sets.","O(N*M) worst case where N is the cardinality of the smallest set and M is the number of sets.","7.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SINTERCARD_History,0,SINTERCARD_Tips,0,sinterCardCommand,-3,CMD_READONLY,ACL_CATEGORY_SET,SINTERCARD_Keyspecs,1,sintercardGetKeys,3),.args=SINTERCARD_Args},
{MAKE_CMD("sinterstore","Stores the intersect of multiple sets in a key.","O(N*M) worst case where N is the cardinality of the smallest set and M is the number of sets.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SINTERSTORE_History,0,SINTERSTORE_Tips,0,sinterstoreCommand,-3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SET,SINTERSTORE_Keyspecs,2,NULL,2),.args=SINTERSTORE_Args},
{MAKE_CMD("sismember","Determines whether a member belongs to a set.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SISMEMBER_History,0,SISMEMBER_Tips,0,sismemberCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SET,SISMEMBER_Keyspecs,1,NULL,2),.args=SISMEMBER_Args},
{MAKE_CMD("smembers","Returns all members of a set.","O(N) where N is the set cardinality.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SMEMBERS_History,0,SMEMBERS_Tips,1,sinterCommand,2,CMD_READONLY,ACL_CATEGORY_SET,SMEMBERS_Keyspecs,1,NULL,1),.args=SMEMBERS_Args},
{MAKE_CMD("smembers","Returns all members of a set.","O(N) where N is the set cardinality.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SMEMBERS_History,0,SMEMBERS_Tips,1,smembersCommand,2,CMD_READONLY,ACL_CATEGORY_SET,SMEMBERS_Keyspecs,1,NULL,1),.args=SMEMBERS_Args},
{MAKE_CMD("smismember","Determines whether multiple members belong to a set.","O(N) where N is the number of elements being checked for membership","6.2.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SMISMEMBER_History,0,SMISMEMBER_Tips,0,smismemberCommand,-3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SET,SMISMEMBER_Keyspecs,1,NULL,2),.args=SMISMEMBER_Args},
{MAKE_CMD("smove","Moves a member from one set to another.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SMOVE_History,0,SMOVE_Tips,0,smoveCommand,4,CMD_WRITE|CMD_FAST,ACL_CATEGORY_SET,SMOVE_Keyspecs,2,NULL,3),.args=SMOVE_Args},
{MAKE_CMD("spop","Returns one or more random members from a set after removing them. Deletes the set if the last member was popped.","Without the count argument O(1), otherwise O(N) where N is the value of the passed count.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SPOP_History,1,SPOP_Tips,1,spopCommand,-2,CMD_WRITE|CMD_FAST,ACL_CATEGORY_SET,SPOP_Keyspecs,1,NULL,2),.args=SPOP_Args},
+3
View File
@@ -14,6 +14,9 @@
"acl_categories": [
"HASH"
],
"command_tips": [
"NONDETERMINISTIC_OUTPUT"
],
"key_specs": [
{
"flags": [
+3
View File
@@ -14,6 +14,9 @@
"acl_categories": [
"HASH"
],
"command_tips": [
"NONDETERMINISTIC_OUTPUT"
],
"key_specs": [
{
"flags": [
+1 -1
View File
@@ -5,7 +5,7 @@
"group": "set",
"since": "1.0.0",
"arity": 2,
"function": "sinterCommand",
"function": "smembersCommand",
"command_flags": [
"READONLY"
],
+8 -1
View File
@@ -2502,6 +2502,13 @@ static int updateWatchdogPeriod(const char **err) {
}
static int updateAppendonly(const char **err) {
/* If loading flag is set, AOF might have been stopped temporarily, and it
* will be restarted depending on server.aof_enabled flag after loading is
* completed. So, we just need to update 'server.aof_enabled' which has been
* updated already before calling this function. */
if (server.loading)
return 1;
if (!server.aof_enabled && server.aof_state != AOF_OFF) {
stopAppendOnly();
} else if (server.aof_enabled && server.aof_state == AOF_OFF) {
@@ -3080,7 +3087,7 @@ standardConfig static_configs[] = {
createBoolConfig("activedefrag", NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, server.active_defrag_enabled, 0, isValidActiveDefrag, NULL),
createBoolConfig("syslog-enabled", NULL, IMMUTABLE_CONFIG, server.syslog_enabled, 0, NULL, NULL),
createBoolConfig("cluster-enabled", NULL, IMMUTABLE_CONFIG, server.cluster_enabled, 0, NULL, NULL),
createBoolConfig("appendonly", NULL, MODIFIABLE_CONFIG | DENY_LOADING_CONFIG, server.aof_enabled, 0, NULL, updateAppendonly),
createBoolConfig("appendonly", NULL, MODIFIABLE_CONFIG, server.aof_enabled, 0, NULL, updateAppendonly),
createBoolConfig("cluster-allow-reads-when-down", NULL, MODIFIABLE_CONFIG, server.cluster_allow_reads_when_down, 0, NULL, NULL),
createBoolConfig("cluster-allow-pubsubshard-when-down", NULL, MODIFIABLE_CONFIG, server.cluster_allow_pubsubshard_when_down, 1, NULL, NULL),
createBoolConfig("crash-log-enabled", NULL, MODIFIABLE_CONFIG, server.crashlog_enabled, 1, NULL, updateSighandlerEnabled),
+8
View File
@@ -32,6 +32,14 @@
#define redis_stat stat
#endif
#ifndef CACHE_LINE_SIZE
#if defined(__aarch64__) && defined(__APPLE__)
#define CACHE_LINE_SIZE 128
#else
#define CACHE_LINE_SIZE 64
#endif
#endif
/* Test for proc filesystem */
#ifdef __linux__
#define HAVE_PROC_STAT 1
+50 -23
View File
@@ -49,6 +49,9 @@ void updateLFU(robj *val) {
* found in the specified DB. This function implements the functionality of
* lookupKeyRead(), lookupKeyWrite() and their ...WithFlags() variants.
*
* 'deref' is an optional output dictEntry reference argument, to get the
* associated dictEntry* of the key in case the key is found.
*
* Side-effects of calling this function:
*
* 1. A key gets expired if it reached it's TTL.
@@ -72,7 +75,7 @@ void updateLFU(robj *val) {
* Even if the key expiry is master-driven, we can correctly report a key is
* expired on replicas even if the master is lagging expiring our key via DELs
* in the replication link. */
robj *lookupKey(redisDb *db, robj *key, int flags) {
robj *lookupKey(redisDb *db, robj *key, int flags, dictEntry **deref) {
dictEntry *de = dbFind(db, key->ptr);
robj *val = NULL;
if (de) {
@@ -123,6 +126,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
/* TODO: Use separate misses stats and notify event for WRITE */
}
if (val && deref) *deref = de;
return val;
}
@@ -137,7 +141,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
* the key. */
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
serverAssert(!(flags & LOOKUP_WRITE));
return lookupKey(db, key, flags);
return lookupKey(db, key, flags, NULL);
}
/* Like lookupKeyReadWithFlags(), but does not use any flag, which is the
@@ -153,13 +157,20 @@ robj *lookupKeyRead(redisDb *db, robj *key) {
* Returns the linked value object if the key exists or NULL if the key
* does not exist in the specified DB. */
robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags) {
return lookupKey(db, key, flags | LOOKUP_WRITE);
return lookupKey(db, key, flags | LOOKUP_WRITE, NULL);
}
robj *lookupKeyWrite(redisDb *db, robj *key) {
return lookupKeyWriteWithFlags(db, key, LOOKUP_NONE);
}
/* Like lookupKeyWrite(), but accepts an optional dictEntry input,
* which can be used if we already have one, thus saving the dbFind call.
*/
robj *lookupKeyWriteWithDictEntry(redisDb *db, robj *key, dictEntry **deref) {
return lookupKey(db, key, LOOKUP_NONE | LOOKUP_WRITE, deref);
}
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) {
robj *o = lookupKeyRead(c->db, key);
if (!o) addReplyOrErrorObject(c, reply);
@@ -294,6 +305,14 @@ void dbReplaceValue(redisDb *db, robj *key, robj *val) {
dbSetValue(db, key, val, 0, NULL);
}
/* Replace an existing key with a new value, we just replace value and don't
* emit any events.
* The dictEntry input is optional, can be used if we already have one.
*/
void dbReplaceValueWithDictEntry(redisDb *db, robj *key, robj *val, dictEntry *de) {
dbSetValue(db, key, val, 0, de);
}
/* High level Set operation. This function can be used in order to set
* a key, whatever it was existing or not, to a new object.
*
@@ -308,6 +327,12 @@ void dbReplaceValue(redisDb *db, robj *key, robj *val) {
* The client 'c' argument may be set to NULL if the operation is performed
* in a context where there is no clear client performing the operation. */
void setKey(client *c, redisDb *db, robj *key, robj *val, int flags) {
setKeyWithDictEntry(c,db,key,val,flags,NULL);
}
/* Like setKey(), but accepts an optional dictEntry input,
* which can be used if we already have one, thus saving the dictFind call. */
void setKeyWithDictEntry(client *c, redisDb *db, robj *key, robj *val, int flags, dictEntry *de) {
int keyfound = 0;
if (flags & SETKEY_ALREADY_EXIST)
@@ -322,7 +347,7 @@ void setKey(client *c, redisDb *db, robj *key, robj *val, int flags) {
} else if (keyfound<0) {
dbAddInternal(db,key,val,1);
} else {
dbSetValue(db,key,val,1,NULL);
dbSetValue(db,key,val,1,de);
}
incrRefCount(val);
if (!(flags & SETKEY_KEEPTTL)) removeExpire(db,key);
@@ -452,12 +477,18 @@ int dbDelete(redisDb *db, robj *key) {
* using an sdscat() call to append some data, or anything else.
*/
robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
return dbUnshareStringValueWithDictEntry(db,key,o,NULL);
}
/* Like dbUnshareStringValue(), but accepts a optional dictEntry,
* which can be used if we already have one, thus saving the dbFind call. */
robj *dbUnshareStringValueWithDictEntry(redisDb *db, robj *key, robj *o, dictEntry *de) {
serverAssert(o->type == OBJ_STRING);
if (o->refcount != 1 || o->encoding != OBJ_ENCODING_RAW) {
robj *decoded = getDecodedObject(o);
o = createRawStringObject(decoded->ptr, sdslen(decoded->ptr));
decrRefCount(decoded);
dbReplaceValue(db,key,o);
dbReplaceValueWithDictEntry(db,key,o,de);
}
return o;
}
@@ -575,11 +606,11 @@ redisDb *initTempDb(void) {
}
/* Discard tempDb, this can be slow (similar to FLUSHALL), but it's always async. */
void discardTempDb(redisDb *tempDb, void(callback)(dict*)) {
void discardTempDb(redisDb *tempDb) {
int async = 1;
/* Release temp DBs. */
emptyDbStructure(tempDb, -1, async, callback);
emptyDbStructure(tempDb, -1, async, NULL);
for (int i=0; i<server.dbnum; i++) {
/* Destroy global HFE DS before deleting the hashes since ebuckets DS is
* embedded in the stored objects. */
@@ -944,11 +975,10 @@ void scanCallback(void *privdata, const dictEntry *de) {
serverAssert(!((data->type != LLONG_MAX) && o));
/* Filter an element if it isn't the type we want. */
/* TODO: uncomment in redis 8.0
if (!o && data->type != LLONG_MAX) {
robj *rval = dictGetVal(de);
if (!objectTypeCompare(rval, data->type)) return;
}*/
}
/* Filter element if it does not match the pattern. */
void *keyStr = dictGetKey(de);
@@ -1095,9 +1125,8 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) {
typename = c->argv[i+1]->ptr;
type = getObjectTypeByName(typename);
if (type == LLONG_MAX) {
/* TODO: uncomment in redis 8.0
addReplyErrorFormat(c, "unknown type name '%s'", typename);
return; */
return;
}
i+= 2;
} else if (!strcasecmp(c->argv[i]->ptr, "novalues")) {
@@ -1285,16 +1314,7 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) {
while ((ln = listNext(&li))) {
sds key = listNodeValue(ln);
initStaticStringObject(kobj, key);
/* Filter an element if it isn't the type we want. */
/* TODO: remove this in redis 8.0 */
if (typename) {
robj* typecheck = lookupKeyReadWithFlags(c->db, &kobj, LOOKUP_NOTOUCH|LOOKUP_NONOTIFY);
if (!typecheck || !objectTypeCompare(typecheck, type)) {
listDelNode(keys, ln);
}
continue;
}
if (expireIfNeeded(c->db, &kobj, 0) != KEY_VALID) {
if (expireIfNeeded(c->db, &kobj, 0)) {
listDelNode(keys, ln);
}
}
@@ -1839,16 +1859,23 @@ int removeExpire(redisDb *db, robj *key) {
return kvstoreDictDelete(db->expires, getKeySlot(key->ptr), key->ptr) == DICT_OK;
}
/* Set an expire to the specified key. If the expire is set in the context
* of an user calling a command 'c' is the client, otherwise 'c' is set
* to NULL. The 'when' parameter is the absolute unix time in milliseconds
* after which the key will no longer be considered valid. */
void setExpire(client *c, redisDb *db, robj *key, long long when) {
dictEntry *kde, *de, *existing;
setExpireWithDictEntry(c,db,key,when,NULL);
}
/* Like setExpire(), but accepts an optional dictEntry input,
* which can be used if we already have one, thus saving the kvstoreDictFind call. */
void setExpireWithDictEntry(client *c, redisDb *db, robj *key, long long when, dictEntry *kde) {
dictEntry *de, *existing;
/* Reuse the sds from the main dict in the expire dict */
int slot = getKeySlot(key->ptr);
kde = kvstoreDictFind(db->keys, slot, key->ptr);
if (!kde) kde = kvstoreDictFind(db->keys, slot, key->ptr);
serverAssertWithInfo(NULL,key,kde != NULL);
de = kvstoreDictAddRaw(db->expires, slot, dictGetKey(kde), &existing);
if (existing) {
+2
View File
@@ -567,6 +567,7 @@ NULL
addReplyError(c,"Error trying to load the RDB dump, check server logs.");
return;
}
applyAppendOnlyConfig(); /* Check if AOF config was changed while loading */
serverLog(LL_NOTICE,"DB reloaded by DEBUG RELOAD");
addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"loadaof")) {
@@ -582,6 +583,7 @@ NULL
addReplyError(c, "Error trying to load the AOF files, check server logs.");
return;
}
applyAppendOnlyConfig(); /* Check if AOF config was changed while loading */
server.dirty = 0; /* Prevent AOF / replication */
serverLog(LL_NOTICE,"Append Only File loaded by DEBUG LOADAOF");
addReply(c,shared.ok);
+28
View File
@@ -54,6 +54,17 @@ void* activeDefragAlloc(void *ptr) {
return newptr;
}
/* Raw memory allocation for defrag, avoid using tcache. */
void *activeDefragAllocRaw(size_t size) {
return zmalloc_no_tcache(size);
}
/* Raw memory free for defrag, avoid using tcache. */
void activeDefragFreeRaw(void *ptr) {
zfree_no_tcache(ptr);
server.stat_active_defrag_hits++;
}
/*Defrag helper for sds strings
*
* returns NULL in case the allocation wasn't moved.
@@ -1078,6 +1089,7 @@ void activeDefragCycle(void) {
slot = -1;
defrag_later_item_in_progress = 0;
db = NULL;
moduleDefragEnd();
goto update_metrics;
}
return;
@@ -1119,6 +1131,10 @@ void activeDefragCycle(void) {
break; /* this will exit the function and we'll continue on the next cycle */
}
if (current_db == -1) {
moduleDefragStart();
}
/* Move on to next database, and stop if we reached the last one. */
if (++current_db >= server.dbnum) {
/* defrag other items not part of the db / keys */
@@ -1140,6 +1156,8 @@ void activeDefragCycle(void) {
db = NULL;
server.active_defrag_running = 0;
moduleDefragEnd();
computeDefragCycles(); /* if another scan is needed, start it right away */
if (server.active_defrag_running != 0 && ustime() < endtime)
continue;
@@ -1259,6 +1277,16 @@ void *activeDefragAlloc(void *ptr) {
return NULL;
}
void *activeDefragAllocRaw(size_t size) {
/* fallback to regular allocation */
return zmalloc(size);
}
void activeDefragFreeRaw(void *ptr) {
/* fallback to regular free */
zfree(ptr);
}
robj *activeDefragStringOb(robj *ob) {
UNUSED(ob);
return NULL;
+12
View File
@@ -257,6 +257,18 @@ dictStats* dictGetStatsHt(dict *d, int htidx, int full);
void dictCombineStats(dictStats *from, dictStats *into);
void dictFreeStats(dictStats *stats);
#define dictForEach(d, ty, m, ...) do { \
dictIterator *di = dictGetIterator(d); \
dictEntry *de; \
while ((de = dictNext(di)) != NULL) { \
ty *m = dictGetVal(de); \
do { \
__VA_ARGS__ \
} while(0); \
} \
dictReleaseIterator(di); \
} while(0);
#ifdef REDIS_TEST
int dictTest(int argc, char *argv[], int flags);
#endif
+1 -3
View File
@@ -465,12 +465,10 @@ void expireSlaveKeys(void) {
if ((dbids & 1) != 0) {
redisDb *db = server.db+dbid;
dictEntry *expire = dbFindExpires(db, keyname);
int expired = 0;
if (expire &&
activeExpireCycleTryExpire(server.db+dbid,expire,start))
{
expired = 1;
/* Propagate the DEL (writable replicas do not propagate anything to other replicas,
* but they might propagate to AOF) and trigger module hooks. */
postExecutionUnitOperations();
@@ -480,7 +478,7 @@ void expireSlaveKeys(void) {
* corresponding bit in the new bitmap we set as value.
* At the end of the loop if the bitmap is zero, it means we
* no longer need to keep track of this key. */
if (expire && !expired) {
else if (expire) {
noexpire++;
new_dbids |= (uint64_t)1 << dbid;
}
+1 -1
View File
@@ -171,7 +171,7 @@ void functionsLibCtxClear(functionsLibCtx *lib_ctx) {
stats->n_lib = 0;
}
dictReleaseIterator(iter);
curr_functions_lib_ctx->cache_memory = 0;
lib_ctx->cache_memory = 0;
}
void functionsLibCtxClearCurrent(int async) {
+4 -4
View File
@@ -796,8 +796,8 @@ void georadiusGeneric(client *c, int srcKeyIndex, int flags) {
if (withcoords) {
addReplyArrayLen(c, 2);
addReplyHumanLongDouble(c, gp->longitude);
addReplyHumanLongDouble(c, gp->latitude);
addReplyDouble(c,gp->longitude);
addReplyDouble(c,gp->latitude);
}
}
} else {
@@ -959,8 +959,8 @@ void geoposCommand(client *c) {
continue;
}
addReplyArrayLen(c,2);
addReplyHumanLongDouble(c,xy[0]);
addReplyHumanLongDouble(c,xy[1]);
addReplyDouble(c,xy[0]);
addReplyDouble(c,xy[1]);
}
}
}
+5 -12
View File
@@ -429,7 +429,7 @@ uint64_t MurmurHash64A (const void * key, int len, unsigned int seed) {
* of the pattern 000..1 of the element hash. As a side effect 'regp' is
* set to the register index this element hashes to. */
int hllPatLen(unsigned char *ele, size_t elesize, long *regp) {
uint64_t hash, bit, index;
uint64_t hash, index;
int count;
/* Count the number of zeroes starting from bit HLL_REGISTERS
@@ -439,21 +439,14 @@ int hllPatLen(unsigned char *ele, size_t elesize, long *regp) {
* Note that the final "1" ending the sequence of zeroes must be
* included in the count, so if we find "001" the count is 3, and
* the smallest count possible is no zeroes at all, just a 1 bit
* at the first position, that is a count of 1.
*
* This may sound like inefficient, but actually in the average case
* there are high probabilities to find a 1 after a few iterations. */
* at the first position, that is a count of 1. */
hash = MurmurHash64A(ele,elesize,0xadc83b19ULL);
index = hash & HLL_P_MASK; /* Register index. */
hash >>= HLL_P; /* Remove bits used to address the register. */
hash |= ((uint64_t)1<<HLL_Q); /* Make sure the loop terminates
and count will be <= Q+1. */
bit = 1;
count = 1; /* Initialized to 1 since we count the "00000...1" pattern. */
while((hash & bit) == 0) {
count++;
bit <<= 1;
}
count = __builtin_ctzll(hash) + 1;
*regp = (int) index;
return count;
}
@@ -648,7 +641,7 @@ int hllSparseSet(robj *o, long index, uint8_t count) {
* for future reallocates on incremental growth. But we do not allocate more than
* 'server.hll_sparse_max_bytes' bytes for the sparse representation.
* If the available size of hyperloglog sds string is not enough for the increment
* we need, we promote the hypreloglog to dense representation in 'step 3'.
* we need, we promote the hyperloglog to dense representation in 'step 3'.
*/
if (sdsalloc(o->ptr) < server.hll_sparse_max_bytes && sdsavail(o->ptr) < 3) {
size_t newlen = sdslen(o->ptr) + 3;
+28 -1
View File
@@ -124,7 +124,9 @@ static void cumulativeKeyCountAdd(kvstore *kvs, int didx, long delta) {
dict *d = kvstoreGetDict(kvs, didx);
size_t dsize = dictSize(d);
int non_empty_dicts_delta = dsize == 1? 1 : dsize == 0? -1 : 0;
/* Increment if dsize is 1 and delta is positive (first element inserted, dict becomes non-empty).
* Decrement if dsize is 0 (dict becomes empty). */
int non_empty_dicts_delta = (dsize == 1 && delta > 0) ? 1 : (dsize == 0) ? -1 : 0;
kvs->non_empty_dicts += non_empty_dicts_delta;
/* BIT does not need to be calculated when there's only one dict. */
@@ -1026,6 +1028,31 @@ int kvstoreTest(int argc, char **argv, int flags) {
kvstoreRelease(kvs);
}
TEST("Verify non-empty dict count is correctly updated") {
kvstore *kvs = kvstoreCreate(&KvstoreDictTestType, 2, KVSTORE_ALLOCATE_DICTS_ON_DEMAND);
for (int idx = 0; idx < 4; idx++) {
for (i = 0; i < 16; i++) {
de = kvstoreDictAddRaw(kvs, idx, stringFromInt(i), NULL);
assert(de != NULL);
/* When the first element is inserted, the number of non-empty dictionaries is increased by 1. */
if (i == 0) assert(kvstoreNumNonEmptyDicts(kvs) == idx + 1);
}
}
/* Step by step, clear all dictionaries and ensure non-empty dict count is updated */
for (int idx = 0; idx < 4; idx++) {
kvs_di = kvstoreGetDictSafeIterator(kvs, idx);
while((de = kvstoreDictIteratorNext(kvs_di)) != NULL) {
key = dictGetKey(de);
assert(kvstoreDictDelete(kvs, idx, key) == DICT_OK);
/* When the dictionary is emptied, the number of non-empty dictionaries is reduced by 1. */
if (kvstoreDictSize(kvs, idx) == 0) assert(kvstoreNumNonEmptyDicts(kvs) == 3 - idx);
}
kvstoreReleaseDictIterator(kvs_di);
}
kvstoreRelease(kvs);
}
kvstoreRelease(kvs1);
kvstoreRelease(kvs2);
return 0;
+10 -4
View File
@@ -687,8 +687,8 @@ int lpGetIntegerValue(unsigned char *p, long long *lval) {
* will be returned. 'user' is passed to this callback.
* Skip 'skip' entries between every comparison.
* Returns NULL when the field could not be found. */
unsigned char *lpFindCb(unsigned char *lp, unsigned char *p,
void *user, lpCmp cmp, unsigned int skip)
static inline unsigned char *lpFindCbInternal(unsigned char *lp, unsigned char *p,
void *user, lpCmp cmp, unsigned int skip)
{
int skipcnt = 0;
unsigned char *value;
@@ -707,7 +707,7 @@ unsigned char *lpFindCb(unsigned char *lp, unsigned char *p,
assert(p >= lp + LP_HDR_SIZE && p + entry_size < lp + lp_bytes);
}
if (cmp(lp, p, user, value, ll) == 0)
if (unlikely(cmp(lp, p, user, value, ll) == 0))
return p;
/* Reset skip count */
@@ -734,6 +734,12 @@ unsigned char *lpFindCb(unsigned char *lp, unsigned char *p,
return NULL;
}
unsigned char *lpFindCb(unsigned char *lp, unsigned char *p,
void *user, lpCmp cmp, unsigned int skip)
{
return lpFindCbInternal(lp, p, user, cmp, skip);
}
struct lpFindArg {
unsigned char *s; /* Item to search */
uint32_t slen; /* Item len */
@@ -787,7 +793,7 @@ unsigned char *lpFind(unsigned char *lp, unsigned char *p, unsigned char *s,
.s = s,
.slen = slen
};
return lpFindCb(lp, p, &arg, lpFindCmp, skip);
return lpFindCbInternal(lp, p, &arg, lpFindCmp, skip);
}
/* Insert, delete or replace the specified string element 'elestr' of length
+72 -13
View File
@@ -2287,6 +2287,8 @@ void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int api
module->options = 0;
module->info_cb = 0;
module->defrag_cb = 0;
module->defrag_start_cb = 0;
module->defrag_end_cb = 0;
module->loadmod = NULL;
module->num_commands_with_acl_categories = 0;
module->onload = 1;
@@ -4274,7 +4276,7 @@ int RM_SetAbsExpire(RedisModuleKey *key, mstime_t expire) {
void RM_ResetDataset(int restart_aof, int async) {
if (restart_aof && server.aof_state != AOF_OFF) stopAppendOnly();
flushAllDataAndResetRDB((async? EMPTYDB_ASYNC: EMPTYDB_NO_FLAGS) | EMPTYDB_NOFUNCTIONS);
if (server.aof_enabled && restart_aof) restartAOFAfterSYNC();
if (server.aof_enabled && restart_aof) startAppendOnlyWithRetry();
}
/* Returns the number of keys in the current db. */
@@ -12463,10 +12465,15 @@ void modulePipeReadable(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Helper function for the MODULE and HELLO command: send the list of the
* loaded modules to the client. */
void addReplyLoadedModules(client *c) {
const long ln = dictSize(modules);
/* In case no module is load we avoid iterator creation */
addReplyArrayLen(c,ln);
if (ln == 0) {
return;
}
dictIterator *di = dictGetIterator(modules);
dictEntry *de;
addReplyArrayLen(c,dictSize(modules));
while ((de = dictNext(di)) != NULL) {
sds name = dictGetKey(de);
struct RedisModule *module = dictGetVal(de);
@@ -13070,7 +13077,7 @@ int RM_RdbLoad(RedisModuleCtx *ctx, RedisModuleRdbStream *stream, int flags) {
int ret = rdbLoad(stream->data.filename,NULL,RDBFLAGS_NONE);
if (server.current_client) unprotectClient(server.current_client);
if (server.aof_state != AOF_OFF) startAppendOnly();
if (server.aof_enabled) startAppendOnlyWithRetry();
if (ret != RDB_OK) {
errno = (ret == RDB_NOT_EXIST) ? ENOENT : EIO;
@@ -13451,6 +13458,16 @@ int RM_RegisterDefragFunc(RedisModuleCtx *ctx, RedisModuleDefragFunc cb) {
return REDISMODULE_OK;
}
/* Register a defrag callbacks that will be called when defrag operation starts and ends.
*
* The callbacks are the same as `RM_RegisterDefragFunc` but the user
* can also assume the callbacks are called when the defrag operation starts and ends. */
int RM_RegisterDefragCallbacks(RedisModuleCtx *ctx, RedisModuleDefragFunc start, RedisModuleDefragFunc end) {
ctx->module->defrag_start_cb = start;
ctx->module->defrag_end_cb = end;
return REDISMODULE_OK;
}
/* When the data type defrag callback iterates complex structures, this
* function should be called periodically. A zero (false) return
* indicates the callback may continue its work. A non-zero value (true)
@@ -13529,6 +13546,30 @@ void *RM_DefragAlloc(RedisModuleDefragCtx *ctx, void *ptr) {
return activeDefragAlloc(ptr);
}
/* Allocate memory for defrag purposes
*
* On the common cases user simply want to reallocate a pointer with a single
* owner. For such usecase RM_DefragAlloc is enough. But on some usecases the user
* might want to replace a pointer with multiple owners in different keys.
* In such case, an in place replacement can not work because the other key still
* keep a pointer to the old value.
*
* RM_DefragAllocRaw and RM_DefragFreeRaw allows to control when the memory
* for defrag purposes will be allocated and when it will be freed,
* allow to support more complex defrag usecases. */
void *RM_DefragAllocRaw(RedisModuleDefragCtx *ctx, size_t size) {
UNUSED(ctx);
return activeDefragAllocRaw(size);
}
/* Free memory for defrag purposes
*
* See RM_DefragAllocRaw for more information. */
void RM_DefragFreeRaw(RedisModuleDefragCtx *ctx, void *ptr) {
UNUSED(ctx);
activeDefragFreeRaw(ptr);
}
/* Defrag a RedisModuleString previously allocated by RM_Alloc, RM_Calloc, etc.
* See RM_DefragAlloc() for more information on how the defragmentation process
* works.
@@ -13610,17 +13651,32 @@ int moduleDefragValue(robj *key, robj *value, int dbid) {
/* Call registered module API defrag functions */
void moduleDefragGlobals(void) {
dictIterator *di = dictGetIterator(modules);
dictEntry *de;
dictForEach(modules, struct RedisModule, module,
if (module->defrag_cb) {
RedisModuleDefragCtx defrag_ctx = { 0, NULL, NULL, -1};
module->defrag_cb(&defrag_ctx);
}
);
}
while ((de = dictNext(di)) != NULL) {
struct RedisModule *module = dictGetVal(de);
if (!module->defrag_cb)
continue;
RedisModuleDefragCtx defrag_ctx = { 0, NULL, NULL, -1};
module->defrag_cb(&defrag_ctx);
}
dictReleaseIterator(di);
/* Call registered module API defrag start functions */
void moduleDefragStart(void) {
dictForEach(modules, struct RedisModule, module,
if (module->defrag_start_cb) {
RedisModuleDefragCtx defrag_ctx = { 0, NULL, NULL, -1};
module->defrag_start_cb(&defrag_ctx);
}
);
}
/* Call registered module API defrag end functions */
void moduleDefragEnd(void) {
dictForEach(modules, struct RedisModule, module,
if (module->defrag_end_cb) {
RedisModuleDefragCtx defrag_ctx = { 0, NULL, NULL, -1};
module->defrag_end_cb(&defrag_ctx);
}
);
}
/* Returns the name of the key currently being processed.
@@ -13980,7 +14036,10 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API(GetCurrentCommandName);
REGISTER_API(GetTypeMethodVersion);
REGISTER_API(RegisterDefragFunc);
REGISTER_API(RegisterDefragCallbacks);
REGISTER_API(DefragAlloc);
REGISTER_API(DefragAllocRaw);
REGISTER_API(DefragFreeRaw);
REGISTER_API(DefragRedisModuleString);
REGISTER_API(DefragShouldStop);
REGISTER_API(DefragCursorSet);
+96 -32
View File
@@ -26,7 +26,11 @@ static void setProtocolError(const char *errstr, client *c);
static void pauseClientsByClient(mstime_t end, int isPauseClientAll);
int postponeClientRead(client *c);
char *getClientSockname(client *c);
static inline int clientTypeIsSlave(client *c);
int ProcessingEventsWhileBlocked = 0; /* See processEventsWhileBlocked(). */
__thread sds thread_reusable_qb = NULL;
__thread int thread_reusable_qb_used = 0; /* Avoid multiple clients using reusable query
* buffer due to nested command execution. */
/* Return the size consumed from the allocator, for the specified SDS string,
* including internal fragmentation. This function is used in order to compute
@@ -144,7 +148,7 @@ client *createClient(connection *conn) {
c->ref_repl_buf_node = NULL;
c->ref_block_pos = 0;
c->qb_pos = 0;
c->querybuf = sdsempty();
c->querybuf = NULL;
c->querybuf_peak = 0;
c->reqtype = 0;
c->argc = 0;
@@ -391,7 +395,7 @@ void _addReplyToBufferOrList(client *c, const char *s, size_t len) {
* replication link that caused a reply to be generated we'll simply disconnect it.
* Note this is the simplest way to check a command added a response. Replication links are used to write data but
* not for responses, so we should normally never get here on a replica client. */
if (getClientType(c) == CLIENT_TYPE_SLAVE) {
if (unlikely(clientTypeIsSlave(c))) {
sds cmdname = c->lastcmd ? c->lastcmd->fullname : NULL;
logInvalidUseAndFreeClientAsync(c, "Replica generated a reply to command '%s'",
cmdname ? cmdname : "<unknown>");
@@ -733,7 +737,7 @@ void *addReplyDeferredLen(client *c) {
* replication link that caused a reply to be generated we'll simply disconnect it.
* Note this is the simplest way to check a command added a response. Replication links are used to write data but
* not for responses, so we should normally never get here on a replica client. */
if (getClientType(c) == CLIENT_TYPE_SLAVE) {
if (unlikely(clientTypeIsSlave(c))) {
sds cmdname = c->lastcmd ? c->lastcmd->fullname : NULL;
logInvalidUseAndFreeClientAsync(c, "Replica generated a reply to command '%s'",
cmdname ? cmdname : "<unknown>");
@@ -970,6 +974,12 @@ void addReplyLongLong(client *c, long long ll) {
}
}
void addReplyLongLongFromStr(client *c, robj *str) {
addReplyProto(c,":",1);
addReply(c,str);
addReplyProto(c,"\r\n",2);
}
void addReplyAggregateLen(client *c, long length, int prefix) {
serverAssert(length >= 0);
if (prepareClientToWrite(c) != C_OK) return;
@@ -1242,7 +1252,7 @@ void copyReplicaOutputBuffer(client *dst, client *src) {
/* Return true if the specified client has pending reply buffers to write to
* the socket. */
int clientHasPendingReplies(client *c) {
if (getClientType(c) == CLIENT_TYPE_SLAVE) {
if (unlikely(clientTypeIsSlave(c))) {
/* Replicas use global shared replication buffer instead of
* private output buffer. */
serverAssert(c->bufpos == 0 && listLength(c->reply) == 0);
@@ -1575,6 +1585,28 @@ void deauthenticateAndCloseClient(client *c) {
}
}
/* Resets the reusable query buffer used by the given client.
* If any data remained in the buffer, the client will take ownership of the buffer
* and a new empty buffer will be allocated for the reusable buffer. */
static void resetReusableQueryBuf(client *c) {
serverAssert(c->flags & CLIENT_REUSABLE_QUERYBUFFER);
if (c->querybuf != thread_reusable_qb || sdslen(c->querybuf) > c->qb_pos) {
/* If querybuf has been reallocated or there is still data left,
* let the client take ownership of the reusable buffer. */
thread_reusable_qb = NULL;
} else {
/* It is safe to dereference and reuse the reusable query buffer. */
c->querybuf = NULL;
c->qb_pos = 0;
sdsclear(thread_reusable_qb);
}
/* Mark that the client is no longer using the reusable query buffer
* and indicate that it is no longer used by any client. */
c->flags &= ~CLIENT_REUSABLE_QUERYBUFFER;
thread_reusable_qb_used = 0;
}
void freeClient(client *c) {
listNode *ln;
@@ -1623,12 +1655,14 @@ void freeClient(client *c) {
}
/* Log link disconnection with slave */
if (getClientType(c) == CLIENT_TYPE_SLAVE) {
if (clientTypeIsSlave(c)) {
serverLog(LL_NOTICE,"Connection with replica %s lost.",
replicationGetSlaveName(c));
}
/* Free the query buffer */
if (c->flags & CLIENT_REUSABLE_QUERYBUFFER)
resetReusableQueryBuf(c);
sdsfree(c->querybuf);
c->querybuf = NULL;
@@ -1703,7 +1737,7 @@ void freeClient(client *c) {
/* We need to remember the time when we started to have zero
* attached slaves, as after some time we'll free the replication
* backlog. */
if (getClientType(c) == CLIENT_TYPE_SLAVE && listLength(server.slaves) == 0)
if (clientTypeIsSlave(c) && listLength(server.slaves) == 0)
server.repl_no_slaves_since = server.unixtime;
refreshGoodSlavesCount();
/* Fire the replica change modules event. */
@@ -1916,7 +1950,7 @@ static int _writevToClient(client *c, ssize_t *nwritten) {
* to client. */
int _writeToClient(client *c, ssize_t *nwritten) {
*nwritten = 0;
if (getClientType(c) == CLIENT_TYPE_SLAVE) {
if (unlikely(clientTypeIsSlave(c))) {
serverAssert(c->bufpos == 0 && listLength(c->reply) == 0);
replBufBlock *o = listNodeValue(c->ref_repl_buf_node);
@@ -2003,7 +2037,7 @@ int writeToClient(client *c, int handler_installed) {
!(c->flags & CLIENT_SLAVE)) break;
}
if (getClientType(c) == CLIENT_TYPE_SLAVE) {
if (unlikely(clientTypeIsSlave(c))) {
atomicIncr(server.stat_net_repl_output_bytes, totwritten);
} else {
atomicIncr(server.stat_net_output_bytes, totwritten);
@@ -2207,7 +2241,7 @@ int processInlineBuffer(client *c) {
/* Newline from slaves can be used to refresh the last ACK time.
* This is useful for a slave to ping back while loading a big
* RDB file. */
if (querylen == 0 && getClientType(c) == CLIENT_TYPE_SLAVE)
if (querylen == 0 && clientTypeIsSlave(c))
c->repl_ack_time = server.unixtime;
/* Masters should never send us inline protocol to run actual
@@ -2674,6 +2708,11 @@ void readQueryFromClient(connection *conn) {
if (c->reqtype == PROTO_REQ_MULTIBULK && c->multibulklen && c->bulklen != -1
&& c->bulklen >= PROTO_MBULK_BIG_ARG)
{
/* For big argv, the client always uses its private query buffer.
* Using the reusable query buffer would eventually expand it beyond 32k,
* causing the client to take ownership of the reusable query buffer. */
if (!c->querybuf) c->querybuf = sdsempty();
ssize_t remaining = (size_t)(c->bulklen+2)-(sdslen(c->querybuf)-c->qb_pos);
big_arg = 1;
@@ -2685,6 +2724,26 @@ void readQueryFromClient(connection *conn) {
* but doesn't need align to the next arg, we can read more data. */
if (c->flags & CLIENT_MASTER && readlen < PROTO_IOBUF_LEN)
readlen = PROTO_IOBUF_LEN;
} else if (c->querybuf == NULL) {
if (unlikely(thread_reusable_qb_used)) {
/* The reusable query buffer is already used by another client,
* switch to using the client's private query buffer. This only
* occurs when commands are executed nested via processEventsWhileBlocked(). */
c->querybuf = sdsnewlen(NULL, PROTO_IOBUF_LEN);
sdsclear(c->querybuf);
} else {
/* Create the reusable query buffer if it doesn't exist. */
if (!thread_reusable_qb) {
thread_reusable_qb = sdsnewlen(NULL, PROTO_IOBUF_LEN);
sdsclear(thread_reusable_qb);
}
/* Assign the reusable query buffer to the client and mark it as in use. */
serverAssert(sdslen(thread_reusable_qb) == 0);
c->querybuf = thread_reusable_qb;
c->flags |= CLIENT_REUSABLE_QUERYBUFFER;
thread_reusable_qb_used = 1;
}
}
qblen = sdslen(c->querybuf);
@@ -2708,7 +2767,7 @@ void readQueryFromClient(connection *conn) {
nread = connRead(c->conn, c->querybuf+qblen, readlen);
if (nread == -1) {
if (connGetState(conn) == CONN_STATE_CONNECTED) {
return;
goto done;
} else {
serverLog(LL_VERBOSE, "Reading from client: %s",connGetLastError(c->conn));
freeClientAsync(c);
@@ -2760,6 +2819,10 @@ void readQueryFromClient(connection *conn) {
c = NULL;
done:
if (c && (c->flags & CLIENT_REUSABLE_QUERYBUFFER)) {
serverAssert(c->qb_pos == 0); /* Ensure the client's query buffer is trimmed in processInputBuffer */
resetReusableQueryBuf(c);
}
beforeNextClient(c);
}
@@ -2875,8 +2938,8 @@ sds catClientInfoString(sds s, client *client) {
" ssub=%i", (int) dictSize(client->pubsubshard_channels),
" multi=%i", (client->flags & CLIENT_MULTI) ? client->mstate.count : -1,
" watch=%i", (int) listLength(client->watched_keys),
" qbuf=%U", (unsigned long long) sdslen(client->querybuf),
" qbuf-free=%U", (unsigned long long) sdsavail(client->querybuf),
" qbuf=%U", client->querybuf ? (unsigned long long) sdslen(client->querybuf) : 0,
" qbuf-free=%U", client->querybuf ? (unsigned long long) sdsavail(client->querybuf) : 0,
" argv-mem=%U", (unsigned long long) client->argv_len_sum,
" multi-mem=%U", (unsigned long long) client->mstate.argv_len_sums,
" rbs=%U", (unsigned long long) client->buf_usable_size,
@@ -3662,29 +3725,30 @@ void helloCommand(client *c) {
if (ver) c->resp = ver;
addReplyMapLen(c,6 + !server.sentinel_mode);
addReplyBulkCString(c,"server");
addReplyBulkCString(c,"redis");
ADD_REPLY_BULK_CBUFFER_STRING_CONSTANT(c,"server");
ADD_REPLY_BULK_CBUFFER_STRING_CONSTANT(c,"redis");
addReplyBulkCString(c,"version");
addReplyBulkCString(c,REDIS_VERSION);
ADD_REPLY_BULK_CBUFFER_STRING_CONSTANT(c,"version");
ADD_REPLY_BULK_CBUFFER_STRING_CONSTANT(c,REDIS_VERSION);
ADD_REPLY_BULK_CBUFFER_STRING_CONSTANT(c,"proto");
addReplyBulkCString(c,"proto");
addReplyLongLong(c,c->resp);
addReplyBulkCString(c,"id");
ADD_REPLY_BULK_CBUFFER_STRING_CONSTANT(c,"id");
addReplyLongLong(c,c->id);
addReplyBulkCString(c,"mode");
ADD_REPLY_BULK_CBUFFER_STRING_CONSTANT(c,"mode");
if (server.sentinel_mode) addReplyBulkCString(c,"sentinel");
else if (server.cluster_enabled) addReplyBulkCString(c,"cluster");
else addReplyBulkCString(c,"standalone");
if (!server.sentinel_mode) {
addReplyBulkCString(c,"role");
ADD_REPLY_BULK_CBUFFER_STRING_CONSTANT(c,"role");
addReplyBulkCString(c,server.masterhost ? "replica" : "master");
}
addReplyBulkCString(c,"modules");
ADD_REPLY_BULK_CBUFFER_STRING_CONSTANT(c,"modules");
addReplyLoadedModules(c);
}
@@ -3834,7 +3898,7 @@ void rewriteClientCommandArgument(client *c, int i, robj *newval) {
* the caller wishes. The main usage of this function currently is
* enforcing the client output length limits. */
size_t getClientOutputBufferMemoryUsage(client *c) {
if (getClientType(c) == CLIENT_TYPE_SLAVE) {
if (unlikely(clientTypeIsSlave(c))) {
size_t repl_buf_size = 0;
size_t repl_node_num = 0;
size_t repl_node_size = sizeof(listNode) + sizeof(replBufBlock);
@@ -3856,9 +3920,10 @@ size_t getClientOutputBufferMemoryUsage(client *c) {
* the client output buffer memory usage portion of the total. */
size_t getClientMemoryUsage(client *c, size_t *output_buffer_mem_usage) {
size_t mem = getClientOutputBufferMemoryUsage(c);
if (output_buffer_mem_usage != NULL)
*output_buffer_mem_usage = mem;
mem += sdsZmallocSize(c->querybuf);
mem += c->querybuf ? sdsZmallocSize(c->querybuf) : 0;
mem += zmalloc_size(c);
mem += c->buf_usable_size;
/* For efficiency (less work keeping track of the argv memory), it doesn't include the used memory
@@ -3897,6 +3962,12 @@ int getClientType(client *c) {
return CLIENT_TYPE_NORMAL;
}
static inline int clientTypeIsSlave(client *c) {
if (unlikely((c->flags & CLIENT_SLAVE) && !(c->flags & CLIENT_MONITOR)))
return 1;
return 0;
}
int getClientTypeByName(char *name) {
if (!strcasecmp(name,"normal")) return CLIENT_TYPE_NORMAL;
else if (!strcasecmp(name,"slave")) return CLIENT_TYPE_SLAVE;
@@ -3986,7 +4057,7 @@ int closeClientOnOutputBufferLimitReached(client *c, int async) {
serverAssert(c->reply_bytes < SIZE_MAX-(1024*64));
/* Note that c->reply_bytes is irrelevant for replica clients
* (they use the global repl buffers). */
if ((c->reply_bytes == 0 && getClientType(c) != CLIENT_TYPE_SLAVE) ||
if ((c->reply_bytes == 0 && !clientTypeIsSlave(c)) ||
c->flags & CLIENT_CLOSE_ASAP) return 0;
if (checkClientOutputBufferLimits(c)) {
sds client = catClientInfoString(sdsempty(),c);
@@ -4213,13 +4284,6 @@ void processEventsWhileBlocked(void) {
* ========================================================================== */
#define IO_THREADS_MAX_NUM 128
#ifndef CACHE_LINE_SIZE
#if defined(__aarch64__) && defined(__APPLE__)
#define CACHE_LINE_SIZE 128
#else
#define CACHE_LINE_SIZE 64
#endif
#endif
typedef struct __attribute__((aligned(CACHE_LINE_SIZE))) threads_pending {
redisAtomic unsigned long value;
@@ -4423,7 +4487,7 @@ int handleClientsWithPendingWritesUsingThreads(void) {
* buffer, to guarantee data accessing thread safe, we must put all
* replicas client into io_threads_list[0] i.e. main thread handles
* sending the output buffer of all replicas. */
if (getClientType(c) == CLIENT_TYPE_SLAVE) {
if (unlikely(clientTypeIsSlave(c))) {
listAddNodeTail(io_threads_list[0],c);
continue;
}
+29 -6
View File
@@ -3161,7 +3161,13 @@ emptykey:
/* Mark that we are loading in the global state and setup the fields
* needed to provide loading stats. */
void startLoading(size_t size, int rdbflags, int async) {
/* Load the DB */
loadingSetFlags(NULL, size, async);
loadingFireEvent(rdbflags);
}
/* Initialize stats, set loading flags and filename if provided. */
void loadingSetFlags(char *filename, size_t size, int async) {
rdbFileBeingLoaded = filename;
server.loading = 1;
if (async == 1) server.async_loading = 1;
server.loading_start_time = time(NULL);
@@ -3171,7 +3177,9 @@ void startLoading(size_t size, int rdbflags, int async) {
server.rdb_last_load_keys_expired = 0;
server.rdb_last_load_keys_loaded = 0;
blockingOperationStarts();
}
void loadingFireEvent(int rdbflags) {
/* Fire the loading modules start event. */
int subevent;
if (rdbflags & RDBFLAGS_AOF_PREAMBLE)
@@ -3187,8 +3195,8 @@ void startLoading(size_t size, int rdbflags, int async) {
* needed to provide loading stats.
* 'filename' is optional and used for rdb-check on error */
void startLoadingFile(size_t size, char* filename, int rdbflags) {
rdbFileBeingLoaded = filename;
startLoading(size, rdbflags, 0);
loadingSetFlags(filename, size, 0);
loadingFireEvent(rdbflags);
}
/* Refresh the absolute loading progress info */
@@ -3702,14 +3710,21 @@ eoferr:
return C_ERR;
}
int rdbLoad(char *filename, rdbSaveInfo *rsi, int rdbflags) {
return rdbLoadWithEmptyFunc(filename, rsi, rdbflags, NULL);
}
/* Like rdbLoadRio() but takes a filename instead of a rio stream. The
* filename is open for reading and a rio stream object created in order
* to do the actual loading. Moreover the ETA displayed in the INFO
* output is initialized and finalized.
*
* If you pass an 'rsi' structure initialized with RDB_SAVE_INFO_INIT, the
* loading code will fill the information fields in the structure. */
int rdbLoad(char *filename, rdbSaveInfo *rsi, int rdbflags) {
* loading code will fill the information fields in the structure.
*
* If emptyDbFunc is not NULL, it will be called to flush old db or to
* discard partial db on error. */
int rdbLoadWithEmptyFunc(char *filename, rdbSaveInfo *rsi, int rdbflags, void (*emptyDbFunc)(void)) {
FILE *fp;
rio rdb;
int retval;
@@ -3727,12 +3742,20 @@ int rdbLoad(char *filename, rdbSaveInfo *rsi, int rdbflags) {
if (fstat(fileno(fp), &sb) == -1)
sb.st_size = 0;
startLoadingFile(sb.st_size, filename, rdbflags);
loadingSetFlags(filename, sb.st_size, 0);
/* Note that inside loadingSetFlags(), server.loading is set.
* emptyDbCallback() may yield back to event-loop to reply -LOADING. */
if (emptyDbFunc)
emptyDbFunc(); /* Flush existing db. */
loadingFireEvent(rdbflags);
rioInitWithFile(&rdb,fp);
retval = rdbLoadRio(&rdb,rdbflags,rsi);
fclose(fp);
if (retval != C_OK && emptyDbFunc)
emptyDbFunc(); /* Clean up partial db. */
stopLoading(retval==C_OK);
/* Reclaim the cache backed by rdb */
if (retval == C_OK && !(rdbflags & RDBFLAGS_KEEP_CACHE)) {
+1
View File
@@ -136,6 +136,7 @@ uint64_t rdbLoadLen(rio *rdb, int *isencoded);
int rdbLoadLenByRef(rio *rdb, int *isencoded, uint64_t *lenptr);
int rdbSaveObjectType(rio *rdb, robj *o);
int rdbLoadObjectType(rio *rdb);
int rdbLoadWithEmptyFunc(char *filename, rdbSaveInfo *rsi, int rdbflags, void (*emptyDbFunc)(void));
int rdbLoad(char *filename, rdbSaveInfo *rsi, int rdbflags);
int rdbSaveBackground(int req, char *filename, rdbSaveInfo *rsi, int rdbflags);
int rdbSaveToSlavesSockets(int req, rdbSaveInfo *rsi);
+6
View File
@@ -3264,6 +3264,9 @@ static int issueCommandRepeat(int argc, char **argv, long repeat) {
config.cluster_reissue_command = 0;
return REDIS_ERR;
}
/* Reset dbnum after reconnecting so we can re-select the previous db in cliSelect(). */
config.dbnum = 0;
cliSelect();
}
config.cluster_reissue_command = 0;
if (config.cluster_send_asking) {
@@ -3691,6 +3694,8 @@ static int evalMode(int argc, char **argv) {
/* Call it */
int eval_ldb = config.eval_ldb; /* Save it, may be reverted. */
retval = issueCommand(argc+3-got_comma, argv2);
for (j = 0; j < argc+3-got_comma; j++) sdsfree(argv2[j]);
free(argv2);
if (eval_ldb) {
if (!config.eval_ldb) {
/* If the debugging session ended immediately, there was an
@@ -6076,6 +6081,7 @@ static int clusterManagerFixSlotsCoverage(char *all_slots) {
if (!clusterManagerCheckRedisReply(n, reply, NULL)) {
fixed = -1;
if (reply) freeReplyObject(reply);
if (slot_nodes) listRelease(slot_nodes);
goto cleanup;
}
assert(reply->type == REDIS_REPLY_ARRAY);
+9 -1
View File
@@ -35,6 +35,7 @@
*/
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
@@ -46,8 +47,15 @@ void _serverAssert(const char *estr, const char *file, int line) {
}
void _serverPanic(const char *file, int line, const char *msg, ...) {
va_list ap;
char fmtmsg[256];
va_start(ap,msg);
vsnprintf(fmtmsg,sizeof(fmtmsg),msg,ap);
va_end(ap);
fprintf(stderr, "------------------------------------------------");
fprintf(stderr, "!!! Software Failure. Press left mouse button to continue");
fprintf(stderr, "Guru Meditation: %s #%s:%d",msg,file,line);
fprintf(stderr, "Guru Meditation: %s #%s:%d",fmtmsg,file,line);
abort();
}
+6
View File
@@ -1296,7 +1296,10 @@ REDISMODULE_API int *(*RedisModule_GetCommandKeys)(RedisModuleCtx *ctx, RedisMod
REDISMODULE_API int *(*RedisModule_GetCommandKeysWithFlags)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, int *num_keys, int **out_flags) REDISMODULE_ATTR;
REDISMODULE_API const char *(*RedisModule_GetCurrentCommandName)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_RegisterDefragFunc)(RedisModuleCtx *ctx, RedisModuleDefragFunc func) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_RegisterDefragCallbacks)(RedisModuleCtx *ctx, RedisModuleDefragFunc start, RedisModuleDefragFunc end) REDISMODULE_ATTR;
REDISMODULE_API void *(*RedisModule_DefragAlloc)(RedisModuleDefragCtx *ctx, void *ptr) REDISMODULE_ATTR;
REDISMODULE_API void *(*RedisModule_DefragAllocRaw)(RedisModuleDefragCtx *ctx, size_t size) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_DefragFreeRaw)(RedisModuleDefragCtx *ctx, void *ptr) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleString *(*RedisModule_DefragRedisModuleString)(RedisModuleDefragCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_DefragShouldStop)(RedisModuleDefragCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_DefragCursorSet)(RedisModuleDefragCtx *ctx, unsigned long cursor) REDISMODULE_ATTR;
@@ -1662,7 +1665,10 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(GetCommandKeysWithFlags);
REDISMODULE_GET_API(GetCurrentCommandName);
REDISMODULE_GET_API(RegisterDefragFunc);
REDISMODULE_GET_API(RegisterDefragCallbacks);
REDISMODULE_GET_API(DefragAlloc);
REDISMODULE_GET_API(DefragAllocRaw);
REDISMODULE_GET_API(DefragFreeRaw);
REDISMODULE_GET_API(DefragRedisModuleString);
REDISMODULE_GET_API(DefragShouldStop);
REDISMODULE_GET_API(DefragCursorSet);
+45 -36
View File
@@ -3,13 +3,15 @@
* Copyright (c) 2009-Present, Redis Ltd.
* All rights reserved.
*
* Copyright (c) 2024-present, Valkey contributors.
* All rights reserved.
*
* Licensed under your choice of the Redis Source Available License 2.0
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
*
* Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
*/
#include "server.h"
#include "cluster.h"
#include "bio.h"
@@ -1736,12 +1738,24 @@ void replicationSendNewlineToMaster(void) {
}
/* Callback used by emptyData() while flushing away old data to load
* the new dataset received by the master and by discardTempDb()
* after loading succeeded or failed. */
* the new dataset received by the master or to clear partial db if loading
* fails. */
void replicationEmptyDbCallback(dict *d) {
UNUSED(d);
if (server.repl_state == REPL_STATE_TRANSFER)
replicationSendNewlineToMaster();
processEventsWhileBlocked();
}
/* Function to flush old db or the partial db on error. */
static void rdbLoadEmptyDbFunc(void) {
serverAssert(server.loading);
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Flushing old data");
int empty_db_flags = server.repl_slave_lazy_flush ? EMPTYDB_ASYNC :
EMPTYDB_NO_FLAGS;
emptyData(-1, empty_db_flags, replicationEmptyDbCallback);
}
/* Once we have a link with the master and the synchronization was
@@ -1765,6 +1779,9 @@ void replicationCreateMasterClient(connection *conn, int dbid) {
* connection. */
server.master->flags |= CLIENT_MASTER;
/* Allocate a private query buffer for the master client instead of using the reusable query buffer.
* This is done because the master's query buffer data needs to be preserved for my sub-replicas to use. */
server.master->querybuf = sdsempty();
server.master->authenticated = 1;
server.master->reploff = server.master_initial_offset;
server.master->read_reploff = server.master->reploff;
@@ -1778,27 +1795,6 @@ void replicationCreateMasterClient(connection *conn, int dbid) {
if (dbid != -1) selectDb(server.master,dbid);
}
/* This function will try to re-enable the AOF file after the
* master-replica synchronization: if it fails after multiple attempts
* the replica cannot be considered reliable and exists with an
* error. */
void restartAOFAfterSYNC(void) {
unsigned int tries, max_tries = 10;
for (tries = 0; tries < max_tries; ++tries) {
if (startAppendOnly() == C_OK) break;
serverLog(LL_WARNING,
"Failed enabling the AOF after successful master synchronization! "
"Trying it again in one second.");
sleep(1);
}
if (tries == max_tries) {
serverLog(LL_WARNING,
"FATAL: this replica instance finished the synchronization with "
"its master, but the AOF can't be turned on. Exiting now.");
exit(1);
}
}
static int useDisklessLoad(void) {
/* compute boolean decision to use diskless load */
int enabled = server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB ||
@@ -1831,7 +1827,7 @@ redisDb *disklessLoadInitTempDb(void) {
/* Helper function for readSyncBulkPayload() to discard our tempDb
* when the loading succeeded or failed. */
void disklessLoadDiscardTempDb(redisDb *tempDb) {
discardTempDb(tempDb, replicationEmptyDbCallback);
discardTempDb(tempDb);
}
/* If we know we got an entirely different data set from our master
@@ -2057,9 +2053,6 @@ void readSyncBulkPayload(connection *conn) {
NULL);
} else {
replicationAttachToNewMaster();
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Flushing old data");
emptyData(-1,empty_db_flags,replicationEmptyDbCallback);
}
/* Before loading the DB into memory we need to delete the readable
@@ -2093,13 +2086,22 @@ void readSyncBulkPayload(connection *conn) {
functionsLibCtxClear(functions_lib_ctx);
}
loadingSetFlags(NULL, server.repl_transfer_size, asyncLoading);
if (server.repl_diskless_load != REPL_DISKLESS_LOAD_SWAPDB) {
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Flushing old data");
/* Note that inside loadingSetFlags(), server.loading is set.
* replicationEmptyDbCallback() may yield back to event-loop to
* reply -LOADING. */
emptyData(-1, empty_db_flags, replicationEmptyDbCallback);
}
loadingFireEvent(RDBFLAGS_REPLICATION);
rioInitWithConn(&rdb,conn,server.repl_transfer_size);
/* Put the socket in blocking mode to simplify RDB transfer.
* We'll restore it when the RDB is received. */
connBlock(conn);
connRecvTimeout(conn, server.repl_timeout*1000);
startLoading(server.repl_transfer_size, RDBFLAGS_REPLICATION, asyncLoading);
int loadingFailed = 0;
rdbLoadingCtx loadingCtx = { .dbarray = dbarray, .functions_lib_ctx = functions_lib_ctx };
@@ -2120,7 +2122,6 @@ void readSyncBulkPayload(connection *conn) {
}
if (loadingFailed) {
stopLoading(0);
cancelReplicationHandshake(1);
rioFreeConn(&rdb, NULL);
@@ -2138,6 +2139,11 @@ void readSyncBulkPayload(connection *conn) {
emptyData(-1,empty_db_flags,replicationEmptyDbCallback);
}
/* Note that replicationEmptyDbCallback() may yield back to event
* loop to reply -LOADING if flushing the db takes a long time. So,
* stopLoading() must be called after emptyData() above. */
stopLoading(0);
/* Note that there's no point in restarting the AOF on SYNC
* failure, it'll be restarted when sync succeeds or the replica
* gets promoted. */
@@ -2213,7 +2219,7 @@ void readSyncBulkPayload(connection *conn) {
return;
}
if (rdbLoad(server.rdb_filename,&rsi,RDBFLAGS_REPLICATION) != RDB_OK) {
if (rdbLoadWithEmptyFunc(server.rdb_filename,&rsi,RDBFLAGS_REPLICATION,rdbLoadEmptyDbFunc) != RDB_OK) {
serverLog(LL_WARNING,
"Failed trying to load the MASTER synchronization "
"DB from disk, check server logs.");
@@ -2225,9 +2231,6 @@ void readSyncBulkPayload(connection *conn) {
bg_unlink(server.rdb_filename);
}
/* If disk-based RDB loading fails, remove the half-loaded dataset. */
emptyData(-1, empty_db_flags, replicationEmptyDbCallback);
/* Note that there's no point in restarting the AOF on sync failure,
it'll be restarted when sync succeeds or replica promoted. */
return;
@@ -2281,7 +2284,10 @@ void readSyncBulkPayload(connection *conn) {
/* Restart the AOF subsystem now that we finished the sync. This
* will trigger an AOF rewrite, and when done will start appending
* to the new file. */
if (server.aof_enabled) restartAOFAfterSYNC();
if (server.aof_enabled) {
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Starting AOF after a successful sync");
startAppendOnlyWithRetry();
}
return;
error:
@@ -3098,7 +3104,10 @@ void replicationUnsetMaster(void) {
/* Restart the AOF subsystem in case we shut it down during a sync when
* we were still a slave. */
if (server.aof_enabled && server.aof_state == AOF_OFF) restartAOFAfterSYNC();
if (server.aof_enabled && server.aof_state == AOF_OFF) {
serverLog(LL_NOTICE, "Restarting AOF after becoming master");
startAppendOnlyWithRetry();
}
}
/* This function is called when the slave lose the connection with the
+29 -5
View File
@@ -2,8 +2,13 @@
* Copyright (c) 2009-Present, Redis Ltd.
* All rights reserved.
*
* Copyright (c) 2024-present, Valkey contributors.
* All rights reserved.
*
* Licensed under your choice of the Redis Source Available License 2.0
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
*
* Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
*/
#include "server.h"
@@ -734,6 +739,8 @@ long long getInstantaneousMetric(int metric) {
*
* The function always returns 0 as it never terminates the client. */
int clientsCronResizeQueryBuffer(client *c) {
/* If the client query buffer is NULL, it is using the reusable query buffer and there is nothing to do. */
if (c->querybuf == NULL) return 0;
size_t querybuf_size = sdsalloc(c->querybuf);
time_t idletime = server.unixtime - c->lastinteraction;
@@ -743,7 +750,18 @@ int clientsCronResizeQueryBuffer(client *c) {
/* There are two conditions to resize the query buffer: */
if (idletime > 2) {
/* 1) Query is idle for a long time. */
c->querybuf = sdsRemoveFreeSpace(c->querybuf, 1);
size_t remaining = sdslen(c->querybuf) - c->qb_pos;
if (!(c->flags & CLIENT_MASTER) && !remaining) {
/* If the client is not a master and no data is pending,
* The client can safely use the reusable query buffer in the next read - free the client's querybuf. */
sdsfree(c->querybuf);
/* By setting the querybuf to NULL, the client will use the reusable query buffer in the next read.
* We don't move the client to the reusable query buffer immediately, because if we allocated a private
* query buffer for the client, it's likely that the client will use it again soon. */
c->querybuf = NULL;
} else {
c->querybuf = sdsRemoveFreeSpace(c->querybuf, 1);
}
} else if (querybuf_size > PROTO_RESIZE_THRESHOLD && querybuf_size/2 > c->querybuf_peak) {
/* 2) Query buffer is too big for latest peak and is larger than
* resize threshold. Trim excess space but only up to a limit,
@@ -759,7 +777,7 @@ int clientsCronResizeQueryBuffer(client *c) {
/* Reset the peak again to capture the peak memory usage in the next
* cycle. */
c->querybuf_peak = sdslen(c->querybuf);
c->querybuf_peak = c->querybuf ? sdslen(c->querybuf) : 0;
/* We reset to either the current used, or currently processed bulk size,
* which ever is bigger. */
if (c->bulklen != -1 && (size_t)c->bulklen + 2 > c->querybuf_peak) c->querybuf_peak = c->bulklen + 2;
@@ -834,8 +852,9 @@ size_t ClientsPeakMemInput[CLIENTS_PEAK_MEM_USAGE_SLOTS] = {0};
size_t ClientsPeakMemOutput[CLIENTS_PEAK_MEM_USAGE_SLOTS] = {0};
int clientsCronTrackExpansiveClients(client *c, int time_idx) {
size_t in_usage = sdsZmallocSize(c->querybuf) + c->argv_len_sum +
(c->argv ? zmalloc_size(c->argv) : 0);
size_t qb_size = c->querybuf ? sdsZmallocSize(c->querybuf) : 0;
size_t argv_size = c->argv ? zmalloc_size(c->argv) : 0;
size_t in_usage = qb_size + c->argv_len_sum + argv_size;
size_t out_usage = getClientOutputBufferMemoryUsage(c);
/* Track the biggest values observed so far in this slot. */
@@ -6567,7 +6586,7 @@ void dismissMemory(void* ptr, size_t size_hint) {
void dismissClientMemory(client *c) {
/* Dismiss client query buffer and static reply buffer. */
dismissMemory(c->buf, c->buf_usable_size);
dismissSds(c->querybuf);
if (c->querybuf) dismissSds(c->querybuf);
/* Dismiss argv array only if we estimate it contains a big buffer. */
if (c->argc && c->argv_len_sum/c->argc >= server.page_size) {
for (int i = 0; i < c->argc; i++) {
@@ -7212,6 +7231,11 @@ int main(int argc, char **argv) {
loadDataFromDisk();
aofOpenIfNeededOnServerStart();
aofDelHistoryFiles();
/* While loading data, we delay applying "appendonly" config change.
* If there was a config change while we were inside loadDataFromDisk()
* above, we'll apply it here. */
applyAppendOnlyConfig();
if (server.cluster_enabled) {
serverAssert(verifyClusterConfigWithData() == C_OK);
}
+23 -3
View File
@@ -388,6 +388,7 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
#define CLIENT_MODULE_PREVENT_AOF_PROP (1ULL<<48) /* Module client do not want to propagate to AOF */
#define CLIENT_MODULE_PREVENT_REPL_PROP (1ULL<<49) /* Module client do not want to propagate to replica */
#define CLIENT_REPROCESSING_COMMAND (1ULL<<50) /* The client is re-processing the command. */
#define CLIENT_REUSABLE_QUERYBUFFER (1ULL<<51) /* The client is using the reusable query buffer. */
/* Any flag that does not let optimize FLUSH SYNC to run it in bg as blocking client ASYNC */
#define CLIENT_AVOID_BLOCKING_ASYNC_FLUSH (CLIENT_DENY_BLOCKING|CLIENT_MULTI|CLIENT_LUA_DEBUG|CLIENT_LUA_DEBUG_SYNC|CLIENT_MODULE)
@@ -820,6 +821,8 @@ struct RedisModule {
int blocked_clients; /* Count of RedisModuleBlockedClient in this module. */
RedisModuleInfoFunc info_cb; /* Callback for module to add INFO fields. */
RedisModuleDefragFunc defrag_cb; /* Callback for global data defrag. */
RedisModuleDefragFunc defrag_start_cb; /* Callback indicating defrag started. */
RedisModuleDefragFunc defrag_end_cb; /* Callback indicating defrag ended. */
struct moduleLoadQueueEntry *loadmod; /* Module load arguments for config rewrite. */
int num_commands_with_acl_categories; /* Number of commands in this module included in acl categories */
int onload; /* Flag to identify if the call is being made from Onload (0 or 1) */
@@ -2555,6 +2558,8 @@ robj *moduleTypeDupOrReply(client *c, robj *fromkey, robj *tokey, int todb, robj
int moduleDefragValue(robj *key, robj *obj, int dbid);
int moduleLateDefrag(robj *key, robj *value, unsigned long *cursor, long long endtime, int dbid);
void moduleDefragGlobals(void);
void moduleDefragStart(void);
void moduleDefragEnd(void);
void *moduleGetHandleByName(char *modulename);
int moduleIsModuleCommand(void *module_handle, struct redisCommand *cmd);
@@ -2627,6 +2632,7 @@ void addReplyDouble(client *c, double d);
void addReplyBigNum(client *c, const char *num, size_t len);
void addReplyHumanLongDouble(client *c, long double d);
void addReplyLongLong(client *c, long long ll);
void addReplyLongLongFromStr(client *c, robj* str);
void addReplyArrayLen(client *c, long length);
void addReplyMapLen(client *c, long length);
void addReplySetLen(client *c, long length);
@@ -2690,6 +2696,8 @@ void initThreadedIO(void);
client *lookupClientByID(uint64_t id);
int authRequired(client *c);
void putClientInPendingWriteQueue(client *c);
/* reply macros */
#define ADD_REPLY_BULK_CBUFFER_STRING_CONSTANT(c, str) addReplyBulkCBuffer(c, str, strlen(str))
/* logreqres.c - logging of requests and responses */
void reqresReset(client *c, int free_buf);
@@ -2740,7 +2748,7 @@ robj *listTypeGet(listTypeEntry *entry);
unsigned char *listTypeGetValue(listTypeEntry *entry, size_t *vlen, long long *lval);
void listTypeInsert(listTypeEntry *entry, robj *value, int where);
void listTypeReplace(listTypeEntry *entry, robj *value);
int listTypeEqual(listTypeEntry *entry, robj *o);
int listTypeEqual(listTypeEntry *entry, robj *o, size_t object_len);
void listTypeDelete(listTypeIterator *iter, listTypeEntry *entry);
robj *listTypeDup(robj *o);
void listTypeDelRange(robj *o, long start, long stop);
@@ -2877,6 +2885,8 @@ const char *getFailoverStateString(void);
/* Generic persistence functions */
void startLoadingFile(size_t size, char* filename, int rdbflags);
void startLoading(size_t size, int rdbflags, int async);
void loadingSetFlags(char *filename, size_t size, int async);
void loadingFireEvent(int rdbflags);
void loadingAbsProgress(off_t pos);
void loadingIncrProgress(off_t size);
void stopLoading(int success);
@@ -2904,9 +2914,10 @@ int rewriteAppendOnlyFileBackground(void);
int loadAppendOnlyFiles(aofManifest *am);
void stopAppendOnly(void);
int startAppendOnly(void);
void startAppendOnlyWithRetry(void);
void applyAppendOnlyConfig(void);
void backgroundRewriteDoneHandler(int exitcode, int bysignal);
void killAppendOnlyChild(void);
void restartAOFAfterSYNC(void);
void aofLoadManifestFromDisk(void);
void aofOpenIfNeededOnServerStart(void);
void aofManifestFree(aofManifest *am);
@@ -3127,6 +3138,8 @@ void checkChildrenDone(void);
int setOOMScoreAdj(int process_class);
void rejectCommandFormat(client *c, const char *fmt, ...);
void *activeDefragAlloc(void *ptr);
void *activeDefragAllocRaw(size_t size);
void activeDefragFreeRaw(void *ptr);
robj *activeDefragStringOb(robj* ob);
void dismissSds(sds s);
void dismissMemory(void* ptr, size_t size_hint);
@@ -3355,10 +3368,12 @@ void propagateDeletion(redisDb *db, robj *key, int lazy);
int keyIsExpired(redisDb *db, robj *key);
long long getExpire(redisDb *db, robj *key);
void setExpire(client *c, redisDb *db, robj *key, long long when);
void setExpireWithDictEntry(client *c, redisDb *db, robj *key, long long when, dictEntry *kde);
int checkAlreadyExpired(long long when);
int parseExtendedExpireArgumentsOrReply(client *c, int *flags);
robj *lookupKeyRead(redisDb *db, robj *key);
robj *lookupKeyWrite(redisDb *db, robj *key);
robj *lookupKeyWriteWithDictEntry(redisDb *db, robj *key, dictEntry **deref);
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply);
robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply);
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags);
@@ -3378,6 +3393,7 @@ int objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
dictEntry *dbAdd(redisDb *db, robj *key, robj *val);
int dbAddRDBLoad(redisDb *db, sds key, robj *val);
void dbReplaceValue(redisDb *db, robj *key, robj *val);
void dbReplaceValueWithDictEntry(redisDb *db, robj *key, robj *val, dictEntry *de);
#define SETKEY_KEEPTTL 1
#define SETKEY_NO_SIGNAL 2
@@ -3385,11 +3401,14 @@ void dbReplaceValue(redisDb *db, robj *key, robj *val);
#define SETKEY_DOESNT_EXIST 8
#define SETKEY_ADD_OR_UPDATE 16 /* Key most likely doesn't exists */
void setKey(client *c, redisDb *db, robj *key, robj *val, int flags);
void setKeyWithDictEntry(client *c, redisDb *db, robj *key, robj *val, int flags, dictEntry *de);
robj *dbRandomKey(redisDb *db);
int dbGenericDelete(redisDb *db, robj *key, int async, int flags);
int dbSyncDelete(redisDb *db, robj *key);
int dbDelete(redisDb *db, robj *key);
robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o);
robj *dbUnshareStringValueWithDictEntry(redisDb *db, robj *key, robj *o, dictEntry *de);
#define EMPTYDB_NO_FLAGS 0 /* No flags. */
#define EMPTYDB_ASYNC (1<<0) /* Reclaim memory in another thread. */
@@ -3399,7 +3418,7 @@ long long emptyDbStructure(redisDb *dbarray, int dbnum, int async, void(callback
void flushAllDataAndResetRDB(int flags);
long long dbTotalServerKeyCount(void);
redisDb *initTempDb(void);
void discardTempDb(redisDb *tempDb, void(callback)(dict*));
void discardTempDb(redisDb *tempDb);
int selectDb(client *c, int id);
@@ -3634,6 +3653,7 @@ void scardCommand(client *c);
void spopCommand(client *c);
void srandmemberCommand(client *c);
void sinterCommand(client *c);
void smembersCommand(client *c);
void sinterCardCommand(client *c);
void sinterstoreCommand(client *c);
void sunionCommand(client *c);
+36 -9
View File
@@ -1985,6 +1985,21 @@ uint64_t hashTypeRemoveFromExpires(ebuckets *hexpires, robj *o) {
return expireTime;
}
int hashTypeIsFieldsWithExpire(robj *o) {
if (o->encoding == OBJ_ENCODING_LISTPACK) {
return 0;
} else if (o->encoding == OBJ_ENCODING_LISTPACK_EX) {
return EB_EXPIRE_TIME_INVALID != listpackExGetMinExpire(o);
} else { /* o->encoding == OBJ_ENCODING_HT */
dict *d = o->ptr;
/* If dict doesn't holds HFE metadata */
if (!isDictWithMetaHFE(d))
return 0;
dictExpireMetadata *meta = (dictExpireMetadata *) dictMetadata(d);
return ebGetTotalItems(meta->hfe, &hashFieldExpireBucketsType) != 0;
}
}
/* Add hash to global HFE DS and update key for notifications.
*
* key - must be the same key instance that is persisted in db->dict
@@ -2315,6 +2330,14 @@ void hdelCommand(client *c) {
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,OBJ_HASH)) return;
/* Hash field expiration is optimized to avoid frequent update global HFE DS for
* each field deletion. Eventually active-expiration will run and update or remove
* the hash from global HFE DS gracefully. Nevertheless, statistic "subexpiry"
* might reflect wrong number of hashes with HFE to the user if it is the last
* field with expiration. The following logic checks if this is indeed the last
* field with expiration and removes it from global HFE DS. */
int isHFE = hashTypeIsFieldsWithExpire(o);
for (j = 2; j < c->argc; j++) {
if (hashTypeDelete(o,c->argv[j]->ptr,1)) {
deleted++;
@@ -2328,9 +2351,13 @@ void hdelCommand(client *c) {
if (deleted) {
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_HASH,"hdel",c->argv[1],c->db->id);
if (keyremoved)
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],
c->db->id);
if (keyremoved) {
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", c->argv[1], c->db->id);
} else {
if (isHFE && (hashTypeIsFieldsWithExpire(o) == 0)) /* is it last HFE */
ebRemove(&c->db->hexpires, &hashExpireBucketsType, o);
}
server.dirty += deleted;
}
addReplyLongLong(c,deleted);
@@ -2401,7 +2428,11 @@ void genericHgetallCommand(client *c, int flags) {
/* We return a map if the user requested keys and values, like in the
* HGETALL case. Otherwise to use a flat array makes more sense. */
length = hashTypeLength(o, 1 /*subtractExpiredFields*/);
if ((length = hashTypeLength(o, 1 /*subtractExpiredFields*/)) == 0) {
addReply(c, emptyResp);
return;
}
if (flags & OBJ_HASH_KEY && flags & OBJ_HASH_VALUE) {
addReplyMapLen(c, length);
} else {
@@ -2410,11 +2441,7 @@ void genericHgetallCommand(client *c, int flags) {
hi = hashTypeInitIterator(o);
/* Skip expired fields if the hash has an expire time set at global HFE DS. We could
* set it to constant 1, but then it will make another lookup for each field expiration */
int skipExpiredFields = (EB_EXPIRE_TIME_INVALID == hashTypeGetMinExpire(o, 0)) ? 0 : 1;
while (hashTypeNext(hi, skipExpiredFields) != C_ERR) {
while (hashTypeNext(hi, 1 /*skipExpiredFields*/) != C_ERR) {
if (flags & OBJ_HASH_KEY) {
addHashIteratorCursorToReply(c, hi, OBJ_HASH_KEY);
count++;
+9 -6
View File
@@ -383,12 +383,12 @@ int listTypeReplaceAtIndex(robj *o, int index, robj *value) {
}
/* Compare the given object with the entry at the current position. */
int listTypeEqual(listTypeEntry *entry, robj *o) {
int listTypeEqual(listTypeEntry *entry, robj *o, size_t object_len) {
serverAssertWithInfo(NULL,o,sdsEncodedObject(o));
if (entry->li->encoding == OBJ_ENCODING_QUICKLIST) {
return quicklistCompare(&entry->entry,o->ptr,sdslen(o->ptr));
return quicklistCompare(&entry->entry,o->ptr,object_len);
} else if (entry->li->encoding == OBJ_ENCODING_LISTPACK) {
return lpCompare(entry->lpe,o->ptr,sdslen(o->ptr));
return lpCompare(entry->lpe,o->ptr,object_len);
} else {
serverPanic("Unknown list encoding");
}
@@ -538,8 +538,9 @@ void linsertCommand(client *c) {
/* Seek pivot from head to tail */
iter = listTypeInitIterator(subject,0,LIST_TAIL);
const size_t object_len = sdslen(c->argv[3]->ptr);
while (listTypeNext(iter,&entry)) {
if (listTypeEqual(&entry,c->argv[3])) {
if (listTypeEqual(&entry,c->argv[3],object_len)) {
listTypeInsert(&entry,c->argv[4],where);
inserted = 1;
break;
@@ -999,8 +1000,9 @@ void lposCommand(client *c) {
listTypeEntry entry;
long llen = listTypeLength(o);
long index = 0, matches = 0, matchindex = -1, arraylen = 0;
const size_t ele_len = sdslen(ele->ptr);
while (listTypeNext(li,&entry) && (maxlen == 0 || index < maxlen)) {
if (listTypeEqual(&entry,ele)) {
if (listTypeEqual(&entry,ele,ele_len)) {
matches++;
matchindex = (direction == LIST_TAIL) ? index : llen - index - 1;
if (matches >= rank) {
@@ -1052,8 +1054,9 @@ void lremCommand(client *c) {
}
listTypeEntry entry;
const size_t object_len = sdslen(c->argv[3]->ptr);
while (listTypeNext(li,&entry)) {
if (listTypeEqual(&entry,obj)) {
if (listTypeEqual(&entry,obj,object_len)) {
listTypeDelete(li, &entry);
server.dirty++;
removed++;
+31 -1
View File
@@ -1244,7 +1244,7 @@ int qsortCompareSetsByRevCardinality(const void *s1, const void *s2) {
return 0;
}
/* SINTER / SMEMBERS / SINTERSTORE / SINTERCARD
/* SINTER / SINTERSTORE / SINTERCARD
*
* 'cardinality_only' work for SINTERCARD, only return the cardinality
* with minimum processing and memory overheads.
@@ -1420,6 +1420,36 @@ void sinterCommand(client *c) {
sinterGenericCommand(c, c->argv+1, c->argc-1, NULL, 0, 0);
}
/* SMEMBERS key */
void smembersCommand(client *c) {
setTypeIterator *si;
char *str;
size_t len;
int64_t intobj;
robj *setobj = lookupKeyRead(c->db, c->argv[1]);
if (checkType(c,setobj,OBJ_SET)) return;
if (!setobj) {
addReply(c, shared.emptyset[c->resp]);
return;
}
/* Prepare the response. */
unsigned long length = setTypeSize(setobj);
addReplySetLen(c,length);
/* Iterate through the elements of the set. */
si = setTypeInitIterator(setobj);
while (setTypeNext(si, &str, &len, &intobj) != -1) {
if (str != NULL)
addReplyBulkCBuffer(c, str, len);
else
addReplyBulkLongLong(c, intobj);
length--;
}
setTypeReleaseIterator(si);
serverAssert(length == 0); /* fail on corrupt data */
}
/* SINTERCARD numkeys key [key ...] [LIMIT limit] */
void sinterCardCommand(client *c) {
long j;
+21 -11
View File
@@ -1405,11 +1405,6 @@ int streamRangeHasTombstones(stream *s, streamID *start, streamID *end) {
return 0;
}
if (streamCompareID(&s->first_id,&s->max_deleted_entry_id) > 0) {
/* The latest tombstone is before the first entry. */
return 0;
}
if (start) {
start_id = *start;
} else {
@@ -1446,6 +1441,17 @@ void streamReplyWithCGLag(client *c, stream *s, streamCG *cg) {
/* The lag of a newly-initialized stream is 0. */
lag = 0;
valid = 1;
} else if (!s->length) { /* All entries deleted, now empty. */
lag = 0;
valid = 1;
} else if (streamCompareID(&cg->last_id,&s->first_id) < 0 &&
streamCompareID(&s->max_deleted_entry_id,&s->first_id) < 0)
{
/* When both the consumer group's last_id and the maximum tombstone are behind
* the stream's first entry, the consumer group's lag will always be equal to
* the number of remainin entries in the stream. */
lag = s->length;
valid = 1;
} else if (cg->entries_read != SCG_INVALID_ENTRIES_READ && !streamRangeHasTombstones(s,&cg->last_id,NULL)) {
/* No fragmentation ahead means that the group's logical reads counter
* is valid for performing the lag calculation. */
@@ -1502,6 +1508,10 @@ long long streamEstimateDistanceFromFirstEverEntry(stream *s, streamID *id) {
return s->entries_added;
}
/* There are fragmentations between the `id` and the stream's last-generated-id. */
if (!streamIDEqZero(id) && streamCompareID(id,&s->max_deleted_entry_id) < 0)
return SCG_INVALID_ENTRIES_READ;
int cmp_last = streamCompareID(id,&s->last_id);
if (cmp_last == 0) {
/* Return the exact counter of the last entry in the stream. */
@@ -1684,10 +1694,10 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
while(streamIteratorGetID(&si,&id,&numfields)) {
/* Update the group last_id if needed. */
if (group && streamCompareID(&id,&group->last_id) > 0) {
if (group->entries_read != SCG_INVALID_ENTRIES_READ && !streamRangeHasTombstones(s,&id,NULL)) {
/* A valid counter and no future tombstones mean we can
* increment the read counter to keep tracking the group's
* progress. */
if (group->entries_read != SCG_INVALID_ENTRIES_READ && !streamRangeHasTombstones(s,&group->last_id,NULL)) {
/* A valid counter and no tombstones between the group's last-delivered-id
* and the stream's last-generated-id mean we can increment the read counter
* to keep tracking the group's progress. */
group->entries_read++;
} else if (s->entries_added) {
/* The group's counter may be invalid, so we try to obtain it. */
@@ -2190,9 +2200,9 @@ void xreadCommand(client *c) {
streams_arg = i+1;
streams_count = (c->argc-streams_arg);
if ((streams_count % 2) != 0) {
char symbol = xreadgroup ? '>' : '$';
const char *symbol = xreadgroup ? "ID or '>'" : "ID, '+', or '$'";
addReplyErrorFormat(c,"Unbalanced '%s' list of streams: "
"for each stream key an ID or '%c' must be "
"for each stream key an %s must be "
"specified.", c->cmd->fullname,symbol);
return;
}
+34 -37
View File
@@ -73,7 +73,8 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire,
if (getGenericCommand(c) == C_ERR) return;
}
found = (lookupKeyWrite(c->db,key) != NULL);
dictEntry *de = NULL;
found = (lookupKeyWriteWithDictEntry(c->db,key,&de) != NULL);
if ((flags & OBJ_SET_NX && found) ||
(flags & OBJ_SET_XX && !found))
@@ -88,12 +89,12 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire,
setkey_flags |= ((flags & OBJ_KEEPTTL) || expire) ? SETKEY_KEEPTTL : 0;
setkey_flags |= found ? SETKEY_ALREADY_EXIST : SETKEY_DOESNT_EXIST;
setKey(c,c->db,key,val,setkey_flags);
setKeyWithDictEntry(c,c->db,key,val,setkey_flags,de);
server.dirty++;
notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
if (expire) {
setExpire(c,c->db,key,milliseconds);
setExpireWithDictEntry(c,c->db,key,milliseconds,de);
/* Propagate as SET Key Value PXAT millisecond-timestamp if there is
* EX/PX/EXAT flag. */
if (!(flags & OBJ_PXAT)) {
@@ -422,6 +423,7 @@ void setrangeCommand(client *c) {
robj *o;
long offset;
sds value = c->argv[3]->ptr;
const size_t value_len = sdslen(value);
if (getLongFromObjectOrReply(c,c->argv[2],&offset,NULL) != C_OK)
return;
@@ -431,19 +433,20 @@ void setrangeCommand(client *c) {
return;
}
o = lookupKeyWrite(c->db,c->argv[1]);
dictEntry *de;
o = lookupKeyWriteWithDictEntry(c->db,c->argv[1],&de);
if (o == NULL) {
/* Return 0 when setting nothing on a non-existing string */
if (sdslen(value) == 0) {
if (value_len == 0) {
addReply(c,shared.czero);
return;
}
/* Return when the resulting string exceeds allowed size */
if (checkStringLength(c,offset,sdslen(value)) != C_OK)
if (checkStringLength(c,offset,value_len) != C_OK)
return;
o = createObject(OBJ_STRING,sdsnewlen(NULL, offset+sdslen(value)));
o = createObject(OBJ_STRING,sdsnewlen(NULL, offset+value_len));
dbAdd(c->db,c->argv[1],o);
} else {
size_t olen;
@@ -454,22 +457,22 @@ void setrangeCommand(client *c) {
/* Return existing string length when setting nothing */
olen = stringObjectLen(o);
if (sdslen(value) == 0) {
if (value_len == 0) {
addReplyLongLong(c,olen);
return;
}
/* Return when the resulting string exceeds allowed size */
if (checkStringLength(c,offset,sdslen(value)) != C_OK)
if (checkStringLength(c,offset,value_len) != C_OK)
return;
/* Create a copy when the object is shared or encoded. */
o = dbUnshareStringValue(c->db,c->argv[1],o);
o = dbUnshareStringValueWithDictEntry(c->db,c->argv[1],o,de);
}
if (sdslen(value) > 0) {
o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value));
memcpy((char*)o->ptr+offset,value,sdslen(value));
if (value_len > 0) {
o->ptr = sdsgrowzero(o->ptr,offset+value_len);
memcpy((char*)o->ptr+offset,value,value_len);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_STRING,
"setrange",c->argv[1],c->db->id);
@@ -499,24 +502,15 @@ void getrangeCommand(client *c) {
strlen = sdslen(str);
}
/* Convert negative indexes */
if (start < 0 && end < 0 && start > end) {
if (start < 0) start += strlen;
if (end < 0) end += strlen;
if (strlen == 0 || start >= (long long)strlen || end < 0 || start > end) {
addReply(c,shared.emptybulk);
return;
}
if (start < 0) start = strlen+start;
if (end < 0) end = strlen+end;
if (start < 0) start = 0;
if (end < 0) end = 0;
if ((unsigned long long)end >= strlen) end = strlen-1;
/* Precondition: end >= 0 && end < strlen, so the only condition where
* nothing can be returned is: start > end. */
if (start > end || strlen == 0) {
addReply(c,shared.emptybulk);
} else {
addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
}
if (end >= (long long)strlen) end = strlen-1;
addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
}
void mgetCommand(client *c) {
@@ -580,8 +574,8 @@ void msetnxCommand(client *c) {
void incrDecrCommand(client *c, long long incr) {
long long value, oldvalue;
robj *o, *new;
o = lookupKeyWrite(c->db,c->argv[1]);
dictEntry *de;
o = lookupKeyWriteWithDictEntry(c->db,c->argv[1],&de);
if (checkType(c,o,OBJ_STRING)) return;
if (getLongLongFromObjectOrReply(c,o,&value,NULL) != C_OK) return;
@@ -602,7 +596,7 @@ void incrDecrCommand(client *c, long long incr) {
} else {
new = createStringObjectFromLongLongForValue(value);
if (o) {
dbReplaceValue(c->db,c->argv[1],new);
dbReplaceValueWithDictEntry(c->db,c->argv[1],new,de);
} else {
dbAdd(c->db,c->argv[1],new);
}
@@ -610,7 +604,7 @@ void incrDecrCommand(client *c, long long incr) {
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_STRING,"incrby",c->argv[1],c->db->id);
server.dirty++;
addReplyLongLong(c, value);
addReplyLongLongFromStr(c,new);
}
void incrCommand(client *c) {
@@ -644,7 +638,8 @@ void incrbyfloatCommand(client *c) {
long double incr, value;
robj *o, *new;
o = lookupKeyWrite(c->db,c->argv[1]);
dictEntry *de;
o = lookupKeyWriteWithDictEntry(c->db,c->argv[1],&de);
if (checkType(c,o,OBJ_STRING)) return;
if (getLongDoubleFromObjectOrReply(c,o,&value,NULL) != C_OK ||
getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != C_OK)
@@ -657,7 +652,7 @@ void incrbyfloatCommand(client *c) {
}
new = createStringObjectFromLongDouble(value,1);
if (o)
dbReplaceValue(c->db,c->argv[1],new);
dbReplaceValueWithDictEntry(c->db,c->argv[1],new,de);
else
dbAdd(c->db,c->argv[1],new);
signalModifiedKey(c,c->db,c->argv[1]);
@@ -677,7 +672,8 @@ void appendCommand(client *c) {
size_t totlen;
robj *o, *append;
o = lookupKeyWrite(c->db,c->argv[1]);
dictEntry *de;
o = lookupKeyWriteWithDictEntry(c->db,c->argv[1],&de);
if (o == NULL) {
/* Create the key */
c->argv[2] = tryObjectEncoding(c->argv[2]);
@@ -691,12 +687,13 @@ void appendCommand(client *c) {
/* "append" is an argument, so always an sds */
append = c->argv[2];
if (checkStringLength(c,stringObjectLen(o),sdslen(append->ptr)) != C_OK)
const size_t append_len = sdslen(append->ptr);
if (checkStringLength(c,stringObjectLen(o),append_len) != C_OK)
return;
/* Append the value */
o = dbUnshareStringValue(c->db,c->argv[1],o);
o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
o = dbUnshareStringValueWithDictEntry(c->db,c->argv[1],o,de);
o->ptr = sdscatlen(o->ptr,append->ptr,append_len);
totlen = sdslen(o->ptr);
}
signalModifiedKey(c,c->db,c->argv[1]);
+2 -2
View File
@@ -1,2 +1,2 @@
#define REDIS_VERSION "255.255.255"
#define REDIS_VERSION_NUM 0x00ffffff
#define REDIS_VERSION "7.9.224"
#define REDIS_VERSION_NUM 0x000709e0
+40 -8
View File
@@ -31,6 +31,7 @@ void zlibc_free(void *ptr) {
#include <string.h>
#include "zmalloc.h"
#include "atomicvar.h"
#include "redisassert.h"
#define UNUSED(x) ((void)(x))
@@ -67,10 +68,34 @@ void zlibc_free(void *ptr) {
#define dallocx(ptr,flags) je_dallocx(ptr,flags)
#endif
#define update_zmalloc_stat_alloc(__n) atomicIncr(used_memory,(__n))
#define update_zmalloc_stat_free(__n) atomicDecr(used_memory,(__n))
#define MAX_THREADS 16 /* Keep it a power of 2 so we can use '&' instead of '%'. */
#define THREAD_MASK (MAX_THREADS - 1)
static redisAtomic size_t used_memory = 0;
typedef struct used_memory_entry {
redisAtomic long long used_memory;
char padding[CACHE_LINE_SIZE - sizeof(long long)];
} used_memory_entry;
static __attribute__((aligned(CACHE_LINE_SIZE))) used_memory_entry used_memory[MAX_THREADS];
static redisAtomic size_t num_active_threads = 0;
static __thread long my_thread_index = -1;
static inline void init_my_thread_index(void) {
if (unlikely(my_thread_index == -1)) {
atomicGetIncr(num_active_threads, my_thread_index, 1);
my_thread_index &= THREAD_MASK;
}
}
static void update_zmalloc_stat_alloc(long long num) {
init_my_thread_index();
atomicIncr(used_memory[my_thread_index].used_memory, num);
}
static void update_zmalloc_stat_free(long long num) {
init_my_thread_index();
atomicDecr(used_memory[my_thread_index].used_memory, num);
}
static void zmalloc_default_oom(size_t size) {
fprintf(stderr, "zmalloc: Out of memory trying to allocate %zu bytes\n",
@@ -436,9 +461,18 @@ char *zstrdup(const char *s) {
}
size_t zmalloc_used_memory(void) {
size_t um;
atomicGet(used_memory,um);
return um;
size_t local_num_active_threads;
long long total_mem = 0;
atomicGet(num_active_threads,local_num_active_threads);
if (local_num_active_threads > MAX_THREADS) {
local_num_active_threads = MAX_THREADS;
}
for (size_t i = 0; i < local_num_active_threads; ++i) {
long long thread_used_mem;
atomicGet(used_memory[i].used_memory, thread_used_mem);
total_mem += thread_used_mem;
}
return total_mem;
}
void zmalloc_set_oom_handler(void (*oom_handler)(size_t)) {
@@ -655,8 +689,6 @@ size_t zmalloc_get_rss(void) {
#if defined(USE_JEMALLOC)
#include "redisassert.h"
/* Compute the total memory wasted in fragmentation of inside small arena bins.
* Done by summing the memory in unused regs in all slabs of all small bins.
*
+17 -1
View File
@@ -32,6 +32,13 @@ proc get_one_of_my_replica {id} {
}
set replica_port [lindex [lindex [lindex [R $id role] 2] 0] 1]
set replica_id_num [get_instance_id_by_port redis $replica_port]
# To avoid -LOADING reply, wait until replica syncs with master.
wait_for_condition 1000 50 {
[RI $replica_id_num master_link_status] eq {up}
} else {
fail "Replica did not sync in time."
}
return $replica_id_num
}
@@ -70,6 +77,7 @@ proc test_slave_load_expired_keys {aof} {
# wait for replica to be in sync with master
wait_for_condition 500 10 {
[RI $replica_id master_link_status] eq {up} &&
[R $replica_id dbsize] eq [R $master_id dbsize]
} else {
fail "replica didn't sync"
@@ -104,8 +112,15 @@ proc test_slave_load_expired_keys {aof} {
# start the replica again (loading an RDB or AOF file)
restart_instance redis $replica_id
# Replica may start a full sync after restart, trying in a loop to avoid
# -LOADING reply in that case.
wait_for_condition 1000 50 {
[catch {set replica_dbsize_3 [R $replica_id dbsize]} e] == 0
} else {
fail "Replica is not up."
}
# make sure the keys are still there
set replica_dbsize_3 [R $replica_id dbsize]
assert {$replica_dbsize_3 > $replica_dbsize_0}
# restore settings
@@ -113,6 +128,7 @@ proc test_slave_load_expired_keys {aof} {
# wait for the master to expire all keys and replica to get the DELs
wait_for_condition 500 10 {
[RI $replica_id master_link_status] eq {up} &&
[R $replica_id dbsize] eq $master_dbsize_0
} else {
fail "keys didn't expire"
+6 -1
View File
@@ -124,6 +124,10 @@ test "Verify health as fail for killed node" {
}
}
test "Verify that other nodes can correctly output the new master's slots" {
assert_not_equal {} [dict get [get_node_info_from_shard [R 4 CLUSTER MYID] 8 "shard"] slots]
}
set primary_id 4
set replica_id 0
@@ -133,7 +137,8 @@ test "Restarting primary node" {
test "Instance #0 gets converted into a replica" {
wait_for_condition 1000 50 {
[RI $replica_id role] eq {slave}
[RI $replica_id role] eq {slave} &&
[RI $replica_id master_link_status] eq {up}
} else {
fail "Old primary was not converted into replica"
}
@@ -32,6 +32,17 @@ test "Cluster nodes hard reset" {
} else {
set node_timeout 3000
}
# Wait until slave is synced. Otherwise, it may reply -LOADING
# for any commands below.
if {[RI $id role] eq {slave}} {
wait_for_condition 50 1000 {
[RI $id master_link_status] eq {up}
} else {
fail "Slave were not able to sync."
}
}
catch {R $id flushall} ; # May fail for readonly slaves.
R $id MULTI
R $id cluster reset hard
+46
View File
@@ -655,4 +655,50 @@ tags {"aof external:skip"} {
}
}
}
start_server {overrides {loading-process-events-interval-bytes 1024}} {
test "Allow changing appendonly config while loading from AOF on startup" {
# Set AOF enabled, populate db and restart.
r config set appendonly yes
r config set key-load-delay 100
r config rewrite
populate 10000
restart_server 0 false false
# Disable AOF while loading from the disk.
assert_equal 1 [s loading]
r config set appendonly no
assert_equal 1 [s loading]
# Speed up loading, verify AOF disabled.
r config set key-load-delay 0
wait_done_loading r
assert_equal {10000} [r dbsize]
assert_equal 0 [s aof_enabled]
}
test "Allow changing appendonly config while loading from RDB on startup" {
# Set AOF disabled, populate db and restart.
r flushall
r config set appendonly no
r config set key-load-delay 100
r config rewrite
populate 10000
r save
restart_server 0 false false
# Enable AOF while loading from the disk.
assert_equal 1 [s loading]
r config set appendonly yes
assert_equal 1 [s loading]
# Speed up loading, verify AOF enabled, do a quick sanity.
r config set key-load-delay 0
wait_done_loading r
assert_equal {10000} [r dbsize]
assert_equal 1 [s aof_enabled]
r set t 1
assert_equal {1} [r get t]
}
}
}
+13 -2
View File
@@ -122,6 +122,7 @@ foreach sanitize_dump {no yes} {
set restore_failed false
set report_and_restart false
set sent {}
set expired_subkeys [s expired_subkeys]
# RESTORE can fail, but hopefully not terminate
if { [catch { r restore "_$k" 0 $dump REPLACE } err] } {
set restore_failed true
@@ -145,7 +146,17 @@ foreach sanitize_dump {no yes} {
# if RESTORE didn't fail or terminate, run some random traffic on the new key
incr stat_successful_restore
if { [ catch {
set sent [generate_fuzzy_traffic_on_key "_$k" 1] ;# traffic for 1 second
set type [r type "_$k"]
if {$type eq {none}} {
# The key has been removed due to expiration.
# Ensure the server didn't terminate during expiration and verify
# expire stats to confirm the key was removed due to expiration.
r ping
assert_morethan [s expired_subkeys] $expired_subkeys
} else {
set sent [generate_fuzzy_traffic_on_key "_$k" $type 1] ;# traffic for 1 second
}
incr stat_traffic_commands_sent [llength $sent]
r del "_$k" ;# in case the server terminated, here's where we'll detect it.
if {$dbsize != [r dbsize]} {
@@ -182,7 +193,7 @@ foreach sanitize_dump {no yes} {
dump_server_log $srv
}
puts "Server crashed (by signal: $by_signal), with payload: $printable_dump"
puts "Server crashed (by signal: $by_signal, err: $err), with payload: $printable_dump"
set print_commands true
}
}
+12
View File
@@ -885,5 +885,17 @@ test {corrupt payload: fuzzer findings - set with duplicate elements causes sdif
}
} {} {logreqres:skip} ;# This test violates {"uniqueItems": true}
test {corrupt payload: fuzzer findings - set with invalid length causes smembers to hang} {
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
# In the past, it generated a broken protocol and left the client hung in smembers
r config set sanitize-dump-payload no
assert_equal {OK} [r restore _set 0 "\x14\x16\x16\x00\x00\x00\x0c\x00\x81\x61\x02\x81\x62\x02\x81\x63\x02\x01\x01\x02\x01\x03\x01\xff\x0c\x00\x91\x00\x56\x73\xc1\x82\xd5\xbd" replace]
assert_encoding listpack _set
catch { r SMEMBERS _set } err
assert_equal [count_log_message 0 "crashed by signal"] 0
assert_equal [count_log_message 0 "ASSERTION FAILED"] 1
}
}
} ;# tags
+24
View File
@@ -809,3 +809,27 @@ if {!$::tls} { ;# fake_redis_node doesn't support TLS
assert_equal "a\n1\nb\n2\nc\n3" [exec {*}$cmdline ZRANGE new_zset 0 -1 WITHSCORES]
}
}
start_server {tags {"cli external:skip"}} {
test_interactive_cli_with_prompt "db_num showed in redis-cli after reconnected" {
run_command $fd "select 0\x0D"
run_command $fd "set a zoo-0\x0D"
run_command $fd "select 6\x0D"
run_command $fd "set a zoo-6\x0D"
r save
# kill server and restart
exec kill [s process_id]
wait_for_log_messages 0 {"*Redis is now ready to exit*"} 0 1000 10
catch {[run_command $fd "ping\x0D"]} err
restart_server 0 true false 0
# redis-cli should show '[6]' after reconnected and return 'zoo-6'
write_cli $fd "GET a\x0D"
after 100
set result [format_output [read_cli $fd]]
set regex {not connected> GET a.*"zoo-6".*127\.0\.0\.1:[0-9]*\[6\]>}
assert_equal 1 [regexp $regex $result]
}
}
+140 -1
View File
@@ -652,7 +652,6 @@ foreach testType {Successful Aborted} {
}
test {Blocked commands and configs during async-loading} {
assert_error {LOADING*} {$replica config set appendonly no}
assert_error {LOADING*} {$replica REPLICAOF no one}
}
@@ -1457,3 +1456,143 @@ start_server {tags {"repl external:skip"}} {
}
}
}
foreach disklessload {disabled on-empty-db} {
test "Replica should reply LOADING while flushing a large db (disklessload: $disklessload)" {
start_server {} {
set replica [srv 0 client]
start_server {} {
set master [srv 0 client]
set master_host [srv 0 host]
set master_port [srv 0 port]
$replica config set repl-diskless-load $disklessload
# Populate replica with many keys, master with a few keys.
$replica debug populate 2000000
populate 3 master 10
# Start the replication process...
$replica replicaof $master_host $master_port
wait_for_condition 100 100 {
[s -1 loading] eq 1
} else {
fail "Replica didn't get into loading mode"
}
# If replica has a large db, it may take some time to discard it
# after receiving new db from the master. In this case, replica
# should reply -LOADING. Replica may reply -LOADING while
# loading the new db as well. To test the first case, populated
# replica with large amount of keys and master with a few keys.
# Discarding old db will take a long time and loading new one
# will be quick. So, if we receive -LOADING, most probably it is
# when flushing the db.
wait_for_condition 1 10000 {
[catch {$replica ping} err] &&
[string match *LOADING* $err]
} else {
# There is a chance that we may not catch LOADING response
# if flushing db happens too fast compared to test execution
# Then, we may consider increasing key count or introducing
# artificial delay to db flush.
fail "Replica did not reply LOADING."
}
catch {$replica shutdown nosave}
}
}
} {} {repl external:skip}
}
start_server {tags {"repl external:skip"} overrides {save {}}} {
set master [srv 0 client]
set master_host [srv 0 host]
set master_port [srv 0 port]
populate 10000 master 10
start_server {overrides {save {} rdb-del-sync-files yes loading-process-events-interval-bytes 1024}} {
test "Allow appendonly config change while loading rdb on slave" {
set replica [srv 0 client]
# While loading rdb on slave, verify appendonly config changes are allowed
# 1- Change appendonly config from no to yes
$replica config set appendonly no
$replica config set key-load-delay 100
$replica debug populate 1000
# Start the replication process...
$replica replicaof $master_host $master_port
wait_for_condition 10 1000 {
[s loading] eq 1
} else {
fail "Replica didn't get into loading mode"
}
# Change config while replica is loading data
$replica config set appendonly yes
assert_equal 1 [s loading]
# Speed up loading and verify aof is enabled
$replica config set key-load-delay 0
wait_done_loading $replica
assert_equal 1 [s aof_enabled]
# Quick sanity for AOF
$replica replicaof no one
set prev [s aof_current_size]
$replica set x 100
assert_morethan [s aof_current_size] $prev
# 2- While loading rdb, change appendonly from yes to no
$replica config set appendonly yes
$replica config set key-load-delay 100
$replica flushall
# Start the replication process...
$replica replicaof $master_host $master_port
wait_for_condition 10 1000 {
[s loading] eq 1
} else {
fail "Replica didn't get into loading mode"
}
# Change config while replica is loading data
$replica config set appendonly no
assert_equal 1 [s loading]
# Speed up loading and verify aof is disabled
$replica config set key-load-delay 0
wait_done_loading $replica
assert_equal 0 [s 0 aof_enabled]
}
}
}
start_server {tags {"repl external:skip"}} {
set replica [srv 0 client]
start_server {} {
set master [srv 0 client]
set master_host [srv 0 host]
set master_port [srv 0 port]
test "Replica flushes db lazily when replica-lazy-flush enabled" {
$replica config set replica-lazy-flush yes
$replica debug populate 1000
populate 1 master 10
# Start the replication process...
$replica replicaof $master_host $master_port
wait_for_condition 100 100 {
[s -1 lazyfreed_objects] >= 1000 &&
[s -1 master_link_status] eq {up}
} else {
fail "Replica did not free db lazily"
}
}
}
}
+29
View File
@@ -3,6 +3,7 @@
#include "redismodule.h"
#include <stdlib.h>
#include <string.h>
static RedisModuleType *FragType;
@@ -17,9 +18,12 @@ unsigned long int last_set_cursor = 0;
unsigned long int datatype_attempts = 0;
unsigned long int datatype_defragged = 0;
unsigned long int datatype_raw_defragged = 0;
unsigned long int datatype_resumes = 0;
unsigned long int datatype_wrong_cursor = 0;
unsigned long int global_attempts = 0;
unsigned long int defrag_started = 0;
unsigned long int defrag_ended = 0;
unsigned long int global_defragged = 0;
int global_strings_len = 0;
@@ -47,16 +51,29 @@ static void defragGlobalStrings(RedisModuleDefragCtx *ctx)
}
}
static void defragStart(RedisModuleDefragCtx *ctx) {
REDISMODULE_NOT_USED(ctx);
defrag_started++;
}
static void defragEnd(RedisModuleDefragCtx *ctx) {
REDISMODULE_NOT_USED(ctx);
defrag_ended++;
}
static void FragInfo(RedisModuleInfoCtx *ctx, int for_crash_report) {
REDISMODULE_NOT_USED(for_crash_report);
RedisModule_InfoAddSection(ctx, "stats");
RedisModule_InfoAddFieldLongLong(ctx, "datatype_attempts", datatype_attempts);
RedisModule_InfoAddFieldLongLong(ctx, "datatype_defragged", datatype_defragged);
RedisModule_InfoAddFieldLongLong(ctx, "datatype_raw_defragged", datatype_raw_defragged);
RedisModule_InfoAddFieldLongLong(ctx, "datatype_resumes", datatype_resumes);
RedisModule_InfoAddFieldLongLong(ctx, "datatype_wrong_cursor", datatype_wrong_cursor);
RedisModule_InfoAddFieldLongLong(ctx, "global_attempts", global_attempts);
RedisModule_InfoAddFieldLongLong(ctx, "global_defragged", global_defragged);
RedisModule_InfoAddFieldLongLong(ctx, "defrag_started", defrag_started);
RedisModule_InfoAddFieldLongLong(ctx, "defrag_ended", defrag_ended);
}
struct FragObject *createFragObject(unsigned long len, unsigned long size, int maxstep) {
@@ -79,10 +96,13 @@ static int fragResetStatsCommand(RedisModuleCtx *ctx, RedisModuleString **argv,
datatype_attempts = 0;
datatype_defragged = 0;
datatype_raw_defragged = 0;
datatype_resumes = 0;
datatype_wrong_cursor = 0;
global_attempts = 0;
global_defragged = 0;
defrag_started = 0;
defrag_ended = 0;
RedisModule_ReplyWithSimpleString(ctx, "OK");
return REDISMODULE_OK;
@@ -188,6 +208,14 @@ int FragDefrag(RedisModuleDefragCtx *ctx, RedisModuleString *key, void **value)
}
}
/* Defrag the values array itself using RedisModule_DefragAllocRaw
* and RedisModule_DefragFreeRaw for testing purposes. */
void *new_values = RedisModule_DefragAllocRaw(ctx, o->len * sizeof(void*));
memcpy(new_values, o->values, o->len * sizeof(void*));
RedisModule_DefragFreeRaw(ctx, o->values);
o->values = new_values;
datatype_raw_defragged++;
last_set_cursor = 0;
return 0;
}
@@ -230,6 +258,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
RedisModule_RegisterInfoFunc(ctx, FragInfo);
RedisModule_RegisterDefragFunc(ctx, defragGlobalStrings);
RedisModule_RegisterDefragCallbacks(ctx, defragStart, defragEnd);
return REDISMODULE_OK;
}
+9
View File
@@ -217,6 +217,7 @@ proc test {name code {okpattern undefined} {tags {}}} {
send_data_packet $::test_server_fd testing $name
set failed false
set test_start_time [clock milliseconds]
if {[catch {set retval [uplevel 1 $code]} error]} {
set assertion [string match "assertion:*" $error]
@@ -231,6 +232,7 @@ proc test {name code {okpattern undefined} {tags {}}} {
lappend ::tests_failed $details
incr ::num_failed
set failed true
send_data_packet $::test_server_fd err [join $details "\n"]
if {$::stop_on_failure} {
@@ -253,10 +255,17 @@ proc test {name code {okpattern undefined} {tags {}}} {
lappend ::tests_failed $details
incr ::num_failed
set failed true
send_data_packet $::test_server_fd err [join $details "\n"]
}
}
if {$::dump_logs && $failed} {
foreach srv $::servers {
dump_server_log $srv
}
}
if {$::traceleaks} {
set output [exec leaks redis-server]
if {![string match {*0 leaks*} $output]} {
+1 -2
View File
@@ -698,7 +698,7 @@ proc latencyrstat_percentiles {cmd r} {
}
}
proc generate_fuzzy_traffic_on_key {key duration} {
proc generate_fuzzy_traffic_on_key {key type duration} {
# Commands per type, blocking commands removed
# TODO: extract these from COMMAND DOCS, and improve to include other types
set string_commands {APPEND BITCOUNT BITFIELD BITOP BITPOS DECR DECRBY GET GETBIT GETRANGE GETSET INCR INCRBY INCRBYFLOAT MGET MSET MSETNX PSETEX SET SETBIT SETEX SETNX SETRANGE LCS STRLEN}
@@ -709,7 +709,6 @@ proc generate_fuzzy_traffic_on_key {key duration} {
set stream_commands {XACK XADD XCLAIM XDEL XGROUP XINFO XLEN XPENDING XRANGE XREAD XREADGROUP XREVRANGE XTRIM}
set commands [dict create string $string_commands hash $hash_commands zset $zset_commands list $list_commands set $set_commands stream $stream_commands]
set type [r type $key]
set cmds [dict get $commands $type]
set start_time [clock seconds]
set sent {}
+37 -11
View File
@@ -1,3 +1,16 @@
#
# Copyright (c) 2009-Present, Redis Ltd.
# All rights reserved.
#
# Copyright (c) 2024-present, Valkey contributors.
# All rights reserved.
#
# Licensed under your choice of the Redis Source Available License 2.0
# (RSALv2) or the Server Side Public License v1 (SSPLv1).
#
# Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
#
proc get_slot_field {slot_output shard_id node_id attrib_id} {
return [lindex [lindex [lindex $slot_output $shard_id] $node_id] $attrib_id]
}
@@ -116,10 +129,11 @@ test "Verify the nodes configured with prefer hostname only show hostname for ne
# Have everyone forget node 6 and isolate it from the cluster.
isolate_node 6
# Set hostnames for the masters, now that the node is isolated
R 0 config set cluster-announce-hostname "shard-1.com"
R 1 config set cluster-announce-hostname "shard-2.com"
R 2 config set cluster-announce-hostname "shard-3.com"
set primaries 3
for {set j 0} {$j < $primaries} {incr j} {
# Set hostnames for the masters, now that the node is isolated
R $j config set cluster-announce-hostname "shard-$j.com"
}
# Prevent Node 0 and Node 6 from properly meeting,
# they'll hang in the handshake phase. This allows us to
@@ -149,9 +163,17 @@ test "Verify the nodes configured with prefer hostname only show hostname for ne
} else {
fail "Node did not learn about the 2 shards it can talk to"
}
set slot_result [R 6 CLUSTER SLOTS]
assert_equal [lindex [get_slot_field $slot_result 0 2 3] 1] "shard-2.com"
assert_equal [lindex [get_slot_field $slot_result 1 2 3] 1] "shard-3.com"
wait_for_condition 50 100 {
[lindex [get_slot_field [R 6 CLUSTER SLOTS] 0 2 3] 1] eq "shard-1.com"
} else {
fail "hostname for shard-1 didn't reach node 6"
}
wait_for_condition 50 100 {
[lindex [get_slot_field [R 6 CLUSTER SLOTS] 1 2 3] 1] eq "shard-2.com"
} else {
fail "hostname for shard-2 didn't reach node 6"
}
# Also make sure we know about the isolated master, we
# just can't reach it.
@@ -170,10 +192,14 @@ test "Verify the nodes configured with prefer hostname only show hostname for ne
} else {
fail "Node did not learn about the 2 shards it can talk to"
}
set slot_result [R 6 CLUSTER SLOTS]
assert_equal [lindex [get_slot_field $slot_result 0 2 3] 1] "shard-1.com"
assert_equal [lindex [get_slot_field $slot_result 1 2 3] 1] "shard-2.com"
assert_equal [lindex [get_slot_field $slot_result 2 2 3] 1] "shard-3.com"
for {set j 0} {$j < $primaries} {incr j} {
wait_for_condition 50 100 {
[lindex [get_slot_field [R 6 CLUSTER SLOTS] $j 2 3] 1] eq "shard-$j.com"
} else {
fail "hostname information for shard-$j didn't reach node 6"
}
}
}
test "Test restart will keep hostname information" {
+8
View File
@@ -18,6 +18,9 @@ start_server {tags {"modules"} overrides {{save ""}}} {
set info [r info defragtest_stats]
assert {[getInfoProperty $info defragtest_datatype_attempts] > 0}
assert_equal 0 [getInfoProperty $info defragtest_datatype_resumes]
assert_morethan [getInfoProperty $info defragtest_datatype_raw_defragged] 0
assert_morethan [getInfoProperty $info defragtest_defrag_started] 0
assert_morethan [getInfoProperty $info defragtest_defrag_ended] 0
}
test {Module defrag: late defrag with cursor works} {
@@ -32,6 +35,9 @@ start_server {tags {"modules"} overrides {{save ""}}} {
set info [r info defragtest_stats]
assert {[getInfoProperty $info defragtest_datatype_resumes] > 10}
assert_equal 0 [getInfoProperty $info defragtest_datatype_wrong_cursor]
assert_morethan [getInfoProperty $info defragtest_datatype_raw_defragged] 0
assert_morethan [getInfoProperty $info defragtest_defrag_started] 0
assert_morethan [getInfoProperty $info defragtest_defrag_ended] 0
}
test {Module defrag: global defrag works} {
@@ -41,6 +47,8 @@ start_server {tags {"modules"} overrides {{save ""}}} {
after 2000
set info [r info defragtest_stats]
assert {[getInfoProperty $info defragtest_global_attempts] > 0}
assert_morethan [getInfoProperty $info defragtest_defrag_started] 0
assert_morethan [getInfoProperty $info defragtest_defrag_ended] 0
}
}
}
+3
View File
@@ -83,6 +83,9 @@ start_server {tags {"modules"}} {
# Verify the value in the loaded rdb
assert_equal v1 [r get k]
# Verify aof is still enabled after RM_RdbLoad() call
assert_equal 1 [s aof_enabled]
r flushdb
r config set rdb-key-save-delay 0
r config set appendonly no
+2 -2
View File
@@ -61,9 +61,9 @@ start_server {tags {"modules"}} {
r hmset hh f1 v1 f2 v2 f3 v3
r hpexpire hh 1 fields 3 f1 f2 f3
after 10
assert_equal [r scan.scan_key hh] {}
r debug set-active-expire 1
lsort [r scan.scan_key hh]
} {} {needs:debug}
} {OK} {needs:debug}
test {Module scan zset listpack} {
r zadd zz 1 f1 2 f2
+81 -4
View File
@@ -1,3 +1,15 @@
#
# Copyright (c) 2009-Present, Redis Ltd.
# All rights reserved.
#
# Copyright (c) 2024-present, Valkey contributors.
# All rights reserved.
#
# Licensed under your choice of the Redis Source Available License 2.0
# (RSALv2) or the Server Side Public License v1 (SSPLv1).
#
# Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
#
proc client_idle_sec {name} {
set clients [split [r client list] "\r\n"]
set c [lsearch -inline $clients *name=$name*]
@@ -24,16 +36,45 @@ start_server {tags {"querybuf slow"}} {
# The test will run at least 2s to check if client query
# buffer will be resized when client idle 2s.
test "query buffer resized correctly" {
set rd [redis_client]
set rd [redis_deferring_client]
$rd client setname test_client
$rd read
# Make sure query buff has size of 0 bytes at start as the client uses the reusable qb.
assert {[client_query_buffer test_client] == 0}
# Pause cron to prevent premature shrinking (timing issue).
r debug pause-cron 1
# Send partial command to client to make sure it doesn't use the reusable qb.
$rd write "*3\r\n\$3\r\nset\r\n\$2\r\na"
$rd flush
# Wait for the client to start using a private query buffer.
wait_for_condition 1000 10 {
[client_query_buffer test_client] > 0
} else {
fail "client should start using a private query buffer"
}
# send the rest of the command
$rd write "a\r\n\$1\r\nb\r\n"
$rd flush
assert_equal {OK} [$rd read]
set orig_test_client_qbuf [client_query_buffer test_client]
# Make sure query buff has less than the peak resize threshold (PROTO_RESIZE_THRESHOLD) 32k
# but at least the basic IO reading buffer size (PROTO_IOBUF_LEN) 16k
assert {$orig_test_client_qbuf >= 16384 && $orig_test_client_qbuf < 32768}
set MAX_QUERY_BUFFER_SIZE [expr 32768 + 2] ; # 32k + 2, allowing for potential greedy allocation of (16k + 1) * 2 bytes for the query buffer.
assert {$orig_test_client_qbuf >= 16384 && $orig_test_client_qbuf <= $MAX_QUERY_BUFFER_SIZE}
# Allow shrinking to occur
r debug pause-cron 0
# Check that the initial query buffer is resized after 2 sec
wait_for_condition 1000 10 {
[client_idle_sec test_client] >= 3 && [client_query_buffer test_client] == 0
[client_idle_sec test_client] >= 3 && [client_query_buffer test_client] < $orig_test_client_qbuf
} else {
fail "query buffer was not resized"
}
@@ -75,10 +116,27 @@ start_server {tags {"querybuf slow"}} {
test "query buffer resized correctly with fat argv" {
set rd [redis_client]
$rd client setname test_client
# Pause cron to prevent premature shrinking (timing issue).
r debug pause-cron 1
$rd write "*3\r\n\$3\r\nset\r\n\$1\r\na\r\n\$1000000\r\n"
$rd flush
# Wait for the client to start using a private query buffer of > 1000000 size.
wait_for_condition 1000 10 {
[client_query_buffer test_client] > 1000000
} else {
fail "client should start using a private query buffer"
}
after 20
# Send the start of the arg and make sure the client is not using reusable qb for it rather a private buf of > 1000000 size.
$rd write "a"
$rd flush
r debug pause-cron 0
after 120
if {[client_query_buffer test_client] < 1000000} {
fail "query buffer should not be resized when client idle time smaller than 2s"
}
@@ -92,5 +150,24 @@ start_server {tags {"querybuf slow"}} {
$rd close
}
}
start_server {tags {"querybuf"}} {
test "Client executes small argv commands using reusable query buffer" {
set rd [redis_deferring_client]
$rd client setname test_client
$rd read
set res [r client list]
# Verify that the client does not create a private query buffer after
# executing a small parameter command.
assert_match {*name=test_client * qbuf=0 qbuf-free=0 * cmd=client|setname *} $res
# The client executing the command is currently using the reusable query buffer,
# so the size shown is that of the reusable query buffer. It will be returned
# to the reusable query buffer after command execution.
assert_match {*qbuf=26 qbuf-free=* cmd=client|list *} $res
$rd close
}
}
+3 -22
View File
@@ -109,25 +109,9 @@ proc test_scan {type} {
after 2
# TODO: remove this in redis 8.0
set cur 0
set keys {}
while 1 {
set res [r scan $cur type "string1"]
set cur [lindex $res 0]
set k [lindex $res 1]
lappend keys {*}$k
if {$cur == 0} break
}
assert_equal 0 [llength $keys]
# make sure that expired key have been removed by scan command
assert_equal 1000 [scan [regexp -inline {keys\=([\d]*)} [r info keyspace]] keys=%d]
# TODO: uncomment in redis 8.0
#assert_error "*unknown type name*" {r scan 0 type "string1"}
assert_error "*unknown type name*" {r scan 0 type "string1"}
# expired key will be no touched by scan command
#assert_equal 1001 [scan [regexp -inline {keys\=([\d]*)} [r info keyspace]] keys=%d]
assert_equal 1001 [scan [regexp -inline {keys\=([\d]*)} [r info keyspace]] keys=%d]
r debug set-active-expire 1
} {OK} {needs:debug}
@@ -191,11 +175,8 @@ proc test_scan {type} {
assert_equal 1000 [llength $keys]
# make sure that expired key have been removed by scan command
assert_equal 1000 [scan [regexp -inline {keys\=([\d]*)} [r info keyspace]] keys=%d]
# TODO: uncomment in redis 8.0
# make sure that only the expired key in the type match will been removed by scan command
#assert_equal 1001 [scan [regexp -inline {keys\=([\d]*)} [r info keyspace]] keys=%d]
assert_equal 1001 [scan [regexp -inline {keys\=([\d]*)} [r info keyspace]] keys=%d]
r debug set-active-expire 1
} {OK} {needs:debug}
+8
View File
@@ -2069,6 +2069,14 @@ start_server {tags {"scripting"}} {
} 1 x
r replicaof [srv -1 host] [srv -1 port]
# To avoid -LOADING reply, wait until replica syncs with master.
wait_for_condition 50 100 {
[s master_link_status] eq {up}
} else {
fail "Replica did not sync in time."
}
assert_error {EXECABORT Transaction discarded because of: READONLY *} {$rr exec}
assert_error {READONLY You can't write against a read only replica. script: *} {$rr2 exec}
$rr close
+40 -22
View File
@@ -560,10 +560,10 @@ start_server {tags {"external:skip needs:debug"}} {
# Test with small hash
r debug set-active-expire 0
r del myhash
r hset myhash1 f1 v1 f2 v2 f3 v3 f4 v4 f5 v5 f6 v6
r hpexpire myhash1 1 NX FIELDS 3 f2 f4 f6
r hset myhash f1 v1 f2 v2 f3 v3 f4 v4 f5 v5 f6 v6
r hpexpire myhash 1 NX FIELDS 3 f2 f4 f6
after 10
assert_equal [lsort [r hgetall myhash1]] "f1 f3 f5 v1 v3 v5"
assert_equal [lsort [r hgetall myhash]] "f1 f3 f5 v1 v3 v5"
# Test with large hash
r del myhash
@@ -573,6 +573,13 @@ start_server {tags {"external:skip needs:debug"}} {
}
after 10
assert_equal [lsort [r hgetall myhash]] [lsort "f1 f2 f3 v1 v2 v3"]
# hash that all fields are expired return empty result
r del myhash
r hset myhash f1 v1 f2 v2 f3 v3 f4 v4 f5 v5 f6 v6
r hpexpire myhash 1 FIELDS 6 f1 f2 f3 f4 f5 f6
after 10
assert_equal [r hgetall myhash] ""
r debug set-active-expire 1
}
@@ -786,6 +793,36 @@ start_server {tags {"external:skip needs:debug"}} {
}
}
test "Statistics - Hashes with HFEs ($type)" {
r config resetstat
r flushall
# hash1: 5 fields, 3 with TTL. subexpiry incr +1
r hset myhash f1 v1 f2 v2 f3 v3 f4 v4 f5 v5
r hpexpire myhash 150 FIELDS 3 f1 f2 f3
assert_match [get_stat_subexpiry r] 1
# hash2: 5 fields, 3 with TTL. subexpiry incr +1
r hset myhash2 f1 v1 f2 v2 f3 v3 f4 v4 f5 v5
assert_match [get_stat_subexpiry r] 1
r hpexpire myhash2 100 FIELDS 3 f1 f2 f3
assert_match [get_stat_subexpiry r] 2
# hash3: 2 fields, 1 with TTL. HDEL field with TTL. subexpiry decr -1
r hset myhash3 f1 v1 f2 v2
r hpexpire myhash3 100 FIELDS 1 f2
assert_match [get_stat_subexpiry r] 3
r hdel myhash3 f2
assert_match [get_stat_subexpiry r] 2
# Expired fields of hash1 and hash2. subexpiry decr -2
wait_for_condition 50 50 {
[get_stat_subexpiry r] == 0
} else {
fail "Hash field expiry statistics failed"
}
}
r config set hash-max-listpack-entries 512
}
@@ -899,25 +936,6 @@ start_server {tags {"external:skip needs:debug"}} {
r config set hash-max-listpack-value 64
}
test "Statistics - Hashes with HFEs" {
r config resetstat
r del myhash
r hset myhash f1 v1 f2 v2 f3 v3 f4 v4 f5 v5
r hpexpire myhash 100 FIELDS 3 f1 f2 f3
assert_match [get_stat_subexpiry r] 1
r hset myhash2 f1 v1 f2 v2 f3 v3 f4 v4 f5 v5
assert_match [get_stat_subexpiry r] 1
r hpexpire myhash2 100 FIELDS 3 f1 f2 f3
assert_match [get_stat_subexpiry r] 2
wait_for_condition 50 50 {
[get_stat_subexpiry r] == 0
} else {
fail "Hash field expiry statistics failed"
}
}
}
start_server {tags {"external:skip needs:debug"}} {
+69 -1
View File
@@ -319,7 +319,7 @@ start_server {
r XADD mystream 666 f v
r XGROUP CREATE mystream mygroup $
assert_error "ERR Unbalanced 'xreadgroup' list of streams: for each stream key an ID or '>' must be specified." {r XREADGROUP GROUP mygroup Alice COUNT 1 STREAMS mystream }
assert_error "ERR Unbalanced 'xread' list of streams: for each stream key an ID or '$' must be specified." {r XREAD COUNT 1 STREAMS mystream }
assert_error "ERR Unbalanced 'xread' list of streams: for each stream key an ID, '+', or '$' must be specified." {r XREAD COUNT 1 STREAMS mystream }
}
test {Blocking XREAD: key deleted} {
@@ -1239,6 +1239,74 @@ start_server {
assert_equal [dict get $group lag] 2
}
test {Consumer Group Lag with XDELs and tombstone after the last_id of consume group} {
r DEL x
r XGROUP CREATE x g1 $ MKSTREAM
r XADD x 1-0 data a
r XREADGROUP GROUP g1 alice STREAMS x > ;# Read one entry
r XADD x 2-0 data c
r XADD x 3-0 data d
r XDEL x 2-0
# Now the latest tombstone(2-0) is before the first entry(3-0), but there is still
# a tombstone(2-0) after the last_id(1-0) of the consume group.
set reply [r XINFO STREAM x FULL]
set group [lindex [dict get $reply groups] 0]
assert_equal [dict get $group entries-read] 1
assert_equal [dict get $group lag] {}
r XDEL x 1-0
# Although there is a tombstone(2-0) after the consumer group's last_id(1-0), all
# entries before the maximal tombstone have been deleted. This means that both the
# last_id and the largest tombstone are behind the first entry. Therefore, tombstones
# no longer affect the lag, which now reflects the remaining entries in the stream.
set reply [r XINFO STREAM x FULL]
set group [lindex [dict get $reply groups] 0]
assert_equal [dict get $group entries-read] 1
assert_equal [dict get $group lag] 1
# Now there is a tombstone(2-0) after the last_id of the consume group, so after consuming
# entry(3-0), the group's counter will be invalid.
r XREADGROUP GROUP g1 alice STREAMS x >
set reply [r XINFO STREAM x FULL]
set group [lindex [dict get $reply groups] 0]
assert_equal [dict get $group entries-read] 3
assert_equal [dict get $group lag] 0
}
test {Consumer group lag with XTRIM} {
r DEL x
r XGROUP CREATE x mygroup $ MKSTREAM
r XADD x 1-0 data a
r XADD x 2-0 data b
r XADD x 3-0 data c
r XADD x 4-0 data d
r XADD x 5-0 data e
r XREADGROUP GROUP mygroup alice COUNT 1 STREAMS x >
set reply [r XINFO STREAM x FULL]
set group [lindex [dict get $reply groups] 0]
assert_equal [dict get $group entries-read] 1
assert_equal [dict get $group lag] 4
# Although XTRIM doesn't update the `max-deleted-entry-id`, it always updates the
# position of the first entry. When trimming causes the first entry to be behind
# the consumer group's last_id, the consumer group's lag will always be equal to
# the number of remainin entries in the stream.
r XTRIM x MAXLEN 1
set reply [r XINFO STREAM x FULL]
set group [lindex [dict get $reply groups] 0]
assert_equal [dict get $reply max-deleted-entry-id] "0-0"
assert_equal [dict get $group entries-read] 1
assert_equal [dict get $group lag] 1
# When all the entries were deleted, the lag is always 0.
r XTRIM x MAXLEN 0
set reply [r XINFO STREAM x FULL]
set group [lindex [dict get $reply groups] 0]
assert_equal [dict get $group lag] 0
}
test {Loading from legacy (Redis <= v6.2.x, rdb_ver < 10) persistence} {
# The payload was DUMPed from a v5 instance after:
# XADD x 1-0 data a
+12
View File
@@ -464,6 +464,12 @@ start_server {tags {"string"}} {
assert_equal "" [r getrange mykey 5 3]
assert_equal " World" [r getrange mykey 5 5000]
assert_equal "Hello World" [r getrange mykey -5000 10000]
assert_equal "" [r getrange mykey 0 -100]
assert_equal "" [r getrange mykey 1 -100]
assert_equal "" [r getrange mykey -1 -100]
assert_equal "" [r getrange mykey -100 -99]
assert_equal "" [r getrange mykey -100 -100]
assert_equal "" [r getrange mykey -100 -101]
}
test "GETRANGE against integer-encoded value" {
@@ -474,6 +480,12 @@ start_server {tags {"string"}} {
assert_equal "" [r getrange mykey 5 3]
assert_equal "4" [r getrange mykey 3 5000]
assert_equal "1234" [r getrange mykey -5000 10000]
assert_equal "" [r getrange mykey 0 -100]
assert_equal "" [r getrange mykey 1 -100]
assert_equal "" [r getrange mykey -1 -100]
assert_equal "" [r getrange mykey -100 -99]
assert_equal "" [r getrange mykey -100 -100]
assert_equal "" [r getrange mykey -100 -101]
}
test "GETRANGE fuzzing" {