Compare commits

..
109 Commits
Author SHA1 Message Date
antirez 26375709c5 Redis 2.8.9. 2014-04-22 10:14:57 +02:00
antirez c5ee48a9d9 Fuzzy test for ZREMRANGEBYLEX added. 2014-04-18 16:16:20 +02:00
antirez be145453c6 ZREMRANGEBYLEX memory leak removed calling zslFreeLexRange(). 2014-04-18 16:16:20 +02:00
antirez 115782b1d7 PFCOUNT multi-key test added. 2014-04-18 16:16:20 +02:00
antirez 2cd20d3c4c Speedup hllRawSum() processing 8 bytes per iteration.
The internal HLL raw encoding used by PFCOUNT when merging multiple keys
is aligned to 8 bits (1 byte per register) so we can exploit this to
improve performances by processing multiple bytes per iteration.

In benchmarks the new code was several times faster with HLLs with many
registers set to zero, while no slowdown was observed with populated
HLLs.
2014-04-18 16:16:20 +02:00
antirez 3f5cd0c660 Speedup SUM(2^-reg[m]) in HyperLogLog computation.
When the register is set to zero, we need to add 2^-0 to E, which is 1,
but it is faster to just add 'ez' at the end, which is the number of
registers set to zero, a value we need to compute anyway.
2014-04-18 16:16:20 +02:00
antirez f0eca148ff PFCOUNT support for multi-key union. 2014-04-18 16:16:20 +02:00
antirez d25fd8be64 HyperLogLog low level merge extracted from PFMERGE. 2014-04-18 16:16:20 +02:00
antirez 4a26648a22 ZREMRANGEBYLEX implemented. 2014-04-18 16:16:20 +02:00
antirez 3fc22561e7 Always pass sorted set range objects by reference. 2014-04-18 16:16:04 +02:00
antirez ea0af9ace3 ZREMRANGE* commands refactored into a single generic function. 2014-04-18 16:14:38 +02:00
antirez 019e6f1889 Sorted set lex ranges stress tester. 2014-04-18 16:14:38 +02:00
antirez 9c9aef24a2 Basic ZRANGEBYLEX / ZLEXCOUNT tests. 2014-04-17 00:08:38 +02:00
antirez 11f89497b4 Pass by pointer and release of lex ranges.
Given that the code was written with a 2 years pause... something
strange happened in the middle. So there was no function to free a
lex range min/max objects, and in some places the range was passed by
value.
2014-04-17 00:08:37 +02:00
antirez d975bb587f ZLEXCOUNT implemented.
Like ZCOUNT for lexicographical ranges.
2014-04-16 15:26:28 +02:00
antirez 71dfb87f76 More HyperLogLog tests. 2014-04-16 15:26:28 +02:00
antirez f2e59eae44 HyperLogLog invalid representation error code set to INVALIDOBJ. 2014-04-16 15:26:28 +02:00
antirez 469b7c5168 PFDEBUG TODENSE added.
Converts HyperLogLogs from sparse to dense. Used for testing.
2014-04-16 15:26:28 +02:00
antirez 614fcd491e User-defined switch point between sparse-dense HLL encodings. 2014-04-16 15:26:28 +02:00
antirez 08da2b79fc PFSELFTEST improved with sparse encoding checks. 2014-04-16 15:26:27 +02:00
antirez 5a0bab2a8f PFDEBUG ENCODING added. 2014-04-16 15:26:27 +02:00
antirez 05563310f4 Set HLL_SPARSE_MAX to 3000.
After running a few benchmarks, 3000 looks like a reasonable value to
keep HLLs with a few thousand elements small while the CPU cost is
still not huge.

This covers all the cases where the dense representation would use N
orders of magnitude more space, like in the case of many HLLs with
carinality of a few tens or hundreds.

It is not impossible that in the future this gets user configurable,
however it is easy to pick an unreasoable value just looking at savings
in the space dimension without checking what happens in the time
dimension.
2014-04-16 15:26:27 +02:00
antirez e77a3e40f2 Error message for invalid HLL objects unified. 2014-04-16 15:26:27 +02:00
antirez c4b2a7a7f4 PFMERGE fixed to work with sparse encoding. 2014-04-16 15:26:27 +02:00
antirez ee682c4c48 Mark PFDEBUG as write command in the commands table.
It is safer since it is able to have side effects.
2014-04-16 15:26:27 +02:00
antirez 9ef8ef410b Correctly replicate PFDEBUG GETREG.
Even if it is a debugging command, make sure that when it forces a
change in encoding, the command is propagated.
2014-04-16 15:26:27 +02:00
antirez 91e05e1618 Added assertion in hllSparseAdd() when promotion to dense occurs.
If we converted to dense, a register must be updated in the dense
representation.
2014-04-16 15:26:27 +02:00
antirez 68a4bf70e0 hllSparseAdd(): speed optimization.
Mostly by reordering opcodes check conditional by frequency of opcodes
in larger sparse-encoded HLLs.
2014-04-16 15:26:27 +02:00
antirez 21e328a6d7 Detect corrupted sparse HLLs in hllSparseSum(). 2014-04-16 15:26:27 +02:00
antirez 4244aa29f9 hllSparseAdd(): faster code removing conditional.
Bottleneck found profiling. Big run time improvement found when testing
after the change.
2014-04-16 15:26:27 +02:00
antirez 4cd11077d2 Comment typo in hllSparseAdd(). first -> fits. 2014-04-16 15:26:27 +02:00
antirez 08183cfef5 Merge adjacent VAL opcodes in hllSparseAdd().
As more values are added splitting ZERO or XZERO opcodes, try to merge
adjacent VAL opcodes if they have the same value.
2014-04-16 15:26:27 +02:00
antirez d428569d13 More robust HLL_SPARSE macros protecting 'p' with parens.
Now the macros will work with arguments such as "ptr+1".
2014-04-16 15:26:27 +02:00
antirez 2d1e35a58a hllSparseAdd() opcode seek stop condition fixed. 2014-04-16 15:26:27 +02:00
antirez 29c7d1974b Fixed error message generation in PFDEBUG GETREG.
Bulk length for registers was emitted too early, so if there was a bug
the reply looked like a long array with just one element, blocking the
client as result.
2014-04-16 15:26:27 +02:00
antirez 023f2f7a10 Fixed memmove() count in hllSparseAdd(). 2014-04-16 15:26:27 +02:00
antirez 91391760ca hllSparseAdd(): more correct dense conversion conditional.
We want to promote if the total string size exceeds the resulting size
after the upgrade.
2014-04-16 15:26:27 +02:00
antirez 5d87160f8a hllSparseToDense(): sanity check added.
The function checks if all the HLL_REGISTERS were processed during the
convertion from sparse to dense encoding, returning REDIS_OK or
REDIS_ERR to signal a corruption problem.

