Compare commits

..
76 Commits
Author SHA1 Message Date
antirez 75fcab8c23 version bumped to 2.1.5 2010-10-21 17:55:44 +02:00
antirez 13a49af44b prevent small integer sharing when maxmemory is active. So every object will use a private LRU field and the LRU algorithm can work well 2010-10-15 18:04:05 +02:00
antirez 9f8ded8ced CONFIG RESETSTAT no longer resets the server uptime. Now keyspace hits/misses are reset as well. 2010-10-15 12:29:05 +02:00
antirez 95506e4611 minor aesthetic change 2010-10-15 12:22:48 +02:00
antirez 53eeeaff08 added keyspace_hits and keyspace_misses fields in INFO output 2010-10-15 12:19:21 +02:00
antirez a36879293d maxmemory-samples implemented in CONFIG command and configuration file 2010-10-15 11:57:38 +02:00
antirez 670bf2fd36 Don't increment dirty on expireIfNeeded() as natural expires are not considered database changes. This will avoid useless read-only commands in the AOF file as a result of, for instance, a GET operation triggering an expirIfNeeded() call resulting in an expired key removed. 2010-10-15 11:29:03 +02:00
antirez b33ef40105 Merge remote branch 'pietern/ziplist-regression' 2010-10-14 21:23:01 +02:00
antirez 165346ca29 implemented different algorithms for maxmemory 2010-10-14 21:22:21 +02:00
Pieter Noordhuis 306c6a02e3 Replace ziplist stresser and fix regression 2010-10-14 21:11:42 +02:00
antirez ef59a8bc9e Object approximated LRU algorithm enhanced / fixed / refactored. This is used for the VM currently but will soon be used for maxmemory expiring. 2010-10-14 13:52:58 +02:00
antirez b4f2e412d0 free memory if the maxmemory parameter is reduced via CONFIG SET 2010-10-11 16:46:21 +02:00
antirez 1dd10ca233 maxmemory fixed, we now try to release memory just before we check for the memory limit. Before fixing there was code between the attempt to free memory and the check for memory limits, and this code could result into allocations going again after the memory limit. 2010-10-11 13:05:09 +02:00
antirez 144a5e72f2 fixed an alignment problem with time_t is 32 bit, long is 64 bit, and arch is sparc or any other where unaligned accesses will result to sigbus 2010-10-07 16:21:35 +02:00
antirez fdc0bde935 minor typo fixed, reported by Thomas Bassetto 2010-10-07 12:49:14 +02:00
antirez 4610b0332c intset stress testing added, ziplist stress testing relocated in a more appropriate place 2010-09-24 11:15:06 +02:00
antirez ef27ba988b explicit regression test for a ziplist bug added 2010-09-24 10:37:00 +02:00
antirez 1a06bf93c4 ziplist implementation fuzzy tests 2010-09-24 10:30:15 +02:00
antirez e43505d6e9 Merge remote branch 'pietern/ziplist-fix' 2010-09-24 01:15:16 +02:00
Pieter Noordhuis b0d605c1d6 Add regression test and fix for >255 byte string entries 2010-09-23 22:04:19 +02:00
antirez 30d31cc8bb Contributing file added 2010-09-23 18:24:47 +02:00
antirez 963238f713 more tests for sds.c 2010-09-23 16:39:02 +02:00
antirez 136cf53f22 minimal C test framework + a first example sds.c tests 2010-09-23 16:05:17 +02:00
Pieter Noordhuis 56e52b69fe Update rdb.c to properly work with new memory strategy for sorted sets 2010-09-22 18:07:52 +02:00
antirez 50a9fad5d5 two leaks fixed 2010-09-22 17:49:04 +02:00
antirez beb7756dcb error generation format reverted to the new style after merge 2010-09-22 16:10:13 +02:00
antirez b882056c93 Merge remote branch 'pietern/zset-mem' 2010-09-22 16:09:33 +02:00
antirez 5ca2f0c498 preventive conflict resolution to merge pietern/zset-mem 2010-09-22 16:09:30 +02:00
antirez 5171777bf1 Merge remote branch 'pietern/ziplist-eff' 2010-09-22 12:59:25 +02:00
antirez a4f3f93b90 new parsing code bugfixing 2010-09-17 16:05:01 +02:00
antirez 34a719d250 try to parse the request in a smarter way to gain speed... work in progress 2010-09-17 15:26:07 +02:00
antirez 2b00385d51 Added used CPU statistics in INFO output, obtained via getrusage() 2010-09-16 13:28:58 +02:00
antirez 4c2e506a39 modified a bit addReply() to play better with copy on write now that we have a static buffer. Changed the name of a function from _ensureFileEvent() to _installWriteEvent(). 2010-09-16 13:08:40 +02:00
antirez 83f39c7ab2 Merge remote branch 'pietern/networking-perf' 2010-09-16 12:02:18 +02:00
Pieter Noordhuis f335779240 Static buffer in client struct has a constant size 2010-09-16 11:59:53 +02:00
antirez 89f9f83769 Merge remote branch 'pietern/networking-perf' 2010-09-16 11:38:40 +02:00
antirez 3856f14759 This should fix Issue 332: when there is a background process saving we still allow the hash tables to grow, but only when a critical treshold is reached. Formerly we prevented the resize at all triggering pathological O(N) behavior. Also there is a fix for the statistics in INFO about the number of keys expired 2010-09-15 14:09:41 +02:00
antirez 412e457c27 fixed typo in the latest commit 2010-09-14 15:18:18 +02:00
antirez 1d18f50458 Advertise the existence of redis-check dump --fix when logging an error about corrupted AOF file 2010-09-14 15:09:37 +02:00
Pedro Melo 0997b4119d Fixed missed use of INSTALL_TOP
Thanks to sylr@github

