Compare commits
76
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75fcab8c23 | ||
|
|
13a49af44b | ||
|
|
9f8ded8ced | ||
|
|
95506e4611 | ||
|
|
53eeeaff08 | ||
|
|
a36879293d | ||
|
|
670bf2fd36 | ||
|
|
b33ef40105 | ||
|
|
165346ca29 | ||
|
|
306c6a02e3 | ||
|
|
ef59a8bc9e | ||
|
|
b4f2e412d0 | ||
|
|
1dd10ca233 | ||
|
|
144a5e72f2 | ||
|
|
fdc0bde935 | ||
|
|
4610b0332c | ||
|
|
ef27ba988b | ||
|
|
1a06bf93c4 | ||
|
|
e43505d6e9 | ||
|
|
b0d605c1d6 | ||
|
|
30d31cc8bb | ||
|
|
963238f713 | ||
|
|
136cf53f22 | ||
|
|
56e52b69fe | ||
|
|
50a9fad5d5 | ||
|
|
beb7756dcb | ||
|
|
b882056c93 | ||
|
|
5ca2f0c498 | ||
|
|
5171777bf1 | ||
|
|
a4f3f93b90 | ||
|
|
34a719d250 | ||
|
|
2b00385d51 | ||
|
|
4c2e506a39 | ||
|
|
83f39c7ab2 | ||
|
|
f335779240 | ||
|
|
89f9f83769 | ||
|
|
3856f14759 | ||
|
|
412e457c27 | ||
|
|
1d18f50458 | ||
|
|
0997b4119d | ||
|
|
e984050fb9 | ||
|
|
e13865033d | ||
|
|
3c23ee1ba2 | ||
|
|
bc63407be6 | ||
|
|
7f9a4db3c0 | ||
|
|
da14590bd9 | ||
|
|
155fb4b45e | ||
|
|
7f00cd2264 | ||
|
|
efc5d4cc0d | ||
|
|
106bd87a3c | ||
|
|
84403fe7c1 | ||
|
|
169d2ef1e0 | ||
|
|
abe18d0e00 | ||
|
|
9e83ac06ef | ||
|
|
556bdfbab9 | ||
|
|
b435f64510 | ||
|
|
49128f0b9d | ||
|
|
3ab203762f | ||
|
|
60361e5aac | ||
|
|
36c19d03e0 | ||
|
|
4a7893ca9c | ||
|
|
b70d355521 | ||
|
|
cd76bb651d | ||
|
|
2403fc9fde | ||
|
|
0537e7bf80 | ||
|
|
73db2acc37 | ||
|
|
eddb388ef9 | ||
|
|
a047bf52a4 | ||
|
|
57b0738011 | ||
|
|
b301c1fc2b | ||
|
|
834ef78e27 | ||
|
|
ed0dd55402 | ||
|
|
36babc1e31 | ||
|
|
c470538142 | ||
|
|
69ef89f2cf | ||
|
|
2159782b51 |
@@ -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!
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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> <a href="#BLPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis >">BLPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis ></a><br> <a href="#BRPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis >">BRPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis ></a><br> <a href="#Non blocking behavior">Non blocking behavior</a><br> <a href="#Blocking behavior">Blocking behavior</a><br> <a href="#Multiple clients blocking for the same keys">Multiple clients blocking for the same keys</a><br> <a href="#Return value">Return value</a>
|
||||
<b>BlpopCommand: Contents</b><br> <a href="#BLPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis >">BLPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis ></a><br> <a href="#BRPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis >">BRPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis ></a><br> <a href="#Non blocking behavior">Non blocking behavior</a><br> <a href="#Blocking behavior">Blocking behavior</a><br> <a href="#Multiple clients blocking for the same keys">Multiple clients blocking for the same keys</a><br> <a href="#blocking POP inside a MULTI/EXEC transaction">blocking POP inside a MULTI/EXEC transaction</a><br> <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>
|
||||
|
||||
+13
-13
File diff suppressed because one or more lines are too long
+1
-2
@@ -27,12 +27,11 @@
|
||||
|
||||
<div class="narrow">
|
||||
#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
@@ -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> <a href="#EXPIRE _key_ _seconds_">EXPIRE _key_ _seconds_</a><br> <a href="#EXPIREAT _key_ _unixtime_ (Redis >">EXPIREAT _key_ _unixtime_ (Redis ></a><br> <a href="#How the expire is removed from a key">How the expire is removed from a key</a><br> <a href="#Restrictions with write operations against volatile keys">Restrictions with write operations against volatile keys</a><br> <a href="#Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a><br> <a href="#Enhanced Lazy Expiration algorithm">Enhanced Lazy Expiration algorithm</a><br> <a href="#Version 1.0">Version 1.0</a><br> <a href="#Version 1.1">Version 1.1</a><br> <a href="#Return value">Return value</a><br> <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> <a href="#EXPIRE _key_ _seconds_">EXPIRE _key_ _seconds_</a><br> <a href="#EXPIREAT _key_ _unixtime_ (Redis >">EXPIREAT _key_ _unixtime_ (Redis ></a><br> <a href="#PERSIST _key_">PERSIST _key_</a><br> <a href="#How the expire is removed from a key">How the expire is removed from a key</a><br> <a href="#Restrictions with write operations against volatile keys">Restrictions with write operations against volatile keys</a><br> <a href="#Restrictions for write operations with volatile keys as sources">Restrictions for write operations with volatile keys as sources</a><br> <a href="#Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a><br> <a href="#Enhanced Lazy Expiration algorithm">Enhanced Lazy Expiration algorithm</a><br> <a href="#Version 1.0">Version 1.0</a><br> <a href="#Version 1.1">Version 1.1</a><br> <a href="#Return value">Return value</a><br> <a href="#FAQ: Can you explain better why Redis < 2.1.3 deletes keys with an EXPIRE on write operations?">FAQ: Can you explain better why Redis < 2.1.3 deletes keys with an EXPIRE on write operations?</a><br> <a href="#FAQ: How this limitations were solved in Redis versions > 2.1.3?">FAQ: How this limitations were solved in Redis versions > 2.1.3?</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">ExpireCommand</h1>
|
||||
@@ -28,12 +28,15 @@
|
||||
<div class="narrow">
|
||||
#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 >">EXPIREAT _key_ _unixtime_ (Redis ></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 < 2.1.3, Redis >= 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 < 2.1.3 deletes keys with an EXPIRE on write operations?">FAQ: Can you explain better why Redis < 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> set a 100
|
||||
@@ -76,7 +86,8 @@ EXPIRE a 5
|
||||
INCR a
|
||||
</pre>
|
||||
Imagine a Redis version that does not implement the "Delete keys with an expire set on write operation" 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 > 2.1.3?">FAQ: How this limitations were solved in Redis versions > 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
File diff suppressed because one or more lines are too long
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
== 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>
|
||||
== 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>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
== 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>
|
||||
== 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>
|
||||
|
||||
@@ -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 " " 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>
|
||||
|
||||
@@ -28,10 +28,9 @@
|
||||
<div class="narrow">
|
||||
#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
@@ -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> <a href="#MULTI">MULTI</a><br> <a href="#COMMAND_1 ...">COMMAND_1 ...</a><br> <a href="#COMMAND_2 ...">COMMAND_2 ...</a><br> <a href="#COMMAND_N ...">COMMAND_N ...</a><br> <a href="#EXEC or DISCARD">EXEC or DISCARD</a><br> <a href="#Usage">Usage</a><br> <a href="#The DISCARD command">The DISCARD command</a><br> <a href="#Return value">Return value</a>
|
||||
<b>MultiExecCommand: Contents</b><br> <a href="#WATCH key1 key2 ... keyN (Redis >">WATCH key1 key2 ... keyN (Redis ></a><br> <a href="#UNWATCH">UNWATCH</a><br> <a href="#MULTI">MULTI</a><br> <a href="#COMMAND_1 ...">COMMAND_1 ...</a><br> <a href="#COMMAND_2 ...">COMMAND_2 ...</a><br> <a href="#COMMAND_N ...">COMMAND_N ...</a><br> <a href="#EXEC or DISCARD">EXEC or DISCARD</a><br> <a href="#Usage">Usage</a><br> <a href="#The DISCARD command">The DISCARD command</a><br> <a href="#Check and Set (CAS) transactions using WATCH">Check and Set (CAS) transactions using WATCH</a><br> <a href="#WATCH explained">WATCH explained</a><br> <a href="#WATCH used to implement ZPOP">WATCH used to implement ZPOP</a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">MultiExecCommand</h1>
|
||||
@@ -26,15 +26,21 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="GenericCommandsSidebar.html">GenericCommandsSidebar</a><h1><a name="MULTI">MULTI</a></h1>
|
||||
#sidebar <a href="GenericCommandsSidebar.html">GenericCommandsSidebar</a><h1><a name="WATCH key1 key2 ... keyN (Redis >">WATCH key1 key2 ... keyN (Redis ></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 "queue" 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 "queue" 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">
|
||||
?> r.multi
|
||||
=> "OK"
|
||||
>> r.incr "foo"
|
||||
@@ -46,9 +52,14 @@
|
||||
>> r.exec
|
||||
=> [1, 1, 2]
|
||||
</pre>
|
||||
<blockquote>As it is possible to see from the session above, MULTI returns an "array" 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 "QUEUED" 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 "array" 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 "QUEUED" 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 "bad" 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 "bad" 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">
|
||||
?> r.set("foo",1)
|
||||
=> true
|
||||
>> r.multi
|
||||
@@ -84,9 +100,64 @@ INCR a b c
|
||||
=> "OK"
|
||||
>> r.get("foo")
|
||||
=> "1"
|
||||
</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 "11" instead of "12".<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
@@ -26,11 +26,42 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
= 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 "surname_1992" to the string "Smith".
|
||||
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 "mylist" 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 "formal" doc.)<br/><br/>Later in this document you can find detailed information about Redis commands,
|
||||
= 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 ("antirez") to a key ("redis"). 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> lpush programming_languages C
|
||||
OK
|
||||
redis> lpush programming_languages Ruby
|
||||
OK
|
||||
redis> rpush programming_languages Python
|
||||
OK
|
||||
redis> rpop programming_languages
|
||||
Python
|
||||
redis> 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> lpush cities NYC
|
||||
OK
|
||||
redis> lpush cities SF
|
||||
OK
|
||||
redis> lpush cities Tokyo
|
||||
OK
|
||||
redis> lpush cities London
|
||||
OK
|
||||
redis> lpush cities Paris
|
||||
OK
|
||||
redis> lrange cities 0 2
|
||||
1. Paris
|
||||
2. London
|
||||
3. Tokyo
|
||||
redis> ltrim cities 0 1
|
||||
OK
|
||||
redis> lpop cities
|
||||
Paris
|
||||
redis> lpop cities
|
||||
London
|
||||
redis> 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 "formal" 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 "+OK". This means
|
||||
our key was added without problems. Actually SET can never fail but
|
||||
the "+OK" 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 "get foo",
|
||||
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 "\r\n" 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 "\r\n" 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 "$-1" 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 "$-1" 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
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
== 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>
|
||||
== 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
@@ -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>
|
||||
|
||||
|
||||
@@ -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> <a href="#ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` (Redis >">ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` (Redis ></a><br> <a href="#ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` `[`WITHSCORES`]` (Redis >">ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` `[`WITHSCORES`]` (Redis ></a><br> <a href="#Exclusive intervals and infinity">Exclusive intervals and infinity</a><br> <a href="#Return value">Return value</a><br> <a href="#Examples">Examples</a>
|
||||
<b>ZrangebyscoreCommand: Contents</b><br> <a href="#ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` (Redis >">ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` (Redis ></a><br> <a href="#ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` `[`WITHSCORES`]` (Redis >">ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` `[`WITHSCORES`]` (Redis ></a><br> <a href="#ZCOUNT _key_ _min_ _max_">ZCOUNT _key_ _min_ _max_</a><br> <a href="#Exclusive intervals and infinity">Exclusive intervals and infinity</a><br> <a href="#Return value">Return value</a><br> <a href="#Examples">Examples</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">ZrangebyscoreCommand</h1>
|
||||
@@ -28,9 +28,11 @@
|
||||
<div class="narrow">
|
||||
#sidebar <a href="SortedSetCommandsSidebar.html">SortedSetCommandsSidebar</a><h1><a name="ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` (Redis >">ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` (Redis ></a></h1> 1.1) =
|
||||
<h1><a name="ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` `[`WITHSCORES`]` (Redis >">ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` `[`WITHSCORES`]` (Redis ></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 "up to a given value".<br/><br/>Also while the interval is for default closed (inclusive) it's possible to specify open intervals prefixing the score with a "(" 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>> 1.3 and <= 5</b>, while for ins
|
||||
ZRANGEBYSCORE zset (5 (10
|
||||
</pre>
|
||||
Will return all the values with score <b>> 5 and < 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> zadd zset 1 foo
|
||||
@@ -56,6 +58,8 @@ redis> zrangebyscore zset -inf +inf
|
||||
2. "bar"
|
||||
3. "biz"
|
||||
4. "foz"
|
||||
redis> zcount zset 1 2
|
||||
(integer) 2
|
||||
redis> zrangebyscore zset 1 2
|
||||
1. "foo"
|
||||
2. "bar"
|
||||
|
||||
@@ -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> <a href="#ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
<b>ZunionCommand: Contents</b><br> <a href="#ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a><br> <a href="#ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a><br> <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 >">ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></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 >">ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a></h1> 1.3.12) =
|
||||
<h1><a name="ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></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>.
|
||||
|
||||
@@ -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> <a href="#ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
<b>ZunionstoreCommand: Contents</b><br> <a href="#ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a><br> <a href="#ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a><br> <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 >">ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></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 >">ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a></h1> 1.3.12) =
|
||||
<h1><a name="ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></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
@@ -26,12 +26,12 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
= 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"><-></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>
|
||||
= 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"><-></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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "2.1.4"
|
||||
#define REDIS_VERSION "2.1.5"
|
||||
|
||||
@@ -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
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
;;
|
||||
|
||||
Reference in New Issue
Block a user