A bug in PFDEBUG GETREG was fixed: when the object is converted to the
dense representation we need to reassign the new pointer to the header
structure pointer.
2014-04-16 15:26:27 +02:00
antirez a18e7afbe8 PFDEBUG DECODE added.
Provides a human readable description of the opcodes composing a
run-length encoded HLL (sparse encoding).
The command is only useful for debugging / development tasks.
2014-04-16 15:26:27 +02:00
antirez 5c86d9e7b0 PFDEBUG added, PFGETREG removed.
PFDEBUG will be the interface to do debugging tasks with a key
containing an HLL object.
2014-04-16 15:26:27 +02:00
antirez 0e0ddcc4fa hllSparseToDense API changed to take ref to object.
The new API takes directly the object doing everything needed to
turn it into a dense representation, including setting the new
representation as object->ptr.
2014-04-16 15:26:27 +02:00
antirez 533235f7e3 hllSparseAdd() sanity check for span != 0 added. 2014-04-16 15:26:27 +02:00
antirez f12fb49bb2 Fix hllSparseAdd() new sequence replacement when next is NULL.
sdsIncrLen() must be called anyway even if we are replacing the last
oppcode of the sparse representation.
2014-04-16 15:26:27 +02:00
antirez a3e2adc0ae Fix seqlen computation in hllSparseAdd(). 2014-04-16 15:26:27 +02:00
antirez eaa73ef73a Abstract hllSparseAdd() / hllDenseAdd() via hllAdd(). 2014-04-16 15:26:27 +02:00
antirez 95de3eaea0 hllSparseSum(): multiply 1 * runlen for zero entries. 2014-04-16 15:26:27 +02:00
antirez c277169ed7 Macro HLL_SPARSE_XZERO_LEN fixed. 2014-04-16 15:26:27 +02:00
antirez b6ca71a40a Fix HLL sparse object creation #2.
Two vars initialized to wrong values in createHLLObject().
2014-04-16 15:26:27 +02:00
antirez 8b39fee4c8 Increment pointer while iterating sparse HLL object. 2014-04-16 15:26:27 +02:00
antirez c1ddead779 Fix HLL sparse object creation.
The function didn't considered the fact that each XZERO opcode is
two bytes.
2014-04-16 15:26:27 +02:00
antirez 0a7ba6faef Create HyperLogLog objects with sparse encoding. 2014-04-16 15:26:27 +02:00
antirez 8e889728bf HyperLogLog sparse to dense conversion function. 2014-04-16 15:26:27 +02:00
antirez aadd513160 HyperLogLog sparse representation initial implementation.
Code never tested, but the basic layout is shaped in this commit.
Also missing:

1) Sparse -> Dense conversion function.
2) New HLL object creation using the sparse representation.
3) Implementation of PFMERGE for the sparse representation.
2014-04-16 15:26:27 +02:00
antirez e5e5facba1 hllCount() refactored to support multiple representations. 2014-04-16 15:26:27 +02:00
antirez 88847a216f hllAdd() refactored into two functions.
Also dense representation access macro renamed accordingly.
2014-04-16 15:26:27 +02:00
antirez 21c97e71cd HyperLogLog refactoring to support different encodings.
Metadata are now placed at the start of the representation as an header.
There is a proper structure to access the representation.
Still work to do in order to truly abstract the implementation from the
representation, commands still work assuming dense representation.
2014-04-16 15:26:27 +02:00
antirez ed96e115d0 HyperLogLog sparse representation slightly modified.
After running a few simulations with different alternative encodings,
it was found that the VAL opcode performs better using 5 bits for the
value and 2 bits for the run length, at least for cardinalities in the
range of interest.
2014-04-16 15:26:27 +02:00
antirez 93c75f1bf4 HyperLogLog sparse representation description and macros. 2014-04-16 15:26:26 +02:00
antirez 2b1385b9ec Add casting to match printf format.
adjustOpenFilesLimit() and clusterUpdateSlotsWithConfig() that were
assuming uint64_t is the same as unsigned long long, which is true
probably for all the systems out there that we target, but still GCC
emitted a warning since technically they are two different types.
2014-04-16 15:26:22 +02:00
antirez 12d1d18b59 ZRANGEBYLEX and ZREVRANGEBYLEX implementation. 2014-04-16 15:26:09 +02:00
antirez a10bfded15 PFCOUNT: always unshare/decode the object.
This will be a non-op most of the times since the object will be
unshared / decoded, however it is more technically correct to start this
way since the object may be decoded even in the read-only code path.
2014-04-16 15:26:09 +02:00
antirez ee764b0f8d Changed HyperLogLog hash seed to a non-zero value.
Using a seed of zero has the side effect of having the empty string
hashing to what is a very special case in the context of HyperLogLog: a
very long run of zeroes.

This did not influenced the correctness of the result with 16k registers
because of the harmonic mean, but still it is inconvenient that a so
obvious value maps to a so special hash.

The seed 0xadc83b19 is used instead, which is the first 64 bits of the
SHA1 of the empty string.

Reference: issue #1657.
2014-04-16 15:26:09 +02:00
antirez 091f5677a0 Initial HyperLogLog tests. 2014-04-16 15:26:09 +02:00
antirez a97675923d Return "WRONGTYPE" error on PF* type mismatch. 2014-04-16 15:26:09 +02:00
antirez faa7f259cc Fix PFADD infinite loop.
We need to guarantee that the last bit is 1, otherwise an element may
hash to just zeroes with probability 1/(2^64) and trigger an infinite
loop.

See issue #1657.
2014-04-16 15:26:09 +02:00
antirez 8ae6887091 Make hll-gnuplot-graph.rb callable from cli. 2014-04-16 15:26:09 +02:00
antirez bdd8701c60 Remove HyperLogLog type checking duplicated code. 2014-04-16 15:26:09 +02:00
antirez b3baa51403 PFGETREG added for testing purposes.
The new command allows to get a dump of the registers stored
into an HyperLogLog data structure for testing / debugging purposes.
2014-04-16 15:26:09 +02:00
antirez 348d5ea246 PFCOUNT: unshare the object when cached cardinality is modified. 2014-04-16 15:26:09 +02:00
antirez ba0dc0be2c PFSELFTEST improved to test the approximation error. 2014-04-16 15:26:09 +02:00
antirez c2cd48718d hll-gnuplot-graph.rb improved with new filter.
The function to generate graphs is also more flexible as now includes
step and max value. The step of the samples generation function is no
longer limited to min step of 1000.
2014-04-16 15:26:09 +02:00
antirez ff551fad24 HyperLogLog: added magic / version.
This will allow future changes like compressed representations.
Currently the magic is not checked for performance reasons but this may
change in the future, for example if we add new types encoded in strings
that may have the same size of HyperLogLogs.
2014-04-16 15:26:09 +02:00
Raymond Myers e08100a9ba Fixed pfadd/pfcount commands emitting hll* events instead of pf* events 2014-04-16 15:26:09 +02:00
Raymond Myers 23acae6e1c Change HLL* to PF* in error messages 2014-04-16 15:26:09 +02:00
antirez e3773b0a0e Include redis.h before other stuff in hyperloglog.c.
Otherwise fmacros.h is included later and this may break compilation on
different systems.
2014-04-16 15:26:09 +02:00
antirez badf23f57b HyperLogLog API prefix modified from "P" to "PF".
Using both the initials of Philippe Flajolet instead of just "P".
2014-04-16 15:26:03 +02:00
antirez 6e71459c73 Makefile.dep updated with hyperloglog.o deps. 2014-04-16 15:25:28 +02:00
antirez 2ee2bec2d6 HyperLogLog: make API use the P prefix in honor of Philippe Flajolet. 2014-04-16 15:24:15 +02:00
antirez 1e90c0066c HLLMERGE fixed by adding a... missing loop! 2014-04-16 15:23:21 +02:00
antirez be7eef911b hll-gnuplot-graph.rb: added new filter "all". 2014-04-16 15:23:21 +02:00
antirez f2e707b679 HyperLogLog apply bias correction using a polynomial.
Better results can be achieved by compensating for the bias of the raw
approximation just after 2.5m (when LINEARCOUNTING is no longer used) by
using a polynomial that approximates the bias at a given cardinality.