Signed-off-by: Pedro Melo <melo@simplicidade.org>
2010-09-13 16:50:57 +01:00
Pedro Melo e984050fb9 Make sure INSTALL_TOP exists before we install to it
Signed-off-by: Pedro Melo <melo@simplicidade.org>
2010-09-13 16:11:55 +01:00
Pedro Melo e13865033d Rename INSTALL_TOP to PREFIX; update documentation
Signed-off-by: Pedro Melo <melo@simplicidade.org>
2010-09-13 16:09:11 +01:00
Pieter Noordhuis 3c23ee1ba2 Fix another test that sometimes returned the swapped object instead of encoding 2010-09-13 16:59:46 +02:00
antirez bc63407be6 redis-cli does no longer try to auto detect if it is used inside a pipe. To read last argument from stdandard input there is to use the -x option. This will make it playing better inside cron scripts and in general when stdin is hacked. 2010-09-09 16:38:10 +02:00
antirez 7f9a4db3c0 Fix for the init script provided with Redis, thanks to Rowan. This fixes issue 316 2010-09-09 10:24:56 +02:00
antirez da14590bd9 Fix re-enabled again, I forgot to check if VM was enabled before calling handleClientsBlockedOnSwappedKey() 2010-09-08 13:47:28 +02:00
antirez 155fb4b45e latest fix reverted, there is some problem reported by the CI test 2010-09-08 13:45:51 +02:00
antirez 7f00cd2264 Fixed a race condition in VM happening when a key was deleted while there was a client waiting for this key to be resumed from swap to memory. The client would hang forever. 2010-09-08 13:26:16 +02:00
Pieter Noordhuis efc5d4cc0d Fix test that sometimes returned the swapped object instead of encoding 2010-09-07 11:49:33 +02:00
Pieter Noordhuis 106bd87a3c Fix bug where the client is not present in server.clients when free'ing it
When creating the readable event results in an error (this happens when
the server hits OS limits), the client was not added to the list of
clients when freeClient was called. This results in an assertion error.
It is better to check this condition first and free the client
immediately when this condition occurs. Port of 00a90feb.
2010-09-07 10:25:34 +02:00
Pieter Noordhuis 84403fe7c1 Allow a random seed argument for the ziplist test binary 2010-09-07 00:08:42 +02:00
Pieter Noordhuis 169d2ef1e0 Fix updating the prevlen field of consecutive entries
In the condition where the prevlen field of the next entry on insert
and delete operations needs more bytes to be properly encoded, the next
entry also needs to be updated with a new prevlen. This patch makes sure
that this effect cascades throughout the ziplist.
2010-09-07 00:04:57 +02:00
antirez abe18d0e00 Fix for solaris compilation bug Issue 325 2010-09-06 10:12:44 +02:00
Pieter Noordhuis 9e83ac06ef Merge branch 'master' into networking-perf
Resolved conflict in src/db.c and changed adding an error to the reply
in blockingPopGenericCommand to use the new API.
2010-09-03 16:44:50 +02:00
antirez 556bdfbab9 added some comment and changed coding style for fix for 237 2010-09-03 10:24:18 +02:00
Anko painting b435f64510 fix for issue 237 2010-09-02 21:13:27 -07:00
Pieter Noordhuis 49128f0b9d Fix bug in gluing a deferred multi bulk length to the next reply chunk 2010-09-02 23:34:41 +02:00
Pieter Noordhuis 3ab203762f Use specialized function to add status and error replies 2010-09-02 23:33:06 +02:00
Pieter Noordhuis 60361e5aac Add sds function that can be called with va_list 2010-09-02 21:00:15 +02:00
Pieter Noordhuis 36c19d03e0 Changed reply buildup internals 2010-09-02 19:52:04 +02:00
Pieter Noordhuis 4a7893ca9c Removed unneeded function 2010-09-02 19:52:04 +02:00
Pieter Noordhuis b70d355521 Use existing reply functions where possible 2010-09-02 19:52:04 +02:00
Pieter Noordhuis cd76bb651d Free the sds in addReplySds when it cannot be added to the reply 2010-09-02 19:52:04 +02:00
Pieter Noordhuis 2403fc9fde Intialize bufpos in the fake client 2010-09-02 19:52:04 +02:00
Pieter Noordhuis 0537e7bf80 Use specialized function to add multi bulk reply length 2010-09-02 12:51:14 +02:00
antirez 73db2acc37 memory fragmentation reporting in INFO also added for Mac OS X 2010-09-02 10:57:58 +02:00
antirez eddb388ef9 memory fragmentation ratio in INFO output 2010-09-02 10:34:39 +02:00
antirez a047bf52a4 fixed a few harmless warnings complining on Linux 2010-09-01 18:31:30 +02:00
Pieter Noordhuis 57b0738011 Don't build a reply when replaying the AOF 2010-08-30 16:51:39 +02:00
Pieter Noordhuis b301c1fc2b Wrapper for adding unknown multi bulk length to reply list 2010-08-30 16:39:14 +02:00
Pieter Noordhuis 834ef78e27 Refactor reply buildup for speed on large multi bulk replies 2010-08-30 16:39:08 +02:00
Pieter Noordhuis ed0dd55402 Show the current throughput while benchmarking 2010-08-30 11:25:02 +02:00
Pieter Noordhuis 36babc1e31 Refactor reply parsing code in redis-benchmark for efficiency 2010-08-30 11:14:54 +02:00
Pieter Noordhuis c470538142 Make ziplist schema more efficient for strings with length > 15 2010-08-13 19:29:22 +02:00
Pieter Noordhuis 69ef89f2cf Reference zset score in zskiplistNode from dict entries
This avoids the extra allocation of sizeof(double) for storing the score
of a zset entry in the hash table. Saves sizeof(double) + malloc
overhead = approx. 16 bytes per zset entry.
2010-08-03 20:49:53 +02:00
Pieter Noordhuis 2159782b51 Use flexible array in zskiplistNode to reduce memory usage 2010-08-03 19:22:09 +02:00
58 changed files with 2043 additions and 763 deletions
+13
View File
@@ -0,0 +1,13 @@
1. Enter irc.freenode.org #redis and start talking with 'antirez' and/or 'pietern' to check if there is interest for such a feature and to understand the probability of it being merged. We'll try hard to keep Redis simple... so you'll likely encounter an high resistence.
2. Drop a message to the Redis Google Group with a proposal of semantics/API.
3. If steps 1 and 2 are ok, use the following procedure to submit a patch:
a. Fork Redis on github
b. Create a topic branch (git checkout -b my_branch)
c. Push to your branch (git push origin my_branch)
d. Create an issue in the Redis google code site with a link to your patch
e. Done :)
Thanks!
+12 -1
View File
@@ -3,7 +3,18 @@ To compile Redis, do the following:
cd src; make
The compilation will produce a redis-server binary.
Copy this file where you want.
To install Redis, use
make install
and all the binaries will be installed on /usr/local/bin.
Alternatively:
make PREFIX=/some/other/directory
to have the binaries in /some/other/directory/bin.
Run the server using the following command line:
+2 -1
View File
@@ -16,7 +16,8 @@ It is as simple as:
Redis is just a single binary, but if you want to install it you can use
the "make install" target that will copy the binary in /usr/local/bin
for default.
for default. You can also use "make PREFIX=/some/other/directory install"
if you wish to use a different destination.
You can run a 32 bit Redis binary using:
+4 -2
View File
@@ -16,7 +16,7 @@
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>BlpopCommand: Contents</b><br>&nbsp;&nbsp;<a href="#BLPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis &gt;">BLPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis &gt;</a><br>&nbsp;&nbsp;<a href="#BRPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis &gt;">BRPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis &gt;</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Non blocking behavior">Non blocking behavior</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Blocking behavior">Blocking behavior</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Multiple clients blocking for the same keys">Multiple clients blocking for the same keys</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a>
<b>BlpopCommand: Contents</b><br>&nbsp;&nbsp;<a href="#BLPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis &gt;">BLPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis &gt;</a><br>&nbsp;&nbsp;<a href="#BRPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis &gt;">BRPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis &gt;</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Non blocking behavior">Non blocking behavior</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Blocking behavior">Blocking behavior</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Multiple clients blocking for the same keys">Multiple clients blocking for the same keys</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#blocking POP inside a MULTI/EXEC transaction">blocking POP inside a MULTI/EXEC transaction</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a>
</div>
<h1 class="wikiname">BlpopCommand</h1>
@@ -35,8 +35,10 @@
<h2><a name="Blocking behavior">Blocking behavior</a></h2><blockquote>If none of the specified keys exist or contain non empty lists, BLPOPblocks until some other client performs a <a href="RpushCommand.html">LPUSH</a> oran <a href="RpushCommand.html">RPUSH</a> operation against one of the lists.</blockquote>
<blockquote>Once new data is present on one of the lists, the client finally returnswith the name of the key unblocking it and the popped value.</blockquote>
<blockquote>When blocking, if a non-zero timeout is specified, the client will unblockreturning a nil special value if the specified amount of seconds passedwithout a push operation against at least one of the specified keys.</blockquote>
<blockquote>A timeout of zero means instead to block forever.</blockquote>
<blockquote>The timeout argument is interpreted as an integer value. A timeout of zero means instead to block forever.</blockquote>
<h2><a name="Multiple clients blocking for the same keys">Multiple clients blocking for the same keys</a></h2><blockquote>Multiple clients can block for the same key. They are put intoa queue, so the first to be served will be the one that started to waitearlier, in a first-blpopping first-served fashion.</blockquote>
<h2><a name="blocking POP inside a MULTI/EXEC transaction">blocking POP inside a MULTI/EXEC transaction</a></h2><blockquote>BLPOP and BRPOP can be used with pipelining (sending multiple commands and reading the replies in batch), but it does not make sense to use BLPOP or BRPOP inside a MULTI/EXEC block (a Redis transaction).</blockquote>
<blockquote>The behavior of BLPOP inside MULTI/EXEC when the list is empty is to return a multi-bulk nil reply, exactly what happens when the timeout is reached. If you like science fiction, think at it like if inside MULTI/EXEC the time will flow at infinite speed :) </blockquote>
<h2><a name="Return value">Return value</a></h2><blockquote>BLPOP returns a two-elements array via a multi bulk reply in order to returnboth the unblocking key and the popped value.</blockquote>
<blockquote>When a non-zero timeout is specified, and the BLPOP operation timed out,the return value is a nil multi bulk reply. Most client values will returnfalse or nil accordingly to the programming language used.</blockquote>
<a href="ReplyTypes.html">Multi bulk reply</a>
File diff suppressed because one or more lines are too long
+1 -2
View File
@@ -27,12 +27,11 @@
<div class="narrow">
&iuml;&raquo;&iquest;#sidebar <a href="GenericCommandsSidebar.html">GenericCommandsSidebar</a><h1><a name="DEL _key1_ _key2_ ... _keyN_">DEL _key1_ _key2_ ... _keyN_</a></h1>
<i>Time complexity: O(1)</i><blockquote>Remove the specified keys. If a given key does not existno operation is performed for this key. The commnad returns the number ofkeys removed.</blockquote>
<i>Time complexity: O(1)</i><blockquote>Remove the specified keys. If a given key does not existno operation is performed for this key. The command returns the number ofkeys removed.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically:<br/><br/><pre class="codeblock python" name="code">
an integer greater than 0 if one or more keys were removed
0 if none of the specified key existed
</pre>
</div>
</div>
+21 -10
View File
@@ -16,7 +16,7 @@
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>ExpireCommand: Contents</b><br>&nbsp;&nbsp;<a href="#EXPIRE _key_ _seconds_">EXPIRE _key_ _seconds_</a><br>&nbsp;&nbsp;<a href="#EXPIREAT _key_ _unixtime_ (Redis &gt;">EXPIREAT _key_ _unixtime_ (Redis &gt;</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#How the expire is removed from a key">How the expire is removed from a key</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Restrictions with write operations against volatile keys">Restrictions with write operations against volatile keys</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Enhanced Lazy Expiration algorithm">Enhanced Lazy Expiration algorithm</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Version 1.0">Version 1.0</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Version 1.1">Version 1.1</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#FAQ: Can you explain better why Redis deletes keys with an EXPIRE on write operations?">FAQ: Can you explain better why Redis deletes keys with an EXPIRE on write operations?</a>
<b>ExpireCommand: Contents</b><br>&nbsp;&nbsp;<a href="#EXPIRE _key_ _seconds_">EXPIRE _key_ _seconds_</a><br>&nbsp;&nbsp;<a href="#EXPIREAT _key_ _unixtime_ (Redis &gt;">EXPIREAT _key_ _unixtime_ (Redis &gt;</a><br>&nbsp;&nbsp;<a href="#PERSIST _key_">PERSIST _key_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#How the expire is removed from a key">How the expire is removed from a key</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Restrictions with write operations against volatile keys">Restrictions with write operations against volatile keys</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Restrictions for write operations with volatile keys as sources">Restrictions for write operations with volatile keys as sources</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Enhanced Lazy Expiration algorithm">Enhanced Lazy Expiration algorithm</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Version 1.0">Version 1.0</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Version 1.1">Version 1.1</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#FAQ: Can you explain better why Redis &lt; 2.1.3 deletes keys with an EXPIRE on write operations?">FAQ: Can you explain better why Redis &lt; 2.1.3 deletes keys with an EXPIRE on write operations?</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#FAQ: How this limitations were solved in Redis versions &gt; 2.1.3?">FAQ: How this limitations were solved in Redis versions &gt; 2.1.3?</a>
</div>
<h1 class="wikiname">ExpireCommand</h1>
@@ -28,12 +28,15 @@
<div class="narrow">
&iuml;&raquo;&iquest;#sidebar <a href="GenericCommandsSidebar.html">GenericCommandsSidebar</a><h1><a name="EXPIRE _key_ _seconds_">EXPIRE _key_ _seconds_</a></h1>
<h1><a name="EXPIREAT _key_ _unixtime_ (Redis &gt;">EXPIREAT _key_ _unixtime_ (Redis &gt;</a></h1> 1.1)=
<h1><a name="PERSIST _key_">PERSIST _key_</a></h1>
<i>Time complexity: O(1)</i><blockquote>Set a timeout on the specified key. After the timeout the key will beautomatically delete by the server. A key with an associated timeout issaid to be <i>volatile</i> in Redis terminology.</blockquote>
<blockquote>Voltile keys are stored on disk like the other keys, the timeout is persistenttoo like all the other aspects of the dataset. Saving a dataset containingthe dataset and stopping the server does not stop the flow of time as Redisregisters on disk when the key will no longer be available as Unix time, andnot the remaining seconds.</blockquote>
<blockquote>Voltile keys are stored on disk like the other keys, the timeout is persistenttoo like all the other aspects of the dataset. Saving a dataset containingexpires and stopping the server does not stop the flow of time as Redisstores on disk the time when the key will no longer be available as Unixtime, and not the remaining seconds.</blockquote>
<blockquote>EXPIREAT works exctly like EXPIRE but instead to get the number of secondsrepresenting the Time To Live of the key as a second argument (that is arelative way of specifing the TTL), it takes an absolute one in the form ofa UNIX timestamp (Number of seconds elapsed since 1 Gen 1970).</blockquote>
<blockquote>EXPIREAT was introduced in order to implement [Persistence append only saving mode] so that EXPIRE commands are automatically translated into EXPIREAT commands for the append only file. Of course EXPIREAT can alsoused by programmers that need a way to simply specify that a given key should expire at a given time in the future.</blockquote>
<h2><a name="How the expire is removed from a key">How the expire is removed from a key</a></h2><blockquote>When the key is set to a new value using the SET command, the INCR commandor any other command that modify the value stored at key the timeout isremoved from the key and the key becomes non volatile.</blockquote>
<h2><a name="Restrictions with write operations against volatile keys">Restrictions with write operations against volatile keys</a></h2><blockquote>Write operations like LPUSH, LSET and every other command that has theeffect of modifying the value stored at a volatile key have a special semantic:basically a volatile key is destroyed when it is target of a write operation.See for example the following usage pattern:</blockquote>
<blockquote>EXPIREAT was introduced in order to implement <a href="AppendOnlyFileHowto.html">the Append Only File persistence mode</a>so that EXPIRE commands are automatically translated into EXPIREAT commands for the append only file. Of course EXPIREAT can alsoused by programmers that need a way to simply specify that a given key should expire at a given time in the future.</blockquote>
<blockquote>Since Redis 2.1.3 you can update the value of the timeout of a key alreadyhaving an expire set. It is also possible to undo the expire at allturning the key into a normal key using the PERSIST command.</blockquote>
<h2><a name="How the expire is removed from a key">How the expire is removed from a key</a></h2><blockquote>When the key is set to a new value using the SET command, or when a keyis destroied via DEL, the timeout is removed from the key.</blockquote>
<h2><a name="Restrictions with write operations against volatile keys">Restrictions with write operations against volatile keys</a></h2><blockquote>IMPORTANT: Since Redis 2.1.3 or greater, there are no restrictions aboutthe operations you can perform against volatile keys, however older versionsof Redis, including the current stable version 2.0.0, has the followinglimitations:</blockquote>
<blockquote>Write operations like LPUSH, LSET and every other command that has theeffect of modifying the value stored at a volatile key have a special semantic:basically a volatile key is destroyed when it is target of a write operation.See for example the following usage pattern:</blockquote>
<pre class="codeblock python" name="code">
% ./redis-cli lpush mylist foobar /Users/antirez/hack/redis
OK
@@ -45,8 +48,13 @@ OK
OK
% ./redis-cli lrange mylist 0 -1 /Users/antirez/hack/redis
1. newelement
</pre><blockquote>What happened here is that lpush against the key with a timeout set deletedthe key before to perform the operation. There is so a simple rule, writeoperations against volatile keys will destroy the key before to perform theoperation. Why Redis uses this behavior? In order to retain an importantproperty: a server that receives a given number of commands in the samesequence will end with the same dataset in memory. Without the delete-on-writesemantic what happens is that the state of the server depends on the timeof the commands to. This is not a desirable property in a distributed databasethat supports replication.</blockquote>
<h2><a name="Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a></h2><blockquote>Trying to call EXPIRE against a key that already has an associated timeoutwill not change the timeout of the key, but will just return 0. If insteadthe key does not have a timeout associated the timeout will be set and EXPIREwill return 1.</blockquote>
</pre><blockquote>What happened here is that LPUSH against the key with a timeout set deletedthe key before to perform the operation. There is so a simple rule, writeoperations against volatile keys will destroy the key before to perform theoperation. Why Redis uses this behavior? In order to retain an importantproperty: a server that receives a given number of commands in the samesequence will end with the same dataset in memory. Without the delete-on-writesemantic what happens is that the state of the server depends on the timethe commands were issued. This is not a desirable property in a distributed databasethat supports replication.</blockquote>
<h2><a name="Restrictions for write operations with volatile keys as sources">Restrictions for write operations with volatile keys as sources</a></h2>Even when the volatile key is not modified as part of a write operation, if it is
read in a composite write operation (such as SINTERSTORE) it will be cleared at the
start of the operation. This is done to avoid concurrency issues in replication.
Imagine a key that is about to expire and the composite operation is run against it.
On a slave node, this key might already be expired, which leaves you with a
desync in your dataset.<h2><a name="Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a></h2><blockquote>Trying to call EXPIRE against a key that already has an associated timeoutwill not change the timeout of the key, but will just return 0. If insteadthe key does not have a timeout associated the timeout will be set and EXPIREwill return 1.</blockquote>
<h2><a name="Enhanced Lazy Expiration algorithm">Enhanced Lazy Expiration algorithm</a></h2><blockquote>Redis does not constantly monitor keys that are going to be expired.Keys are expired simply when some client tries to access a key, andthe key is found to be timed out.</blockquote>
<blockquote>Of course this is not enough as there are expired keys that will neverbe accessed again. This keys should be expired anyway, so once everysecond Redis test a few keys at random among keys with an expire set.All the keys that are already expired are deleted from the keyspace. </blockquote>
<h3><a name="Version 1.0">Version 1.0</a></h3><blockquote>Each time a fixed number of keys where tested (100 by default). So ifyou had a client setting keys with a very short expire faster than 100for second the memory continued to grow. When you stopped to insertnew keys the memory started to be freed, 100 keys every second in thebest conditions. Under a peak Redis continues to use more and more RAMeven if most keys are expired in each sweep.</blockquote>
@@ -56,8 +64,10 @@ OK
<blockquote>This means that at any given moment the maximum amount of keys alreadyexpired that are using memory is at max equal to max setting operations per second divided by 4.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically:<br/><br/><pre class="codeblock python python" name="code">
1: the timeout was set.
0: the timeout was not set since the key already has an associated timeout, or the key does not exist.
</pre><h2><a name="FAQ: Can you explain better why Redis deletes keys with an EXPIRE on write operations?">FAQ: Can you explain better why Redis deletes keys with an EXPIRE on write operations?</a></h2>
0: the timeout was not set since the key already has an associated timeout
(this may happen only in Redis versions &lt; 2.1.3, Redis &gt;= 2.1.3 will
happily update the timeout), or the key does not exist.
</pre><h2><a name="FAQ: Can you explain better why Redis &lt; 2.1.3 deletes keys with an EXPIRE on write operations?">FAQ: Can you explain better why Redis &lt; 2.1.3 deletes keys with an EXPIRE on write operations?</a></h2>
Ok let's start with the problem:
<pre class="codeblock python python python" name="code">
redis&gt; set a 100
@@ -76,7 +86,8 @@ EXPIRE a 5
INCR a
</pre>
Imagine a Redis version that does not implement the &quot;Delete keys with an expire set on write operation&quot; semantic.
Running the above example with the 10 seconds pause will lead to 'a' being set to the value of 1, as it no longer exists when INCR is called 10 seconds later.<br/><br/>Instead if we drop the 10 seconds pause, the result is that 'a' is set to 101.<br/><br/>And in the practice timing changes! For instance the client may wait 10 seconds before INCR, but the sequence written in the Append Only File (and later replayed-back as fast as possible when Redis is restarted) will not have the pause. Even if we add a timestamp in the AOF, when the time difference is smaller than our timer resolution, we have a race condition.<br/><br/>The same happens with master-slave replication. Again, consider the example above: the client will use the same sequence of commands without the 10 seconds pause, but the replication link will slow down for a few seconds due to a network problem. Result? The master will contain 'a' set to 101, the slave 'a' set to 1.<br/><br/>The only way to avoid this but at the same time have reliable non time dependent timeouts on keys is to destroy volatile keys when a write operation is attempted against it.<br/><br/>After all Redis is one of the rare fully persistent databases that will give you EXPIRE. This comes to a cost :)
Running the above example with the 10 seconds pause will lead to 'a' being set to the value of 1, as it no longer exists when INCR is called 10 seconds later.<br/><br/>Instead if we drop the 10 seconds pause, the result is that 'a' is set to 101.<br/><br/>And in the practice timing changes! For instance the client may wait 10 seconds before INCR, but the sequence written in the Append Only File (and later replayed-back as fast as possible when Redis is restarted) will not have the pause. Even if we add a timestamp in the AOF, when the time difference is smaller than our timer resolution, we have a race condition.<br/><br/>The same happens with master-slave replication. Again, consider the example above: the client will use the same sequence of commands without the 10 seconds pause, but the replication link will slow down for a few seconds due to a network problem. Result? The master will contain 'a' set to 101, the slave 'a' set to 1.<br/><br/>The only way to avoid this but at the same time have reliable non time dependent timeouts on keys is to destroy volatile keys when a write operation is attempted against it.<br/><br/>After all Redis is one of the rare fully persistent databases that will give you EXPIRE. This comes to a cost :)<h2><a name="FAQ: How this limitations were solved in Redis versions &gt; 2.1.3?">FAQ: How this limitations were solved in Redis versions &gt; 2.1.3?</a></h2>Since Redis 2.1.3 there are no longer restrictions in the use you can do of write commands against volatile keys, still the replication and AOF file are guaranteed to be fully consistent.<br/><br/>In order to obtain a correct behavior without sacrificing consistency now when a key expires, a DEL operation is synthesized in both the AOF file and against all the attached slaves. This way the expiration process is centralized in the master instance, and there is no longer a chance of consistency errors.<br/><br/>However while the slaves while connected to a master will not expire keys independently, they'll still take the full state of the expires existing in the dataset, so when a slave is elected to a master it will be able to expire the keys independently, fully acting as a master.
</div>
</div>
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -26,7 +26,7 @@
</div>
<div class="narrow">
&iuml;&raquo;&iquest;== Generic Commands ==<br/><br/><ul><li> <a href="ExistsCommand.html">EXISTS</a></li><li> <a href="DelCommand.html">DEL</a></li><li> <a href="TypeCommand.html">TYPE</a></li><li> <a href="KeysCommand.html">KEYS</a></li><li> <a href="RandomkeyCommand.html">RANDOMKEY</a></li><li> <a href="RenameCommand.html">RENAME</a></li><li> <a href="RenamenxCommand.html">RENAMENX</a></li><li> <a href="DbsizeCommand.html">DBSIZE</a></li><li> <a href="ExpireCommand.html">EXPIRE</a></li><li> <a href="TtlCommand.html">TTL</a></li><li> <a href="SelectCommand.html">SELECT</a></li><li> <a href="MoveCommand.html">MOVE</a></li><li> <a href="FlushdbCommand.html">FLUSHDB</a></li><li> <a href="FlushallCommand.html">FLUSHALL</a></li></ul>
&iuml;&raquo;&iquest;== Generic Commands ==<br/><br/><ul><li> <a href="ExistsCommand.html">EXISTS</a></li><li> <a href="DelCommand.html">DEL</a></li><li> <a href="TypeCommand.html">TYPE</a></li><li> <a href="KeysCommand.html">KEYS</a></li><li> <a href="RandomkeyCommand.html">RANDOMKEY</a></li><li> <a href="RenameCommand.html">RENAME</a></li><li> <a href="RenamenxCommand.html">RENAMENX</a></li><li> <a href="DbsizeCommand.html">DBSIZE</a></li><li> <a href="ExpireCommand.html">EXPIRE</a></li><li> <a href="ExpireCommand.html">PERSIST</a></li><li> <a href="TtlCommand.html">TTL</a></li><li> <a href="SelectCommand.html">SELECT</a></li><li> <a href="MoveCommand.html">MOVE</a></li><li> <a href="FlushdbCommand.html">FLUSHDB</a></li><li> <a href="FlushallCommand.html">FLUSHALL</a></li><li> <a href="MultiExecCommand.html">Redis Transactions</a></li></ul>
</div>
</div>
+1 -1
View File
@@ -26,7 +26,7 @@
</div>
<div class="narrow">
&iuml;&raquo;&iquest;== Hash Commands ==<br/><br/><ul><li> <a href="HsetCommand.html">HSET</a></li><li> <a href="HgetCommand.html">HGET</a></li><li> <a href="HsetnxCommand.html">HSETNX</a></li><li> <a href="HmsetCommand.html">HMSET</a></li><li> <a href="HincrbyCommand.html">HINCRBY</a></li><li> <a href="HexistsCommand.html">HEXISTS</a></li><li> <a href="HdelCommand.html">HDEL</a></li><li> <a href="HlenCommand.html">HLEN</a></li><li> <a href="HgetallCommand.html">HKEYS</a></li><li> <a href="HgetallCommand.html">HVALS</a></li><li> <a href="HgetallCommand.html">HGETALL</a></li></ul>
&iuml;&raquo;&iquest;== Hash Commands ==<br/><br/><ul><li> <a href="HsetCommand.html">HSET</a></li><li> <a href="HgetCommand.html">HGET</a></li><li> <a href="HsetnxCommand.html">HSETNX</a></li><li> <a href="HmsetCommand.html">HMSET</a></li><li> <a href="HmgetCommand.html">HMGET</a></li><li> <a href="HincrbyCommand.html">HINCRBY</a></li><li> <a href="HexistsCommand.html">HEXISTS</a></li><li> <a href="HdelCommand.html">HDEL</a></li><li> <a href="HlenCommand.html">HLEN</a></li><li> <a href="HgetallCommand.html">HKEYS</a></li><li> <a href="HgetallCommand.html">HVALS</a></li><li> <a href="HgetallCommand.html">HGETALL</a></li></ul>
</div>
</div>
+2 -1
View File
@@ -32,7 +32,8 @@
<blockquote>the slow commands that may ruin the DB performance if not usedwith care*.</blockquote>
<blockquote>In other words this command is intended only for debugging and *special* operations like creating a script to change the DB schema. Don't use it in your normal code. Use Redis <a href="Sets.html">Sets</a> in order to group together a subset of objects.</blockquote>
Glob style patterns examples:
<blockquote>* h?llo will match hello hallo hhllo* h*llo will match hllo heeeello* h<code name="code" class="python">[</code>ae<code name="code" class="python">]</code>llo will match hello and hallo, but not hillo</blockquote>Use \ to escape special chars if you want to match them verbatim.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a>, specifically a string in the form of space separated list of keys. Note that most client libraries will return an Array of keys and not a single string with space separated keys (that is, split by &quot; &quot; is performed in the client library usually).</b></blockquote>
<blockquote>* h?llo will match hello hallo hhllo* h*llo will match hllo heeeello* h<code name="code" class="python">[</code>ae<code name="code" class="python">]</code>llo will match hello and hallo, but not hillo</blockquote>Use \ to escape special chars if you want to match them verbatim.<h2><a name="Return value">Return value</a></h2>
<a href="ReplyTypes.html">Multi bulk reply</a></b></blockquote>
</div>
</div>
+1 -2
View File
@@ -28,10 +28,9 @@
<div class="narrow">
&iuml;&raquo;&iquest;#sidebar <a href="ListCommandsSidebar.html">ListCommandsSidebar</a><h1><a name="LINDEX _key_ _index_">LINDEX _key_ _index_</a></h1>
<i>Time complexity: O(n) (with n being the length of the list)</i><blockquote>Return the specified element of the list stored at the specifiedkey. 0 is the first element, 1 the second and so on. Negative indexesare supported, for example -1 is the last element, -2 the penultimateand so on.</blockquote>
<blockquote>If the value stored at key is not of list type an error is returned.If the index is out of range an empty string is returned.</blockquote>
<blockquote>If the value stored at key is not of list type an error is returned.If the index is out of range a 'nil' reply is returned.</blockquote>
<blockquote>Note that even if the average time complexity is O(n) asking forthe first or the last element of the list is O(1).</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a>, specifically the requested element.
</div>
</div>
+88 -17
View File
@@ -16,7 +16,7 @@
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>MultiExecCommand: Contents</b><br>&nbsp;&nbsp;<a href="#MULTI">MULTI</a><br>&nbsp;&nbsp;<a href="#COMMAND_1 ...">COMMAND_1 ...</a><br>&nbsp;&nbsp;<a href="#COMMAND_2 ...">COMMAND_2 ...</a><br>&nbsp;&nbsp;<a href="#COMMAND_N ...">COMMAND_N ...</a><br>&nbsp;&nbsp;<a href="#EXEC or DISCARD">EXEC or DISCARD</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Usage">Usage</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#The DISCARD command">The DISCARD command</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a>
<b>MultiExecCommand: Contents</b><br>&nbsp;&nbsp;<a href="#WATCH key1 key2 ... keyN (Redis &gt;">WATCH key1 key2 ... keyN (Redis &gt;</a><br>&nbsp;&nbsp;<a href="#UNWATCH">UNWATCH</a><br>&nbsp;&nbsp;<a href="#MULTI">MULTI</a><br>&nbsp;&nbsp;<a href="#COMMAND_1 ...">COMMAND_1 ...</a><br>&nbsp;&nbsp;<a href="#COMMAND_2 ...">COMMAND_2 ...</a><br>&nbsp;&nbsp;<a href="#COMMAND_N ...">COMMAND_N ...</a><br>&nbsp;&nbsp;<a href="#EXEC or DISCARD">EXEC or DISCARD</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Usage">Usage</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#The DISCARD command">The DISCARD command</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Check and Set (CAS) transactions using WATCH">Check and Set (CAS) transactions using WATCH</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#WATCH explained">WATCH explained</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#WATCH used to implement ZPOP">WATCH used to implement ZPOP</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a>
</div>
<h1 class="wikiname">MultiExecCommand</h1>
@@ -26,15 +26,21 @@
</div>
<div class="narrow">
&iuml;&raquo;&iquest;#sidebar <a href="GenericCommandsSidebar.html">GenericCommandsSidebar</a><h1><a name="MULTI">MULTI</a></h1>
&iuml;&raquo;&iquest;#sidebar <a href="GenericCommandsSidebar.html">GenericCommandsSidebar</a><h1><a name="WATCH key1 key2 ... keyN (Redis &gt;">WATCH key1 key2 ... keyN (Redis &gt;</a></h1> 2.1.0)=
<h1><a name="UNWATCH">UNWATCH</a></h1>
<h1><a name="MULTI">MULTI</a></h1>
<h1><a name="COMMAND_1 ...">COMMAND_1 ...</a></h1>
<h1><a name="COMMAND_2 ...">COMMAND_2 ...</a></h1>
<h1><a name="COMMAND_N ...">COMMAND_N ...</a></h1>
<h1><a name="EXEC or DISCARD">EXEC or DISCARD</a></h1><blockquote>MULTI, EXEC and DISCARD commands are the fundation of Redis Transactions.A Redis Transaction allows to execute a group of Redis commands in a singlestep, with two important guarantees:</blockquote>
<ul><li> All the commands in a transaction are serialized and executed sequentially. It can never happen that a request issued by another client is served <b>in the middle</b> of the execution of a Redis transaction. This guarantees that the commands are executed as a single atomic operation.</li><li> Either all of the commands or none are processed. The EXEC command triggers the execution of all the commands in the transaction, so if a client loses the connection to the server in the context of a transaction before calling the MULTI command none of the operations are performed, instead if the EXEC command is called, all the operations are performed. An exception to this rule is when the Append Only File is enabled: every command that is part of a Redis transaction will log in the AOF as long as the operation is completed, so if the Redis server crashes or is killed by the system administrator in some hard way it is possible that only a partial number of operations are registered.</li></ul>
<h2><a name="Usage">Usage</a></h2><blockquote>A Redis transaction is entered using the MULTI command. The command alwaysreplies with OK. At this point the user can issue multiple commands. Insteadto execute this commands Redis will &quot;queue&quot; them. All the commands areexecuted once EXEC is called.</blockquote>
<blockquote>Calling DISCARD instead will flush the transaction queue and will exitthe transaction.</blockquote>
<blockquote>The following is an example using the Ruby client:</blockquote><pre class="codeblock python" name="code">
<h1><a name="EXEC or DISCARD">EXEC or DISCARD</a></h1>MULTI, EXEC, DISCARD and WATCH commands are the fundation of Redis Transactions.
A Redis Transaction allows the execution of a group of Redis commands in a single
step, with two important guarantees:<br/><br/><ul><li> All the commands in a transaction are serialized and executed sequentially. It can never happen that a request issued by another client is served <b>in the middle</b> of the execution of a Redis transaction. This guarantees that the commands are executed as a single atomic operation.</li><li> Either all of the commands or none are processed. The EXEC command triggers the execution of all the commands in the transaction, so if a client loses the connection to the server in the context of a transaction before calling the MULTI command none of the operations are performed, instead if the EXEC command is called, all the operations are performed. An exception to this rule is when the Append Only File is enabled: every command that is part of a Redis transaction will log in the AOF as long as the operation is completed, so if the Redis server crashes or is killed by the system administrator in some hard way it is possible that only a partial number of operations are registered.</li></ul>
Since Redis 2.1.0, it's also possible to add a further guarantee to the above two, in the form of optimistic locking of a set of keys in a way very similar to a CAS (check and set) operation. This is documented later in this manual page.<h2><a name="Usage">Usage</a></h2>A Redis transaction is entered using the MULTI command. The command always
replies with OK. At this point the user can issue multiple commands. Instead
to execute this commands Redis will &quot;queue&quot; them. All the commands are
executed once EXEC is called.<br/><br/>Calling DISCARD instead will flush the transaction queue and will exit
the transaction.<br/><br/>The following is an example using the Ruby client:
<pre class="codeblock python" name="code">
?&gt; r.multi
=&gt; &quot;OK&quot;
&gt;&gt; r.incr &quot;foo&quot;
@@ -46,9 +52,14 @@
&gt;&gt; r.exec
=&gt; [1, 1, 2]
</pre>
<blockquote>As it is possible to see from the session above, MULTI returns an &quot;array&quot; ofreplies, where every element is the reply of a single command in thetransaction, in the same order the commands were queued.</blockquote>
<blockquote>When a Redis connection is in the context of a MULTI request, all the commandswill reply with a simple string &quot;QUEUED&quot; if they are correct from thepoint of view of the syntax and arity (number of arguments) of the commaand.Some command is still allowed to fail during execution time.</blockquote>
<blockquote>This is more clear if at protocol level: in the following example one commandwill fail when executed even if the syntax is right:</blockquote><pre class="codeblock python python" name="code">
As it is possible to see from the session above, MULTI returns an &quot;array&quot; of
replies, where every element is the reply of a single command in the
transaction, in the same order the commands were queued.<br/><br/>When a Redis connection is in the context of a MULTI request, all the commands
will reply with a simple string &quot;QUEUED&quot; if they are correct from the
point of view of the syntax and arity (number of arguments) of the commaand.
Some command is still allowed to fail during execution time.<br/><br/>This is more clear if at protocol level: in the following example one command
will fail when executed even if the syntax is right:
<pre class="codeblock python python" name="code">
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
@@ -64,16 +75,21 @@ EXEC
+OK
-ERR Operation against a key holding the wrong kind of value
</pre>
<blockquote>MULTI returned a two elements bulk reply in witch one of this is a +OKcode and one is a -ERR reply. It's up to the client lib to find a sensibleway to provide the error to the user.</blockquote>
<blockquote>IMPORTANT: even when a command will raise an error, all the other commandsin the queue will be processed. Redis will NOT stop the processing ofcommands once an error is found.</blockquote>
<blockquote>Another example, again using the write protocol with telnet, shows howsyntax errors are reported ASAP instead:</blockquote><pre class="codeblock python python python" name="code">
MULTI returned a two elements bulk reply in witch one of this is a +OK
code and one is a -ERR reply. It's up to the client lib to find a sensible
way to provide the error to the user.<br/><br/><blockquote>IMPORTANT: even when a command will raise an error, all the other commandsin the queue will be processed. Redis will NOT stop the processing ofcommands once an error is found.</blockquote>
Another example, again using the write protocol with telnet, shows how
syntax errors are reported ASAP instead:
<pre class="codeblock python python python" name="code">
MULTI
+OK
INCR a b c
-ERR wrong number of arguments for 'incr' command
</pre>
<blockquote>This time due to the syntax error the &quot;bad&quot; INCR command is not queuedat all.</blockquote>
<h2><a name="The DISCARD command">The DISCARD command</a></h2><blockquote>DISCARD can be used in order to abort a transaction. No command will beexecuted, and the state of the client is again the normal one, outsideof a transaction. Example using the Ruby client:</blockquote><pre class="codeblock python python python python" name="code">
This time due to the syntax error the &quot;bad&quot; INCR command is not queued
at all.<h2><a name="The DISCARD command">The DISCARD command</a></h2>DISCARD can be used in order to abort a transaction. No command will be
executed, and the state of the client is again the normal one, outside
<blockquote>of a transaction. Example using the Ruby client:</blockquote><pre class="codeblock python python python python" name="code">
?&gt; r.set(&quot;foo&quot;,1)
=&gt; true
&gt;&gt; r.multi
@@ -84,9 +100,64 @@ INCR a b c
=&gt; &quot;OK&quot;
&gt;&gt; r.get(&quot;foo&quot;)
=&gt; &quot;1&quot;
</pre><h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi bulk reply</a>, specifically:<br/><br/><pre class="codeblock python python python python python" name="code">
The result of a MULTI/EXEC command is a multi bulk reply where every element is the return value of every command in the atomic transaction.
</pre><h2><a name="Check and Set (CAS) transactions using WATCH">Check and Set (CAS) transactions using WATCH</a></h2>WATCH is used in order to provide a CAS (Check and Set) behavior to
Redis Transactions.<br/><br/>WATCHed keys are monitored in order to detect changes against this keys.
If at least a watched key will be modified before the EXEC call, the
whole transaction will abort, and EXEC will return a nil object
(A Null Multi Bulk reply) to notify that the transaction failed.<br/><br/>For example imagine we have the need to atomically increment the value
of a key by 1 (I know we have INCR, let's suppose we don't have it).<br/><br/>The first try may be the following:
<pre class="codeblock python python python python python" name="code">
val = GET mykey
val = val + 1
SET mykey $val
</pre>
This will work reliably only if we have a single client performing the operation in a given time.
If multiple clients will try to increment the key about at the same time
there will be a race condition. For instance client A and B will read the
old value, for instance, 10. The value will be incremented to 11 by both
the clients, and finally SET as the value of the key. So the final value
will be &quot;11&quot; instead of &quot;12&quot;.<br/><br/>Thanks to WATCH we are able to model the problem very well:
<pre class="codeblock python python python python python python" name="code">
WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC
</pre>
Using the above code, if there are race conditions and another client
modified the result of <i>val</i> in the time between our call to WATCH and
our call to EXEC, the transaction will fail.<br/><br/>We'll have just to re-iterate the operation hoping this time we'll not get
a new race. This form of locking is called <b>optimistic locking</b> and is
a very powerful form of locking as in many problems there are multiple
clients accessing a much bigger number of keys, so it's very unlikely that
there are collisions: usually operations don't need to be performed
multiple times.<h2><a name="WATCH explained">WATCH explained</a></h2>So what is WATCH really about? It is a command that will make the EXEC
conditional: we are asking Redis to perform the transaction only if no
other client modified any of the WATCHed keys. Otherwise the transaction is not
entered at all. (Note that if you WATCH a volatile key and Redis expires the key after you WATCHed it, EXEC will still work. <a href="http://code.google.com/p/redis/issues/detail?id=270" target="_blank">More</a>.)<br/><br/>WATCH can be called multiple times. Simply all the WATCH calls will
have the effects to watch for changes starting from the call, up to the
moment EXEC is called.<br/><br/>When EXEC is called, either if it will fail or succeed, all keys are
UNWATCHed. Also when a client connection is closed, everything gets
UNWATCHed.<br/><br/>It is also possible to use the UNWATCH command (without arguments) in order
to flush all the watched keys. Sometimes this is useful as we
optimistically lock a few keys, since possibly we need to perform a transaction
to alter those keys, but after reading the current content of the keys
we don't want to proceed. When this happens we just call UNWATCH so that
the connection can already be used freely for new transactions.<h2><a name="WATCH used to implement ZPOP">WATCH used to implement ZPOP</a></h2>A good example to illustrate how WATCH can be used to create new atomic
operations otherwise not supported by Redis is to implement ZPOP, that is
a command that pops the element with the lower score from a sorted set
in an atomic way. This is the simplest implementation:
<pre class="codeblock python python python python python python python" name="code">
WATCH zset
ele = ZRANGE zset 0 0
MULTI
ZREM zset ele
EXEC
</pre>
If EXEC fails (returns a nil value) we just re-iterate the operation.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi bulk reply</a>, specifically:<br/><br/><pre class="codeblock python python python python python python python python" name="code">
The result of a MULTI/EXEC command is a multi bulk reply where every element is the return value of every command in the atomic transaction.
</pre>If a MULTI/EXEC transaction is aborted because of WATCH detected modified keys, a <a href="ReplyTypes.html">Null Multi Bulk reply</a> is returned.
</div>
</div>
+40 -9
View File
@@ -26,11 +26,42 @@
</div>
<div class="narrow">
&iuml;&raquo;&iquest;= Introduction =<br/><br/>Redis is a database. To be specific, Redis is a database implementing a dictionary, where every key is associated with a value. For example I can set the key &quot;surname_1992&quot; to the string &quot;Smith&quot;.
What makes Redis different from many other key-value stores, is that every single value has a type. The following types are supported:<br/><br/><ul><li> <a href="Strings.html">Strings</a></li><li> <a href="Lists.html">Lists</a></li><li> <a href="Sets.html">Sets</a></li><li> <a href="SortedSets.html">Sorted Set</a> (since version 1.1)</li></ul>
The type of a value determines what operations (called commands) are available for the value itself.
For example you can append elements to a list stored at the key &quot;mylist&quot; using the LPUSH or RPUSH command in O(1). Later you'll be able to get a range of elements with LRANGE or trim the list with LTRIM. Sets are very flexible too, it is possible to add and remove elements from Sets (unsorted collections of strings), and then ask for server-side intersection, union, difference of Sets. Each command is performed through server-side atomic operations.
Please refer to the <a href="CommandReference.html">Command Reference</a> to see the full list of operations associated to these data types.<br/><br/>In other words, you can look at Redis as a data structures server. A Redis user is virtually provided with an interface to <a href="http://en.wikipedia.org/wiki/Abstract_data_type" target="_blank">Abstract Data Types</a>, saving her from the responsibility to implement concrete data structures and algorithms. Indeed both algorithms and data structures in Redis are properly choosed in order to obtain the best performance.<h1><a name="All data in memory, but saved on disk">All data in memory, but saved on disk</a></h1>Redis loads and mantains the whole dataset into memory, but the dataset is persistent, since at the same time it is saved on disk, so that when the server is restarted data can be loaded back in memory.<br/><br/>There are two kind of persistence supported: the first one is called snapshotting. In this mode Redis, from time to time, writes a dump on disk asynchronously. The dataset is loaded from the dump every time the server is (re)started.<br/><br/>Redis can be configured to save the dataset when a certain number of changes is reached and after a given number of seconds elapses. For example, you can configure Redis to save after 1000 changes and at most 60 seconds since the last save. You can specify any combination for these numbers.<br/><br/>Because data is written asynchronously, when a system crash occurs, the last few queries can get lost (that is acceptable in many applications but not in all). In order to make this a non issue Redis supports another, safer persistence mode, called <a href="AppendOnlyFileHowto.html">Append Only File</a>, where every command received altering the dataset (so not a read-only command, but a write command) is written on an append only file ASAP. This commands are <i>replayed</i> when the server is restarted in order to rebuild the dataset in memory.<br/><br/>Redis Append Only File supports a very handy feature: the server is able to safely rebuild the append only file in background in a non-blocking fashion when it gets too long. You can find <a href="AppendOnlyFileHowto.html">more details in the Append Only File HOWTO</a>.<h1><a name="Master-Slave replication made trivial">Master-Slave replication made trivial</a></h1>Whatever will be the persistence mode you'll use Redis supports master-slave replications if you want to stay really safe or if you need to scale to huge amounts of reads.<br/><br/><b>Redis Replication is trivial to setup</b>. So trivial that all you need to do in order to configure a Redis server to be a slave of another one, with automatic synchronization if the link will go down and so forth, is the following config line: <code name="code" class="python">slaveof 192.168.1.100 6379</code>. <a href="ReplicationHowto.html">We provide a Replication Howto</a> if you want to know more about this feature.<h1><a name="It's persistent but supports expires">It's persistent but supports expires</a></h1>Redis can be used as a <b>memcached on steroids</b> because is as fast as memcached but with a number of features more. Like memcached, Redis also supports setting timeouts to keys so that this key will be automatically removed when a given amount of time passes.<h1><a name="Beyond key-value databases">Beyond key-value databases</a></h1>All these features allow to use Redis as the sole DB for your scalable application without the need of any relational database. <a href="TwitterAlikeExample.html">We wrote a simple Twitter clone in PHP + Redis</a> to show a real world example, the link points to an article explaining the design and internals in very simple words.<h1><a name="Multiple databases support">Multiple databases support</a></h1>Redis supports multiple databases with commands to atomically move keys from one database to the other. By default DB 0 is selected for every new connection, but using the SELECT command it is possible to select a different database. The MOVE operation can move an item from one DB to another atomically. This can be used as a base for locking free algorithms together with the 'RANDOMKEY' commands.<h1><a name="Know more about Redis!">Know more about Redis!</a></h1>To really get a feeling about what Redis is and how it works please try reading <a href="IntroductionToRedisDataTypes.html">A fifteen minutes introduction to Redis data types</a>.<br/><br/>To know a bit more about how Redis works <i>internally</i> continue reading.<h1><a name="Redis Tutorial">Redis Tutorial</a></h1>(note, you can skip this section if you are only interested in &quot;formal&quot; doc.)<br/><br/>Later in this document you can find detailed information about Redis commands,
&iuml;&raquo;&iquest;= Introduction =<br/><br/>Redis is an extremely fast and powerful key-value store database and server implemented in ANSI C. Redis offers many different ways to do one straightforward thing: store a value (&quot;antirez&quot;) to a key (&quot;redis&quot;). While the format of keys must always be simple strings, the power is with the values, which support the following data types:<br/><br/><ul><li> <a href="Strings.html">Strings</a></li><li> <a href="Lists.html">Lists</a></li><li> <a href="Sets.html">Sets</a></li><li> <a href="SortedSets.html">Sorted Sets (zsets)</a></li><li> <a href="Hashes.html">Hashes</a></li></ul>
Each value type has an associated list of commands which can operate on them, and the <a href="CommandReference.html">The Redis Command Reference</a> contains an up to date list of these commands, organized primarily by data type. The Redis source also includes a <a href="RedisCLI.html">Redis command line interface</a> which allows you to interact directly with the server, and is the means by which this introduction will provide examples. Once you walk through the <a href="QuickStart.html">Redis Quick Start Guide</a> to get your instance of Redis running, you can follow along. <br/><br/>One of the most powerful aspects of Redis is the wide range of commands which are optimized to work with specific data value types and executed as atomic server-side operations. The <a href="Lists.html">List</a> type is a great example - Redis implements O(1) operations such as <a href="RpushCommand.html">LPUSH</a> or <a href="RpushCommand.html">RPUSH</a>, which have accompanying <a href="LpopCommand.html">LPOP</a> and <a href="LpopCommand.html">RPOP</a> methods:<br/><br/><pre class="codeblock python" name="code">
redis&gt; lpush programming_languages C
OK
redis&gt; lpush programming_languages Ruby
OK
redis&gt; rpush programming_languages Python
OK
redis&gt; rpop programming_languages
Python
redis&gt; lpop programming_languages
Ruby
</pre>More complex operations are available for each data type as well. Continuing with lists, you can get a range of elements with <a href="LrangeCommand.html">LRANGE</a> (O(start+n)) or trim the list with <a href="LtrimCommand.html">LTRIM</a> (O(n)):<br/><br/><pre class="codeblock python python" name="code">
redis&gt; lpush cities NYC
OK
redis&gt; lpush cities SF
OK
redis&gt; lpush cities Tokyo
OK
redis&gt; lpush cities London
OK
redis&gt; lpush cities Paris
OK
redis&gt; lrange cities 0 2
1. Paris
2. London
3. Tokyo
redis&gt; ltrim cities 0 1
OK
redis&gt; lpop cities
Paris
redis&gt; lpop cities
London
redis&gt; lpop cities
(nil)
</pre>You can also add and remove elements from a set, and perform intersections, unions, and differences. <br/><br/>Redis can also be looked at as a data structures server. A Redis user is virtually provided with an interface to <a href="http://en.wikipedia.org/wiki/Abstract_data_type" target="_blank">Abstract Data Types</a>, saving them from the responsibility of implementing concrete data structures and algorithms -- indeed both algorithms and data structures in Redis are properly chosen in order to obtain the best performance.<h1><a name="All data in memory, but saved on disk">All data in memory, but saved on disk</a></h1>Redis loads and mantains the whole dataset into memory, but the dataset is persistent, since at the same time it is saved on disk, so that when the server is restarted data can be loaded back in memory.<br/><br/>There are two kinds of persistence supported: the first one is called snapshotting. In this mode Redis periodically writes to disk asynchronously. The dataset is loaded from the dump every time the server is (re)started.<br/><br/>Redis can be configured to save the dataset when a certain number of changes is reached and after a given number of seconds elapses. For example, you can configure Redis to save after 1000 changes and at most 60 seconds since the last save. You can specify any combination for these numbers.<br/><br/>Because data is written asynchronously, when a system crash occurs, the last few queries can get lost (that is acceptable in many applications but not in all). In order to make this a non issue Redis supports another, safer persistence mode, called <a href="AppendOnlyFileHowto.html">Append Only File</a>, where every command received altering the dataset (so not a read-only command, but a write command) is written on an append only file ASAP. This commands are <i>replayed</i> when the server is restarted in order to rebuild the dataset in memory.<br/><br/>Redis Append Only File supports a very handy feature: the server is able to safely rebuild the append only file in background in a non-blocking fashion when it gets too long. You can find <a href="AppendOnlyFileHowto.html">more details in the Append Only File HOWTO</a>.<h1><a name="Master-Slave replication made trivial">Master-Slave replication made trivial</a></h1>Whatever will be the persistence mode you'll use Redis supports master-slave replications if you want to stay really safe or if you need to scale to huge amounts of reads.<br/><br/><b>Redis Replication is trivial to setup</b>. So trivial that all you need to do in order to configure a Redis server to be a slave of another one, with automatic synchronization if the link will go down and so forth, is the following config line: <code name="code" class="python">slaveof 192.168.1.100 6379</code>. <a href="ReplicationHowto.html">We provide a Replication Howto</a> if you want to know more about this feature.<h1><a name="It's persistent but supports expires">It's persistent but supports expires</a></h1>Redis can be used as a <b>memcached on steroids</b> because is as fast as memcached but with a number of features more. Like memcached, Redis also supports setting timeouts to keys so that this key will be automatically removed when a given amount of time passes.<h1><a name="Beyond key-value databases">Beyond key-value databases</a></h1>All these features allow to use Redis as the sole DB for your scalable application without the need of any relational database. <a href="TwitterAlikeExample.html">We wrote a simple Twitter clone in PHP + Redis</a> to show a real world example, the link points to an article explaining the design and internals in very simple words.<h1><a name="Multiple databases support">Multiple databases support</a></h1>Redis supports multiple databases with commands to atomically move keys from one database to the other. By default DB 0 is selected for every new connection, but using the SELECT command it is possible to select a different database. The MOVE operation can move an item from one DB to another atomically. This can be used as a base for locking free algorithms together with the 'RANDOMKEY' commands.<h1><a name="Know more about Redis!">Know more about Redis!</a></h1>To really get a feeling about what Redis is and how it works please try reading <a href="IntroductionToRedisDataTypes.html">A fifteen minutes introduction to Redis data types</a>.<br/><br/>To know a bit more about how Redis works <i>internally</i> continue reading.<h1><a name="Redis Tutorial">Redis Tutorial</a></h1>(note, you can skip this section if you are only interested in &quot;formal&quot; doc.)<br/><br/>Later in this document you can find detailed information about Redis commands,
the protocol specification, and so on. This kind of documentation is useful
but... if you are new to Redis it is also BORING! The Redis protocol is designed
so that is both pretty efficient to be parsed by computers, but simple enough
@@ -40,7 +71,7 @@ feeling about it, and how it works.<br/><br/>To start just compile redis with 'm
The server will start and log stuff on the standard output, if you want
it to log more edit redis.conf, set the loglevel to debug, and restart it.<br/><br/>You can specify a configuration file as unique parameter:<br/><br/><blockquote>./redis-server /etc/redis.conf</blockquote>
This is NOT required. The server will start even without a configuration file
using a default built-in configuration.<br/><br/>Now let's try to set a key to a given value:<br/><br/><pre class="codeblock python" name="code">
using a default built-in configuration.<br/><br/>Now let's try to set a key to a given value:<br/><br/><pre class="codeblock python python python" name="code">
$ telnet localhost 6379
Trying 127.0.0.1...
Connected to localhost.
@@ -59,17 +90,17 @@ the point of view of both the server and client but allows us to play with
Redis with the telnet command easily.<br/><br/>The last line of the chat between server and client is &quot;+OK&quot;. This means
our key was added without problems. Actually SET can never fail but
the &quot;+OK&quot; sent lets us know that the server received everything and
the command was actually executed.<br/><br/>Let's try to get the key content now:<br/><br/><pre class="codeblock python python" name="code">
the command was actually executed.<br/><br/>Let's try to get the key content now:<br/><br/><pre class="codeblock python python python python" name="code">
GET foo
$3
bar
</pre>Ok that's very similar to 'set', just the other way around. We sent &quot;get foo&quot;,
the server replied with a first line that is just the $ character follwed by
the number of bytes the value stored at key contained, followed by the actual
bytes. Again &quot;\r\n&quot; are appended both to the bytes count and the actual data. In Redis slang this is called a bulk reply.<br/><br/>What about requesting a non existing key?<br/><br/><pre class="codeblock python python python" name="code">
bytes. Again &quot;\r\n&quot; are appended both to the bytes count and the actual data. In Redis slang this is called a bulk reply.<br/><br/>What about requesting a non existing key?<br/><br/><pre class="codeblock python python python python python" name="code">
GET blabla
$-1
</pre>When the key does not exist instead of the length, just the &quot;$-1&quot; string is sent. Since a -1 length of a bulk reply has no meaning it is used in order to specifiy a 'nil' value and distinguish it from a zero length value. Another way to check if a given key exists or not is indeed the EXISTS command:<br/><br/><pre class="codeblock python python python python" name="code">
</pre>When the key does not exist instead of the length, just the &quot;$-1&quot; string is sent. Since a -1 length of a bulk reply has no meaning it is used in order to specifiy a 'nil' value and distinguish it from a zero length value. Another way to check if a given key exists or not is indeed the EXISTS command:<br/><br/><pre class="codeblock python python python python python python" name="code">
EXISTS nokey
:0
EXISTS foo
+1 -1
View File
@@ -26,7 +26,7 @@
</div>
<div class="narrow">
&iuml;&raquo;&iquest;== Sorted Set Commands ==<br/><br/><ul><li> <a href="ZaddCommand.html">ZADD</a></li><li> <a href="ZremCommand.html">ZREM</a></li><li> <a href="ZincrbyCommand.html">ZINCRBY</a></li><li> <a href="ZrankCommand.html">ZRANK</a></li><li> <a href="ZrankCommand.html">ZREVRANK</a></li><li> <a href="ZrangeCommand.html">ZRANGE</a></li><li> <a href="ZrangeCommand.html">ZREVRANGE</a></li><li> <a href="ZrangebyscoreCommand.html">ZRANGEBYSCORE</a></li><li> <a href="ZremrangebyrankCommand.html">ZREMRANGEBYRANK</a></li><li> <a href="ZremrangebyscoreCommand.html">ZREMRANGEBYSCORE</a> </li><li> <a href="ZcardCommand.html">ZCARD</a></li><li> <a href="ZscoreCommand.html">ZSCORE</a></li><li> <a href="ZunionstoreCommand.html">ZUNION / ZINTER</a></li><li> <a href="SortCommand.html">SORT</a></li></ul>
&iuml;&raquo;&iquest;== Sorted Set Commands ==<br/><br/><ul><li> <a href="ZaddCommand.html">ZADD</a></li><li> <a href="ZremCommand.html">ZREM</a></li><li> <a href="ZincrbyCommand.html">ZINCRBY</a></li><li> <a href="ZrankCommand.html">ZRANK</a></li><li> <a href="ZrankCommand.html">ZREVRANK</a></li><li> <a href="ZrangeCommand.html">ZRANGE</a></li><li> <a href="ZrangeCommand.html">ZREVRANGE</a></li><li> <a href="ZrangebyscoreCommand.html">ZRANGEBYSCORE</a></li><li> <a href="ZrangebyscoreCommand.html">ZCOUNT</a></li><li> <a href="ZremrangebyrankCommand.html">ZREMRANGEBYRANK</a></li><li> <a href="ZremrangebyscoreCommand.html">ZREMRANGEBYSCORE</a> </li><li> <a href="ZcardCommand.html">ZCARD</a></li><li> <a href="ZscoreCommand.html">ZSCORE</a></li><li> <a href="ZunionstoreCommand.html">ZUNIONSTORE</a></li><li> <a href="ZunionstoreCommand.html">ZINTERSTORE</a></li><li> <a href="SortCommand.html">SORT</a></li></ul>
</div>
</div>
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -30,8 +30,8 @@
<i>Time complexity O(log(N)) with N being the number of elements in the sorted set</i><blockquote>If <i>member</i> already exists in the sorted set adds the <i>increment</i> to its scoreand updates the position of the element in the sorted set accordingly.If <i>member</i> does not already exist in the sorted set it is added with_increment_ as score (that is, like if the previous score was virtually zero).If <i>key</i> does not exist a new sorted set with the specified_member_ as sole member is crated. If the key exists but does not hold asorted set value an error is returned.</blockquote>
<blockquote>The score value can be the string representation of a double precision floatingpoint number. It's possible to provide a negative value to perform a decrement.</blockquote>
<blockquote>For an introduction to sorted sets check the <a href="IntroductionToRedisDataTypes.html">Introduction to Redis data types</a> page.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically:<br/><br/><pre class="codeblock python" name="code">
The score of the member after the increment is performed.
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a><pre class="codeblock python" name="code">
The new score (a double precision floating point number) represented as string.
</pre>
</div>
+7 -3
View File
@@ -16,7 +16,7 @@
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>ZrangebyscoreCommand: Contents</b><br>&nbsp;&nbsp;<a href="#ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` (Redis &gt;">ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` (Redis &gt;</a><br>&nbsp;&nbsp;<a href="#ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` `[`WITHSCORES`]` (Redis &gt;">ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` `[`WITHSCORES`]` (Redis &gt;</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Exclusive intervals and infinity">Exclusive intervals and infinity</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Examples">Examples</a>
<b>ZrangebyscoreCommand: Contents</b><br>&nbsp;&nbsp;<a href="#ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` (Redis &gt;">ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` (Redis &gt;</a><br>&nbsp;&nbsp;<a href="#ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` `[`WITHSCORES`]` (Redis &gt;">ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` `[`WITHSCORES`]` (Redis &gt;</a><br>&nbsp;&nbsp;<a href="#ZCOUNT _key_ _min_ _max_">ZCOUNT _key_ _min_ _max_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Exclusive intervals and infinity">Exclusive intervals and infinity</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Examples">Examples</a>
</div>
<h1 class="wikiname">ZrangebyscoreCommand</h1>
@@ -28,9 +28,11 @@
<div class="narrow">
&iuml;&raquo;&iquest;#sidebar <a href="SortedSetCommandsSidebar.html">SortedSetCommandsSidebar</a><h1><a name="ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` (Redis &gt;">ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` (Redis &gt;</a></h1> 1.1) =
<h1><a name="ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` `[`WITHSCORES`]` (Redis &gt;">ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` `[`WITHSCORES`]` (Redis &gt;</a></h1> 1.3.4) =
<h1><a name="ZCOUNT _key_ _min_ _max_">ZCOUNT _key_ _min_ _max_</a></h1>
<i>Time complexity: O(log(N))+O(M) with N being the number of elements in the sorted set and M the number of elements returned by the command, so if M is constant (for instance you always ask for the first ten elements with LIMIT) you can consider it O(log(N))</i><blockquote>Return the all the elements in the sorted set at key with a score between_min_ and <i>max</i> (including elements with score equal to min or max).</blockquote>
<blockquote>The elements having the same score are returned sorted lexicographically asASCII strings (this follows from a property of Redis sorted sets and does notinvolve further computation).</blockquote>
<blockquote>Using the optional LIMIT it's possible to get only a range of the matchingelements in an SQL-alike way. Note that if <i>offset</i> is large the commandsneeds to traverse the list for <i>offset</i> elements and this adds up to theO(M) figure.</blockquote><h2><a name="Exclusive intervals and infinity">Exclusive intervals and infinity</a></h2>
<blockquote>Using the optional LIMIT it's possible to get only a range of the matchingelements in an SQL-alike way. Note that if <i>offset</i> is large the commandsneeds to traverse the list for <i>offset</i> elements and this adds up to theO(M) figure.</blockquote>
<blockquote>The <b>ZCOUNT</b> command is similar to <b>ZRANGEBYSCORE</b> but instead of returningthe actual elements in the specified interval, it just returns the numberof matching elements.</blockquote><h2><a name="Exclusive intervals and infinity">Exclusive intervals and infinity</a></h2>
<i>min</i> and <i>max</i> can be -inf and +inf, so that you are not required to know what's the greatest or smallest element in order to take, for instance, elements &quot;up to a given value&quot;.<br/><br/>Also while the interval is for default closed (inclusive) it's possible to specify open intervals prefixing the score with a &quot;(&quot; character, so for instance:
<pre class="codeblock python" name="code">
ZRANGEBYSCORE zset (1.3 5
@@ -40,7 +42,7 @@ Will return all the values with score <b>&gt; 1.3 and &lt;= 5</b>, while for ins
ZRANGEBYSCORE zset (5 (10
</pre>
Will return all the values with score <b>&gt; 5 and &lt; 10</b> (5 and 10 excluded).
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi bulk reply</a>, specifically a list of elements in the specified score range.
<h2><a name="Return value">Return value</a></h2>ZRANGEBYSCORE returns a <a href="ReplyTypes.html">Multi bulk reply</a> specifically a list of elements in the specified score range.<br/><br/>ZCOUNT returns a <a href="ReplyTypes.html">Integer reply</a> specifically the number of elements matching the specified score range.
<h2><a name="Examples">Examples</a></h2>
<pre class="codeblock python python python" name="code">
redis&gt; zadd zset 1 foo
@@ -56,6 +58,8 @@ redis&gt; zrangebyscore zset -inf +inf
2. &quot;bar&quot;
3. &quot;biz&quot;
4. &quot;foz&quot;
redis&gt; zcount zset 1 2
(integer) 2
redis&gt; zrangebyscore zset 1 2
1. &quot;foo&quot;
2. &quot;bar&quot;
+4 -3
View File
@@ -16,7 +16,7 @@
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>ZunionCommand: Contents</b><br>&nbsp;&nbsp;<a href="#ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis &gt;">ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis &gt;</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a>
<b>ZunionCommand: Contents</b><br>&nbsp;&nbsp;<a href="#ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis &gt;">ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis &gt;</a><br>&nbsp;&nbsp;<a href="#ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis &gt;">ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis &gt;</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a>
</div>
<h1 class="wikiname">ZunionCommand</h1>
@@ -27,8 +27,9 @@
<div class="narrow">
<h1><a name="ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis &gt;">ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis &gt;</a></h1> 1.3.5) =<br/><br/><i>Time complexity: O(N) + O(M log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set</i><blockquote>Creates a union or intersection of <i>N</i> sorted sets given by keys <i>k1</i> through <i>kN</i>, and stores it at <i>dstkey</i>. It is mandatory to provide the number of input keys <i>N</i>, before passing the input keys and the other (optional) arguments.</blockquote>
<blockquote>As the terms imply, the ZINTER command requires an element to be present in each of the given inputs to be inserted in the result. The ZUNION command inserts all elements across all inputs.</blockquote>
<h1><a name="ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis &gt;">ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis &gt;</a></h1> 1.3.12) =
<h1><a name="ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis &gt;">ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis &gt;</a></h1> 1.3.12) =<br/><br/><i>Time complexity: O(N) + O(M log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set</i><blockquote>Creates a union or intersection of <i>N</i> sorted sets given by keys <i>k1</i> through <i>kN</i>, and stores it at <i>dstkey</i>. It is mandatory to provide the number of input keys <i>N</i>, before passing the input keys and the other (optional) arguments.</blockquote>
<blockquote>As the terms imply, the ZINTERSTORE command requires an element to be present in each of the given inputs to be inserted in the result. The ZUNIONSTORE command inserts all elements across all inputs.</blockquote>
<blockquote>Using the WEIGHTS option, it is possible to add weight to each input sorted set. This means that the score of each element in the sorted set is first multiplied by this weight before being passed to the aggregation. When this option is not given, all weights default to 1.</blockquote>
<blockquote>With the AGGREGATE option, it's possible to specify how the results of the union or intersection are aggregated. This option defaults to SUM, where the score of an element is summed across the inputs where it exists. When this option is set to be either MIN or MAX, the resulting set will contain the minimum or maximum score of an element across the inputs where it exists.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically the number of elements in the sorted set at <i>dstkey</i>.
+5 -3
View File
@@ -16,7 +16,7 @@
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>ZunionstoreCommand: Contents</b><br>&nbsp;&nbsp;<a href="#ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis &gt;">ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis &gt;</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a>
<b>ZunionstoreCommand: Contents</b><br>&nbsp;&nbsp;<a href="#ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis &gt;">ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis &gt;</a><br>&nbsp;&nbsp;<a href="#ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis &gt;">ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis &gt;</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a>
</div>
<h1 class="wikiname">ZunionstoreCommand</h1>
@@ -27,8 +27,10 @@
<div class="narrow">
<h1><a name="ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis &gt;">ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis &gt;</a></h1> 1.3.5) =<br/><br/><i>Time complexity: O(N) + O(M log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set</i><blockquote>Creates a union or intersection of <i>N</i> sorted sets given by keys <i>k1</i> through <i>kN</i>, and stores it at <i>dstkey</i>. It is mandatory to provide the number of input keys <i>N</i>, before passing the input keys and the other (optional) arguments.</blockquote>
<blockquote>As the terms imply, the ZINTER command requires an element to be present in each of the given inputs to be inserted in the result. The ZUNION command inserts all elements across all inputs.</blockquote>
<h1><a name="ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis &gt;">ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis &gt;</a></h1> 1.3.12) =
<h1><a name="ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis &gt;">ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis &gt;</a></h1> 1.3.12) =
<i>Time complexity: O(N) + O(M log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set</i><blockquote>Creates a union or intersection of <i>N</i> sorted sets given by keys <i>k1</i> through <i>kN</i>, and stores it at <i>dstkey</i>. It is mandatory to provide the number of input keys <i>N</i>, before passing the input keys and the other (optional) arguments.</blockquote>
<blockquote>As the terms imply, the ZINTERSTORE command requires an element to be present in each of the given inputs to be inserted in the result. The ZUNIONSTORE command inserts all elements across all inputs.</blockquote>
<blockquote>Using the WEIGHTS option, it is possible to add weight to each input sorted set. This means that the score of each element in the sorted set is first multiplied by this weight before being passed to the aggregation. When this option is not given, all weights default to 1.</blockquote>
<blockquote>With the AGGREGATE option, it's possible to specify how the results of the union or intersection are aggregated. This option defaults to SUM, where the score of an element is summed across the inputs where it exists. When this option is set to be either MIN or MAX, the resulting set will contain the minimum or maximum score of an element across the inputs where it exists.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically the number of elements in the sorted set at <i>dstkey</i>.
+3 -3
View File
@@ -26,12 +26,12 @@
</div>
<div class="narrow">
&iuml;&raquo;&iquest;= Redis Documentation =<br/><br/><a href="http://pyha.ru/wiki/index.php?title=Redis:index" target="_blank">Russian Translation</a>Hello! The followings are pointers to different parts of the Redis Documentation.<br/><br/><ul><li> New! You can now <a href="http://try.redis-db.com" target="_blank">Try Redis directly in your browser!</a>.</li><li> <a href="README.html">The README</a> is the best starting point to know more about the project.</li><li> <a href="QuickStart.html">This short Quick Start</a> provides a five minutes step-by-step istructions on how to download, compile, run and test the basic workings of a Redis server.</li><li> <a href="CommandReference.html">The command reference</a> is a description of all the Redis commands with links to command specific pages. You can also download the <a href="http://go2.wordpress.com/?id=725X1342&site=masonoise.wordpress.com&url=http%3A%2F%2Fmasonoise.files.wordpress.com%2F2010%2F03%2Fredis-cheatsheet-v1.pdf" target="_blank">Redis Commands Cheat-Sheet</a> provided by Mason Jones (btw some command may be missing, the primary source is the wiki).</li><li> <a href="TwitterAlikeExample.html">This is a tuturial about creating a Twitter clone using *only* Redis as database, no relational DB at all is used</a>, it is a good start to understand the key-value database paradigm.</li><li> <a href="IntroductionToRedisDataTypes.html">A Fifteen Minutes Introduction to the Redis Data Types</a> explains how Redis data types work and the basic patterns of working with Redis.</li><li> <a href="http://simonwillison.net/static/2010/redis-tutorial/" target="_blank">the Simon Willison Redis Tutorial</a> is a <b>must read</b>, very good documentation where you will find a lot of real world ideas and use cases.</li><li> <a href="Features.html">The features page</a> (currently in draft) is a good start to understand the strength and limitations of Redis.</li><li> <a href="Benchmarks.html">The benchmark page</a> is about the speed performances of Redis.</li><li> <a href="FAQ.html">Our FAQ</a> contains of course some answers to common questions about Redis.</li><li> <a href="http://www.rediscookbook.org/" target="_blank">The Redis Cookbook</a> is a collaborative effort to provide some good recipe ;)</li></ul>
<h1><a name="HOWTOs about selected features">HOWTOs about selected features</a></h1><ul><li> <a href="ReplicationHowto.html">The Redis Replication HOWTO</a> is what you need to read in order to understand how Redis master <code name="code" class="python">&lt;-&gt;</code> slave replication works.</li><li> <a href="AppendOnlyFileHowto.html">The Append Only File HOWTO</a> explains how the alternative Redis durability mode works. AOF is an alternative to snapshotting on disk from time to time (the default).</li><li> <a href="VirtualMemoryUserGuide.html">Virutal Memory User Guide</a>. A simple to understand guide about using and configuring the Redis Virtual Memory.</li></ul>
&iuml;&raquo;&iquest;= Redis Documentation =<br/><br/><a href="http://pyha.ru/wiki/index.php?title=Redis:index" target="_blank">Russian Translation</a>Hello! The followings are pointers to different parts of the Redis Documentation.<br/><br/><ul><li> New! You can now <a href="http://try.redis-db.com" target="_blank">Try Redis directly in your browser!</a>.</li><li> <a href="README.html">The README</a> is the best starting point to know more about the project.</li><li> <a href="QuickStart.html">This short Quick Start</a> provides a five minutes step-by-step istructions on how to download, compile, run and test the basic workings of a Redis server.</li><li> <a href="CommandReference.html">The command reference</a> is a description of all the Redis commands with links to command specific pages. You can also download the <a href="http://go2.wordpress.com/?id=725X1342&site=masonoise.wordpress.com&url=http%3A%2F%2Fmasonoise.files.wordpress.com%2F2010%2F03%2Fredis-cheatsheet-v1.pdf" target="_blank">Redis Commands Cheat-Sheet</a> provided by Mason Jones (btw some command may be missing, the primary source is the wiki).</li><li> <a href="TwitterAlikeExample.html">This is a tutorial about creating a Twitter clone using *only* Redis as database, no relational DB at all is used</a>, it is a good start to understand the key-value database paradigm.</li><li> <a href="IntroductionToRedisDataTypes.html">A Fifteen Minutes Introduction to the Redis Data Types</a> explains how Redis data types work and the basic patterns of working with Redis.</li><li> <a href="http://simonwillison.net/static/2010/redis-tutorial/" target="_blank">the Simon Willison Redis Tutorial</a> is a <b>must read</b>, very good documentation where you will find a lot of real world ideas and use cases.</li><li> <a href="Features.html">The features page</a> (currently in draft) is a good start to understand the strength and limitations of Redis.</li><li> <a href="Benchmarks.html">The benchmark page</a> is about the speed performances of Redis.</li><li> <a href="FAQ.html">Our FAQ</a> contains of course some answers to common questions about Redis.</li><li> <a href="http://www.rediscookbook.org/" target="_blank">The Redis Cookbook</a> is a collaborative effort to provide some good recipe ;)</li></ul>
<h1><a name="HOWTOs about selected features">HOWTOs about selected features</a></h1><ul><li> <a href="ReplicationHowto.html">The Redis Replication HOWTO</a> is what you need to read in order to understand how Redis master <code name="code" class="python">&lt;-&gt;</code> slave replication works.</li><li> <a href="AppendOnlyFileHowto.html">The Append Only File HOWTO</a> explains how the alternative Redis durability mode works. AOF is an alternative to snapshotting on disk from time to time (the default).</li><li> <a href="VirtualMemoryUserGuide.html">Virtual Memory User Guide</a>. A simple to understand guide about using and configuring the Redis Virtual Memory.</li></ul>
<h1><a name="Hacking">Hacking</a></h1>
<ul><li> <a href="ProtocolSpecification.html">The Protocol Specification</a> is all you need in order to implement a Redis client library for a missing language. PHP, Python, Ruby and Erlang are already supported.</li></ul>
<ul><li> Look at <a href="RedisInternals.html">Redis Internals</a> if you are interested in the implementation details of the Redis server.</li></ul>
<h1><a name="Videos">Videos</a></h1><ul><li> <a href="http://mwrc2009.confreaks.com/13-mar-2009-19-24-redis-key-value-nirvana-ezra-zygmuntowicz.html" target="_blank">watch the Ezra Zygmuntowicz talk about Redis</a> to know the most important Redis ideas in few minutes.</li></ul>
<h1><a name="Videos">Videos</a></h1><ul><li> <a href="http://mwrc2009.confreaks.com/13-mar-2009-19-24-redis-key-value-nirvana-ezra-zygmuntowicz.html" target="_blank">watch the Ezra Zygmuntowicz talk about Redis</a> to know the most important Redis ideas in few minutes.</li><li> <a href="http://www.ustream.tv/recorded/7855635" target="_blank">Salvatore Sanfilippo and Pieter Noordhuis at the SF Redis Meetup</a></li></ul>
</div>
</div>
+19
View File
@@ -148,6 +148,25 @@ dir ./
#
# maxmemory <bytes>
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached? You can select among five behavior:
#
# volatile-lru -> remove the key with an expire set using an LRU algorithm
# allkeys-lru -> remove any key accordingly to the LRU algorithm
# volatile-random -> remove a random key with an expire set
# allkeys->random -> remove a random key, any key
# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
#
# maxmemory-policy volatile-lru
# LRU and minimal TTL algorithms are not precise algorithms but approximated
# algorithms (in order to save memory), so you can select as well the sample
# size to check. For instance for default Redis will check three keys and
# pick the one that was used less recently, you can change the sample size
# using the following configuration directive.
#
# maxmemory-samples 3
############################## APPEND ONLY MODE ###############################
# By default Redis asynchronously dumps the dataset on disk. If you can live
+3 -2
View File
@@ -15,8 +15,8 @@ endif
CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF)
DEBUG?= -g -rdynamic -ggdb
INSTALL_TOP= /usr/local
INSTALL_BIN= $(INSTALL_TOP)/bin
PREFIX= /usr/local
INSTALL_BIN= $(PREFIX)/bin
INSTALL= cp -p
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 vm.o pubsub.o multi.o debug.o sort.o intset.o
@@ -153,6 +153,7 @@ noopt:
make PROF="-pg" ARCH="-arch i386"
install: all
mkdir -p $(INSTALL_BIN)
$(INSTALL) $(PRGNAME) $(INSTALL_BIN)
$(INSTALL) $(BENCHPRGNAME) $(INSTALL_BIN)
$(INSTALL) $(CLIPRGNAME) $(INSTALL_BIN)
+9 -7
View File
@@ -189,6 +189,7 @@ struct redisClient *createFakeClient(void) {
c->querybuf = sdsempty();
c->argc = 0;
c->argv = NULL;
c->bufpos = 0;
c->flags = 0;
/* We set the fake client as a slave waiting for the synchronization
* so that Redis will not try to send replies to this client. */
@@ -272,12 +273,14 @@ int loadAppendOnlyFile(char *filename) {
fakeClient->argc = argc;
fakeClient->argv = argv;
cmd->proc(fakeClient);
/* Discard the reply objects list from the fake client */
while(listLength(fakeClient->reply))
listDelNode(fakeClient->reply,listFirst(fakeClient->reply));
/* The fake client should not have a reply */
redisAssert(fakeClient->bufpos == 0 && listLength(fakeClient->reply) == 0);
/* Clean up, ready for the next command */
for (j = 0; j < argc; j++) decrRefCount(argv[j]);
zfree(argv);
/* Handle swapping while loading big datasets when VM is on */
force_swapout = 0;
if ((zmalloc_used_memory() - server.vm_max_memory) > 1024*1024*32)
@@ -307,7 +310,7 @@ readerr:
}
exit(1);
fmterr:
redisLog(REDIS_WARNING,"Bad file format reading the append only file");
redisLog(REDIS_WARNING,"Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename>");
exit(1);
}
@@ -629,12 +632,11 @@ int rewriteAppendOnlyFileBackground(void) {
void bgrewriteaofCommand(redisClient *c) {
if (server.bgrewritechildpid != -1) {
addReplySds(c,sdsnew("-ERR background append only file rewriting already in progress\r\n"));
addReplyError(c,"Background append only file rewriting already in progress");
return;
}
if (rewriteAppendOnlyFileBackground() == REDIS_OK) {
char *status = "+Background append only file rewriting started\r\n";
addReplySds(c,sdsnew(status));
addReplyStatus(c,"Background append only file rewriting started");
} else {
addReply(c,shared.err);
}
+78 -25
View File
@@ -123,6 +123,27 @@ void loadServerConfig(char *filename) {
server.maxclients = atoi(argv[1]);
} else if (!strcasecmp(argv[0],"maxmemory") && argc == 2) {
server.maxmemory = memtoll(argv[1],NULL);
} else if (!strcasecmp(argv[0],"maxmemory-policy") && argc == 2) {
if (!strcasecmp(argv[1],"volatile-lru")) {
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU;
} else if (!strcasecmp(argv[1],"volatile-random")) {
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_RANDOM;
} else if (!strcasecmp(argv[1],"volatile-ttl")) {
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_TTL;
} else if (!strcasecmp(argv[1],"allkeys-lru")) {
server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_LRU;
} else if (!strcasecmp(argv[1],"allkeys-random")) {
server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_RANDOM;
} else {
err = "Invalid maxmemory policy";
goto loaderr;
}
} else if (!strcasecmp(argv[0],"maxmemory-samples") && argc == 2) {
server.maxmemory_samples = atoi(argv[1]);
if (server.maxmemory_samples <= 0) {
err = "maxmemory-samples must be 1 or greater";
goto loaderr;
}
} else if (!strcasecmp(argv[0],"slaveof") && argc == 3) {
server.masterhost = sdsnew(argv[1]);
server.masterport = atoi(argv[2]);
@@ -241,6 +262,25 @@ void configSetCommand(redisClient *c) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
ll < 0) goto badfmt;
server.maxmemory = ll;
if (server.maxmemory) freeMemoryIfNeeded();
} else if (!strcasecmp(c->argv[2]->ptr,"maxmemory-policy")) {
if (!strcasecmp(o->ptr,"volatile-lru")) {
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU;
} else if (!strcasecmp(o->ptr,"volatile-random")) {
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_RANDOM;
} else if (!strcasecmp(o->ptr,"volatile-ttl")) {
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_TTL;
} else if (!strcasecmp(o->ptr,"allkeys-lru")) {
server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_LRU;
} else if (!strcasecmp(o->ptr,"allkeys-random")) {
server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_RANDOM;
} else {
goto badfmt;
}
} else if (!strcasecmp(c->argv[2]->ptr,"maxmemory-samples")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
ll <= 0) goto badfmt;
server.maxmemory_samples = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"timeout")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
ll < 0 || ll > LONG_MAX) goto badfmt;
@@ -270,8 +310,8 @@ void configSetCommand(redisClient *c) {
stopAppendOnly();
} else {
if (startAppendOnly() == REDIS_ERR) {
addReplySds(c,sdscatprintf(sdsempty(),
"-ERR Unable to turn on AOF. Check server logs.\r\n"));
addReplyError(c,
"Unable to turn on AOF. Check server logs.");
decrRefCount(o);
return;
}
@@ -312,9 +352,8 @@ void configSetCommand(redisClient *c) {
}
sdsfreesplitres(v,vlen);
} else {
addReplySds(c,sdscatprintf(sdsempty(),
"-ERR not supported CONFIG parameter %s\r\n",
(char*)c->argv[2]->ptr));
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
(char*)c->argv[2]->ptr);
decrRefCount(o);
return;
}
@@ -323,22 +362,19 @@ void configSetCommand(redisClient *c) {
return;
badfmt: /* Bad format errors */
addReplySds(c,sdscatprintf(sdsempty(),
"-ERR invalid argument '%s' for CONFIG SET '%s'\r\n",
addReplyErrorFormat(c,"Invalid argument '%s' for CONFIG SET '%s'",
(char*)o->ptr,
(char*)c->argv[2]->ptr));
(char*)c->argv[2]->ptr);
decrRefCount(o);
}
void configGetCommand(redisClient *c) {
robj *o = getDecodedObject(c->argv[2]);
robj *lenobj = createObject(REDIS_STRING,NULL);
void *replylen = addDeferredMultiBulkLength(c);
char *pattern = o->ptr;
char buf[128];
int matches = 0;
addReply(c,lenobj);
decrRefCount(lenobj);
if (stringmatch(pattern,"dbfilename",0)) {
addReplyBulkCString(c,"dbfilename");
addReplyBulkCString(c,server.dbfilename);
@@ -355,17 +391,34 @@ void configGetCommand(redisClient *c) {
matches++;
}
if (stringmatch(pattern,"maxmemory",0)) {
char buf[128];
ll2string(buf,128,server.maxmemory);
ll2string(buf,sizeof(buf),server.maxmemory);
addReplyBulkCString(c,"maxmemory");
addReplyBulkCString(c,buf);
matches++;
}
if (stringmatch(pattern,"timeout",0)) {
char buf[128];
if (stringmatch(pattern,"maxmemory-policy",0)) {
char *s;
ll2string(buf,128,server.maxidletime);
switch(server.maxmemory_policy) {
case REDIS_MAXMEMORY_VOLATILE_LRU: s = "volatile-lru"; break;
case REDIS_MAXMEMORY_VOLATILE_TTL: s = "volatile-ttl"; break;
case REDIS_MAXMEMORY_VOLATILE_RANDOM: s = "volatile-random"; break;
case REDIS_MAXMEMORY_ALLKEYS_LRU: s = "allkeys-lru"; break;
case REDIS_MAXMEMORY_ALLKEYS_RANDOM: s = "allkeys-random"; break;
default: s = "unknown"; break; /* too harmless to panic */
}
addReplyBulkCString(c,"maxmemory-policy");
addReplyBulkCString(c,s);
matches++;
}
if (stringmatch(pattern,"maxmemory-samples",0)) {
ll2string(buf,sizeof(buf),server.maxmemory_samples);
addReplyBulkCString(c,"maxmemory-samples");
addReplyBulkCString(c,buf);
matches++;
}
if (stringmatch(pattern,"timeout",0)) {
ll2string(buf,sizeof(buf),server.maxidletime);
addReplyBulkCString(c,"timeout");
addReplyBulkCString(c,buf);
matches++;
@@ -410,7 +463,7 @@ void configGetCommand(redisClient *c) {
matches++;
}
decrRefCount(o);
lenobj->ptr = sdscatprintf(sdsempty(),"*%d\r\n",matches*2);
setDeferredMultiBulkLength(c,replylen,matches*2);
}
void configCommand(redisClient *c) {
@@ -422,19 +475,19 @@ void configCommand(redisClient *c) {
configGetCommand(c);
} else if (!strcasecmp(c->argv[1]->ptr,"resetstat")) {
if (c->argc != 2) goto badarity;
server.stat_keyspace_hits = 0;
server.stat_keyspace_misses = 0;
server.stat_numcommands = 0;
server.stat_numconnections = 0;
server.stat_expiredkeys = 0;
server.stat_starttime = time(NULL);
addReply(c,shared.ok);
} else {
addReplySds(c,sdscatprintf(sdsempty(),
"-ERR CONFIG subcommand must be one of GET, SET, RESETSTAT\r\n"));
addReplyError(c,
"CONFIG subcommand must be one of GET, SET, RESETSTAT");
}
return;
badarity:
addReplySds(c,sdscatprintf(sdsempty(),
"-ERR Wrong number of arguments for CONFIG %s\r\n",
(char*) c->argv[1]->ptr));
addReplyErrorFormat(c,"Wrong number of arguments for CONFIG %s",
(char*) c->argv[1]->ptr);
}
+10
View File
@@ -21,6 +21,16 @@
#define redis_stat stat
#endif
/* test for proc filesystem */
#ifdef __linux__
#define HAVE_PROCFS 1
#endif
/* test for task_info() */
#if defined(__APPLE__)
#define HAVE_TASKINFO 1
#endif
/* test for backtrace() */
#if defined(__APPLE__) || defined(__linux__)
#define HAVE_BACKTRACE 1
+28 -27
View File
@@ -11,6 +11,9 @@ robj *lookupKey(redisDb *db, robj *key) {
if (de) {
robj *val = dictGetEntryVal(de);
/* Update the access time for the aging algorithm. */
val->lru = server.lruclock;
if (server.vm_enabled) {
if (val->storage == REDIS_VM_MEMORY ||
val->storage == REDIS_VM_SWAPPING)
@@ -18,8 +21,6 @@ robj *lookupKey(redisDb *db, robj *key) {
/* If we were swapping the object out, cancel the operation */
if (val->storage == REDIS_VM_SWAPPING)
vmCancelThreadedIOJob(val);
/* Update the access time for the aging algorithm. */
val->lru = server.lruclock;
} else {
int notify = (val->storage == REDIS_VM_LOADING);
@@ -33,8 +34,10 @@ robj *lookupKey(redisDb *db, robj *key) {
if (notify) handleClientsBlockedOnSwappedKey(db,key);
}
}
server.stat_keyspace_hits++;
return val;
} else {
server.stat_keyspace_misses++;
return NULL;
}
}
@@ -123,6 +126,11 @@ robj *dbRandomKey(redisDb *db) {
/* Delete a key, value, and associated expiration entry if any, from the DB */
int dbDelete(redisDb *db, robj *key) {
/* If VM is enabled make sure to awake waiting clients for this key:
* deleting the key will kill the I/O thread bringing the key from swap
* to memory, so the client will never be notified and unblocked if we
* don't do it now. */
if (server.vm_enabled) handleClientsBlockedOnSwappedKey(db,key);
/* Deleting an entry from the expires dict will not free the sds of
* the key, because it is shared with the main dictionary. */
if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
@@ -199,7 +207,7 @@ void selectCommand(redisClient *c) {
int id = atoi(c->argv[1]->ptr);
if (selectDb(c,id) == REDIS_ERR) {
addReplySds(c,sdsnew("-ERR invalid DB index\r\n"));
addReplyError(c,"invalid DB index");
} else {
addReply(c,shared.ok);
}
@@ -223,11 +231,9 @@ void keysCommand(redisClient *c) {
sds pattern = c->argv[1]->ptr;
int plen = sdslen(pattern), allkeys;
unsigned long numkeys = 0;
robj *lenobj = createObject(REDIS_STRING,NULL);
void *replylen = addDeferredMultiBulkLength(c);
di = dictGetIterator(c->db->dict);
addReply(c,lenobj);
decrRefCount(lenobj);
allkeys = (pattern[0] == '*' && pattern[1] == '\0');
while((de = dictNext(di)) != NULL) {
sds key = dictGetEntryKey(de);
@@ -243,17 +249,15 @@ void keysCommand(redisClient *c) {
}
}
dictReleaseIterator(di);
lenobj->ptr = sdscatprintf(sdsempty(),"*%lu\r\n",numkeys);
setDeferredMultiBulkLength(c,replylen,numkeys);
}
void dbsizeCommand(redisClient *c) {
addReplySds(c,
sdscatprintf(sdsempty(),":%lu\r\n",dictSize(c->db->dict)));
addReplyLongLong(c,dictSize(c->db->dict));
}
void lastsaveCommand(redisClient *c) {
addReplySds(c,
sdscatprintf(sdsempty(),":%lu\r\n",server.lastsave));
addReplyLongLong(c,server.lastsave);
}
void typeCommand(redisClient *c) {
@@ -262,24 +266,23 @@ void typeCommand(redisClient *c) {
o = lookupKeyRead(c->db,c->argv[1]);
if (o == NULL) {
type = "+none";
type = "none";
} else {
switch(o->type) {
case REDIS_STRING: type = "+string"; break;
case REDIS_LIST: type = "+list"; break;
case REDIS_SET: type = "+set"; break;
case REDIS_ZSET: type = "+zset"; break;
case REDIS_HASH: type = "+hash"; break;
default: type = "+unknown"; break;
case REDIS_STRING: type = "string"; break;
case REDIS_LIST: type = "list"; break;
case REDIS_SET: type = "set"; break;
case REDIS_ZSET: type = "zset"; break;
case REDIS_HASH: type = "hash"; break;
default: type = "unknown"; break;
}
}
addReplySds(c,sdsnew(type));
addReply(c,shared.crlf);
addReplyStatus(c,type);
}
void saveCommand(redisClient *c) {
if (server.bgsavechildpid != -1) {
addReplySds(c,sdsnew("-ERR background save in progress\r\n"));
addReplyError(c,"Background save already in progress");
return;
}
if (rdbSave(server.dbfilename) == REDIS_OK) {
@@ -291,12 +294,11 @@ void saveCommand(redisClient *c) {
void bgsaveCommand(redisClient *c) {
if (server.bgsavechildpid != -1) {
addReplySds(c,sdsnew("-ERR background save already in progress\r\n"));
addReplyError(c,"Background save already in progress");
return;
}
if (rdbSaveBackground(server.dbfilename) == REDIS_OK) {
char *status = "+Background saving started\r\n";
addReplySds(c,sdsnew(status));
addReplyStatus(c,"Background saving started");
} else {
addReply(c,shared.err);
}
@@ -305,7 +307,7 @@ void bgsaveCommand(redisClient *c) {
void shutdownCommand(redisClient *c) {
if (prepareForShutdown() == REDIS_OK)
exit(0);
addReplySds(c, sdsnew("-ERR Errors trying to SHUTDOWN. Check logs.\r\n"));
addReplyError(c,"Errors trying to SHUTDOWN. Check logs.");
}
void renameGenericCommand(redisClient *c, int nx) {
@@ -468,7 +470,6 @@ int expireIfNeeded(redisDb *db, robj *key) {
/* Delete the key */
server.stat_expiredkeys++;
server.dirty++;
propagateExpire(db,key);
return dbDelete(db,key);
}
@@ -479,7 +480,7 @@ int expireIfNeeded(redisDb *db, robj *key) {
void expireGenericCommand(redisClient *c, robj *key, robj *param, long offset) {
dictEntry *de;
time_t seconds;
long seconds;
if (getLongFromObjectOrReply(c, param, &seconds, NULL) != REDIS_OK) return;
+18 -17
View File
@@ -211,18 +211,20 @@ void debugCommand(redisClient *c) {
char *strenc;
strenc = strEncoding(val->encoding);
addReplySds(c,sdscatprintf(sdsempty(),
"+Value at:%p refcount:%d "
"encoding:%s serializedlength:%lld\r\n",
addReplyStatusFormat(c,
"Value at:%p refcount:%d "
"encoding:%s serializedlength:%lld "
"lru :%d lru_seconds_idle:%lu",
(void*)val, val->refcount,
strenc, (long long) rdbSavedObjectLen(val,NULL)));
strenc, (long long) rdbSavedObjectLen(val,NULL),
val->lru, estimateObjectIdleTime(val));
} else {
vmpointer *vp = (vmpointer*) val;
addReplySds(c,sdscatprintf(sdsempty(),
"+Value swapped at: page %llu "
"using %llu pages\r\n",
addReplyStatusFormat(c,
"Value swapped at: page %llu "
"using %llu pages",
(unsigned long long) vp->page,
(unsigned long long) vp->usedpages));
(unsigned long long) vp->usedpages);
}
} else if (!strcasecmp(c->argv[1]->ptr,"swapin") && c->argc == 3) {
lookupKeyRead(c->db,c->argv[2]);
@@ -233,7 +235,7 @@ void debugCommand(redisClient *c) {
vmpointer *vp;
if (!server.vm_enabled) {
addReplySds(c,sdsnew("-ERR Virtual Memory is disabled\r\n"));
addReplyError(c,"Virtual Memory is disabled");
return;
}
if (!de) {
@@ -243,9 +245,9 @@ void debugCommand(redisClient *c) {
val = dictGetEntryVal(de);
/* Swap it */
if (val->storage != REDIS_VM_MEMORY) {
addReplySds(c,sdsnew("-ERR This key is not in memory\r\n"));
addReplyError(c,"This key is not in memory");
} else if (val->refcount != 1) {
addReplySds(c,sdsnew("-ERR Object is shared\r\n"));
addReplyError(c,"Object is shared");
} else if ((vp = vmSwapObjectBlocking(val)) != NULL) {
dictGetEntryVal(de) = vp;
addReply(c,shared.ok);
@@ -274,18 +276,17 @@ void debugCommand(redisClient *c) {
addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"digest") && c->argc == 2) {
unsigned char digest[20];
sds d = sdsnew("+");
sds d = sdsempty();
int j;
computeDatasetDigest(digest);
for (j = 0; j < 20; j++)
d = sdscatprintf(d, "%02x",digest[j]);
d = sdscatlen(d,"\r\n",2);
addReplySds(c,d);
addReplyStatus(c,d);
sdsfree(d);
} else {
addReplySds(c,sdsnew(
"-ERR Syntax error, try DEBUG [SEGFAULT|OBJECT <key>|SWAPIN <key>|SWAPOUT <key>|RELOAD]\r\n"));
addReplyError(c,
"Syntax error, try DEBUG [SEGFAULT|OBJECT <key>|SWAPIN <key>|SWAPOUT <key>|RELOAD]");
}
}
+21 -7
View File
@@ -49,8 +49,13 @@
/* Using dictEnableResize() / dictDisableResize() we make possible to
* enable/disable resizing of the hash table as needed. This is very important
* for Redis, as we use copy-on-write and don't want to move too much memory
* around when there is a child performing saving operations. */
* around when there is a child performing saving operations.
*
* Note that even when dict_can_resize is set to 0, not all resizes are
* prevented: an hash table is still allowed to grow if the ratio between
* the number of elements and the buckets > dict_force_resize_ratio. */
static int dict_can_resize = 1;
static unsigned int dict_force_resize_ratio = 5;
/* -------------------------- private prototypes ---------------------------- */
@@ -125,7 +130,7 @@ int _dictInit(dict *d, dictType *type,
}
/* Resize the table to the minimal size that contains all the elements,
* but with the invariant of a USER/BUCKETS ration near to <= 1 */
* but with the invariant of a USER/BUCKETS ratio near to <= 1 */
int dictResize(dict *d)
{
int minimal;
@@ -493,14 +498,23 @@ dictEntry *dictGetRandomKey(dict *d)
/* Expand the hash table if needed */
static int _dictExpandIfNeeded(dict *d)
{
/* If the hash table is empty expand it to the intial size,
* if the table is "full" dobule its size. */
/* Incremental rehashing already in progress. Return. */
if (dictIsRehashing(d)) return DICT_OK;
if (d->ht[0].size == 0)
return dictExpand(d, DICT_HT_INITIAL_SIZE);
if (d->ht[0].used >= d->ht[0].size && dict_can_resize)
/* If the hash table is empty expand it to the intial size. */
if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);
/* If we reached the 1:1 ratio, and we are allowed to resize the hash
* table (global setting) or we should avoid it but the ratio between
* elements/buckets is over the "safe" threshold, we resize doubling
* the number of buckets. */
if (d->ht[0].used >= d->ht[0].size &&
(dict_can_resize ||
d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))
{
return dictExpand(d, ((d->ht[0].size > d->ht[0].used) ?
d->ht[0].size : d->ht[0].used)*2);
}
return DICT_OK;
}
+5 -5
View File
@@ -42,7 +42,7 @@ void queueMultiCommand(redisClient *c, struct redisCommand *cmd) {
void multiCommand(redisClient *c) {
if (c->flags & REDIS_MULTI) {
addReplySds(c,sdsnew("-ERR MULTI calls can not be nested\r\n"));
addReplyError(c,"MULTI calls can not be nested");
return;
}
c->flags |= REDIS_MULTI;
@@ -51,7 +51,7 @@ void multiCommand(redisClient *c) {
void discardCommand(redisClient *c) {
if (!(c->flags & REDIS_MULTI)) {
addReplySds(c,sdsnew("-ERR DISCARD without MULTI\r\n"));
addReplyError(c,"DISCARD without MULTI");
return;
}
@@ -82,7 +82,7 @@ void execCommand(redisClient *c) {
int orig_argc;
if (!(c->flags & REDIS_MULTI)) {
addReplySds(c,sdsnew("-ERR EXEC without MULTI\r\n"));
addReplyError(c,"EXEC without MULTI");
return;
}
@@ -107,7 +107,7 @@ void execCommand(redisClient *c) {
unwatchAllKeys(c); /* Unwatch ASAP otherwise we'll waste CPU cycles */
orig_argv = c->argv;
orig_argc = c->argc;
addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",c->mstate.count));
addReplyMultiBulkLen(c,c->mstate.count);
for (j = 0; j < c->mstate.count; j++) {
c->argc = c->mstate.commands[j].argc;
c->argv = c->mstate.commands[j].argv;
@@ -251,7 +251,7 @@ void watchCommand(redisClient *c) {
int j;
if (c->flags & REDIS_MULTI) {
addReplySds(c,sdsnew("-ERR WATCH inside MULTI is not allowed\r\n"));
addReplyError(c,"WATCH inside MULTI is not allowed");
return;
}
for (j = 1; j < c->argc; j++)
+287 -106
View File
@@ -1,5 +1,4 @@
#include "redis.h"
#include <sys/uio.h>
void *dupClientReplyValue(void *o) {
@@ -12,14 +11,24 @@ int listMatchObjects(void *a, void *b) {
}
redisClient *createClient(int fd) {
redisClient *c = zmalloc(sizeof(*c));
redisClient *c = zmalloc(sizeof(redisClient));
c->bufpos = 0;
anetNonBlock(NULL,fd);
anetTcpNoDelay(NULL,fd);
if (!c) return NULL;
if (aeCreateFileEvent(server.el,fd,AE_READABLE,
readQueryFromClient, c) == AE_ERR)
{
close(fd);
zfree(c);
return NULL;
}
selectDb(c,0);
c->fd = fd;
c->querybuf = sdsempty();
c->newline = NULL;
c->argc = 0;
c->argv = NULL;
c->bulklen = -1;
@@ -43,80 +52,254 @@ redisClient *createClient(int fd) {
c->pubsub_patterns = listCreate();
listSetFreeMethod(c->pubsub_patterns,decrRefCount);
listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
if (aeCreateFileEvent(server.el, c->fd, AE_READABLE,
readQueryFromClient, c) == AE_ERR) {
freeClient(c);
return NULL;
}
listAddNodeTail(server.clients,c);
initClientMultiState(c);
return c;
}
void addReply(redisClient *c, robj *obj) {
if (listLength(c->reply) == 0 &&
int _installWriteEvent(redisClient *c) {
if (c->fd <= 0) return REDIS_ERR;
if (c->bufpos == 0 && listLength(c->reply) == 0 &&
(c->replstate == REDIS_REPL_NONE ||
c->replstate == REDIS_REPL_ONLINE) &&
aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,
sendReplyToClient, c) == AE_ERR) return;
sendReplyToClient, c) == AE_ERR) return REDIS_ERR;
return REDIS_OK;
}
if (server.vm_enabled && obj->storage != REDIS_VM_MEMORY) {
obj = dupStringObject(obj);
obj->refcount = 0; /* getDecodedObject() will increment the refcount */
/* Create a duplicate of the last object in the reply list when
* it is not exclusively owned by the reply list. */
robj *dupLastObjectIfNeeded(list *reply) {
robj *new, *cur;
listNode *ln;
redisAssert(listLength(reply) > 0);
ln = listLast(reply);
cur = listNodeValue(ln);
if (cur->refcount > 1) {
new = dupStringObject(cur);
decrRefCount(cur);
listNodeValue(ln) = new;
}
return listNodeValue(ln);
}
int _addReplyToBuffer(redisClient *c, char *s, size_t len) {
size_t available = sizeof(c->buf)-c->bufpos;
/* If there already are entries in the reply list, we cannot
* add anything more to the static buffer. */
if (listLength(c->reply) > 0) return REDIS_ERR;
/* Check that the buffer has enough space available for this string. */
if (len > available) return REDIS_ERR;
memcpy(c->buf+c->bufpos,s,len);
c->bufpos+=len;
return REDIS_OK;
}
void _addReplyObjectToList(redisClient *c, robj *o) {
robj *tail;
if (listLength(c->reply) == 0) {
incrRefCount(o);
listAddNodeTail(c->reply,o);
} else {
tail = listNodeValue(listLast(c->reply));
/* Append to this object when possible. */
if (tail->ptr != NULL &&
sdslen(tail->ptr)+sdslen(o->ptr) <= REDIS_REPLY_CHUNK_BYTES)
{
tail = dupLastObjectIfNeeded(c->reply);
tail->ptr = sdscatlen(tail->ptr,o->ptr,sdslen(o->ptr));
} else {
incrRefCount(o);
listAddNodeTail(c->reply,o);
}
}
}
/* This method takes responsibility over the sds. When it is no longer
* needed it will be free'd, otherwise it ends up in a robj. */
void _addReplySdsToList(redisClient *c, sds s) {
robj *tail;
if (listLength(c->reply) == 0) {
listAddNodeTail(c->reply,createObject(REDIS_STRING,s));
} else {
tail = listNodeValue(listLast(c->reply));
/* Append to this object when possible. */
if (tail->ptr != NULL &&
sdslen(tail->ptr)+sdslen(s) <= REDIS_REPLY_CHUNK_BYTES)
{
tail = dupLastObjectIfNeeded(c->reply);
tail->ptr = sdscatlen(tail->ptr,s,sdslen(s));
sdsfree(s);
} else {
listAddNodeTail(c->reply,createObject(REDIS_STRING,s));
}
}
}
void _addReplyStringToList(redisClient *c, char *s, size_t len) {
robj *tail;
if (listLength(c->reply) == 0) {
listAddNodeTail(c->reply,createStringObject(s,len));
} else {
tail = listNodeValue(listLast(c->reply));
/* Append to this object when possible. */
if (tail->ptr != NULL &&
sdslen(tail->ptr)+len <= REDIS_REPLY_CHUNK_BYTES)
{
tail = dupLastObjectIfNeeded(c->reply);
tail->ptr = sdscatlen(tail->ptr,s,len);
} else {
listAddNodeTail(c->reply,createStringObject(s,len));
}
}
}
void addReply(redisClient *c, robj *obj) {
if (_installWriteEvent(c) != REDIS_OK) return;
redisAssert(!server.vm_enabled || obj->storage == REDIS_VM_MEMORY);
/* This is an important place where we can avoid copy-on-write
* when there is a saving child running, avoiding touching the
* refcount field of the object if it's not needed.
*
* If the encoding is RAW and there is room in the static buffer
* we'll be able to send the object to the client without
* messing with its page. */
if (obj->encoding == REDIS_ENCODING_RAW) {
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
_addReplyObjectToList(c,obj);
} else {
obj = getDecodedObject(obj);
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
_addReplyObjectToList(c,obj);
decrRefCount(obj);
}
listAddNodeTail(c->reply,getDecodedObject(obj));
}
void addReplySds(redisClient *c, sds s) {
robj *o = createObject(REDIS_STRING,s);
addReply(c,o);
decrRefCount(o);
if (_installWriteEvent(c) != REDIS_OK) {
/* The caller expects the sds to be free'd. */
sdsfree(s);
return;
}
if (_addReplyToBuffer(c,s,sdslen(s)) == REDIS_OK) {
sdsfree(s);
} else {
/* This method free's the sds when it is no longer needed. */
_addReplySdsToList(c,s);
}
}
void addReplyString(redisClient *c, char *s, size_t len) {
if (_installWriteEvent(c) != REDIS_OK) return;
if (_addReplyToBuffer(c,s,len) != REDIS_OK)
_addReplyStringToList(c,s,len);
}
void _addReplyError(redisClient *c, char *s, size_t len) {
addReplyString(c,"-ERR ",5);
addReplyString(c,s,len);
addReplyString(c,"\r\n",2);
}
void addReplyError(redisClient *c, char *err) {
_addReplyError(c,err,strlen(err));
}
void addReplyErrorFormat(redisClient *c, const char *fmt, ...) {
va_list ap;
va_start(ap,fmt);
sds s = sdscatvprintf(sdsempty(),fmt,ap);
va_end(ap);
_addReplyError(c,s,sdslen(s));
sdsfree(s);
}
void _addReplyStatus(redisClient *c, char *s, size_t len) {
addReplyString(c,"+",1);
addReplyString(c,s,len);
addReplyString(c,"\r\n",2);
}
void addReplyStatus(redisClient *c, char *status) {
_addReplyStatus(c,status,strlen(status));
}
void addReplyStatusFormat(redisClient *c, const char *fmt, ...) {
va_list ap;
va_start(ap,fmt);
sds s = sdscatvprintf(sdsempty(),fmt,ap);
va_end(ap);
_addReplyStatus(c,s,sdslen(s));
sdsfree(s);
}
/* Adds an empty object to the reply list that will contain the multi bulk
* length, which is not known when this function is called. */
void *addDeferredMultiBulkLength(redisClient *c) {
/* Note that we install the write event here even if the object is not
* ready to be sent, since we are sure that before returning to the
* event loop setDeferredMultiBulkLength() will be called. */
if (_installWriteEvent(c) != REDIS_OK) return NULL;
listAddNodeTail(c->reply,createObject(REDIS_STRING,NULL));
return listLast(c->reply);
}
/* Populate the length object and try glueing it to the next chunk. */
void setDeferredMultiBulkLength(redisClient *c, void *node, long length) {
listNode *ln = (listNode*)node;
robj *len, *next;
/* Abort when *node is NULL (see addDeferredMultiBulkLength). */
if (node == NULL) return;
len = listNodeValue(ln);
len->ptr = sdscatprintf(sdsempty(),"*%ld\r\n",length);
if (ln->next != NULL) {
next = listNodeValue(ln->next);
/* Only glue when the next node is non-NULL (an sds in this case) */
if (next->ptr != NULL) {
len->ptr = sdscatlen(len->ptr,next->ptr,sdslen(next->ptr));
listDelNode(c->reply,ln->next);
}
}
}
void addReplyDouble(redisClient *c, double d) {
char buf[128];
snprintf(buf,sizeof(buf),"%.17g",d);
addReplySds(c,sdscatprintf(sdsempty(),"$%lu\r\n%s\r\n",
(unsigned long) strlen(buf),buf));
char dbuf[128], sbuf[128];
int dlen, slen;
dlen = snprintf(dbuf,sizeof(dbuf),"%.17g",d);
slen = snprintf(sbuf,sizeof(sbuf),"$%d\r\n%s\r\n",dlen,dbuf);
addReplyString(c,sbuf,slen);
}
void addReplyLongLong(redisClient *c, long long ll) {
void _addReplyLongLong(redisClient *c, long long ll, char prefix) {
char buf[128];
size_t len;
if (ll == 0) {
addReply(c,shared.czero);
return;
} else if (ll == 1) {
addReply(c,shared.cone);
return;
}
buf[0] = ':';
int len;
buf[0] = prefix;
len = ll2string(buf+1,sizeof(buf)-1,ll);
buf[len+1] = '\r';
buf[len+2] = '\n';
addReplySds(c,sdsnewlen(buf,len+3));
addReplyString(c,buf,len+3);
}
void addReplyUlong(redisClient *c, unsigned long ul) {
char buf[128];
size_t len;
void addReplyLongLong(redisClient *c, long long ll) {
_addReplyLongLong(c,ll,':');
}
if (ul == 0) {
addReply(c,shared.czero);
return;
} else if (ul == 1) {
addReply(c,shared.cone);
return;
}
len = snprintf(buf,sizeof(buf),":%lu\r\n",ul);
addReplySds(c,sdsnewlen(buf,len));
void addReplyMultiBulkLen(redisClient *c, long length) {
_addReplyLongLong(c,length,'*');
}
void addReplyBulkLen(redisClient *c, robj *obj) {
size_t len, intlen;
char buf[128];
size_t len;
if (obj->encoding == REDIS_ENCODING_RAW) {
len = sdslen(obj->ptr);
@@ -133,11 +316,7 @@ void addReplyBulkLen(redisClient *c, robj *obj) {
len++;
}
}
buf[0] = '$';
intlen = ll2string(buf+1,sizeof(buf)-1,(long long)len);
buf[intlen+1] = '\r';
buf[intlen+2] = '\n';
addReplySds(c,sdsnewlen(buf,intlen+3));
_addReplyLongLong(c,len,'$');
}
void addReplyBulk(redisClient *c, robj *obj) {
@@ -287,34 +466,6 @@ void freeClient(redisClient *c) {
zfree(c);
}
#define GLUEREPLY_UP_TO (1024)
static void glueReplyBuffersIfNeeded(redisClient *c) {
int copylen = 0;
char buf[GLUEREPLY_UP_TO];
listNode *ln;
listIter li;
robj *o;
listRewind(c->reply,&li);
while((ln = listNext(&li))) {
int objlen;
o = ln->value;
objlen = sdslen(o->ptr);
if (copylen + objlen <= GLUEREPLY_UP_TO) {
memcpy(buf+copylen,o->ptr,objlen);
copylen += objlen;
listDelNode(c->reply,ln);
} else {
if (copylen == 0) return;
break;
}
}
/* Now the output buffer is empty, add the new single element */
o = createObject(REDIS_STRING,sdsnewlen(buf,copylen));
listAddNodeHead(c->reply,o);
}
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
redisClient *c = privdata;
int nwritten = 0, totwritten = 0, objlen;
@@ -331,31 +482,48 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
return;
}
while(listLength(c->reply)) {
if (server.glueoutputbuf && listLength(c->reply) > 1)
glueReplyBuffersIfNeeded(c);
while(c->bufpos > 0 || listLength(c->reply)) {
if (c->bufpos > 0) {
if (c->flags & REDIS_MASTER) {
/* Don't reply to a master */
nwritten = c->bufpos - c->sentlen;
} else {
nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen);
if (nwritten <= 0) break;
}
c->sentlen += nwritten;
totwritten += nwritten;
o = listNodeValue(listFirst(c->reply));
objlen = sdslen(o->ptr);
if (objlen == 0) {
listDelNode(c->reply,listFirst(c->reply));
continue;
}
if (c->flags & REDIS_MASTER) {
/* Don't reply to a master */
nwritten = objlen - c->sentlen;
/* If the buffer was sent, set bufpos to zero to continue with
* the remainder of the reply. */
if (c->sentlen == c->bufpos) {
c->bufpos = 0;
c->sentlen = 0;
}
} else {
nwritten = write(fd, ((char*)o->ptr)+c->sentlen, objlen - c->sentlen);
if (nwritten <= 0) break;
}
c->sentlen += nwritten;
totwritten += nwritten;
/* If we fully sent the object on head go to the next one */
if (c->sentlen == objlen) {
listDelNode(c->reply,listFirst(c->reply));
c->sentlen = 0;
o = listNodeValue(listFirst(c->reply));
objlen = sdslen(o->ptr);
if (objlen == 0) {
listDelNode(c->reply,listFirst(c->reply));
continue;
}
if (c->flags & REDIS_MASTER) {
/* Don't reply to a master */
nwritten = objlen - c->sentlen;
} else {
nwritten = write(fd, ((char*)o->ptr)+c->sentlen,objlen-c->sentlen);
if (nwritten <= 0) break;
}
c->sentlen += nwritten;
totwritten += nwritten;
/* If we fully sent the object on head go to the next one */
if (c->sentlen == objlen) {
listDelNode(c->reply,listFirst(c->reply));
c->sentlen = 0;
}
}
/* Note that we avoid to send more thank REDIS_MAX_WRITE_PER_EVENT
* bytes, in a single threaded server it's a good idea to serve
@@ -464,6 +632,7 @@ void resetClient(redisClient *c) {
freeClientArgv(c);
c->bulklen = -1;
c->multibulk = 0;
c->newline = NULL;
}
void closeTimedoutClients(void) {
@@ -495,6 +664,8 @@ void closeTimedoutClients(void) {
}
void processInputBuffer(redisClient *c) {
int seeknewline = 0;
again:
/* Before to process the input buffer, make sure the client is not
* waitig for a blocking operation such as BLPOP. Note that the first
@@ -503,15 +674,19 @@ again:
* in the input buffer the client may be blocked, and the "goto again"
* will try to reiterate. The following line will make it return asap. */
if (c->flags & REDIS_BLOCKED || c->flags & REDIS_IO_WAIT) return;
if (seeknewline && c->bulklen == -1) c->newline = strchr(c->querybuf,'\n');
seeknewline = 1;
if (c->bulklen == -1) {
/* Read the first line of the query */
char *p = strchr(c->querybuf,'\n');
size_t querylen;
if (p) {
if (c->newline) {
char *p = c->newline;
sds query, *argv;
int argc, j;
c->newline = NULL;
query = c->querybuf;
c->querybuf = sdsempty();
querylen = 1+(p-(query));
@@ -598,8 +773,14 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
return;
}
if (nread) {
size_t oldlen = sdslen(c->querybuf);
c->querybuf = sdscatlen(c->querybuf, buf, nread);
c->lastinteraction = time(NULL);
/* Scan this new piece of the query for the newline. We do this
* here in order to make sure we perform this scan just one time
* per piece of buffer, leading to an O(N) scan instead of O(N*N) */
if (c->bulklen == -1 && c->newline == NULL)
c->newline = strchr(c->querybuf+oldlen,'\n');
} else {
return;
}
+36 -16
View File
@@ -19,14 +19,19 @@ robj *createObject(int type, void *ptr) {
o->encoding = REDIS_ENCODING_RAW;
o->ptr = ptr;
o->refcount = 1;
if (server.vm_enabled) {
/* Note that this code may run in the context of an I/O thread
* and accessing server.lruclock in theory is an error
* (no locks). But in practice this is safe, and even if we read
* garbage Redis will not fail. */
o->lru = server.lruclock;
o->storage = REDIS_VM_MEMORY;
}
/* Set the LRU to the current lruclock (minutes resolution).
* We do this regardless of the fact VM is active as LRU is also
* used for the maxmemory directive when Redis is used as cache.
*
* Note that this code may run in the context of an I/O thread
* and accessing server.lruclock in theory is an error
* (no locks). But in practice this is safe, and even if we read
* garbage Redis will not fail. */
o->lru = server.lruclock;
/* The following is only needed if VM is active, but since the conditional
* is probably more costly than initializing the field it's better to
* have every field properly initialized anyway. */
o->storage = REDIS_VM_MEMORY;
return o;
}
@@ -240,8 +245,12 @@ robj *tryObjectEncoding(robj *o) {
* range and if this is the main thread, since when VM is enabled we
* have the constraint that I/O thread should only handle non-shared
* objects, in order to avoid race conditions (we don't have per-object
* locking). */
if (value >= 0 && value < REDIS_SHARED_INTEGERS &&
* locking).
*
* Note that we also avoid using shared integers when maxmemory is used
* because very object needs to have a private LRU field for the LRU
* algorithm to work well. */
if (server.maxmemory == 0 && value >= 0 && value < REDIS_SHARED_INTEGERS &&
pthread_equal(pthread_self(),server.mainthread)) {
decrRefCount(o);
incrRefCount(shared.integers[value]);
@@ -354,9 +363,9 @@ int getDoubleFromObjectOrReply(redisClient *c, robj *o, double *target, const ch
double value;
if (getDoubleFromObject(o, &value) != REDIS_OK) {
if (msg != NULL) {
addReplySds(c, sdscatprintf(sdsempty(), "-ERR %s\r\n", msg));
addReplyError(c,(char*)msg);
} else {
addReplySds(c, sdsnew("-ERR value is not a double\r\n"));
addReplyError(c,"value is not a double");
}
return REDIS_ERR;
}
@@ -393,9 +402,9 @@ int getLongLongFromObjectOrReply(redisClient *c, robj *o, long long *target, con
long long value;
if (getLongLongFromObject(o, &value) != REDIS_OK) {
if (msg != NULL) {
addReplySds(c, sdscatprintf(sdsempty(), "-ERR %s\r\n", msg));
addReplyError(c,(char*)msg);
} else {
addReplySds(c, sdsnew("-ERR value is not an integer or out of range\r\n"));
addReplyError(c,"value is not an integer or out of range");
}
return REDIS_ERR;
}
@@ -410,9 +419,9 @@ int getLongFromObjectOrReply(redisClient *c, robj *o, long *target, const char *
if (getLongLongFromObjectOrReply(c, o, &value, msg) != REDIS_OK) return REDIS_ERR;
if (value < LONG_MIN || value > LONG_MAX) {
if (msg != NULL) {
addReplySds(c, sdscatprintf(sdsempty(), "-ERR %s\r\n", msg));
addReplyError(c,(char*)msg);
} else {
addReplySds(c, sdsnew("-ERR value is out of range\r\n"));
addReplyError(c,"value is out of range");
}
return REDIS_ERR;
}
@@ -433,3 +442,14 @@ char *strEncoding(int encoding) {
default: return "unknown";
}
}
/* Given an object returns the min number of seconds the object was never
* requested, using an approximated LRU algorithm. */
unsigned long estimateObjectIdleTime(robj *o) {
if (server.lruclock >= o->lru) {
return (server.lruclock - o->lru) * REDIS_LRU_CLOCK_RESOLUTION;
} else {
return ((REDIS_LRU_CLOCK_MAX - o->lru) + server.lruclock) *
REDIS_LRU_CLOCK_RESOLUTION;
}
}
+5 -4
View File
@@ -730,13 +730,14 @@ robj *rdbLoadObject(int type, FILE *fp) {
/* Load every single element of the list/set */
while(zsetlen--) {
robj *ele;
double *score = zmalloc(sizeof(double));
double score;
zskiplistNode *znode;
if ((ele = rdbLoadEncodedStringObject(fp)) == NULL) return NULL;
ele = tryObjectEncoding(ele);
if (rdbLoadDoubleValue(fp,score) == -1) return NULL;
dictAdd(zs->dict,ele,score);
zslInsert(zs->zsl,*score,ele);
if (rdbLoadDoubleValue(fp,&score) == -1) return NULL;
znode = zslInsert(zs->zsl,score,ele);
dictAdd(zs->dict,ele,&znode->score);
incrRefCount(ele); /* added to skiplist */
}
} else if (type == REDIS_HASH) {
+140 -108
View File
@@ -75,6 +75,7 @@ static struct config {
long long start;
long long totlatency;
int *latency;
char *title;
list *clients;
int quiet;
int loop;
@@ -206,16 +207,27 @@ static void clientDone(client c) {
}
}
/* Read a length from the buffer pointed to by *p, store the length in *len,
* and return the number of bytes that the cursor advanced. */
static int readLen(char *p, int *len) {
char *tail = strstr(p,"\r\n");
if (tail == NULL)
return 0;
*tail = '\0';
*len = atoi(p+1);
return tail+2-p;
}
static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask)
{
char buf[1024];
int nread;
char buf[1024], *p;
int nread, pos=0, len=0;
client c = privdata;
REDIS_NOTUSED(el);
REDIS_NOTUSED(fd);
REDIS_NOTUSED(mask);
nread = read(c->fd, buf, 1024);
nread = read(c->fd,buf,sizeof(buf));
if (nread == -1) {
fprintf(stderr, "Reading from socket: %s\n", strerror(errno));
freeClient(c);
@@ -228,82 +240,89 @@ static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask)
}
c->totreceived += nread;
c->ibuf = sdscatlen(c->ibuf,buf,nread);
len = sdslen(c->ibuf);
processdata:
/* Are we waiting for the first line of the command of for sdf
* count in bulk or multi bulk operations? */
if (c->replytype == REPLY_INT ||
c->replytype == REPLY_RETCODE ||
(c->replytype == REPLY_BULK && c->readlen == -1) ||
(c->replytype == REPLY_MBULK && c->readlen == -1) ||
(c->replytype == REPLY_MBULK && c->mbulk == -1)) {
char *p;
/* Check if the first line is complete. This is only true if
* there is a newline inside the buffer. */
if ((p = strchr(c->ibuf,'\n')) != NULL) {
if (c->replytype == REPLY_BULK ||
(c->replytype == REPLY_MBULK && c->mbulk != -1))
{
/* Read the count of a bulk reply (being it a single bulk or
* a multi bulk reply). "$<count>" for the protocol spec. */
*p = '\0';
*(p-1) = '\0';
c->readlen = atoi(c->ibuf+1)+2;
// printf("BULK ATOI: %s\n", c->ibuf+1);
/* Handle null bulk reply "$-1" */
if (c->readlen-2 == -1) {
clientDone(c);
return;
}
/* Leave all the rest in the input buffer */
c->ibuf = sdsrange(c->ibuf,(p-c->ibuf)+1,-1);
/* fall through to reach the point where the code will try
* to check if the bulk reply is complete. */
} else if (c->replytype == REPLY_MBULK && c->mbulk == -1) {
/* Read the count of a multi bulk reply. That is, how many
* bulk replies we have to read next. "*<count>" protocol. */
*p = '\0';
*(p-1) = '\0';
c->mbulk = atoi(c->ibuf+1);
/* Handle null bulk reply "*-1" */
if (c->mbulk == -1) {
clientDone(c);
return;
}
// printf("%p) %d elements list\n", c, c->mbulk);
/* Leave all the rest in the input buffer */
c->ibuf = sdsrange(c->ibuf,(p-c->ibuf)+1,-1);
goto processdata;
} else {
c->ibuf = sdstrim(c->ibuf,"\r\n");
clientDone(c);
return;
}
}
}
/* bulk read, did we read everything? */
if (((c->replytype == REPLY_MBULK && c->mbulk != -1) ||
(c->replytype == REPLY_BULK)) && c->readlen != -1 &&
(unsigned)c->readlen <= sdslen(c->ibuf))
c->replytype == REPLY_RETCODE)
{
// printf("BULKSTATUS mbulk:%d readlen:%d sdslen:%d\n",
// c->mbulk,c->readlen,sdslen(c->ibuf));
if (c->replytype == REPLY_BULK) {
clientDone(c);
} else if (c->replytype == REPLY_MBULK) {
// printf("%p) %d (%d)) ",c, c->mbulk, c->readlen);
// fwrite(c->ibuf,c->readlen,1,stdout);
// printf("\n");
if (--c->mbulk == 0) {
clientDone(c);
/* Check if the first line is complete. This is everything we need
* when waiting for an integer or status code reply.*/
if ((p = strstr(c->ibuf,"\r\n")) != NULL)
goto done;
} else if (c->replytype == REPLY_BULK) {
int advance = 0;
if (c->readlen < 0) {
advance = readLen(c->ibuf+pos,&c->readlen);
if (advance) {
pos += advance;
if (c->readlen == -1) {
goto done;
} else {
/* include the trailing \r\n */
c->readlen += 2;
}
} else {
c->ibuf = sdsrange(c->ibuf,c->readlen,-1);
c->readlen = -1;
goto processdata;
goto skip;
}
}
int canconsume;
if (c->readlen > 0) {
canconsume = c->readlen > (len-pos) ? (len-pos) : c->readlen;
c->readlen -= canconsume;
pos += canconsume;
}
if (c->readlen == 0)
goto done;
} else if (c->replytype == REPLY_MBULK) {
int advance = 0;
if (c->mbulk == -1) {
advance = readLen(c->ibuf+pos,&c->mbulk);
if (advance) {
pos += advance;
if (c->mbulk == -1)
goto done;
} else {
goto skip;
}
}
int canconsume;
while(c->mbulk > 0 && pos < len) {
if (c->readlen > 0) {
canconsume = c->readlen > (len-pos) ? (len-pos) : c->readlen;
c->readlen -= canconsume;
pos += canconsume;
if (c->readlen == 0)
c->mbulk--;
} else {
advance = readLen(c->ibuf+pos,&c->readlen);
if (advance) {
pos += advance;
if (c->readlen == -1) {
c->mbulk--;
continue;
} else {
/* include the trailing \r\n */
c->readlen += 2;
}
} else {
goto skip;
}
}
}
if (c->mbulk == 0)
goto done;
}
skip:
c->ibuf = sdsrange(c->ibuf,pos,-1);
return;
done:
clientDone(c);
return;
}
static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask)
@@ -371,13 +390,13 @@ static void createMissingClients(client c) {
}
}
static void showLatencyReport(char *title) {
static void showLatencyReport(void) {
int j, seen = 0;
float perc, reqpersec;
reqpersec = (float)config.donerequests/((float)config.totlatency/1000);
if (!config.quiet) {
printf("====== %s ======\n", title);
printf("====== %s ======\n", config.title);
printf(" %d requests completed in %.2f seconds\n", config.donerequests,
(float)config.totlatency/1000);
printf(" %d parallel clients\n", config.numclients);
@@ -393,20 +412,20 @@ static void showLatencyReport(char *title) {
}
printf("%.2f requests per second\n\n", reqpersec);
} else {
printf("%s: %.2f requests per second\n", title, reqpersec);
printf("%s: %.2f requests per second\n", config.title, reqpersec);
}
}
static void prepareForBenchmark(void)
{
static void prepareForBenchmark(char *title) {
memset(config.latency,0,sizeof(int)*(MAX_LATENCY+1));
config.title = title;
config.start = mstime();
config.donerequests = 0;
}
static void endBenchmark(char *title) {
static void endBenchmark(void) {
config.totlatency = mstime()-config.start;
showLatencyReport(title);
showLatencyReport();
freeAllClients();
}
@@ -480,6 +499,18 @@ void parseOptions(int argc, char **argv) {
}
}
int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData) {
REDIS_NOTUSED(eventLoop);
REDIS_NOTUSED(id);
REDIS_NOTUSED(clientData);
float dt = (float)(mstime()-config.start)/1000.0;
float rps = (float)config.donerequests/dt;
printf("%s: %.2f\r", config.title, rps);
fflush(stdout);
return 250; /* every 250ms */
}
int main(int argc, char **argv) {
client c;
@@ -491,6 +522,7 @@ int main(int argc, char **argv) {
config.requests = 10000;
config.liveclients = 0;
config.el = aeCreateEventLoop();
aeCreateTimeEvent(config.el,1,showThroughput,NULL,NULL);
config.keepalive = 1;
config.donerequests = 0;
config.datasize = 3;
@@ -514,7 +546,7 @@ int main(int argc, char **argv) {
if (config.idlemode) {
printf("Creating %d idle connections and waiting forever (Ctrl+C when done)\n", config.numclients);
prepareForBenchmark();
prepareForBenchmark("IDLE");
c = createClient();
if (!c) exit(1);
c->obuf = sdsempty();
@@ -525,25 +557,25 @@ int main(int argc, char **argv) {
}
do {
prepareForBenchmark();
prepareForBenchmark("PING");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"PING\r\n");
prepareClientForReply(c,REPLY_RETCODE);
createMissingClients(c);
aeMain(config.el);
endBenchmark("PING");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("PING (multi bulk)");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"*1\r\n$4\r\nPING\r\n");
prepareClientForReply(c,REPLY_RETCODE);
createMissingClients(c);
aeMain(config.el);
endBenchmark("PING (multi bulk)");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("SET");
c = createClient();
if (!c) exit(1);
c->obuf = sdscatprintf(c->obuf,"SET foo_rand000000000000 %d\r\n",config.datasize);
@@ -557,106 +589,106 @@ int main(int argc, char **argv) {
prepareClientForReply(c,REPLY_RETCODE);
createMissingClients(c);
aeMain(config.el);
endBenchmark("SET");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("GET");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"GET foo_rand000000000000\r\n");
prepareClientForReply(c,REPLY_BULK);
createMissingClients(c);
aeMain(config.el);
endBenchmark("GET");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("INCR");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"INCR counter_rand000000000000\r\n");
prepareClientForReply(c,REPLY_INT);
createMissingClients(c);
aeMain(config.el);
endBenchmark("INCR");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("LPUSH");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LPUSH mylist 3\r\nbar\r\n");
prepareClientForReply(c,REPLY_INT);
createMissingClients(c);
aeMain(config.el);
endBenchmark("LPUSH");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("LPOP");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LPOP mylist\r\n");
prepareClientForReply(c,REPLY_BULK);
createMissingClients(c);
aeMain(config.el);
endBenchmark("LPOP");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("SADD");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"SADD myset 24\r\ncounter_rand000000000000\r\n");
prepareClientForReply(c,REPLY_RETCODE);
createMissingClients(c);
aeMain(config.el);
endBenchmark("SADD");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("SPOP");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"SPOP myset\r\n");
prepareClientForReply(c,REPLY_BULK);
createMissingClients(c);
aeMain(config.el);
endBenchmark("SPOP");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("LPUSH (again, in order to bench LRANGE)");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LPUSH mylist 3\r\nbar\r\n");
prepareClientForReply(c,REPLY_RETCODE);
createMissingClients(c);
aeMain(config.el);
endBenchmark("LPUSH (again, in order to bench LRANGE)");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("LRANGE (first 100 elements)");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 99\r\n");
prepareClientForReply(c,REPLY_MBULK);
createMissingClients(c);
aeMain(config.el);
endBenchmark("LRANGE (first 100 elements)");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("LRANGE (first 300 elements)");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 299\r\n");
prepareClientForReply(c,REPLY_MBULK);
createMissingClients(c);
aeMain(config.el);
endBenchmark("LRANGE (first 300 elements)");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("LRANGE (first 450 elements)");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 449\r\n");
prepareClientForReply(c,REPLY_MBULK);
createMissingClients(c);
aeMain(config.el);
endBenchmark("LRANGE (first 450 elements)");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("LRANGE (first 600 elements)");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 599\r\n");
prepareClientForReply(c,REPLY_MBULK);
createMissingClients(c);
aeMain(config.el);
endBenchmark("LRANGE (first 600 elements)");
endBenchmark();
printf("\n");
} while(config.loop);
+6 -3
View File
@@ -496,13 +496,15 @@ void printCentered(int indent, int width, char* body) {
void printValid(uint64_t ops, uint64_t bytes) {
char body[80];
sprintf(body, "Processed %llu valid opcodes (in %llu bytes)", ops, bytes);
sprintf(body, "Processed %llu valid opcodes (in %llu bytes)",
(unsigned long long) ops, (unsigned long long) bytes);
printCentered(4, 80, body);
}
void printSkipped(uint64_t bytes, uint64_t offset) {
char body[80];
sprintf(body, "Skipped %llu bytes (resuming at 0x%08llx)", bytes, offset);
sprintf(body, "Skipped %llu bytes (resuming at 0x%08llx)",
(unsigned long long) bytes, (unsigned long long) offset);
printCentered(4, 80, body);
}
@@ -610,7 +612,8 @@ void process() {
/* print summary on errors */
if (num_errors) {
printf("\n");
printf("Total unprocessable opcodes: %llu\n", num_errors);
printf("Total unprocessable opcodes: %llu\n",
(unsigned long long) num_errors);
}
}
+8 -7
View File
@@ -62,6 +62,7 @@ static struct config {
int pubsub_mode;
int raw_output; /* output mode per command */
int tty; /* flag for default output format */
int stdinarg; /* get last arg from stdin. (-x option) */
char mb_sep;
char *auth;
char *historyfile;
@@ -337,6 +338,8 @@ static int parseOptions(int argc, char **argv) {
i++;
} else if (!strcmp(argv[i],"-h") && lastarg) {
usage();
} else if (!strcmp(argv[i],"-x")) {
config.stdinarg = 1;
} else if (!strcmp(argv[i],"-p") && !lastarg) {
config.hostport = atoi(argv[i+1]);
i++;
@@ -361,7 +364,7 @@ static int parseOptions(int argc, char **argv) {
"automatically used as last argument.\n"
);
} else if (!strcmp(argv[i],"-v")) {
printf("redis-cli shipped with Redis verison %s\n", REDIS_VERSION);
printf("redis-cli shipped with Redis version %s\n", REDIS_VERSION);
exit(0);
} else {
break;
@@ -389,9 +392,8 @@ static sds readArgFromStdin(void) {
static void usage() {
fprintf(stderr, "usage: redis-cli [-iv] [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 arg3 ... argN\n");
fprintf(stderr, "usage: echo \"argN\" | redis-cli [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 ... arg(N-1)\n");
fprintf(stderr, "\nIf a pipe from standard input is detected this data is used as last argument.\n\n");
fprintf(stderr, "example: cat /etc/passwd | redis-cli set my_passwd\n");
fprintf(stderr, "usage: echo \"argN\" | redis-cli -x [options] cmd arg1 arg2 ... arg(N-1)\n\n");
fprintf(stderr, "example: cat /etc/passwd | redis-cli -x set my_passwd\n");
fprintf(stderr, "example: redis-cli get my_passwd\n");
fprintf(stderr, "example: redis-cli -r 100 lpush mylist x\n");
fprintf(stderr, "\nRun in interactive mode: redis-cli -i or just don't pass any command\n");
@@ -456,9 +458,7 @@ static void repl() {
static int noninteractive(int argc, char **argv) {
int retval = 0;
struct stat s;
fstat(fileno(stdin), &s);
if (S_ISFIFO(s.st_mode) || S_ISREG(s.st_mode)) { /* pipe, regular file */
if (config.stdinarg) {
argv = zrealloc(argv, (argc+1)*sizeof(char*));
argv[argc] = readArgFromStdin();
retval = cliSendCommand(argc+1, argv, config.repeat);
@@ -481,6 +481,7 @@ int main(int argc, char **argv) {
config.monitor_mode = 0;
config.pubsub_mode = 0;
config.raw_output = 0;
config.stdinarg = 0;
config.auth = NULL;
config.historyfile = NULL;
config.tty = isatty(fileno(stdout)) || (getenv("FAKETTY") != NULL);
+142 -29
View File
@@ -51,6 +51,7 @@
#include <float.h>
#include <math.h>
#include <pthread.h>
#include <sys/resource.h>
/* Our shared "common" objects */
@@ -339,7 +340,7 @@ dictType zsetDictType = {
NULL, /* val dup */
dictEncObjKeyCompare, /* key compare */
dictRedisObjectDestructor, /* key destructor */
dictVanillaFree /* val destructor of malloc(sizeof(double)) */
NULL /* val destructor */
};
/* Db->dict, keys are sds strings, vals are Redis objects. */
@@ -477,6 +478,10 @@ void activeExpireCycle(void) {
}
}
void updateLRUClock(void) {
server.lruclock = (time(NULL)/REDIS_LRU_CLOCK_RESOLUTION) &
REDIS_LRU_CLOCK_MAX;
}
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
int j, loops = server.cronloops++;
@@ -489,19 +494,19 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
* in objects at every object access, and accuracy is not needed.
* To access a global var is faster than calling time(NULL) */
server.unixtime = time(NULL);
/* We have just 21 bits per object for LRU information.
* So we use an (eventually wrapping) LRU clock with minutes resolution.
/* We have just 22 bits per object for LRU information.
* So we use an (eventually wrapping) LRU clock with 10 seconds resolution.
* 2^22 bits with 10 seconds resoluton is more or less 1.5 years.
*
* When we need to select what object to swap, we compute the minimum
* time distance between the current lruclock and the object last access
* lruclock info. Even if clocks will wrap on overflow, there is
* the interesting property that we are sure that at least
* ABS(A-B) minutes passed between current time and timestamp B.
* Note that even if this will wrap after 1.5 years it's not a problem,
* everything will still work but just some object will appear younger
* to Redis. But for this to happen a given object should never be touched
* for 1.5 years.
*
* This is not precise but we don't need at all precision, but just
* something statistically reasonable.
* Note that you can change the resolution altering the
* REDIS_LRU_CLOCK_RESOLUTION define.
*/
server.lruclock = (time(NULL)/60)&((1<<21)-1);
updateLRUClock();
/* We received a SIGTERM, shutting down here in a safe way, as it is
* not ok doing so inside the signal handler. */
@@ -732,6 +737,8 @@ void initServerConfig() {
server.maxclients = 0;
server.blpop_blocked_clients = 0;
server.maxmemory = 0;
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU;
server.maxmemory_samples = 3;
server.vm_enabled = 0;
server.vm_swap_file = zstrdup("/tmp/redis-%p.vm");
server.vm_page_size = 256; /* 256 bytes per page */
@@ -746,6 +753,7 @@ void initServerConfig() {
server.set_max_intset_entries = REDIS_SET_MAX_INTSET_ENTRIES;
server.shutdown_asap = 0;
updateLRUClock();
resetServerSaveParams();
appendServerSaveParams(60*60,1); /* save after 1 hour and 1 change */
@@ -815,6 +823,8 @@ void initServer() {
server.stat_numconnections = 0;
server.stat_expiredkeys = 0;
server.stat_starttime = time(NULL);
server.stat_keyspace_misses = 0;
server.stat_keyspace_hits = 0;
server.unixtime = time(NULL);
aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL);
if (aeCreateFileEvent(server.el, server.fd, AE_READABLE,
@@ -888,9 +898,6 @@ void call(redisClient *c, struct redisCommand *cmd) {
int processCommand(redisClient *c) {
struct redisCommand *cmd;
/* Free some memory if needed (maxmemory setting) */
if (server.maxmemory) freeMemoryIfNeeded();
/* Handle the multi bulk command type. This is an alternative protocol
* supported by Redis in order to receive commands that are composed of
* multiple binary-safe "bulk" arguments. The latency of processing is
@@ -909,7 +916,7 @@ int processCommand(redisClient *c) {
} else if (c->multibulk) {
if (c->bulklen == -1) {
if (((char*)c->argv[0]->ptr)[0] != '$') {
addReplySds(c,sdsnew("-ERR multi bulk protocol error\r\n"));
addReplyError(c,"multi bulk protocol error");
resetClient(c);
return 1;
} else {
@@ -922,7 +929,7 @@ int processCommand(redisClient *c) {
bulklen < 0 || bulklen > 1024*1024*1024)
{
c->argc--;
addReplySds(c,sdsnew("-ERR invalid bulk write count\r\n"));
addReplyError(c,"invalid bulk write count");
resetClient(c);
return 1;
}
@@ -975,17 +982,14 @@ int processCommand(redisClient *c) {
* such wrong arity, bad command name and so forth. */
cmd = lookupCommand(c->argv[0]->ptr);
if (!cmd) {
addReplySds(c,
sdscatprintf(sdsempty(), "-ERR unknown command '%s'\r\n",
(char*)c->argv[0]->ptr));
addReplyErrorFormat(c,"unknown command '%s'",
(char*)c->argv[0]->ptr);
resetClient(c);
return 1;
} else if ((cmd->arity > 0 && cmd->arity != c->argc) ||
(c->argc < -cmd->arity)) {
addReplySds(c,
sdscatprintf(sdsempty(),
"-ERR wrong number of arguments for '%s' command\r\n",
cmd->name));
addReplyErrorFormat(c,"wrong number of arguments for '%s' command",
cmd->name);
resetClient(c);
return 1;
} else if (cmd->flags & REDIS_CMD_BULK && c->bulklen == -1) {
@@ -999,7 +1003,7 @@ int processCommand(redisClient *c) {
bulklen < 0 || bulklen > 1024*1024*1024)
{
c->argc--;
addReplySds(c,sdsnew("-ERR invalid bulk write count\r\n"));
addReplyError(c,"invalid bulk write count");
resetClient(c);
return 1;
}
@@ -1026,16 +1030,21 @@ int processCommand(redisClient *c) {
/* Check if the user is authenticated */
if (server.requirepass && !c->authenticated && cmd->proc != authCommand) {
addReplySds(c,sdsnew("-ERR operation not permitted\r\n"));
addReplyError(c,"operation not permitted");
resetClient(c);
return 1;
}
/* Handle the maxmemory directive */
/* Handle the maxmemory directive.
*
* First we try to free some memory if possible (if there are volatile
* keys in the dataset). If there are not the only thing we can do
* is returning an error. */
if (server.maxmemory) freeMemoryIfNeeded();
if (server.maxmemory && (cmd->flags & REDIS_CMD_DENYOOM) &&
zmalloc_used_memory() > server.maxmemory)
{
addReplySds(c,sdsnew("-ERR command not allowed when used memory > 'maxmemory'\r\n"));
addReplyError(c,"command not allowed when used memory > 'maxmemory'");
resetClient(c);
return 1;
}
@@ -1045,7 +1054,7 @@ int processCommand(redisClient *c) {
&&
cmd->proc != subscribeCommand && cmd->proc != unsubscribeCommand &&
cmd->proc != psubscribeCommand && cmd->proc != punsubscribeCommand) {
addReplySds(c,sdsnew("-ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context\r\n"));
addReplyError(c,"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context");
resetClient(c);
return 1;
}
@@ -1109,7 +1118,7 @@ void authCommand(redisClient *c) {
addReply(c,shared.ok);
} else {
c->authenticated = 0;
addReplySds(c,sdscatprintf(sdsempty(),"-ERR invalid password\r\n"));
addReplyError(c,"invalid password");
}
}
@@ -1150,6 +1159,10 @@ sds genRedisInfoString(void) {
time_t uptime = time(NULL)-server.stat_starttime;
int j;
char hmem[64];
struct rusage self_ru, c_ru;
getrusage(RUSAGE_SELF, &self_ru);
getrusage(RUSAGE_CHILDREN, &c_ru);
bytesToHuman(hmem,zmalloc_used_memory());
info = sdscatprintf(sdsempty(),
@@ -1161,11 +1174,17 @@ sds genRedisInfoString(void) {
"process_id:%ld\r\n"
"uptime_in_seconds:%ld\r\n"
"uptime_in_days:%ld\r\n"
"lru_clock:%ld\r\n"
"used_cpu_sys:%.2f\r\n"
"used_cpu_user:%.2f\r\n"
"used_cpu_sys_childrens:%.2f\r\n"
"used_cpu_user_childrens:%.2f\r\n"
"connected_clients:%d\r\n"
"connected_slaves:%d\r\n"
"blocked_clients:%d\r\n"
"used_memory:%zu\r\n"
"used_memory_human:%s\r\n"
"mem_fragmentation_ratio:%.2f\r\n"
"changes_since_last_save:%lld\r\n"
"bgsave_in_progress:%d\r\n"
"last_save_time:%ld\r\n"
@@ -1173,6 +1192,8 @@ sds genRedisInfoString(void) {
"total_connections_received:%lld\r\n"
"total_commands_processed:%lld\r\n"
"expired_keys:%lld\r\n"
"keyspace_hits:%lld\r\n"
"keyspace_misses:%lld\r\n"
"hash_max_zipmap_entries:%zu\r\n"
"hash_max_zipmap_value:%zu\r\n"
"pubsub_channels:%ld\r\n"
@@ -1187,11 +1208,17 @@ sds genRedisInfoString(void) {
(long) getpid(),
uptime,
uptime/(3600*24),
(unsigned long) server.lruclock,
(float)self_ru.ru_utime.tv_sec+(float)self_ru.ru_utime.tv_usec/1000000,
(float)self_ru.ru_stime.tv_sec+(float)self_ru.ru_stime.tv_usec/1000000,
(float)c_ru.ru_utime.tv_sec+(float)c_ru.ru_utime.tv_usec/1000000,
(float)c_ru.ru_stime.tv_sec+(float)c_ru.ru_stime.tv_usec/1000000,
listLength(server.clients)-listLength(server.slaves),
listLength(server.slaves),
server.blpop_blocked_clients,
zmalloc_used_memory(),
hmem,
zmalloc_get_fragmentation_ratio(),
server.dirty,
server.bgsavechildpid != -1,
server.lastsave,
@@ -1199,6 +1226,8 @@ sds genRedisInfoString(void) {
server.stat_numconnections,
server.stat_numcommands,
server.stat_expiredkeys,
server.stat_keyspace_hits,
server.stat_keyspace_misses,
server.hash_max_zipmap_entries,
server.hash_max_zipmap_value,
dictSize(server.pubsub_channels),
@@ -1315,10 +1344,93 @@ int tryFreeOneObjectFromFreelist(void) {
* memory usage.
*/
void freeMemoryIfNeeded(void) {
/* Remove keys accordingly to the active policy as long as we are
* over the memory limit. */
while (server.maxmemory && zmalloc_used_memory() > server.maxmemory) {
int j, k, freed = 0;
/* Basic strategy -- remove objects from the free list. */
if (tryFreeOneObjectFromFreelist() == REDIS_OK) continue;
for (j = 0; j < server.dbnum; j++) {
long bestval;
sds bestkey = NULL;
struct dictEntry *de;
redisDb *db = server.db+j;
dict *dict;
if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU ||
server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM)
{
dict = server.db[j].dict;
} else {
dict = server.db[j].expires;
}
if (dictSize(dict) == 0) continue;
/* volatile-random and allkeys-random policy */
if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM ||
server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_RANDOM)
{
de = dictGetRandomKey(dict);
bestkey = dictGetEntryKey(de);
}
/* volatile-lru and allkeys-lru policy */
else if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU ||
server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU)
{
for (k = 0; k < server.maxmemory_samples; k++) {
sds thiskey;
long thisval;
robj *o;
de = dictGetRandomKey(dict);
thiskey = dictGetEntryKey(de);
o = dictGetEntryVal(de);
thisval = estimateObjectIdleTime(o);
/* Higher idle time is better candidate for deletion */
if (bestkey == NULL || thisval > bestval) {
bestkey = thiskey;
bestval = thisval;
}
}
}
/* volatile-ttl */
else if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_TTL) {
for (k = 0; k < server.maxmemory_samples; k++) {
sds thiskey;
long thisval;
de = dictGetRandomKey(dict);
thiskey = dictGetEntryKey(de);
thisval = (long) dictGetEntryVal(de);
/* Expire sooner (minor expire unix timestamp) is better
* candidate for deletion */
if (bestkey == NULL || thisval < bestval) {
bestkey = thiskey;
bestval = thisval;
}
}
}
/* Finally remove the selected key. */
if (bestkey) {
robj *keyobj = createStringObject(bestkey,sdslen(bestkey));
dbDelete(db,keyobj);
server.stat_expiredkeys++;
decrRefCount(keyobj);
freed++;
}
}
if (!freed) return; /* nothing to free... */
}
while(0) {
int j, k, freed = 0;
for (j = 0; j < server.dbnum; j++) {
int minttl = -1;
sds minkey = NULL;
@@ -1341,6 +1453,7 @@ void freeMemoryIfNeeded(void) {
}
keyobj = createStringObject(minkey,sdslen(minkey));
dbDelete(server.db+j,keyobj);
server.stat_expiredkeys++;
decrRefCount(keyobj);
}
}
+47 -12
View File
@@ -47,6 +47,7 @@
#define REDIS_MAX_WRITE_PER_EVENT (1024*64)
#define REDIS_REQUEST_MAX_SIZE (1024*1024*256) /* max bytes in inline command */
#define REDIS_SHARED_INTEGERS 10000
#define REDIS_REPLY_CHUNK_BYTES (5*1500) /* 5 TCP packets with default MTU */
/* If more then REDIS_WRITEV_THRESHOLD write packets are pending use writev */
#define REDIS_WRITEV_THRESHOLD 3
@@ -197,6 +198,13 @@
#define REDIS_OP_DIFF 1
#define REDIS_OP_INTER 2
/* Redis maxmemory strategies */
#define REDIS_MAXMEMORY_VOLATILE_LRU 0
#define REDIS_MAXMEMORY_VOLATILE_TTL 1
#define REDIS_MAXMEMORY_VOLATILE_RANDOM 2
#define REDIS_MAXMEMORY_ALLKEYS_LRU 3
#define REDIS_MAXMEMORY_ALLKEYS_RANDOM 4
/* We can print the stacktrace, so our assert is defined this way: */
#define redisAssert(_e) ((_e)?(void)0 : (_redisAssert(#_e,__FILE__,__LINE__),_exit(1)))
#define redisPanic(_e) _redisPanic(#_e,__FILE__,__LINE__),_exit(1)
@@ -210,6 +218,8 @@ void _redisPanic(char *msg, char *file, int line);
/* A redis object, that is a type able to hold a string / list / set */
/* The actual Redis Object */
#define REDIS_LRU_CLOCK_MAX ((1<<21)-1) /* Max value of obj->lru */
#define REDIS_LRU_CLOCK_RESOLUTION 10 /* LRU clock resolution in seconds */
typedef struct redisObject {
unsigned type:4;
unsigned storage:2; /* REDIS_VM_MEMORY or REDIS_VM_SWAPPING */
@@ -285,6 +295,7 @@ typedef struct redisClient {
int dictid;
sds querybuf;
robj **argv, **mbargv;
char *newline; /* pointing to the detected newline in querybuf */
int argc, mbargc;
long bulklen; /* bulk read len. -1 if not in bulk read mode */
int multibulk; /* multi bulk command format active */
@@ -309,6 +320,10 @@ typedef struct redisClient {
list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */
dict *pubsub_channels; /* channels a client is interested in (SUBSCRIBE) */
list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */
/* Response buffer */
int bufpos;
char buf[REDIS_REPLY_CHUNK_BYTES];
} redisClient;
struct saveparam {
@@ -342,12 +357,14 @@ struct redisServer {
aeEventLoop *el;
int cronloops; /* number of times the cron function run */
list *objfreelist; /* A list of freed objects to avoid malloc() */
time_t lastsave; /* Unix time of last save succeeede */
time_t lastsave; /* Unix time of last save succeeede */
/* Fields used only for stats */
time_t stat_starttime; /* server start time */
long long stat_numcommands; /* number of processed commands */
long long stat_numconnections; /* number of connections received */
long long stat_expiredkeys; /* number of expired keys */
time_t stat_starttime; /* server start time */
long long stat_numcommands; /* number of processed commands */
long long stat_numconnections; /* number of connections received */
long long stat_expiredkeys; /* number of expired keys */
long long stat_keyspace_hits; /* number of successful lookups of keys */
long long stat_keyspace_misses; /* number of failed lookups of keys */
/* Configuration */
int verbosity;
int glueoutputbuf;
@@ -384,6 +401,8 @@ struct redisServer {
int replstate;
unsigned int maxclients;
unsigned long long maxmemory;
int maxmemory_policy;
int maxmemory_samples;
unsigned int blpop_blocked_clients;
unsigned int vm_blocked_clients;
/* Sort parameters - qsort_r() is only available under BSD so we
@@ -485,13 +504,14 @@ typedef struct _redisSortOperation {
} redisSortOperation;
/* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode {
struct zskiplistNode **forward;
struct zskiplistNode *backward;
unsigned int *span;
double score;
robj *obj;
double score;
struct zskiplistNode *backward;
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned int span;
} level[];
} zskiplistNode;
typedef struct zskiplist {
@@ -588,6 +608,8 @@ void resetClient(redisClient *c);
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask);
void sendReplyToClientWritev(aeEventLoop *el, int fd, void *privdata, int mask);
void addReply(redisClient *c, robj *obj);
void *addDeferredMultiBulkLength(redisClient *c);
void setDeferredMultiBulkLength(redisClient *c, void *node, long length);
void addReplySds(redisClient *c, sds s);
void processInputBuffer(redisClient *c);
void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
@@ -597,11 +619,23 @@ void addReplyBulkCString(redisClient *c, char *s);
void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void addReply(redisClient *c, robj *obj);
void addReplySds(redisClient *c, sds s);
void addReplyError(redisClient *c, char *err);
void addReplyStatus(redisClient *c, char *status);
void addReplyDouble(redisClient *c, double d);
void addReplyLongLong(redisClient *c, long long ll);
void addReplyUlong(redisClient *c, unsigned long ul);
void addReplyMultiBulkLen(redisClient *c, long length);
void *dupClientReplyValue(void *o);
#ifdef __GNUC__
void addReplyErrorFormat(redisClient *c, const char *fmt, ...)
__attribute__((format(printf, 2, 3)));
void addReplyStatusFormat(redisClient *c, const char *fmt, ...)
__attribute__((format(printf, 2, 3)));
#else
void addReplyErrorFormat(redisClient *c, const char *fmt, ...);
void addReplyStatusFormat(redisClient *c, const char *fmt, ...);
#endif
/* List data type */
void listTypeTryConversion(robj *subject, robj *value);
void listTypePush(robj *subject, robj *value, int where);
@@ -657,6 +691,7 @@ int getLongLongFromObject(robj *o, long long *target);
char *strEncoding(int encoding);
int compareStringObjects(robj *a, robj *b);
int equalStringObjects(robj *a, robj *b);
unsigned long estimateObjectIdleTime(robj *o);
/* Replication */
void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc);
@@ -688,7 +723,7 @@ void backgroundRewriteDoneHandler(int statloc);
/* Sorted sets data type */
zskiplist *zslCreate(void);
void zslFree(zskiplist *zsl);
void zslInsert(zskiplist *zsl, double score, robj *obj);
zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj);
/* Core functions */
void freeMemoryIfNeeded(void);
+3 -3
View File
@@ -179,7 +179,7 @@ void syncCommand(redisClient *c) {
/* Refuse SYNC requests if we are a slave but the link with our master
* is not ok... */
if (server.masterhost && server.replstate != REDIS_REPL_CONNECTED) {
addReplySds(c,sdsnew("-ERR Can't SYNC while not connected with my master\r\n"));
addReplyError(c,"Can't SYNC while not connected with my master");
return;
}
@@ -188,7 +188,7 @@ void syncCommand(redisClient *c) {
* buffer registering the differences between the BGSAVE and the current
* dataset, so that we can copy to other slaves if needed. */
if (listLength(c->reply) != 0) {
addReplySds(c,sdsnew("-ERR SYNC is invalid with pending input\r\n"));
addReplyError(c,"SYNC is invalid with pending input");
return;
}
@@ -226,7 +226,7 @@ void syncCommand(redisClient *c) {
redisLog(REDIS_NOTICE,"Starting BGSAVE for SYNC");
if (rdbSaveBackground(server.dbfilename) != REDIS_OK) {
redisLog(REDIS_NOTICE,"Replication failed, can't BGSAVE");
addReplySds(c,sdsnew("-ERR Unalbe to perform background save\r\n"));
addReplyError(c,"Unable to perform background save");
return;
}
c->replstate = REDIS_REPL_WAIT_BGSAVE_END;
+110 -10
View File
@@ -33,7 +33,6 @@
#include "sds.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include "zmalloc.h"
@@ -156,8 +155,8 @@ sds sdscpy(sds s, char *t) {
return sdscpylen(s, t, strlen(t));
}
sds sdscatprintf(sds s, const char *fmt, ...) {
va_list ap;
sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
va_list cpy;
char *buf, *t;
size_t buflen = 16;
@@ -169,9 +168,8 @@ sds sdscatprintf(sds s, const char *fmt, ...) {
if (buf == NULL) return NULL;
#endif
buf[buflen-2] = '\0';
va_start(ap, fmt);
vsnprintf(buf, buflen, fmt, ap);
va_end(ap);
va_copy(cpy,ap);
vsnprintf(buf, buflen, fmt, cpy);
if (buf[buflen-2] != '\0') {
zfree(buf);
buflen *= 2;
@@ -184,6 +182,15 @@ sds sdscatprintf(sds s, const char *fmt, ...) {
return t;
}
sds sdscatprintf(sds s, const char *fmt, ...) {
va_list ap;
char *t;
va_start(ap, fmt);
t = sdscatvprintf(s,fmt,ap);
va_end(ap);
return t;
}
sds sdstrim(sds s, const char *cset) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
char *start, *end, *sp, *ep;
@@ -216,13 +223,16 @@ sds sdsrange(sds s, int start, int end) {
}
newlen = (start > end) ? 0 : (end-start)+1;
if (newlen != 0) {
if (start >= (signed)len) start = len-1;
if (end >= (signed)len) end = len-1;
newlen = (start > end) ? 0 : (end-start)+1;
if (start >= (signed)len) {
newlen = 0;
} else if (end >= (signed)len) {
end = len-1;
newlen = (start > end) ? 0 : (end-start)+1;
}
} else {
start = 0;
}
if (start != 0) memmove(sh->buf, sh->buf+start, newlen);
if (start && newlen) memmove(sh->buf, sh->buf+start, newlen);
sh->buf[newlen] = 0;
sh->free = sh->free+(sh->len-newlen);
sh->len = newlen;
@@ -471,3 +481,93 @@ err:
if (current) sdsfree(current);
return NULL;
}
#ifdef SDS_TEST_MAIN
#include <stdio.h>
#include "testhelp.h"
int main(void) {
{
sds x = sdsnew("foo"), y;
test_cond("Create a string and obtain the length",
sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0)
sdsfree(x);
x = sdsnewlen("foo",2);
test_cond("Create a string with specified length",
sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0)
x = sdscat(x,"bar");
test_cond("Strings concatenation",
sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0);
x = sdscpy(x,"a");
test_cond("sdscpy() against an originally longer string",
sdslen(x) == 1 && memcmp(x,"a\0",2) == 0)
x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk");
test_cond("sdscpy() against an originally shorter string",
sdslen(x) == 33 &&
memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0)
sdsfree(x);
x = sdscatprintf(sdsempty(),"%d",123);
test_cond("sdscatprintf() seems working in the base case",
sdslen(x) == 3 && memcmp(x,"123\0",4) ==0)
sdsfree(x);
x = sdstrim(sdsnew("xxciaoyyy"),"xy");
test_cond("sdstrim() correctly trims characters",
sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0)
y = sdsrange(sdsdup(x),1,1);
test_cond("sdsrange(...,1,1)",
sdslen(y) == 1 && memcmp(y,"i\0",2) == 0)
sdsfree(y);
y = sdsrange(sdsdup(x),1,-1);
test_cond("sdsrange(...,1,-1)",
sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
sdsfree(y);
y = sdsrange(sdsdup(x),-2,-1);
test_cond("sdsrange(...,-2,-1)",
sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0)
sdsfree(y);
y = sdsrange(sdsdup(x),2,1);
test_cond("sdsrange(...,2,1)",
sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
sdsfree(y);
y = sdsrange(sdsdup(x),1,100);
test_cond("sdsrange(...,1,100)",
sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
sdsfree(y);
y = sdsrange(sdsdup(x),100,100);
test_cond("sdsrange(...,100,100)",
sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
sdsfree(y);
sdsfree(x);
x = sdsnew("foo");
y = sdsnew("foa");
test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0)
sdsfree(y);
sdsfree(x);
x = sdsnew("bar");
y = sdsnew("bar");
test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0)
sdsfree(y);
sdsfree(x);
x = sdsnew("aar");
y = sdsnew("bar");
test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0)
}
test_report()
}
#endif
+2
View File
@@ -32,6 +32,7 @@
#define __SDS_H
#include <sys/types.h>
#include <stdarg.h>
typedef char *sds;
@@ -53,6 +54,7 @@ sds sdscat(sds s, char *t);
sds sdscpylen(sds s, char *t, size_t len);
sds sdscpy(sds s, char *t);
sds sdscatvprintf(sds s, const char *fmt, va_list ap);
#ifdef __GNUC__
sds sdscatprintf(sds s, const char *fmt, ...)
__attribute__((format(printf, 2, 3)));
+1
View File
@@ -1,6 +1,7 @@
/* Solaris specific fixes */
#if defined(__GNUC__)
#include <math.h>
#undef isnan
#define isnan(x) \
__extension__({ __typeof (x) __x_a = (x); \
+2 -2
View File
@@ -307,7 +307,7 @@ void sortCommand(redisClient *c) {
outputlen = getop ? getop*(end-start+1) : end-start+1;
if (storekey == NULL) {
/* STORE option not specified, sent the sorting result to client */
addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",outputlen));
addReplyMultiBulkLen(c,outputlen);
for (j = start; j <= end; j++) {
listNode *ln;
listIter li;
@@ -369,7 +369,7 @@ void sortCommand(redisClient *c) {
* replaced. */
server.dirty += 1+outputlen;
touchWatchedKey(c->db,storekey);
addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",outputlen));
addReplyLongLong(c,outputlen);
}
/* Cleanup */
+7 -10
View File
@@ -249,7 +249,7 @@ void hmsetCommand(redisClient *c) {
robj *o;
if ((c->argc % 2) == 1) {
addReplySds(c,sdsnew("-ERR wrong number of arguments for HMSET\r\n"));
addReplyError(c,"wrong number of arguments for HMSET");
return;
}
@@ -315,7 +315,7 @@ void hmgetCommand(redisClient *c) {
/* Note the check for o != NULL happens inside the loop. This is
* done because objects that cannot be found are considered to be
* an empty hash. The reply should then be a series of NULLs. */
addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",c->argc-2));
addReplyMultiBulkLen(c,c->argc-2);
for (i = 2; i < c->argc; i++) {
if (o != NULL && (value = hashTypeGet(o,c->argv[i])) != NULL) {
addReplyBulk(c,value);
@@ -346,21 +346,19 @@ void hlenCommand(redisClient *c) {
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_HASH)) return;
addReplyUlong(c,hashTypeLength(o));
addReplyLongLong(c,hashTypeLength(o));
}
void genericHgetallCommand(redisClient *c, int flags) {
robj *o, *lenobj, *obj;
robj *o, *obj;
unsigned long count = 0;
hashTypeIterator *hi;
void *replylen = NULL;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
|| checkType(c,o,REDIS_HASH)) return;
lenobj = createObject(REDIS_STRING,NULL);
addReply(c,lenobj);
decrRefCount(lenobj);
replylen = addDeferredMultiBulkLength(c);
hi = hashTypeInitIterator(o);
while (hashTypeNext(hi) != REDIS_ERR) {
if (flags & REDIS_HASH_KEY) {
@@ -377,8 +375,7 @@ void genericHgetallCommand(redisClient *c, int flags) {
}
}
hashTypeReleaseIterator(hi);
lenobj->ptr = sdscatprintf(sdsempty(),"*%lu\r\n",count);
setDeferredMultiBulkLength(c,replylen,count);
}
void hkeysCommand(redisClient *c) {
+7 -7
View File
@@ -342,7 +342,7 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
server.dirty++;
}
addReplyUlong(c,listTypeLength(subject));
addReplyLongLong(c,listTypeLength(subject));
}
void lpushxCommand(redisClient *c) {
@@ -366,7 +366,7 @@ void linsertCommand(redisClient *c) {
void llenCommand(redisClient *c) {
robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.czero);
if (o == NULL || checkType(c,o,REDIS_LIST)) return;
addReplyUlong(c,listTypeLength(o));
addReplyLongLong(c,listTypeLength(o));
}
void lindexCommand(redisClient *c) {
@@ -494,7 +494,7 @@ void lrangeCommand(redisClient *c) {
rangelen = (end-start)+1;
/* Return the result in form of a multi-bulk reply */
addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",rangelen));
addReplyMultiBulkLen(c,rangelen);
listTypeIterator *li = listTypeInitIterator(o,start,REDIS_TAIL);
for (j = 0; j < rangelen; j++) {
redisAssert(listTypeNext(li,&entry));
@@ -594,7 +594,7 @@ void lremCommand(redisClient *c) {
decrRefCount(obj);
if (listTypeLength(subject) == 0) dbDelete(c->db,c->argv[1]);
addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",removed));
addReplyLongLong(c,removed);
if (removed) touchWatchedKey(c->db,c->argv[1]);
}
@@ -772,7 +772,7 @@ int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele) {
redisAssert(ln != NULL);
receiver = ln->value;
addReplySds(receiver,sdsnew("*2\r\n"));
addReplyMultiBulkLen(receiver,2);
addReplyBulk(receiver,key);
addReplyBulk(receiver,ele);
unblockClientWaitingData(receiver);
@@ -792,7 +792,7 @@ void blockingPopGenericCommand(redisClient *c, int where) {
/* Make sure the timeout is not negative */
if (lltimeout < 0) {
addReplySds(c,sdsnew("-ERR timeout is negative\r\n"));
addReplyError(c,"timeout is negative");
return;
}
@@ -822,7 +822,7 @@ void blockingPopGenericCommand(redisClient *c, int where) {
* "real" command will add the last element (the value)
* for us. If this souds like an hack to you it's just
* because it is... */
addReplySds(c,sdsnew("*2\r\n"));
addReplyMultiBulkLen(c,2);
addReplyBulk(c,argv[1]);
popGenericCommand(c,where);
+6 -7
View File
@@ -276,7 +276,7 @@ void scardCommand(redisClient *c) {
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_SET)) return;
addReplyUlong(c,setTypeSize(o));
addReplyLongLong(c,setTypeSize(o));
}
void spopCommand(redisClient *c) {
@@ -320,7 +320,8 @@ int qsortCompareSetsByCardinality(const void *s1, const void *s2) {
void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum, robj *dstkey) {
robj **sets = zmalloc(sizeof(robj*)*setnum);
setTypeIterator *si;
robj *ele, *lenobj = NULL, *dstset = NULL;
robj *ele, *dstset = NULL;
void *replylen = NULL;
unsigned long j, cardinality = 0;
for (j = 0; j < setnum; j++) {
@@ -356,9 +357,7 @@ void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum,
* to the output list and save the pointer to later modify it with the
* right length */
if (!dstkey) {
lenobj = createObject(REDIS_STRING,NULL);
addReply(c,lenobj);
decrRefCount(lenobj);
replylen = addDeferredMultiBulkLength(c);
} else {
/* If we have a target key where to store the resulting set
* create this key with an empty set inside */
@@ -400,7 +399,7 @@ void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum,
touchWatchedKey(c->db,dstkey);
server.dirty++;
} else {
lenobj->ptr = sdscatprintf(sdsempty(),"*%lu\r\n",cardinality);
setDeferredMultiBulkLength(c,replylen,cardinality);
}
zfree(sets);
}
@@ -470,7 +469,7 @@ void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *
/* Output the content of the resulting set, if not in STORE mode */
if (!dstkey) {
addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",cardinality));
addReplyMultiBulkLen(c,cardinality);
si = setTypeInitIterator(dstset);
while((ele = setTypeNext(si)) != NULL) {
addReplyBulk(c,ele);
+4 -4
View File
@@ -12,7 +12,7 @@ void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expir
if (getLongFromObjectOrReply(c, expire, &seconds, NULL) != REDIS_OK)
return;
if (seconds <= 0) {
addReplySds(c,sdsnew("-ERR invalid expire time in SETEX\r\n"));
addReplyError(c,"invalid expire time in SETEX");
return;
}
}
@@ -79,7 +79,7 @@ void getsetCommand(redisClient *c) {
void mgetCommand(redisClient *c) {
int j;
addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",c->argc-1));
addReplyMultiBulkLen(c,c->argc-1);
for (j = 1; j < c->argc; j++) {
robj *o = lookupKeyRead(c->db,c->argv[j]);
if (o == NULL) {
@@ -98,7 +98,7 @@ void msetGenericCommand(redisClient *c, int nx) {
int j, busykeys = 0;
if ((c->argc % 2) == 0) {
addReplySds(c,sdsnew("-ERR wrong number of arguments for MSET\r\n"));
addReplyError(c,"wrong number of arguments for MSET");
return;
}
/* Handle the NX flag. The MSETNX semantic is to return zero and don't
@@ -211,7 +211,7 @@ void appendCommand(redisClient *c) {
}
touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
addReplySds(c,sdscatprintf(sdsempty(),":%lu\r\n",(unsigned long)totlen));
addReplyLongLong(c,totlen);
}
void substrCommand(redisClient *c) {
+138 -162
View File
@@ -24,13 +24,7 @@
* from tail to head, useful for ZREVRANGE. */
zskiplistNode *zslCreateNode(int level, double score, robj *obj) {
zskiplistNode *zn = zmalloc(sizeof(*zn));
zn->forward = zmalloc(sizeof(zskiplistNode*) * level);
if (level > 1)
zn->span = zmalloc(sizeof(unsigned int) * (level - 1));
else
zn->span = NULL;
zskiplistNode *zn = zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));
zn->score = score;
zn->obj = obj;
return zn;
@@ -45,11 +39,8 @@ zskiplist *zslCreate(void) {
zsl->length = 0;
zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL);
for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) {
zsl->header->forward[j] = NULL;
/* span has space for ZSKIPLIST_MAXLEVEL-1 elements */
if (j < ZSKIPLIST_MAXLEVEL-1)
zsl->header->span[j] = 0;
zsl->header->level[j].forward = NULL;
zsl->header->level[j].span = 0;
}
zsl->header->backward = NULL;
zsl->tail = NULL;
@@ -58,19 +49,15 @@ zskiplist *zslCreate(void) {
void zslFreeNode(zskiplistNode *node) {
decrRefCount(node->obj);
zfree(node->forward);
zfree(node->span);
zfree(node);
}
void zslFree(zskiplist *zsl) {
zskiplistNode *node = zsl->header->forward[0], *next;
zskiplistNode *node = zsl->header->level[0].forward, *next;
zfree(zsl->header->forward);
zfree(zsl->header->span);
zfree(zsl->header);
while(node) {
next = node->forward[0];
next = node->level[0].forward;
zslFreeNode(node);
node = next;
}
@@ -84,7 +71,7 @@ int zslRandomLevel(void) {
return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}
void zslInsert(zskiplist *zsl, double score, robj *obj) {
zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj) {
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
unsigned int rank[ZSKIPLIST_MAXLEVEL];
int i, level;
@@ -93,13 +80,12 @@ void zslInsert(zskiplist *zsl, double score, robj *obj) {
for (i = zsl->level-1; i >= 0; i--) {
/* store rank that is crossed to reach the insert position */
rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];
while (x->forward[i] &&
(x->forward[i]->score < score ||
(x->forward[i]->score == score &&
compareStringObjects(x->forward[i]->obj,obj) < 0))) {
rank[i] += i > 0 ? x->span[i-1] : 1;
x = x->forward[i];
while (x->level[i].forward &&
(x->level[i].forward->score < score ||
(x->level[i].forward->score == score &&
compareStringObjects(x->level[i].forward->obj,obj) < 0))) {
rank[i] += x->level[i].span;
x = x->level[i].forward;
}
update[i] = x;
}
@@ -112,56 +98,51 @@ void zslInsert(zskiplist *zsl, double score, robj *obj) {
for (i = zsl->level; i < level; i++) {
rank[i] = 0;
update[i] = zsl->header;
update[i]->span[i-1] = zsl->length;
update[i]->level[i].span = zsl->length;
}
zsl->level = level;
}
x = zslCreateNode(level,score,obj);
for (i = 0; i < level; i++) {
x->forward[i] = update[i]->forward[i];
update[i]->forward[i] = x;
x->level[i].forward = update[i]->level[i].forward;
update[i]->level[i].forward = x;
/* update span covered by update[i] as x is inserted here */
if (i > 0) {
x->span[i-1] = update[i]->span[i-1] - (rank[0] - rank[i]);
update[i]->span[i-1] = (rank[0] - rank[i]) + 1;
}
x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
update[i]->level[i].span = (rank[0] - rank[i]) + 1;
}
/* increment span for untouched levels */
for (i = level; i < zsl->level; i++) {
update[i]->span[i-1]++;
update[i]->level[i].span++;
}
x->backward = (update[0] == zsl->header) ? NULL : update[0];
if (x->forward[0])
x->forward[0]->backward = x;
if (x->level[0].forward)
x->level[0].forward->backward = x;
else
zsl->tail = x;
zsl->length++;
return x;
}
/* Internal function used by zslDelete, zslDeleteByScore and zslDeleteByRank */
void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update) {
int i;
for (i = 0; i < zsl->level; i++) {
if (update[i]->forward[i] == x) {
if (i > 0) {
update[i]->span[i-1] += x->span[i-1] - 1;
}
update[i]->forward[i] = x->forward[i];
if (update[i]->level[i].forward == x) {
update[i]->level[i].span += x->level[i].span - 1;
update[i]->level[i].forward = x->level[i].forward;
} else {
/* invariant: i > 0, because update[0]->forward[0]
* is always equal to x */
update[i]->span[i-1] -= 1;
update[i]->level[i].span -= 1;
}
}
if (x->forward[0]) {
x->forward[0]->backward = x->backward;
if (x->level[0].forward) {
x->level[0].forward->backward = x->backward;
} else {
zsl->tail = x->backward;
}
while(zsl->level > 1 && zsl->header->forward[zsl->level-1] == NULL)
while(zsl->level > 1 && zsl->header->level[zsl->level-1].forward == NULL)
zsl->level--;
zsl->length--;
}
@@ -173,16 +154,16 @@ int zslDelete(zskiplist *zsl, double score, robj *obj) {
x = zsl->header;
for (i = zsl->level-1; i >= 0; i--) {
while (x->forward[i] &&
(x->forward[i]->score < score ||
(x->forward[i]->score == score &&
compareStringObjects(x->forward[i]->obj,obj) < 0)))
x = x->forward[i];
while (x->level[i].forward &&
(x->level[i].forward->score < score ||
(x->level[i].forward->score == score &&
compareStringObjects(x->level[i].forward->obj,obj) < 0)))
x = x->level[i].forward;
update[i] = x;
}
/* We may have multiple elements with the same score, what we need
* is to find the element with both the right score and object. */
x = x->forward[0];
x = x->level[0].forward;
if (x && score == x->score && equalStringObjects(x->obj,obj)) {
zslDeleteNode(zsl, x, update);
zslFreeNode(x);
@@ -204,16 +185,16 @@ unsigned long zslDeleteRangeByScore(zskiplist *zsl, double min, double max, dict
x = zsl->header;
for (i = zsl->level-1; i >= 0; i--) {
while (x->forward[i] && x->forward[i]->score < min)
x = x->forward[i];
while (x->level[i].forward && x->level[i].forward->score < min)
x = x->level[i].forward;
update[i] = x;
}
/* We may have multiple elements with the same score, what we need
* is to find the element with both the right score and object. */
x = x->forward[0];
x = x->level[0].forward;
while (x && x->score <= max) {
zskiplistNode *next = x->forward[0];
zslDeleteNode(zsl, x, update);
zskiplistNode *next = x->level[0].forward;
zslDeleteNode(zsl,x,update);
dictDelete(dict,x->obj);
zslFreeNode(x);
removed++;
@@ -231,18 +212,18 @@ unsigned long zslDeleteRangeByRank(zskiplist *zsl, unsigned int start, unsigned
x = zsl->header;
for (i = zsl->level-1; i >= 0; i--) {
while (x->forward[i] && (traversed + (i > 0 ? x->span[i-1] : 1)) < start) {
traversed += i > 0 ? x->span[i-1] : 1;
x = x->forward[i];
while (x->level[i].forward && (traversed + x->level[i].span) < start) {
traversed += x->level[i].span;
x = x->level[i].forward;
}
update[i] = x;
}
traversed++;
x = x->forward[0];
x = x->level[0].forward;
while (x && traversed <= end) {
zskiplistNode *next = x->forward[0];
zslDeleteNode(zsl, x, update);
zskiplistNode *next = x->level[0].forward;
zslDeleteNode(zsl,x,update);
dictDelete(dict,x->obj);
zslFreeNode(x);
removed++;
@@ -260,12 +241,12 @@ zskiplistNode *zslFirstWithScore(zskiplist *zsl, double score) {
x = zsl->header;
for (i = zsl->level-1; i >= 0; i--) {
while (x->forward[i] && x->forward[i]->score < score)
x = x->forward[i];
while (x->level[i].forward && x->level[i].forward->score < score)
x = x->level[i].forward;
}
/* We may have multiple elements with the same score, what we need
* is to find the element with both the right score and object. */
return x->forward[0];
return x->level[0].forward;
}
/* Find the rank for an element by both score and key.
@@ -279,12 +260,12 @@ unsigned long zslistTypeGetRank(zskiplist *zsl, double score, robj *o) {
x = zsl->header;
for (i = zsl->level-1; i >= 0; i--) {
while (x->forward[i] &&
(x->forward[i]->score < score ||
(x->forward[i]->score == score &&
compareStringObjects(x->forward[i]->obj,o) <= 0))) {
rank += i > 0 ? x->span[i-1] : 1;
x = x->forward[i];
while (x->level[i].forward &&
(x->level[i].forward->score < score ||
(x->level[i].forward->score == score &&
compareStringObjects(x->level[i].forward->obj,o) <= 0))) {
rank += x->level[i].span;
x = x->level[i].forward;
}
/* x might be equal to zsl->header, so test if obj is non-NULL */
@@ -303,10 +284,10 @@ zskiplistNode* zslistTypeGetElementByRank(zskiplist *zsl, unsigned long rank) {
x = zsl->header;
for (i = zsl->level-1; i >= 0; i--) {
while (x->forward[i] && (traversed + (i>0 ? x->span[i-1] : 1)) <= rank)
while (x->level[i].forward && (traversed + x->level[i].span) <= rank)
{
traversed += i > 0 ? x->span[i-1] : 1;
x = x->forward[i];
traversed += x->level[i].span;
x = x->level[i].forward;
}
if (traversed == rank) {
return x;
@@ -319,13 +300,11 @@ zskiplistNode* zslistTypeGetElementByRank(zskiplist *zsl, unsigned long rank) {
* Sorted set commands
*----------------------------------------------------------------------------*/
/* This generic command implements both ZADD and ZINCRBY.
* scoreval is the score if the operation is a ZADD (doincrement == 0) or
* the increment if the operation is a ZINCRBY (doincrement == 1). */
void zaddGenericCommand(redisClient *c, robj *key, robj *ele, double scoreval, int doincrement) {
/* This generic command implements both ZADD and ZINCRBY. */
void zaddGenericCommand(redisClient *c, robj *key, robj *ele, double score, int incr) {
robj *zsetobj;
zset *zs;
double *score;
zskiplistNode *znode;
zsetobj = lookupKeyWrite(c->db,key);
if (zsetobj == NULL) {
@@ -339,72 +318,72 @@ void zaddGenericCommand(redisClient *c, robj *key, robj *ele, double scoreval, i
}
zs = zsetobj->ptr;
/* Ok now since we implement both ZADD and ZINCRBY here the code
* needs to handle the two different conditions. It's all about setting
* '*score', that is, the new score to set, to the right value. */
score = zmalloc(sizeof(double));
if (doincrement) {
dictEntry *de;
/* Since both ZADD and ZINCRBY are implemented here, we need to increment
* the score first by the current score if ZINCRBY is called. */
if (incr) {
/* Read the old score. If the element was not present starts from 0 */
de = dictFind(zs->dict,ele);
if (de) {
double *oldscore = dictGetEntryVal(de);
*score = *oldscore + scoreval;
} else {
*score = scoreval;
}
if (isnan(*score)) {
addReplySds(c,
sdsnew("-ERR resulting score is not a number (NaN)\r\n"));
zfree(score);
dictEntry *de = dictFind(zs->dict,ele);
if (de != NULL)
score += *(double*)dictGetEntryVal(de);
if (isnan(score)) {
addReplyError(c,"resulting score is not a number (NaN)");
/* Note that we don't need to check if the zset may be empty and
* should be removed here, as we can only obtain Nan as score if
* there was already an element in the sorted set. */
return;
}
} else {
*score = scoreval;
}
/* What follows is a simple remove and re-insert operation that is common
* to both ZADD and ZINCRBY... */
if (dictAdd(zs->dict,ele,score) == DICT_OK) {
/* case 1: New element */
/* We need to remove and re-insert the element when it was already present
* in the dictionary, to update the skiplist. Note that we delay adding a
* pointer to the score because we want to reference the score in the
* skiplist node. */
if (dictAdd(zs->dict,ele,NULL) == DICT_OK) {
dictEntry *de;
/* New element */
incrRefCount(ele); /* added to hash */
zslInsert(zs->zsl,*score,ele);
znode = zslInsert(zs->zsl,score,ele);
incrRefCount(ele); /* added to skiplist */
/* Update the score in the dict entry */
de = dictFind(zs->dict,ele);
redisAssert(de != NULL);
dictGetEntryVal(de) = &znode->score;
touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
if (doincrement)
addReplyDouble(c,*score);
if (incr)
addReplyDouble(c,score);
else
addReply(c,shared.cone);
} else {
dictEntry *de;
double *oldscore;
robj *curobj;
double *curscore;
int deleted;
/* case 2: Score update operation */
/* Update score */
de = dictFind(zs->dict,ele);
redisAssert(de != NULL);
oldscore = dictGetEntryVal(de);
if (*score != *oldscore) {
int deleted;
curobj = dictGetEntryKey(de);
curscore = dictGetEntryVal(de);
/* Remove and insert the element in the skip list with new score */
deleted = zslDelete(zs->zsl,*oldscore,ele);
/* When the score is updated, reuse the existing string object to
* prevent extra alloc/dealloc of strings on ZINCRBY. */
if (score != *curscore) {
deleted = zslDelete(zs->zsl,*curscore,curobj);
redisAssert(deleted != 0);
zslInsert(zs->zsl,*score,ele);
incrRefCount(ele);
/* Update the score in the hash table */
dictReplace(zs->dict,ele,score);
znode = zslInsert(zs->zsl,score,curobj);
incrRefCount(curobj);
/* Update the score in the current dict entry */
dictGetEntryVal(de) = &znode->score;
touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
} else {
zfree(score);
}
if (doincrement)
addReplyDouble(c,*score);
if (incr)
addReplyDouble(c,score);
else
addReply(c,shared.czero);
}
@@ -426,7 +405,7 @@ void zremCommand(redisClient *c) {
robj *zsetobj;
zset *zs;
dictEntry *de;
double *oldscore;
double curscore;
int deleted;
if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
@@ -439,8 +418,8 @@ void zremCommand(redisClient *c) {
return;
}
/* Delete from the skiplist */
oldscore = dictGetEntryVal(de);
deleted = zslDelete(zs->zsl,*oldscore,c->argv[2]);
curscore = *(double*)dictGetEntryVal(de);
deleted = zslDelete(zs->zsl,curscore,c->argv[2]);
redisAssert(deleted != 0);
/* Delete from the hash table */
@@ -554,6 +533,7 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
zsetopsrc *src;
robj *dstobj;
zset *dstzset;
zskiplistNode *znode;
dictIterator *di;
dictEntry *de;
int touched = 0;
@@ -561,7 +541,8 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
/* expect setnum input keys to be given */
setnum = atoi(c->argv[2]->ptr);
if (setnum < 1) {
addReplySds(c,sdsnew("-ERR at least 1 input key is needed for ZUNIONSTORE/ZINTERSTORE\r\n"));
addReplyError(c,
"at least 1 input key is needed for ZUNIONSTORE/ZINTERSTORE");
return;
}
@@ -644,28 +625,26 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
* from small to large, all src[i > 0].dict are non-empty too */
di = dictGetIterator(src[0].dict);
while((de = dictNext(di)) != NULL) {
double *score = zmalloc(sizeof(double)), value;
*score = src[0].weight * zunionInterDictValue(de);
double score, value;
score = src[0].weight * zunionInterDictValue(de);
for (j = 1; j < setnum; j++) {
dictEntry *other = dictFind(src[j].dict,dictGetEntryKey(de));
if (other) {
value = src[j].weight * zunionInterDictValue(other);
zunionInterAggregate(score, value, aggregate);
zunionInterAggregate(&score, value, aggregate);
} else {
break;
}
}
/* skip entry when not present in every source dict */
if (j != setnum) {
zfree(score);
} else {
/* accept entry only when present in every source dict */
if (j == setnum) {
robj *o = dictGetEntryKey(de);
dictAdd(dstzset->dict,o,score);
incrRefCount(o); /* added to dictionary */
zslInsert(dstzset->zsl,*score,o);
znode = zslInsert(dstzset->zsl,score,o);
incrRefCount(o); /* added to skiplist */
dictAdd(dstzset->dict,o,&znode->score);
incrRefCount(o); /* added to dictionary */
}
}
dictReleaseIterator(di);
@@ -676,11 +655,12 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
di = dictGetIterator(src[i].dict);
while((de = dictNext(di)) != NULL) {
/* skip key when already processed */
if (dictFind(dstzset->dict,dictGetEntryKey(de)) != NULL) continue;
double score, value;
double *score = zmalloc(sizeof(double)), value;
*score = src[i].weight * zunionInterDictValue(de);
/* skip key when already processed */
if (dictFind(dstzset->dict,dictGetEntryKey(de)) != NULL)
continue;
score = src[i].weight * zunionInterDictValue(de);
/* because the zsets are sorted by size, its only possible
* for sets at larger indices to hold this entry */
@@ -688,15 +668,15 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
dictEntry *other = dictFind(src[j].dict,dictGetEntryKey(de));
if (other) {
value = src[j].weight * zunionInterDictValue(other);
zunionInterAggregate(score, value, aggregate);
zunionInterAggregate(&score, value, aggregate);
}
}
robj *o = dictGetEntryKey(de);
dictAdd(dstzset->dict,o,score);
incrRefCount(o); /* added to dictionary */
zslInsert(dstzset->zsl,*score,o);
znode = zslInsert(dstzset->zsl,score,o);
incrRefCount(o); /* added to skiplist */
dictAdd(dstzset->dict,o,&znode->score);
incrRefCount(o); /* added to dictionary */
}
dictReleaseIterator(di);
}
@@ -778,18 +758,17 @@ void zrangeGenericCommand(redisClient *c, int reverse) {
ln = start == 0 ? zsl->tail : zslistTypeGetElementByRank(zsl, llen-start);
} else {
ln = start == 0 ?
zsl->header->forward[0] : zslistTypeGetElementByRank(zsl, start+1);
zsl->header->level[0].forward : zslistTypeGetElementByRank(zsl, start+1);
}
/* Return the result in form of a multi-bulk reply */
addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",
withscores ? (rangelen*2) : rangelen));
addReplyMultiBulkLen(c,withscores ? (rangelen*2) : rangelen);
for (j = 0; j < rangelen; j++) {
ele = ln->obj;
addReplyBulk(c,ele);
if (withscores)
addReplyDouble(c,ln->score);
ln = reverse ? ln->backward : ln->forward[0];
ln = reverse ? ln->backward : ln->level[0].forward;
}
}
@@ -840,8 +819,7 @@ void genericZrangebyscoreCommand(redisClient *c, int justcount) {
if (c->argc != (4 + withscores) && c->argc != (7 + withscores))
badsyntax = 1;
if (badsyntax) {
addReplySds(c,
sdsnew("-ERR wrong number of arguments for ZRANGEBYSCORE\r\n"));
addReplyError(c,"wrong number of arguments for ZRANGEBYSCORE");
return;
}
@@ -866,13 +844,14 @@ void genericZrangebyscoreCommand(redisClient *c, int justcount) {
zset *zsetobj = o->ptr;
zskiplist *zsl = zsetobj->zsl;
zskiplistNode *ln;
robj *ele, *lenobj = NULL;
robj *ele;
void *replylen = NULL;
unsigned long rangelen = 0;
/* Get the first node with the score >= min, or with
* score > min if 'minex' is true. */
ln = zslFirstWithScore(zsl,min);
while (minex && ln && ln->score == min) ln = ln->forward[0];
while (minex && ln && ln->score == min) ln = ln->level[0].forward;
if (ln == NULL) {
/* No element matching the speciifed interval */
@@ -884,16 +863,13 @@ void genericZrangebyscoreCommand(redisClient *c, int justcount) {
* are in the list, so we push this object that will represent
* the multi-bulk length in the output buffer, and will "fix"
* it later */
if (!justcount) {
lenobj = createObject(REDIS_STRING,NULL);
addReply(c,lenobj);
decrRefCount(lenobj);
}
if (!justcount)
replylen = addDeferredMultiBulkLength(c);
while(ln && (maxex ? (ln->score < max) : (ln->score <= max))) {
if (offset) {
offset--;
ln = ln->forward[0];
ln = ln->level[0].forward;
continue;
}
if (limit == 0) break;
@@ -903,14 +879,14 @@ void genericZrangebyscoreCommand(redisClient *c, int justcount) {
if (withscores)
addReplyDouble(c,ln->score);
}
ln = ln->forward[0];
ln = ln->level[0].forward;
rangelen++;
if (limit > 0) limit--;
}
if (justcount) {
addReplyLongLong(c,(long)rangelen);
} else {
lenobj->ptr = sdscatprintf(sdsempty(),"*%lu\r\n",
setDeferredMultiBulkLength(c,replylen,
withscores ? (rangelen*2) : rangelen);
}
}
@@ -933,7 +909,7 @@ void zcardCommand(redisClient *c) {
checkType(c,o,REDIS_ZSET)) return;
zs = o->ptr;
addReplyUlong(c,zs->zsl->length);
addReplyLongLong(c,zs->zsl->length);
}
void zscoreCommand(redisClient *c) {
+54
View File
@@ -0,0 +1,54 @@
/* This is a really minimal testing framework for C.
*
* Example:
*
* test_cond("Check if 1 == 1", 1==1)
* test_cond("Check if 5 > 10", 5 > 10)
* test_report()
*
* Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __TESTHELP_H
#define __TESTHELP_H
int __failed_tests = 0;
int __test_num = 0;
#define test_cond(descr,_c) do { \
__test_num++; printf("%d - %s: ", __test_num, descr); \
if(_c) printf("PASSED\n"); else {printf("FAILED\n"); __failed_tests++;} \
} while(0);
#define test_report() do { \
printf("%d tests, %d passed, %d failed\n", __test_num, \
__test_num-__failed_tests, __failed_tests); \
if (__failed_tests) { \
printf("=== WARNING === We have failed tests here...\n"); \
} \
} while(0);
#endif
+1 -1
View File
@@ -1 +1 @@
#define REDIS_VERSION "2.1.4"
#define REDIS_VERSION "2.1.5"
+6 -1
View File
@@ -110,6 +110,11 @@ void vmInit(void) {
/* LZF requires a lot of stack */
pthread_attr_init(&server.io_threads_attr);
pthread_attr_getstacksize(&server.io_threads_attr, &stacksize);
/* Solaris may report a stacksize of 0, let's set it to 1 otherwise
* multiplying it by 2 in the while loop later will not really help ;) */
if (!stacksize) stacksize = 1;
while (stacksize < REDIS_THREAD_STACK_SIZE) stacksize *= 2;
pthread_attr_setstacksize(&server.io_threads_attr, stacksize);
/* Listen for events in the threaded I/O pipe */
@@ -357,7 +362,7 @@ robj *vmPreviewObject(robj *o) {
double computeObjectSwappability(robj *o) {
/* actual age can be >= minage, but not < minage. As we use wrapping
* 21 bit clocks with minutes resolution for the LRU. */
time_t minage = abs(server.lruclock - o->lru);
time_t minage = estimateObjectIdleTime(o);
long asize = 0, elesize;
robj *ele;
list *l;
+434 -96
View File
@@ -1,17 +1,63 @@
/* Memory layout of a ziplist, containing "foo", "bar", "quux":
* <zlbytes><zllen><len>"foo"<len>"bar"<len>"quux"
/* The ziplist is a specially encoded dually linked list that is designed
* to be very memory efficient. It stores both strings and integer values,
* where integers are encoded as actual integers instead of a series of
* characters. It allows push and pop operations on either side of the list
* in O(1) time. However, because every operation requires a reallocation of
* the memory used by the ziplist, the actual complexity is related to the
* amount of memory used by the ziplist.
*
* <zlbytes> is an unsigned integer to hold the number of bytes that
* the ziplist occupies. This is stored to not have to traverse the ziplist
* to know the new length when pushing.
* ----------------------------------------------------------------------------
*
* <zllen> is the number of items in the ziplist. When this value is
* greater than 254, we need to traverse the entire list to know
* how many items it holds.
* ZIPLIST OVERALL LAYOUT:
* The general layout of the ziplist is as follows:
* <zlbytes><zltail><zllen><entry><entry><zlend>
*
* <len> is the number of bytes occupied by a single entry. When this
* number is greater than 253, the length will occupy 5 bytes, where
* the extra bytes contain an unsigned integer to hold the length.
* <zlbytes> is an unsigned integer to hold the number of bytes that the
* ziplist occupies. This value needs to be stored to be able to resize the
* entire structure without the need to traverse it first.
*
* <zltail> is the offset to the last entry in the list. This allows a pop
* operation on the far side of the list without the need for full traversal.
*
* <zllen> is the number of entries.When this value is larger than 2**16-2,
* we need to traverse the entire list to know how many items it holds.
*
* <zlend> is a single byte special value, equal to 255, which indicates the
* end of the list.
*
* ZIPLIST ENTRIES:
* Every entry in the ziplist is prefixed by a header that contains two pieces
* of information. First, the length of the previous entry is stored to be
* able to traverse the list from back to front. Second, the encoding with an
* optional string length of the entry itself is stored.
*
* The length of the previous entry is encoded in the following way:
* If this length is smaller than 254 bytes, it will only consume a single
* byte that takes the length as value. When the length is greater than or
* equal to 254, it will consume 5 bytes. The first byte is set to 254 to
* indicate a larger value is following. The remaining 4 bytes take the
* length of the previous entry as value.
*
* The other header field of the entry itself depends on the contents of the
* entry. When the entry is a string, the first 2 bits of this header will hold
* the type of encoding used to store the length of the string, followed by the
* actual length of the string. When the entry is an integer the first 2 bits
* are both set to 1. The following 2 bits are used to specify what kind of
* integer will be stored after this header. An overview of the different
* types and encodings is as follows:
*
* |00pppppp| - 1 byte
* String value with length less than or equal to 63 bytes (6 bits).
* |01pppppp|qqqqqqqq| - 2 bytes
* String value with length less than or equal to 16383 bytes (14 bits).
* |10______|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| - 5 bytes
* String value with length greater than or equal to 16384 bytes.
* |1100____| - 1 byte
* Integer encoded as int16_t (2 bytes).
* |1101____| - 1 byte
* Integer encoded as int32_t (4 bytes).
* |1110____| - 1 byte
* Integer encoded as int64_t (8 bytes).
*/
#include <stdio.h>
@@ -25,25 +71,20 @@
int ll2string(char *s, size_t len, long long value);
/* Important note: the ZIP_END value is used to depict the end of the
* ziplist structure. When a pointer contains an entry, the first couple
* of bytes contain the encoded length of the previous entry. This length
* is encoded as ZIP_ENC_RAW length, so the first two bits will contain 00
* and the byte will therefore never have a value of 255. */
#define ZIP_END 255
#define ZIP_BIGLEN 254
/* Entry encoding */
#define ZIP_ENC_RAW 0
#define ZIP_ENC_INT16 1
#define ZIP_ENC_INT32 2
#define ZIP_ENC_INT64 3
#define ZIP_ENCODING(p) ((p)[0] >> 6)
/* Different encoding/length possibilities */
#define ZIP_STR_06B (0 << 6)
#define ZIP_STR_14B (1 << 6)
#define ZIP_STR_32B (2 << 6)
#define ZIP_INT_16B (0xc0 | 0<<4)
#define ZIP_INT_32B (0xc0 | 1<<4)
#define ZIP_INT_64B (0xc0 | 2<<4)
/* Length encoding for raw entries */
#define ZIP_LEN_INLINE 0
#define ZIP_LEN_UINT16 1
#define ZIP_LEN_UINT32 2
/* Macro's to determine type */
#define ZIP_IS_STR(enc) (((enc) & 0xc0) < 0xc0)
#define ZIP_IS_INT(enc) (!ZIP_IS_STR(enc) && ((enc) & 0x30) < 0x30)
/* Utility macros */
#define ZIPLIST_BYTES(zl) (*((uint32_t*)(zl)))
@@ -67,14 +108,25 @@ typedef struct zlentry {
unsigned char *p;
} zlentry;
/* Return the encoding pointer to by 'p'. */
static unsigned int zipEntryEncoding(unsigned char *p) {
/* String encoding: 2 MSBs */
unsigned char b = p[0] & 0xc0;
if (b < 0xc0) {
return b;
} else {
/* Integer encoding: 4 MSBs */
return p[0] & 0xf0;
}
assert(NULL);
}
/* Return bytes needed to store integer encoded by 'encoding' */
static unsigned int zipEncodingSize(unsigned char encoding) {
if (encoding == ZIP_ENC_INT16) {
return sizeof(int16_t);
} else if (encoding == ZIP_ENC_INT32) {
return sizeof(int32_t);
} else if (encoding == ZIP_ENC_INT64) {
return sizeof(int64_t);
static unsigned int zipIntSize(unsigned char encoding) {
switch(encoding) {
case ZIP_INT_16B: return sizeof(int16_t);
case ZIP_INT_32B: return sizeof(int32_t);
case ZIP_INT_64B: return sizeof(int64_t);
}
assert(NULL);
}
@@ -82,23 +134,28 @@ static unsigned int zipEncodingSize(unsigned char encoding) {
/* Decode the encoded length pointed by 'p'. If a pointer to 'lensize' is
* provided, it is set to the number of bytes required to encode the length. */
static unsigned int zipDecodeLength(unsigned char *p, unsigned int *lensize) {
unsigned char encoding = ZIP_ENCODING(p), lenenc;
unsigned char encoding = zipEntryEncoding(p);
unsigned int len;
if (encoding == ZIP_ENC_RAW) {
lenenc = (p[0] >> 4) & 0x3;
if (lenenc == ZIP_LEN_INLINE) {
len = p[0] & 0xf;
if (ZIP_IS_STR(encoding)) {
switch(encoding) {
case ZIP_STR_06B:
len = p[0] & 0x3f;
if (lensize) *lensize = 1;
} else if (lenenc == ZIP_LEN_UINT16) {
len = p[1] | (p[2] << 8);
if (lensize) *lensize = 3;
} else {
len = p[1] | (p[2] << 8) | (p[3] << 16) | (p[4] << 24);
break;
case ZIP_STR_14B:
len = ((p[0] & 0x3f) << 8) | p[1];
if (lensize) *lensize = 2;
break;
case ZIP_STR_32B:
len = (p[1] << 24) | (p[2] << 16) | (p[3] << 8) | p[4];
if (lensize) *lensize = 5;
break;
default:
assert(NULL);
}
} else {
len = zipEncodingSize(encoding);
len = zipIntSize(encoding);
if (lensize) *lensize = 1;
}
return len;
@@ -106,34 +163,36 @@ static unsigned int zipDecodeLength(unsigned char *p, unsigned int *lensize) {
/* Encode the length 'l' writing it in 'p'. If p is NULL it just returns
* the amount of bytes required to encode such a length. */
static unsigned int zipEncodeLength(unsigned char *p, char encoding, unsigned int rawlen) {
unsigned char len = 1, lenenc, buf[5];
if (encoding == ZIP_ENC_RAW) {
if (rawlen <= 0xf) {
static unsigned int zipEncodeLength(unsigned char *p, unsigned char encoding, unsigned int rawlen) {
unsigned char len = 1, buf[5];
if (ZIP_IS_STR(encoding)) {
/* Although encoding is given it may not be set for strings,
* so we determine it here using the raw length. */
if (rawlen <= 0x3f) {
if (!p) return len;
lenenc = ZIP_LEN_INLINE;
buf[0] = rawlen;
} else if (rawlen <= 0xffff) {
len += 2;
buf[0] = ZIP_STR_06B | rawlen;
} else if (rawlen <= 0x3fff) {
len += 1;
if (!p) return len;
lenenc = ZIP_LEN_UINT16;
buf[1] = (rawlen ) & 0xff;
buf[2] = (rawlen >> 8) & 0xff;
buf[0] = ZIP_STR_14B | ((rawlen >> 8) & 0x3f);
buf[1] = rawlen & 0xff;
} else {
len += 4;
if (!p) return len;
lenenc = ZIP_LEN_UINT32;
buf[1] = (rawlen ) & 0xff;
buf[2] = (rawlen >> 8) & 0xff;
buf[3] = (rawlen >> 16) & 0xff;
buf[4] = (rawlen >> 24) & 0xff;
buf[0] = ZIP_STR_32B;
buf[1] = (rawlen >> 24) & 0xff;
buf[2] = (rawlen >> 16) & 0xff;
buf[3] = (rawlen >> 8) & 0xff;
buf[4] = rawlen & 0xff;
}
buf[0] = (lenenc << 4) | (buf[0] & 0xf);
} else {
/* Implies integer encoding, so length is always 1. */
if (!p) return len;
buf[0] = encoding;
}
if (!p) return len;
/* Apparently we need to store the length in 'p' */
buf[0] = (encoding << 6) | (buf[0] & 0x3f);
/* Store this length at p */
memcpy(p,buf,len);
return len;
}
@@ -167,6 +226,14 @@ static unsigned int zipPrevEncodeLength(unsigned char *p, unsigned int len) {
}
}
/* Encode the length of the previous entry and write it to "p". This only
* uses the larger encoding (required in __ziplistCascadeUpdate). */
static void zipPrevEncodeLengthForceLarge(unsigned char *p, unsigned int len) {
if (p == NULL) return;
p[0] = ZIP_BIGLEN;
memcpy(p+1,&len,sizeof(len));
}
/* Return the difference in number of bytes needed to store the new length
* "len" on the entry pointed to by "p". */
static int zipPrevLenByteDiff(unsigned char *p, unsigned int len) {
@@ -198,11 +265,11 @@ static int zipTryEncoding(unsigned char *entry, unsigned int entrylen, long long
/* Great, the string can be encoded. Check what's the smallest
* of our encoding types that can hold this value. */
if (value >= INT16_MIN && value <= INT16_MAX) {
*encoding = ZIP_ENC_INT16;
*encoding = ZIP_INT_16B;
} else if (value >= INT32_MIN && value <= INT32_MAX) {
*encoding = ZIP_ENC_INT32;
*encoding = ZIP_INT_32B;
} else {
*encoding = ZIP_ENC_INT64;
*encoding = ZIP_INT_64B;
}
*v = value;
return 1;
@@ -215,13 +282,13 @@ static void zipSaveInteger(unsigned char *p, int64_t value, unsigned char encodi
int16_t i16;
int32_t i32;
int64_t i64;
if (encoding == ZIP_ENC_INT16) {
if (encoding == ZIP_INT_16B) {
i16 = value;
memcpy(p,&i16,sizeof(i16));
} else if (encoding == ZIP_ENC_INT32) {
} else if (encoding == ZIP_INT_32B) {
i32 = value;
memcpy(p,&i32,sizeof(i32));
} else if (encoding == ZIP_ENC_INT64) {
} else if (encoding == ZIP_INT_64B) {
i64 = value;
memcpy(p,&i64,sizeof(i64));
} else {
@@ -234,13 +301,13 @@ static int64_t zipLoadInteger(unsigned char *p, unsigned char encoding) {
int16_t i16;
int32_t i32;
int64_t i64, ret;
if (encoding == ZIP_ENC_INT16) {
if (encoding == ZIP_INT_16B) {
memcpy(&i16,p,sizeof(i16));
ret = i16;
} else if (encoding == ZIP_ENC_INT32) {
} else if (encoding == ZIP_INT_32B) {
memcpy(&i32,p,sizeof(i32));
ret = i32;
} else if (encoding == ZIP_ENC_INT64) {
} else if (encoding == ZIP_INT_64B) {
memcpy(&i64,p,sizeof(i64));
ret = i64;
} else {
@@ -255,7 +322,7 @@ static zlentry zipEntry(unsigned char *p) {
e.prevrawlen = zipPrevDecodeLength(p,&e.prevrawlensize);
e.len = zipDecodeLength(p+e.prevrawlensize,&e.lensize);
e.headersize = e.prevrawlensize+e.lensize;
e.encoding = ZIP_ENCODING(p+e.prevrawlensize);
e.encoding = zipEntryEncoding(p+e.prevrawlensize);
e.p = p;
return e;
}
@@ -285,11 +352,87 @@ static unsigned char *ziplistResize(unsigned char *zl, unsigned int len) {
return zl;
}
/* When an entry is inserted, we need to set the prevlen field of the next
* entry to equal the length of the inserted entry. It can occur that this
* length cannot be encoded in 1 byte and the next entry needs to be grow
* a bit larger to hold the 5-byte encoded prevlen. This can be done for free,
* because this only happens when an entry is already being inserted (which
* causes a realloc and memmove). However, encoding the prevlen may require
* that this entry is grown as well. This effect may cascade throughout
* the ziplist when there are consecutive entries with a size close to
* ZIP_BIGLEN, so we need to check that the prevlen can be encoded in every
* consecutive entry.
*
* Note that this effect can also happen in reverse, where the bytes required
* to encode the prevlen field can shrink. This effect is deliberately ignored,
* because it can cause a "flapping" effect where a chain prevlen fields is
* first grown and then shrunk again after consecutive inserts. Rather, the
* field is allowed to stay larger than necessary, because a large prevlen
* field implies the ziplist is holding large entries anyway.
*
* The pointer "p" points to the first entry that does NOT need to be
* updated, i.e. consecutive fields MAY need an update. */
static unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p) {
unsigned int curlen = ZIPLIST_BYTES(zl), rawlen, rawlensize;
unsigned int offset, noffset, extra;
unsigned char *np;
zlentry cur, next;
while (p[0] != ZIP_END) {
cur = zipEntry(p);
rawlen = cur.headersize + cur.len;
rawlensize = zipPrevEncodeLength(NULL,rawlen);
/* Abort if there is no next entry. */
if (p[rawlen] == ZIP_END) break;
next = zipEntry(p+rawlen);
/* Abort when "prevlen" has not changed. */
if (next.prevrawlen == rawlen) break;
if (next.prevrawlensize < rawlensize) {
/* The "prevlen" field of "next" needs more bytes to hold
* the raw length of "cur". */
offset = p-zl;
extra = rawlensize-next.prevrawlensize;
zl = ziplistResize(zl,curlen+extra);
ZIPLIST_TAIL_OFFSET(zl) += extra;
p = zl+offset;
/* Move the tail to the back. */
np = p+rawlen;
noffset = np-zl;
memmove(np+rawlensize,
np+next.prevrawlensize,
curlen-noffset-next.prevrawlensize-1);
zipPrevEncodeLength(np,rawlen);
/* Advance the cursor */
p += rawlen;
curlen += extra;
} else {
if (next.prevrawlensize > rawlensize) {
/* This would result in shrinking, which we want to avoid.
* So, set "rawlen" in the available bytes. */
zipPrevEncodeLengthForceLarge(p+rawlen,rawlen);
} else {
zipPrevEncodeLength(p+rawlen,rawlen);
}
/* Stop here, as the raw length of "next" has not changed. */
break;
}
}
return zl;
}
/* Delete "num" entries, starting at "p". Returns pointer to the ziplist. */
static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsigned int num) {
unsigned int i, totlen, deleted = 0;
int nextdiff = 0;
zlentry first = zipEntry(p);
int offset, nextdiff = 0;
zlentry first, tail;
first = zipEntry(p);
for (i = 0; p[0] != ZIP_END && i < num; i++) {
p += zipRawEntryLength(p);
deleted++;
@@ -306,7 +449,14 @@ static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsig
zipPrevEncodeLength(p-nextdiff,first.prevrawlen);
/* Update offset for tail */
ZIPLIST_TAIL_OFFSET(zl) -= totlen+nextdiff;
ZIPLIST_TAIL_OFFSET(zl) -= totlen;
/* When the tail contains more than one entry, we need to take
* "nextdiff" in account as well. Otherwise, a change in the
* size of prevlen doesn't have an effect on the *tail* offset. */
tail = zipEntry(p);
if (p[tail.headersize+tail.len] != ZIP_END)
ZIPLIST_TAIL_OFFSET(zl) += nextdiff;
/* Move tail to the front of the ziplist */
memmove(first.p,p-nextdiff,ZIPLIST_BYTES(zl)-(p-zl)-1+nextdiff);
@@ -316,8 +466,15 @@ static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsig
}
/* Resize and update length */
offset = first.p-zl;
zl = ziplistResize(zl, ZIPLIST_BYTES(zl)-totlen+nextdiff);
ZIPLIST_INCR_LENGTH(zl,-deleted);
p = zl+offset;
/* When nextdiff != 0, the raw length of the next entry has changed, so
* we need to cascade the update throughout the ziplist */
if (nextdiff != 0)
zl = __ziplistCascadeUpdate(zl,p);
}
return zl;
}
@@ -326,29 +483,30 @@ static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsig
static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {
unsigned int curlen = ZIPLIST_BYTES(zl), reqlen, prevlen = 0;
unsigned int offset, nextdiff = 0;
unsigned char *tail;
unsigned char encoding = ZIP_ENC_RAW;
unsigned char encoding = 0;
long long value;
zlentry entry;
zlentry entry, tail;
/* Find out prevlen for the entry that is inserted. */
if (p[0] != ZIP_END) {
entry = zipEntry(p);
prevlen = entry.prevrawlen;
} else {
tail = ZIPLIST_ENTRY_TAIL(zl);
if (tail[0] != ZIP_END) {
prevlen = zipRawEntryLength(tail);
unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl);
if (ptail[0] != ZIP_END) {
prevlen = zipRawEntryLength(ptail);
}
}
/* See if the entry can be encoded */
if (zipTryEncoding(s,slen,&value,&encoding)) {
reqlen = zipEncodingSize(encoding);
/* 'encoding' is set to the appropriate integer encoding */
reqlen = zipIntSize(encoding);
} else {
/* 'encoding' is untouched, however zipEncodeLength will use the
* string length to figure out how to encode it. */
reqlen = slen;
}
/* We need space for both the length of the previous entry and
* the length of the payload. */
reqlen += zipPrevEncodeLength(NULL,prevlen);
@@ -368,22 +526,39 @@ static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsig
if (p[0] != ZIP_END) {
/* Subtract one because of the ZIP_END bytes */
memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);
/* Encode this entry's raw length in the next entry. */
zipPrevEncodeLength(p+reqlen,reqlen);
/* Update offset for tail */
ZIPLIST_TAIL_OFFSET(zl) += reqlen+nextdiff;
ZIPLIST_TAIL_OFFSET(zl) += reqlen;
/* When the tail contains more than one entry, we need to take
* "nextdiff" in account as well. Otherwise, a change in the
* size of prevlen doesn't have an effect on the *tail* offset. */
tail = zipEntry(p+reqlen);
if (p[reqlen+tail.headersize+tail.len] != ZIP_END)
ZIPLIST_TAIL_OFFSET(zl) += nextdiff;
} else {
/* This element will be the new tail. */
ZIPLIST_TAIL_OFFSET(zl) = p-zl;
}
/* When nextdiff != 0, the raw length of the next entry has changed, so
* we need to cascade the update throughout the ziplist */
if (nextdiff != 0) {
offset = p-zl;
zl = __ziplistCascadeUpdate(zl,p+reqlen);
p = zl+offset;
}
/* Write the entry */
p += zipPrevEncodeLength(p,prevlen);
p += zipEncodeLength(p,encoding,slen);
if (encoding != ZIP_ENC_RAW) {
zipSaveInteger(p,value,encoding);
} else {
if (ZIP_IS_STR(encoding)) {
memcpy(p,s,slen);
} else {
zipSaveInteger(p,value,encoding);
}
ZIPLIST_INCR_LENGTH(zl,1);
return zl;
@@ -449,6 +624,7 @@ unsigned char *ziplistPrev(unsigned char *zl, unsigned char *p) {
return NULL;
} else {
entry = zipEntry(p);
assert(entry.prevrawlen > 0);
return p-entry.prevrawlen;
}
}
@@ -463,7 +639,7 @@ unsigned int ziplistGet(unsigned char *p, unsigned char **sstr, unsigned int *sl
if (sstr) *sstr = NULL;
entry = zipEntry(p);
if (entry.encoding == ZIP_ENC_RAW) {
if (ZIP_IS_STR(entry.encoding)) {
if (sstr) {
*slen = entry.len;
*sstr = p+entry.headersize;
@@ -510,7 +686,7 @@ unsigned int ziplistCompare(unsigned char *p, unsigned char *sstr, unsigned int
if (p[0] == ZIP_END) return 0;
entry = zipEntry(p);
if (entry.encoding == ZIP_ENC_RAW) {
if (ZIP_IS_STR(entry.encoding)) {
/* Raw compare */
if (entry.len == slen) {
return memcmp(p+entry.headersize,sstr,slen) == 0;
@@ -554,27 +730,62 @@ unsigned int ziplistSize(unsigned char *zl) {
void ziplistRepr(unsigned char *zl) {
unsigned char *p;
int index = 0;
zlentry entry;
printf("{total bytes %d} {length %u}\n",ZIPLIST_BYTES(zl), ZIPLIST_LENGTH(zl));
printf(
"{total bytes %d} "
"{length %u}\n"
"{tail offset %u}\n",
ZIPLIST_BYTES(zl),
ZIPLIST_LENGTH(zl),
ZIPLIST_TAIL_OFFSET(zl));
p = ZIPLIST_ENTRY_HEAD(zl);
while(*p != ZIP_END) {
entry = zipEntry(p);
printf("{offset %ld, header %u, payload %u} ",p-zl,entry.headersize,entry.len);
printf(
"{"
"addr 0x%08lx, "
"index %2d, "
"offset %5ld, "
"rl: %5u, "
"hs %2u, "
"pl: %5u, "
"pls: %2u, "
"payload %5u"
"} ",
(long unsigned int)p,
index,
p-zl,
entry.headersize+entry.len,
entry.headersize,
entry.prevrawlen,
entry.prevrawlensize,
entry.len);
p += entry.headersize;
if (entry.encoding == ZIP_ENC_RAW) {
fwrite(p,entry.len,1,stdout);
if (ZIP_IS_STR(entry.encoding)) {
if (entry.len > 40) {
fwrite(p,40,1,stdout);
printf("...");
} else {
fwrite(p,entry.len,1,stdout);
}
} else {
printf("%lld", (long long) zipLoadInteger(p,entry.encoding));
}
printf("\n");
p += entry.len;
index++;
}
printf("{end}\n\n");
}
#ifdef ZIPLIST_TEST_MAIN
#include <sys/time.h>
#include "adlist.h"
#include "sds.h"
#define debug(f, ...) { if (DEBUG) printf(f, __VA_ARGS__); }
unsigned char *createList() {
unsigned char *zl = ziplistNew();
@@ -658,12 +869,42 @@ void pop(unsigned char *zl, int where) {
}
}
void randstring(char *target, unsigned int min, unsigned int max) {
int p, len = min+rand()%(max-min+1);
int minval, maxval;
switch(rand() % 3) {
case 0:
minval = 0;
maxval = 255;
break;
case 1:
minval = 48;
maxval = 122;
break;
case 2:
minval = 48;
maxval = 52;
break;
default:
assert(NULL);
}
while(p < len)
target[p++] = minval+rand()%(maxval-minval+1);
return;
}
int main(int argc, char **argv) {
unsigned char *zl, *p;
unsigned char *entry;
unsigned int elen;
long long value;
/* If an argument is given, use it as the random seed. */
if (argc == 2)
srand(atoi(argv[1]));
zl = createIntList();
ziplistRepr(zl);
@@ -915,6 +1156,25 @@ int main(int argc, char **argv) {
ziplistRepr(zl);
}
printf("Regression test for >255 byte strings:\n");
{
char v1[257],v2[257];
memset(v1,'x',256);
memset(v2,'y',256);
zl = ziplistNew();
zl = ziplistPush(zl,(unsigned char*)v1,strlen(v1),ZIPLIST_TAIL);
zl = ziplistPush(zl,(unsigned char*)v2,strlen(v2),ZIPLIST_TAIL);
/* Pop values again and compare their value. */
p = ziplistIndex(zl,0);
assert(ziplistGet(p,&entry,&elen,&value));
assert(strncmp(v1,(char*)entry,elen) == 0);
p = ziplistIndex(zl,1);
assert(ziplistGet(p,&entry,&elen,&value));
assert(strncmp(v2,(char*)entry,elen) == 0);
printf("SUCCESS\n\n");
}
printf("Create long list and check indices:\n");
{
zl = ziplistNew();
@@ -958,7 +1218,85 @@ int main(int argc, char **argv) {
printf("ERROR: \"1025\"\n");
return 1;
}
printf("SUCCESS\n");
printf("SUCCESS\n\n");
}
printf("Stress with random payloads of different encoding:\n");
{
int i,j,len,where;
unsigned char *p;
char buf[1024];
list *ref;
listNode *refnode;
/* Hold temp vars from ziplist */
unsigned char *sstr;
unsigned int slen;
long long sval;
/* In the regression for the cascade bug, it was triggered
* with a random seed of 2. */
srand(2);
for (i = 0; i < 20000; i++) {
zl = ziplistNew();
ref = listCreate();
listSetFreeMethod(ref,sdsfree);
len = rand() % 256;
/* Create lists */
for (j = 0; j < len; j++) {
where = (rand() & 1) ? ZIPLIST_HEAD : ZIPLIST_TAIL;
switch(rand() % 4) {
case 0:
sprintf(buf,"%lld",(0LL + rand()) >> 20);
break;
case 1:
sprintf(buf,"%lld",(0LL + rand()));
break;
case 2:
sprintf(buf,"%lld",(0LL + rand()) << 20);
break;
case 3:
randstring(buf,0,256);
break;
default:
assert(NULL);
}
/* Add to ziplist */
zl = ziplistPush(zl, (unsigned char*)buf, strlen(buf), where);
/* Add to reference list */
if (where == ZIPLIST_HEAD) {
listAddNodeHead(ref,sdsnew(buf));
} else if (where == ZIPLIST_TAIL) {
listAddNodeTail(ref,sdsnew(buf));
} else {
assert(NULL);
}
}
assert(listLength(ref) == ziplistLen(zl));
for (j = 0; j < len; j++) {
/* Naive way to get elements, but similar to the stresser
* executed from the Tcl test suite. */
p = ziplistIndex(zl,j);
refnode = listIndex(ref,j);
assert(ziplistGet(p,&sstr,&slen,&sval));
if (sstr == NULL) {
sprintf(buf,"%lld",sval);
} else {
memcpy(buf,sstr,slen);
buf[slen] = '\0';
}
assert(strcmp(buf,listNodeValue(refnode)) == 0);
}
zfree(zl);
listRelease(ref);
}
printf("SUCCESS\n\n");
}
printf("Stress with variable ziplist size:\n");
+67
View File
@@ -32,6 +32,7 @@
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include "config.h"
#if defined(__sun)
@@ -170,3 +171,69 @@ size_t zmalloc_used_memory(void) {
void zmalloc_enable_thread_safeness(void) {
zmalloc_thread_safe = 1;
}
/* Fragmentation = RSS / allocated-bytes */
#if defined(HAVE_PROCFS)
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
float zmalloc_get_fragmentation_ratio(void) {
size_t allocated = zmalloc_used_memory();
int page = sysconf(_SC_PAGESIZE);
size_t rss;
char buf[4096];
char filename[256];
int fd, count;
char *p, *x;
snprintf(filename,256,"/proc/%d/stat",getpid());
if ((fd = open(filename,O_RDONLY)) == -1) return 0;
if (read(fd,buf,4096) <= 0) {
close(fd);
return 0;
}
close(fd);
p = buf;
count = 23; /* RSS is the 24th field in /proc/<pid>/stat */
while(p && count--) {
p = strchr(p,' ');
if (p) p++;
}
if (!p) return 0;
x = strchr(p,' ');
if (!x) return 0;
*x = '\0';
rss = strtoll(p,NULL,10);
rss *= page;
return (float)rss/allocated;
}
#elif defined(HAVE_TASKINFO)
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <mach/task.h>
#include <mach/mach_init.h>
float zmalloc_get_fragmentation_ratio(void) {
task_t task = MACH_PORT_NULL;
struct task_basic_info t_info;
mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;
if (task_for_pid(current_task(), getpid(), &task) != KERN_SUCCESS)
return 0;
task_info(task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count);
return (float)t_info.resident_size/zmalloc_used_memory();
}
#else
float zmalloc_get_fragmentation_ratio(void) {
return 0;
}
#endif
+1
View File
@@ -38,5 +38,6 @@ void zfree(void *ptr);
char *zstrdup(const char *s);
size_t zmalloc_used_memory(void);
void zmalloc_enable_thread_safeness(void);
float zmalloc_get_fragmentation_ratio(void);
#endif /* _ZMALLOC_H */
+4 -4
View File
@@ -15,8 +15,8 @@ start_server {tags {"hash"}} {
} {8}
test {Is the small hash encoded with a zipmap?} {
r debug object smallhash
} {*zipmap*}
assert_encoding zipmap smallhash
}
test {HSET/HLEN - Big hash creation} {
array set bighash {}
@@ -34,8 +34,8 @@ start_server {tags {"hash"}} {
} {1024}
test {Is the big hash encoded with a zipmap?} {
r debug object bighash
} {*hashtable*}
assert_encoding hashtable bighash
}
test {HGET against the small hash} {
set err {}
+71
View File
@@ -603,5 +603,76 @@ start_server {
assert_equal 1 [r lrem myotherlist 1 2]
assert_equal 3 [r llen myotherlist]
}
}
}
start_server {
tags {list ziplist}
overrides {
"list-max-ziplist-value" 200000
"list-max-ziplist-entries" 256
}
} {
test {Explicit regression for a list bug} {
set mylist {49376042582 {BkG2o\pIC]4YYJa9cJ4GWZalG[4tin;1D2whSkCOW`mX;SFXGyS8sedcff3fQI^tgPCC@^Nu1J6o]meM@Lko]t_jRyo<xSJ1oObDYd`ppZuW6P@fS278YaOx=s6lvdFlMbP0[SbkI^Kr\HBXtuFaA^mDx:yzS4a[skiiPWhT<nNfAf=aQVfclcuwDrfe;iVuKdNvB9kbfq>tK?tH[\EvWqS]b`o2OCtjg:?nUTwdjpcUm]y:pg5q24q7LlCOwQE^}}
r del l
r rpush l [lindex $mylist 0]
r rpush l [lindex $mylist 1]
assert_equal [r lindex l 0] [lindex $mylist 0]
assert_equal [r lindex l 1] [lindex $mylist 1]
}
tags {slow} {
test {ziplist implementation: value encoding and backlink} {
for {set j 0} {$j < 100} {incr j} {
r del l
set l {}
for {set i 0} {$i < 200} {incr i} {
randpath {
set data [string repeat x [randomInt 100000]]
} {
set data [randomInt 65536]
} {
set data [randomInt 4294967296]
} {
set data [randomInt 18446744073709551616]
}
lappend l $data
r rpush l $data
}
assert_equal [llength $l] [r llen l]
# Traverse backward
for {set i 199} {$i >= 0} {incr i -1} {
if {[lindex $l $i] ne [r lindex l $i]} {
assert_equal [lindex $l $i] [r lindex l $i]
}
}
}
}
test {ziplist implementation: encoding stress testing} {
for {set j 0} {$j < 200} {incr j} {
r del l
set l {}
set len [randomInt 400]
for {set i 0} {$i < $len} {incr i} {
set rv [randomValue]
randpath {
lappend l $rv
r rpush l $rv
} {
set l [concat [list $rv] $l]
r lpush l $rv
}
}
assert_equal [llength $l] [r llen l]
for {set i 0} {$i < 200} {incr i} {
if {[lindex $l $i] ne [r lindex l $i]} {
assert_equal [lindex $l $i] [r lindex l $i]
}
}
}
}
}
}
+36
View File
@@ -295,4 +295,40 @@ start_server {
r set x 10
assert_error "ERR*wrong kind*" {r smove myset2 x foo}
}
tags {slow} {
test {intsets implementation stress testing} {
for {set j 0} {$j < 20} {incr j} {
unset -nocomplain s
array set s {}
r del s
set len [randomInt 1024]
for {set i 0} {$i < $len} {incr i} {
randpath {
set data [randomInt 65536]
} {
set data [randomInt 4294967296]
} {
set data [randomInt 18446744073709551616]
}
set s($data) {}
r sadd s $data
}
assert_equal [lsort [r smembers s]] [lsort [array names s]]
set len [array size s]
for {set i 0} {$i < $len} {incr i} {
set e [r spop s]
if {![info exists s($e)]} {
puts "Can't find '$e' on local array"
puts "Local array: [lsort [r smembers s]]"
puts "Remote array: [lsort [array names s]]"
error "exception"
}
array unset s $e
}
assert_equal [r scard s] 0
assert_equal [array size s] 0
}
}
}
}
+2 -3
View File
@@ -21,15 +21,14 @@ case "$1" in
then
echo -n "$PIDFILE does not exist, process is not running\n"
else
PID=$(cat $PIDFILE)
echo -n "Stopping ...\n"
echo -n "Sending SHUTDOWN\r\n" | nc localhost $REDISPORT &
PID=$(cat $PIDFILE)
echo -n "SHUTDOWN\r\n" | nc localhost $REDISPORT &
while [ -x /proc/${PIDFILE} ]
do
echo "Waiting for Redis to shutdown ..."
sleep 1
done
rm $PIDFILE
echo "Redis stopped"
fi
;;