The curve used was found using this web page:

    http://www.xuru.org/rt/PR.asp

That performs polynomial regression given a set of values.
2014-04-16 15:23:21 +02:00
antirez be7fe2b92b HLLMERGE implemented.
Merge N HLL data structures by selecting the max value for every
M[i] register among the set of HLLs.
2014-04-16 15:23:21 +02:00
antirez 39dd1f648d HLLCOUNT is technically a write command
When we update the cached value, we need to propagate the command and
signal the key as modified for WATCH.
2014-04-16 15:23:21 +02:00
antirez f07b1514bb HLLADD: propagate write when only variable name is given.
The following form is given:

    HLLADD myhll

No element is provided in the above case so if 'myhll' var does not
exist the result is to just create an empty HLL structure, and no update
will be performed on the registers.

In this case, the DB should still be set dirty and the command
propagated.
2014-04-16 15:23:21 +02:00
antirez 32e9996846 hll-gnuplot-graph.rb: Use |error| when filter is :max 2014-04-16 15:23:21 +02:00
antirez 9faa80ee8c Ignore txt files inside utils/hyperloglog.
Those are generated to trace graphs using gnuplot.
2014-04-16 15:23:21 +02:00
antirez aad5959ddc HyperLogLog: use LINEARCOUNTING up to 3m.
The HyperLogLog original paper suggests using LINEARCOUNTING for
cardinalities < 2.5m, however for P=14 the median / max error
curves show that a value of '3' is the best pick for m = 16384.
2014-04-16 15:23:21 +02:00
antirez d6ce659ffd hll-gnuplot-graph.rb added to plot HyperLogLog error graphs. 2014-04-16 15:23:21 +02:00
antirez c3df5965f3 HyperLogLog approximated cardinality caching.
The more we add elements to an HyperLogLog counter, the smaller is
the probability that we actually update some register.

From this observation it is easy to see how it is possible to use
caching of a previously computed cardinality and reuse it to serve
HLLCOUNT queries as long as no register was updated in the data
structure.

This commit does exactly this by using just additional 8 bytes for the
data structure to store a 64 bit unsigned integer value cached
cardinality. When the most significant bit of the 64 bit integer is set,
it means that the value computed is no longer usable since at least a
single register was modified and we need to recompute it at the next
call of HLLCOUNT.

The value is always stored in little endian format regardless of the
actual CPU endianess.
2014-04-16 15:23:21 +02:00
antirez 9e178afae9 String value unsharing refactored into proper function.
All the Redis functions that need to modify the string value of a key in
a destructive way (APPEND, SETBIT, SETRANGE, ...) require to make the
object unshared (if refcount > 1) and encoded in raw format (if encoding
is not already REDIS_ENCODING_RAW).

This was cut & pasted many times in multiple places of the code. This
commit puts the small logic needed into a function called
dbUnshareStringValue().
2014-04-16 15:22:56 +02:00
antirez 1c795db9d0 Use endian neutral hash function for HyperLogLog.
We need to be sure that you can save a dataset in a Redis instance,
reload it in a different architecture, and continue to count in the same
HyperLogLog structure.

So 32 and 64 bit, little or bit endian, must all guarantee to output the
same hash for the same element.
2014-04-16 15:19:40 +02:00
antirez 7f30998432 HyperLogLog internal representation modified.
The new representation is more obvious, starting from the LSB of the
first byte and using bits going to MSB, and passing to next byte as
needed.

There was also a subtle error: first two bits were unused, everything
was carried over on the right of two bits, even if it worked because of
the code requirement of always having a byte more at the end.

During the rewrite the code was made safer trying to avoid undefined
behavior due to shifting an uint8_t for more than 8 bits.
2014-04-16 15:19:40 +02:00
antirez 430d0ade75 Remove a few useless operations from hllCount() fast path. 2014-04-16 15:19:40 +02:00
antirez 83650a72a1 HLLCOUNT 3x faster taking fast path for default params. 2014-04-16 15:19:40 +02:00
antirez 3afd25ecc7 Use processor base types in HLL_(GET|SET)_REGISTER.
This speedups the macros by a noticeable factor.
2014-04-16 15:19:40 +02:00
antirez 0ffd5e4a30 HyperLogLog: use precomputed table for 2^(-M[i]). 2014-04-16 15:19:40 +02:00
antirez ca6973d3b6 hll-err.rb: speedup using pipelining. 2014-04-16 15:19:40 +02:00
antirez 2500f2a334 hll-err.rb added to test error rate of Redis HyperLogLog. 2014-04-16 15:19:40 +02:00
antirez 42592a7faa HyperLogLog algorithm fixed in two ways.
There was an error in the computation of 2^register, and the sequence of
zeroes computed after the hashing did not included the "1".
2014-04-16 15:19:40 +02:00
antirez 3035075b2c HLLCOUNT implemented. 2014-04-16 15:19:40 +02:00
antirez 959d0f012a HLLADD implemented. 2014-04-16 15:19:33 +02:00
antirez ac29bb2ae4 hllAdd() low level HyperLogLog "add" implemented. 2014-04-16 15:19:05 +02:00
antirez 2a91f548c4 HyperLogLog: redefine constants using "P". 2014-04-16 15:19:05 +02:00
antirez 3f159bbd44 HLL_SET_REGISTER fixed.
There was an error in the first version of the macro.
Now the HLLSELFTEST test reports success.
2014-04-16 15:19:05 +02:00
antirez a18e880bf1 Use REDIS_HLL_REGISTER_MAX when possible. 2014-04-16 15:19:05 +02:00
antirez 00409d8742 HLL_(SET|GET)_REGISTER types fixed. 2014-04-16 15:19:05 +02:00
antirez de3f821af6 HLLSELFTEST command implemented.
To test the bitfield array of counters set/get macros from the Redis Tcl
suite is hard, so a specialized command that is able to test the
internals was developed.
2014-04-16 15:18:57 +02:00
antirez 02d88fb201 HyperLogLog: initial sketch of registers access. 2014-04-16 15:17:26 +02:00
antirez ddbdd3b0df Document TTL changes in the 2.6 -> 2.8 migration doc. 2014-03-27 15:53:14 +01:00
20 changed files with 2823 additions and 129 deletions
+13
View File
@@ -14,6 +14,16 @@ HIGH: There is a critical bug that may affect a subset of users. Upgrade!
CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
--------------------------------------------------------------------------------
--[ Redis 2.8.9 ] Release date: 22 Apr 2014
# UPGRADE URGENCY: LOW, only new features introduced, no bugs fixed.
* [NEW] The HyperLogLog data structure. You can read more about it
in this blog post. http://antirez.com/news/75
* [NEW] The Sorted Set data type has now support for lexicographic range
queries, check the new commands ZRANGEBYLEX, ZLEXCOUNT and
ZREMRANGEBYLEX, which are documented at http://redis.io.
--[ Redis 2.8.8 ] Release date: 25 Mar 2014
# UPGRADE URGENCY: HIGH for Redis, LOW for Sentinel. There is a potentially
@@ -308,6 +318,9 @@ that you should be aware of:
The following commands changed behavior:
* The TTL and PTTL commands now return -2 if the key does not exist and
-1 if it exists but has no associated expire. Redis 2.6 and previous
versions used to return -1 for both the conditions.
* SORT with ALPHA now sorts according to local collation locale if no STORE
option is used.
* ZADD/ZINCRBY are now able to accept a bigger range of values as valid
+14
View File
@@ -635,6 +635,20 @@ set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
# HyperLogLog sparse representation bytes limit. The limit includes the
# 16 bytes header. When an HyperLogLog using the sparse representation crosses
# this limit, it is convereted into the dense representation.
#
# A value greater than 16000 is totally useless, since at that point the
# dense representation is more memory efficient.
#
# The suggested value is ~ 3000 in order to have the benefits of
# the space efficient encoding without slowing down too much PFADD,
# which is O(N) with the sparse encoding. Thev value can be raised to
# ~ 10000 when CPU is not a concern, but space is, and the data set is
# composed of many HyperLogLogs with cardinality in the 0 - 15000 range.
hll-sparse-max-bytes 3000
# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
# order to help rehashing the main Redis hash table (the one mapping top-level
# keys to values). The hash table implementation Redis uses (see dict.c)
+1 -1
View File
@@ -107,7 +107,7 @@ endif
REDIS_SERVER_NAME=redis-server
REDIS_SENTINEL_NAME=redis-sentinel
REDIS_SERVER_OBJ=adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o migrate.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o
REDIS_SERVER_OBJ=adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o migrate.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o hyperloglog.o
REDIS_CLI_NAME=redis-cli
REDIS_CLI_OBJ=anet.o sds.o adlist.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o
REDIS_BENCHMARK_NAME=redis-benchmark
+4
View File
@@ -26,6 +26,10 @@ debug.o: debug.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
ziplist.h intset.h version.h util.h rdb.h rio.h sha1.h crc64.h bio.h
dict.o: dict.c fmacros.h dict.h zmalloc.h redisassert.h
endianconv.o: endianconv.c
hyperloglog.o: hyperloglog.c redis.h fmacros.h config.h \
../deps/lua/src/lua.h ../deps/lua/src/luaconf.h ae.h sds.h dict.h \
adlist.h zmalloc.h anet.h ziplist.h intset.h version.h util.h rdb.h \
rio.h
intset.o: intset.c intset.h zmalloc.h endianconv.h config.h
lzf_c.o: lzf_c.c lzfP.h
lzf_d.o: lzf_d.c lzfP.h
+1 -8
View File
@@ -223,14 +223,7 @@ void setbitCommand(redisClient *c) {
dbAdd(c->db,c->argv[1],o);
} else {
if (checkType(c,o,REDIS_STRING)) return;
/* Create a copy when the object is shared or encoded. */
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
robj *decoded = getDecodedObject(o);
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
decrRefCount(decoded);
dbOverwrite(c->db,c->argv[1],o);
}
o = dbUnshareStringValue(c->db,c->argv[1],o);
}
/* Grow sds value to the right length if necessary */
+8
View File
@@ -388,6 +388,8 @@ void loadServerConfigFromString(char *config) {
server.zset_max_ziplist_entries = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"zset-max-ziplist-value") && argc == 2) {
server.zset_max_ziplist_value = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"hll-sparse-max-bytes") && argc == 2) {
server.hll_sparse_max_bytes = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"rename-command") && argc == 3) {
struct redisCommand *cmd = lookupCommand(argv[1]);
int retval;
@@ -733,6 +735,9 @@ void configSetCommand(redisClient *c) {
} else if (!strcasecmp(c->argv[2]->ptr,"zset-max-ziplist-value")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.zset_max_ziplist_value = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"hll-sparse-max-bytes")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.hll_sparse_max_bytes = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"lua-time-limit")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.lua_time_limit = ll;
@@ -934,6 +939,8 @@ void configGetCommand(redisClient *c) {
server.zset_max_ziplist_entries);
config_get_numerical_field("zset-max-ziplist-value",
server.zset_max_ziplist_value);
config_get_numerical_field("hll-sparse-max-bytes",
server.hll_sparse_max_bytes);
config_get_numerical_field("lua-time-limit",server.lua_time_limit);
config_get_numerical_field("slowlog-log-slower-than",
server.slowlog_log_slower_than);
@@ -1726,6 +1733,7 @@ int rewriteConfig(char *path) {
rewriteConfigNumericalOption(state,"set-max-intset-entries",server.set_max_intset_entries,REDIS_SET_MAX_INTSET_ENTRIES);
rewriteConfigNumericalOption(state,"zset-max-ziplist-entries",server.zset_max_ziplist_entries,REDIS_ZSET_MAX_ZIPLIST_ENTRIES);
rewriteConfigNumericalOption(state,"zset-max-ziplist-value",server.zset_max_ziplist_value,REDIS_ZSET_MAX_ZIPLIST_VALUE);
rewriteConfigNumericalOption(state,"hll-sparse-max-bytes",server.hll_sparse_max_bytes,REDIS_DEFAULT_HLL_SPARSE_MAX_BYTES);
rewriteConfigYesNoOption(state,"activerehashing",server.activerehashing,REDIS_DEFAULT_ACTIVE_REHASHING);
rewriteConfigClientoutputbufferlimitOption(state);
rewriteConfigNumericalOption(state,"hz",server.hz,REDIS_DEFAULT_HZ);
+38
View File
@@ -166,6 +166,44 @@ int dbDelete(redisDb *db, robj *key) {
}
}
/* Prepare the string object stored at 'key' to be modified destructively
* to implement commands like SETBIT or APPEND.
*
* An object is usually ready to be modified unless one of the two conditions
* are true:
*
* 1) The object 'o' is shared (refcount > 1), we don't want to affect
* other users.
* 2) The object encoding is not "RAW".
*
* If the object is found in one of the above conditions (or both) by the
* function, an unshared / not-encoded copy of the string object is stored
* at 'key' in the specified 'db'. Otherwise the object 'o' itself is
* returned.
*
* USAGE:
*
* The object 'o' is what the caller already obtained by looking up 'key'
* in 'db', the usage pattern looks like this:
*
* o = lookupKeyWrite(db,key);
* if (checkType(c,o,REDIS_STRING)) return;
* o = dbUnshareStringValue(db,key,o);
*
* At this point the caller is ready to modify the object, for example
* using an sdscat() call to append some data, or anything else.
*/
robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
redisAssert(o->type == REDIS_STRING);
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
robj *decoded = getDecodedObject(o);
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
decrRefCount(decoded);
dbOverwrite(db,key,o);
}
return o;
}
long long emptyDb(void(callback)(void*)) {
int j;
long long removed = 0;
+1548
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -587,7 +587,7 @@ unsigned long estimateObjectIdleTime(robj *o) {
}
}
/* This is a helper function for the DEBUG command. We need to lookup keys
/* This is a helper function for the OBJECT command. We need to lookup keys
* without any modification of LRU or other parameters. */
robj *objectCommandLookup(redisClient *c, robj *key) {
dictEntry *de;
+26 -6
View File
@@ -168,12 +168,16 @@ struct redisCommand redisCommandTable[] = {
{"zrem",zremCommand,-3,"w",0,NULL,1,1,1,0,0},
{"zremrangebyscore",zremrangebyscoreCommand,4,"w",0,NULL,1,1,1,0,0},
{"zremrangebyrank",zremrangebyrankCommand,4,"w",0,NULL,1,1,1,0,0},
{"zremrangebylex",zremrangebylexCommand,4,"w",0,NULL,1,1,1,0,0},
{"zunionstore",zunionstoreCommand,-4,"wm",0,zunionInterGetKeys,0,0,0,0,0},
{"zinterstore",zinterstoreCommand,-4,"wm",0,zunionInterGetKeys,0,0,0,0,0},
{"zrange",zrangeCommand,-4,"r",0,NULL,1,1,1,0,0},
{"zrangebyscore",zrangebyscoreCommand,-4,"r",0,NULL,1,1,1,0,0},
{"zrevrangebyscore",zrevrangebyscoreCommand,-4,"r",0,NULL,1,1,1,0,0},
{"zrangebylex",zrangebylexCommand,-4,"r",0,NULL,1,1,1,0,0},
{"zrevrangebylex",zrevrangebylexCommand,-4,"r",0,NULL,1,1,1,0,0},
{"zcount",zcountCommand,4,"r",0,NULL,1,1,1,0,0},
{"zlexcount",zlexcountCommand,4,"r",0,NULL,1,1,1,0,0},
{"zrevrange",zrevrangeCommand,-4,"r",0,NULL,1,1,1,0,0},
{"zcard",zcardCommand,2,"r",0,NULL,1,1,1,0,0},
{"zscore",zscoreCommand,3,"r",0,NULL,1,1,1,0,0},
@@ -258,7 +262,12 @@ struct redisCommand redisCommandTable[] = {
{"time",timeCommand,1,"rR",0,NULL,0,0,0,0,0},
{"bitop",bitopCommand,-4,"wm",0,NULL,2,-1,1,0,0},
{"bitcount",bitcountCommand,-2,"r",0,NULL,1,1,1,0,0},
{"bitpos",bitposCommand,-3,"r",0,NULL,1,1,1,0,0}
{"bitpos",bitposCommand,-3,"r",0,NULL,1,1,1,0,0},
{"pfselftest",pfselftestCommand,1,"r",0,NULL,0,0,0,0,0},
{"pfadd",pfaddCommand,-2,"wm",0,NULL,1,1,1,0,0},
{"pfcount",pfcountCommand,-2,"w",0,NULL,1,1,1,0,0},
{"pfmerge",pfmergeCommand,-2,"wm",0,NULL,1,-1,1,0,0},
{"pfdebug",pfdebugCommand,-3,"w",0,NULL,0,0,0,0,0}
};
/*============================ Utility functions ============================ */
@@ -1273,6 +1282,12 @@ void createSharedObjects(void) {
shared.bulkhdr[j] = createObject(REDIS_STRING,
sdscatprintf(sdsempty(),"$%d\r\n",j));
}
/* The following two shared objects, minstring and maxstrings, are not
* actually used for their value but as a special object meaning
* respectively the minimum possible string and the maximum possible
* string in string comparisons for the ZRANGEBYLEX command. */
shared.minstring = createStringObject("minstring",9);
shared.maxstring = createStringObject("maxstring",9);
}
void initServerConfig() {
@@ -1340,6 +1355,7 @@ void initServerConfig() {
server.set_max_intset_entries = REDIS_SET_MAX_INTSET_ENTRIES;
server.zset_max_ziplist_entries = REDIS_ZSET_MAX_ZIPLIST_ENTRIES;
server.zset_max_ziplist_value = REDIS_ZSET_MAX_ZIPLIST_VALUE;
server.hll_sparse_max_bytes = REDIS_DEFAULT_HLL_SPARSE_MAX_BYTES;
server.shutdown_asap = 0;
server.repl_ping_slave_period = REDIS_REPL_PING_SLAVE_PERIOD;
server.repl_timeout = REDIS_REPL_TIMEOUT;
@@ -1469,24 +1485,28 @@ void adjustOpenFilesLimit(void) {
redisLog(REDIS_WARNING,"Your current 'ulimit -n' "
"of %llu is not enough for Redis to start. "
"Please increase your open file limit to at least "
"%llu. Exiting.", oldlimit, maxfiles);
"%llu. Exiting.",
(unsigned long long) oldlimit,
(unsigned long long) maxfiles);
exit(1);
}
redisLog(REDIS_WARNING,"You requested maxclients of %d "
"requiring at least %llu max file descriptors.",
old_maxclients, maxfiles);
old_maxclients,
(unsigned long long) maxfiles);
redisLog(REDIS_WARNING,"Redis can't set maximum open files "
"to %llu because of OS error: %s.",
maxfiles, strerror(setrlimit_error));
(unsigned long long) maxfiles, strerror(setrlimit_error));
redisLog(REDIS_WARNING,"Current maximum open files is %llu. "
"maxclients has been reduced to %d to compensate for "
"low ulimit. "
"If you need higher maxclients increase 'ulimit -n'.",
oldlimit, server.maxclients);
(unsigned long long) oldlimit, server.maxclients);
} else {
redisLog(REDIS_NOTICE,"Increased maximum number of open files "
"to %llu (it was originally set to %llu).",
maxfiles, oldlimit);
(unsigned long long) maxfiles,
(unsigned long long) oldlimit);
}
}
}
+23 -3
View File
@@ -303,6 +303,9 @@
#define REDIS_ZSET_MAX_ZIPLIST_ENTRIES 128
#define REDIS_ZSET_MAX_ZIPLIST_VALUE 64
/* HyperLogLog defines */
#define REDIS_DEFAULT_HLL_SPARSE_MAX_BYTES 3000
/* Sets operations codes */
#define REDIS_OP_UNION 0
#define REDIS_OP_DIFF 1
@@ -502,7 +505,7 @@ struct sharedObjectsStruct {
*masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr,
*oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk,
*unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *rpop, *lpop,
*lpush, *emptyscan,
*lpush, *emptyscan, *minstring, *maxstring,
*select[REDIS_SHARED_SELECT_CMDS],
*integers[REDIS_SHARED_INTEGERS],
*mbulkhdr[REDIS_SHARED_BULKHDR_LEN], /* "*<value>\r\n" */
@@ -755,6 +758,7 @@ struct redisServer {
size_t set_max_intset_entries;
size_t zset_max_ziplist_entries;
size_t zset_max_ziplist_value;
size_t hll_sparse_max_bytes;
time_t unixtime; /* Unix time sampled every cron cycle. */
long long mstime; /* Like 'unixtime' but with milliseconds resolution. */
/* Pubsub */
@@ -1057,18 +1061,24 @@ unsigned long aofRewriteBufferSize(void);
/* Sorted sets data type */
/* Struct to hold a inclusive/exclusive range spec. */
/* Struct to hold a inclusive/exclusive range spec by score comparison. */
typedef struct {
double min, max;
int minex, maxex; /* are min or max exclusive? */
} zrangespec;
/* Struct to hold an inclusive/exclusive range spec by lexicographic comparison. */
typedef struct {
robj *min, *max; /* May be set to shared.(minstring|maxstring) */
int minex, maxex; /* are min or max exclusive? */
} zlexrangespec;
zskiplist *zslCreate(void);
void zslFree(zskiplist *zsl);
zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj);
unsigned char *zzlInsert(unsigned char *zl, robj *ele, double score);
int zslDelete(zskiplist *zsl, double score, robj *obj);
zskiplistNode *zslFirstInRange(zskiplist *zsl, zrangespec range);
zskiplistNode *zslFirstInRange(zskiplist *zsl, zrangespec *range);
double zzlGetScore(unsigned char *sptr);
void zzlNext(unsigned char *zl, unsigned char **eptr, unsigned char **sptr);
void zzlPrev(unsigned char *zl, unsigned char **eptr, unsigned char **sptr);
@@ -1176,6 +1186,7 @@ void setKey(redisDb *db, robj *key, robj *val);
int dbExists(redisDb *db, robj *key);
robj *dbRandomKey(redisDb *db);
int dbDelete(redisDb *db, robj *key);
robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o);
long long emptyDb(void(callback)(void*));
int selectDb(redisClient *c, int id);
void signalModifiedKey(redisDb *db, robj *key);
@@ -1294,12 +1305,16 @@ void zincrbyCommand(redisClient *c);
void zrangeCommand(redisClient *c);
void zrangebyscoreCommand(redisClient *c);
void zrevrangebyscoreCommand(redisClient *c);
void zrangebylexCommand(redisClient *c);
void zrevrangebylexCommand(redisClient *c);
void zcountCommand(redisClient *c);
void zlexcountCommand(redisClient *c);
void zrevrangeCommand(redisClient *c);
void zcardCommand(redisClient *c);
void zremCommand(redisClient *c);
void zscoreCommand(redisClient *c);
void zremrangebyscoreCommand(redisClient *c);
void zremrangebylexCommand(redisClient *c);
void multiCommand(redisClient *c);
void execCommand(redisClient *c);
void discardCommand(redisClient *c);
@@ -1350,6 +1365,11 @@ void bitopCommand(redisClient *c);
void bitcountCommand(redisClient *c);
void bitposCommand(redisClient *c);
void replconfCommand(redisClient *c);
void pfselftestCommand(redisClient *c);
void pfaddCommand(redisClient *c);
void pfcountCommand(redisClient *c);
void pfmergeCommand(redisClient *c);
void pfdebugCommand(redisClient *c);
#if defined(__GNUC__)
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));
+2 -14
View File
@@ -215,12 +215,7 @@ void setrangeCommand(redisClient *c) {
return;
/* Create a copy when the object is shared or encoded. */
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
robj *decoded = getDecodedObject(o);
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
decrRefCount(decoded);
dbOverwrite(c->db,c->argv[1],o);
}
o = dbUnshareStringValue(c->db,c->argv[1],o);
}
if (sdslen(value) > 0) {
@@ -433,15 +428,8 @@ void appendCommand(redisClient *c) {
if (checkStringLength(c,totlen) != REDIS_OK)
return;
/* If the object is shared or encoded, we have to make a copy */
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
robj *decoded = getDecodedObject(o);
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
decrRefCount(decoded);
dbOverwrite(c->db,c->argv[1],o);
}
/* Append the value */
o = dbUnshareStringValue(c->db,c->argv[1],o);
o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
totlen = sdslen(o->ptr);
}
+706 -95
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1 +1 @@
#define REDIS_VERSION "2.8.8"
#define REDIS_VERSION "2.8.9"
+1
View File
@@ -47,6 +47,7 @@ set ::all_tests {
unit/dump
unit/bitops
unit/memefficiency
unit/hyperloglog
}
# Index to the next test to run in the ::all_tests list.
set ::next_test 0
+159
View File
@@ -0,0 +1,159 @@
start_server {tags {"hll"}} {
test {HyperLogLog self test passes} {
catch {r pfselftest} e
set e
} {OK}
test {PFADD without arguments creates an HLL value} {
r pfadd hll
r exists hll
} {1}
test {Approximated cardinality after creation is zero} {
r pfcount hll
} {0}
test {PFADD returns 1 when at least 1 reg was modified} {
r pfadd hll a b c
} {1}
test {PFADD returns 0 when no reg was modified} {
r pfadd hll a b c
} {0}
test {PFADD works with empty string (regression)} {
r pfadd hll ""
}
# Note that the self test stresses much better the
# cardinality estimation error. We are testing just the
# command implementation itself here.
test {PFCOUNT returns approximated cardinality of set} {
r del hll
set res {}
r pfadd hll 1 2 3 4 5
lappend res [r pfcount hll]
# Call it again to test cached value invalidation.
r pfadd hll 6 7 8 8 9 10
lappend res [r pfcount hll]
set res
} {5 10}
test {HyperLogLogs are promote from sparse to dense} {
r del hll
r config set hll-sparse-max-bytes 3000
set n 0
while {$n < 100000} {
set elements {}
for {set j 0} {$j < 100} {incr j} {lappend elements [expr rand()]}
incr n 100
r pfadd hll {*}$elements
set card [r pfcount hll]
set err [expr {abs($card-$n)}]
assert {$err < (double($card)/100)*5}
if {$n < 1000} {
assert {[r pfdebug encoding hll] eq {sparse}}
} elseif {$n > 10000} {
assert {[r pfdebug encoding hll] eq {dense}}
}
}
}
test {HyperLogLog sparse encoding stress test} {
for {set x 0} {$x < 1000} {incr x} {
r del hll1 hll2
set numele [randomInt 100]
set elements {}
for {set j 0} {$j < $numele} {incr j} {
lappend elements [expr rand()]
}
# Force dense representation of hll2
r pfadd hll2
r pfdebug todense hll2
r pfadd hll1 {*}$elements
r pfadd hll2 {*}$elements
assert {[r pfdebug encoding hll1] eq {sparse}}
assert {[r pfdebug encoding hll2] eq {dense}}
# Cardinality estimated should match exactly.
assert {[r pfcount hll1] eq [r pfcount hll2]}
}
}
test {Corrupted sparse HyperLogLogs are detected: Additionl at tail} {
r del hll
r pfadd hll a b c
r append hll "hello"
set e {}
catch {r pfcount hll} e
set e
} {*INVALIDOBJ*}
test {Corrupted sparse HyperLogLogs are detected: Broken magic} {
r del hll
r pfadd hll a b c
r setrange hll 0 "0123"
set e {}
catch {r pfcount hll} e
set e
} {*WRONGTYPE*}
test {Corrupted sparse HyperLogLogs are detected: Invalid encoding} {
r del hll
r pfadd hll a b c
r setrange hll 4 "x"
set e {}
catch {r pfcount hll} e
set e
} {*WRONGTYPE*}
test {Corrupted dense HyperLogLogs are detected: Wrong length} {
r del hll
r pfadd hll a b c
r setrange hll 4 "\x00"
set e {}
catch {r pfcount hll} e
set e
} {*WRONGTYPE*}
test {PFADD, PFCOUNT, PFMERGE type checking works} {
r set foo bar
catch {r pfadd foo 1} e
assert_match {*WRONGTYPE*} $e
catch {r pfcount foo} e
assert_match {*WRONGTYPE*} $e
catch {r pfmerge bar foo} e
assert_match {*WRONGTYPE*} $e
catch {r pfmerge foo bar} e
assert_match {*WRONGTYPE*} $e
}
test {PFMERGE results on the cardinality of union of sets} {
r del hll hll1 hll2 hll3
r pfadd hll1 a b c
r pfadd hll2 b c d
r pfadd hll3 c d e
r pfmerge hll hll1 hll2 hll3
r pfcount hll
} {5}
test {PFCOUNT multiple-keys merge returns cardinality of union} {
r del hll1 hll2 hll3
for {set x 1} {$x < 10000} {incr x} {
# Force dense representation of hll2
r pfadd hll1 "foo-$x"
r pfadd hll2 "bar-$x"
r pfadd hll3 "zap-$x"
set card [r pfcount hll1 hll2 hll3]
set realcard [expr {$x*3}]
set err [expr {abs($card-$realcard)}]
assert {$err < (double($card)/100)*5}
}
}
test {PFDEBUG GETREG returns the HyperLogLog raw registers} {
r del hll
r pfadd hll 1 2 3
llength [r pfdebug getreg hll]
} {16384}
}
+161
View File
@@ -296,6 +296,62 @@ start_server {tags {"zset"}} {
assert_error "*not*float*" {r zrangebyscore fooz 1 NaN}
}
proc create_default_lex_zset {} {
create_zset zset {0 alpha 0 bar 0 cool 0 down
0 elephant 0 foo 0 great 0 hill
0 omega}
}
test "ZRANGEBYLEX/ZREVRANGEBYLEX/ZCOUNT basics" {
create_default_lex_zset
# inclusive range
assert_equal {alpha bar cool} [r zrangebylex zset - \[cool]
assert_equal {bar cool down} [r zrangebylex zset \[bar \[down]
assert_equal {great hill omega} [r zrangebylex zset \[g +]
assert_equal {cool bar alpha} [r zrevrangebylex zset \[cool -]
assert_equal {down cool bar} [r zrevrangebylex zset \[down \[bar]
assert_equal {omega hill great foo elephant down} [r zrevrangebylex zset + \[d]
assert_equal 3 [r zlexcount zset \[ele \[h]
# exclusive range
assert_equal {alpha bar} [r zrangebylex zset - (cool]
assert_equal {cool} [r zrangebylex zset (bar (down]
assert_equal {hill omega} [r zrangebylex zset (great +]
assert_equal {bar alpha} [r zrevrangebylex zset (cool -]
assert_equal {cool} [r zrevrangebylex zset (down (bar]
assert_equal {omega hill} [r zrevrangebylex zset + (great]
assert_equal 2 [r zlexcount zset (ele (great]
# inclusive and exclusive
assert_equal {} [r zrangebylex zset (az (b]
assert_equal {} [r zrangebylex zset (z +]
assert_equal {} [r zrangebylex zset - \[aaaa]
assert_equal {} [r zrevrangebylex zset \[elez \[elex]
assert_equal {} [r zrevrangebylex zset (hill (omega]
}
test "ZRANGEBYSLEX with LIMIT" {
create_default_lex_zset
assert_equal {alpha bar} [r zrangebylex zset - \[cool LIMIT 0 2]
assert_equal {bar cool} [r zrangebylex zset - \[cool LIMIT 1 2]
assert_equal {} [r zrangebylex zset \[bar \[down LIMIT 0 0]
assert_equal {} [r zrangebylex zset \[bar \[down LIMIT 2 0]
assert_equal {bar} [r zrangebylex zset \[bar \[down LIMIT 0 1]
assert_equal {cool} [r zrangebylex zset \[bar \[down LIMIT 1 1]
assert_equal {bar cool down} [r zrangebylex zset \[bar \[down LIMIT 0 100]
assert_equal {omega hill great foo elephant} [r zrevrangebylex zset + \[d LIMIT 0 5]
assert_equal {omega hill great foo} [r zrevrangebylex zset + \[d LIMIT 0 4]
}
test "ZRANGEBYLEX with invalid lex range specifiers" {
assert_error "*not*string*" {r zrangebylex fooz foo bar}
assert_error "*not*string*" {r zrangebylex fooz \[foo bar}
assert_error "*not*string*" {r zrangebylex fooz foo \[bar}
assert_error "*not*string*" {r zrangebylex fooz +x \[bar}
assert_error "*not*string*" {r zrangebylex fooz -x \[bar}
}
test "ZREMRANGEBYSCORE basics" {
proc remrangebyscore {min max} {
create_zset zset {1 a 2 b 3 c 4 d 5 e}
@@ -708,6 +764,111 @@ start_server {tags {"zset"}} {
assert_equal {} $err
}
test "ZRANGEBYLEX fuzzy test, 100 ranges in $elements element sorted set - $encoding" {
set lexset {}
r del zset
for {set j 0} {$j < $elements} {incr j} {
set e [randstring 0 30 alpha]
lappend lexset $e
r zadd zset 0 $e
}
set lexset [lsort -unique $lexset]
for {set j 0} {$j < 100} {incr j} {
set min [randstring 0 30 alpha]
set max [randstring 0 30 alpha]
set mininc [randomInt 2]
set maxinc [randomInt 2]
if {$mininc} {set cmin "\[$min"} else {set cmin "($min"}
if {$maxinc} {set cmax "\[$max"} else {set cmax "($max"}
set rev [randomInt 2]
if {$rev} {
set cmd zrevrangebylex
} else {
set cmd zrangebylex
}
# Make sure data is the same in both sides
assert {[r zrange zset 0 -1] eq $lexset}
# Get the Redis output
set output [r $cmd zset $cmin $cmax]
if {$rev} {
set outlen [r zlexcount zset $cmax $cmin]
} else {
set outlen [r zlexcount zset $cmin $cmax]
}
# Compute the same output via Tcl
set o {}
set copy $lexset
if {(!$rev && [string compare $min $max] > 0) ||
($rev && [string compare $max $min] > 0)} {
# Empty output when ranges are inverted.
} else {
if {$rev} {
# Invert the Tcl array using Redis itself.
set copy [r zrevrange zset 0 -1]
# Invert min / max as well
lassign [list $min $max $mininc $maxinc] \
max min maxinc mininc
}
foreach e $copy {
set mincmp [string compare $e $min]
set maxcmp [string compare $e $max]
if {
($mininc && $mincmp >= 0 || !$mininc && $mincmp > 0)
&&
($maxinc && $maxcmp <= 0 || !$maxinc && $maxcmp < 0)
} {
lappend o $e
}
}
}
assert {$o eq $output}
assert {$outlen eq [llength $output]}
}
}
test "ZREMRANGEBYLEX fuzzy test, 100 ranges in $elements element sorted set - $encoding" {
set lexset {}
r del zset zsetcopy
for {set j 0} {$j < $elements} {incr j} {
set e [randstring 0 30 alpha]
lappend lexset $e
r zadd zset 0 $e
}
set lexset [lsort -unique $lexset]
for {set j 0} {$j < 100} {incr j} {
# Copy...
r zunionstore zsetcopy 1 zset
set lexsetcopy $lexset
set min [randstring 0 30 alpha]
set max [randstring 0 30 alpha]
set mininc [randomInt 2]
set maxinc [randomInt 2]
if {$mininc} {set cmin "\[$min"} else {set cmin "($min"}
if {$maxinc} {set cmax "\[$max"} else {set cmax "($max"}
# Make sure data is the same in both sides
assert {[r zrange zset 0 -1] eq $lexset}
# Get the range we are going to remove
set torem [r zrangebylex zset $cmin $cmax]
set toremlen [r zlexcount zset $cmin $cmax]
r zremrangebylex zsetcopy $cmin $cmax
set output [r zrange zsetcopy 0 -1]
# Remove the range with Tcl from the original list
if {$toremlen} {
set first [lsearch -exact $lexsetcopy [lindex $torem 0]]
set last [expr {$first+$toremlen-1}]
set lexsetcopy [lreplace $lexsetcopy $first $last]
}
assert {$lexsetcopy eq $output}
}
}
test "ZSETs skiplist implementation backlink consistency test - $encoding" {
set diff 0
for {set j 0} {$j < $elements} {incr j} {
+1
View File
@@ -0,0 +1 @@
*.txt
+27
View File
@@ -0,0 +1,27 @@
# hll-err.rb - Copyright (C) 2014 Salvatore Sanfilippo
# BSD license, See the COPYING file for more information.
#
# Check error of HyperLogLog Redis implementation for different set sizes.
require 'rubygems'
require 'redis'
require 'digest/sha1'
r = Redis.new
r.del('hll')
i = 0
while true do
100.times {
elements = []
1000.times {
ele = Digest::SHA1.hexdigest(i.to_s)
elements << ele
i += 1
}
r.pfadd('hll',*elements)
}
approx = r.pfcount('hll')
abs_err = (approx-i).abs
rel_err = 100.to_f*abs_err/i
puts "#{i} vs #{approx}: #{rel_err}%"
end
+88
View File
@@ -0,0 +1,88 @@
# hll-err.rb - Copyright (C) 2014 Salvatore Sanfilippo
# BSD license, See the COPYING file for more information.
#
# This program is suited to output average and maximum errors of
# the Redis HyperLogLog implementation in a format suitable to print
# graphs using gnuplot.
require 'rubygems'
require 'redis'
require 'digest/sha1'
# Generate an array of [cardinality,relative_error] pairs
# in the 0 - max range, with the specified step.
#
# 'r' is the Redis object used to perform the queries.
# 'seed' must be different every time you want a test performed
# with a different set. The function guarantees that if 'seed' is the
# same, exactly the same dataset is used, and when it is different,
# a totally unrelated different data set is used (without any common
# element in practice).
def run_experiment(r,seed,max,step)
r.del('hll')
i = 0
samples = []
step = 1000 if step > 1000
while i < max do
elements = []
step.times {
ele = Digest::SHA1.hexdigest(i.to_s+seed.to_s)
elements << ele
i += 1
}
r.pfadd('hll',*elements)
approx = r.pfcount('hll')
err = approx-i
rel_err = 100.to_f*err/i
samples << [i,rel_err]
end
samples
end
def filter_samples(numsets,max,step,filter)
r = Redis.new
dataset = {}
(0...numsets).each{|i|
dataset[i] = run_experiment(r,i,max,step)
STDERR.puts "Set #{i}"
}
dataset[0].each_with_index{|ele,index|
if filter == :max
card=ele[0]
err=ele[1].abs
(1...numsets).each{|i|
err = dataset[i][index][1] if err < dataset[i][index][1]
}
puts "#{card} #{err}"
elsif filter == :avg
card=ele[0]
err = 0
(0...numsets).each{|i|
err += dataset[i][index][1]
}
err /= numsets
puts "#{card} #{err}"
elsif filter == :absavg
card=ele[0]
err = 0
(0...numsets).each{|i|
err += dataset[i][index][1].abs
}
err /= numsets
puts "#{card} #{err}"
elsif filter == :all
(0...numsets).each{|i|
card,err = dataset[i][index]
puts "#{card} #{err}"
}
else
raise "Unknown filter #{filter}"
end
}
end
if ARGV.length != 4
puts "Usage: hll-gnuplot-graph <samples> <max> <step> (max|avg|absavg|all)"
exit 1
end
filter_samples(ARGV[0].to_i,ARGV[1].to_i,ARGV[2].to_i,ARGV[3].to_sym)