mirror of
https://github.com/fluencelabs/redis
synced 2025-04-01 15:21:03 +00:00
VM tuning thanks to redis-stat vmstat. Now it performs much better under high load
This commit is contained in:
parent
eb6845621c
commit
b0d8747dae
22
TODO
22
TODO
@ -4,28 +4,20 @@ VERSION 1.4 TODO (Hash type)
|
|||||||
============================
|
============================
|
||||||
|
|
||||||
* BRPOPLPUSH
|
* BRPOPLPUSH
|
||||||
* RPOPLPUSH should notify blocking POP operations
|
|
||||||
* List ops like L/RPUSH L/RPOP should return the new list length.
|
* List ops like L/RPUSH L/RPOP should return the new list length.
|
||||||
* Save dataset / fsync() on SIGTERM
|
* Save dataset / fsync() on SIGTERM
|
||||||
* MULTI/EXEC should support the "EXEC FSYNC" form
|
* MULTI/EXEC should support the "EXEC FSYNC" form?
|
||||||
* Synchronous Virtual Memory
|
|
||||||
* BLPOP & C. tests (write a non blocking Tcl client as first step)
|
* BLPOP & C. tests (write a non blocking Tcl client as first step)
|
||||||
|
|
||||||
Virtual Memory sub-TODO:
|
Virtual Memory sub-TODO:
|
||||||
* Check if the page selection algorithm is working well.
|
* Check if the page selection algorithm is working well
|
||||||
* Divide swappability of objects by refcount
|
* Divide swappability of objects by refcount
|
||||||
* vm-swap-file <filename>. The swap file should go where the user wants, and if it's already there and of the right size we can avoid to create it again.
|
|
||||||
* it should be possible to give the vm-max-memory option in megabyte, gigabyte, ..., just using 2GB, 100MB, and so forth.
|
* it should be possible to give the vm-max-memory option in megabyte, gigabyte, ..., just using 2GB, 100MB, and so forth.
|
||||||
* redis-cli vmstat, calling INFO every second and printing VM stats ala vmstat.
|
* Try to understand what can be moved into I/O threads that currently is instead handled by the main thread. For instance swapping file table scannig to find contiguous page could be a potential candidate (but I'm not convinced it's a good idea, better to improve the algorithm, for instance double the fast forward at every step?).
|
||||||
* protect zmalloc memory usage increments with a mutex
|
|
||||||
|
|
||||||
VERSION 1.6 TODO (Virtual memory)
|
* Hashes (HSET, HGET, HDEL, HEXISTS, HLEN, ...).
|
||||||
=================================
|
|
||||||
|
|
||||||
* Asynchronous Virtual Memory
|
VERSION 2.2 TODO (Fault tolerant sharding)
|
||||||
* Hashes (HSET, HGET, HEXISTS, HLEN, ...).
|
|
||||||
|
|
||||||
VERSION 1.8 TODO (Fault tollerant sharding)
|
|
||||||
===========================================
|
===========================================
|
||||||
|
|
||||||
* Redis-cluster, a fast intermediate layer (proxy) that implements consistent hashing and fault tollerant nodes handling.
|
* Redis-cluster, a fast intermediate layer (proxy) that implements consistent hashing and fault tollerant nodes handling.
|
||||||
@ -34,7 +26,7 @@ Interesting readings about this:
|
|||||||
|
|
||||||
- http://ayende.com/Blog/archive/2009/04/06/designing-rhino-dht-a-fault-tolerant-dynamically-distributed-hash.aspx
|
- http://ayende.com/Blog/archive/2009/04/06/designing-rhino-dht-a-fault-tolerant-dynamically-distributed-hash.aspx
|
||||||
|
|
||||||
VERSION 2.0 TODO (Optimizations and latency)
|
VERSION 2.4 TODO (Optimizations and latency)
|
||||||
============================================
|
============================================
|
||||||
|
|
||||||
* Lower the CPU usage.
|
* Lower the CPU usage.
|
||||||
@ -42,7 +34,7 @@ VERSION 2.0 TODO (Optimizations and latency)
|
|||||||
* Use epool and alike to rewrite ae.c for Linux and other platforms suppporting fater-than-select() mutiplexing APIs.
|
* Use epool and alike to rewrite ae.c for Linux and other platforms suppporting fater-than-select() mutiplexing APIs.
|
||||||
* Implement an UDP interface for low-latency GET/SET operations.
|
* Implement an UDP interface for low-latency GET/SET operations.
|
||||||
|
|
||||||
VERSION 2.2 TODO (Optimizations and latency)
|
VERSION 2.6 TODO (Optimizations and latency)
|
||||||
============================================
|
============================================
|
||||||
|
|
||||||
* JSON command able to access data serialized in JSON format. For instance if I've a key foobar with a json object I can alter the "name" file using somthing like: "JSON SET foobar name Kevin". We should have GET and INCRBY as well.
|
* JSON command able to access data serialized in JSON format. For instance if I've a key foobar with a json object I can alter the "name" file using somthing like: "JSON SET foobar name Kevin". We should have GET and INCRBY as well.
|
||||||
|
59
redis.c
59
redis.c
@ -27,7 +27,7 @@
|
|||||||
* POSSIBILITY OF SUCH DAMAGE.
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define REDIS_VERSION "1.3.2"
|
#define REDIS_VERSION "1.3.3"
|
||||||
|
|
||||||
#include "fmacros.h"
|
#include "fmacros.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
@ -543,7 +543,7 @@ static void sendReplyToClientWritev(aeEventLoop *el, int fd, void *privdata, int
|
|||||||
static void initClientMultiState(redisClient *c);
|
static void initClientMultiState(redisClient *c);
|
||||||
static void freeClientMultiState(redisClient *c);
|
static void freeClientMultiState(redisClient *c);
|
||||||
static void queueMultiCommand(redisClient *c, struct redisCommand *cmd);
|
static void queueMultiCommand(redisClient *c, struct redisCommand *cmd);
|
||||||
static void unblockClient(redisClient *c);
|
static void unblockClientWaitingData(redisClient *c);
|
||||||
static int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele);
|
static int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele);
|
||||||
static void vmInit(void);
|
static void vmInit(void);
|
||||||
static void vmMarkPagesFree(off_t page, off_t count);
|
static void vmMarkPagesFree(off_t page, off_t count);
|
||||||
@ -670,7 +670,7 @@ static struct redisCommand cmdTable[] = {
|
|||||||
{"lrange",lrangeCommand,4,REDIS_CMD_INLINE},
|
{"lrange",lrangeCommand,4,REDIS_CMD_INLINE},
|
||||||
{"ltrim",ltrimCommand,4,REDIS_CMD_INLINE},
|
{"ltrim",ltrimCommand,4,REDIS_CMD_INLINE},
|
||||||
{"lrem",lremCommand,4,REDIS_CMD_BULK},
|
{"lrem",lremCommand,4,REDIS_CMD_BULK},
|
||||||
{"rpoplpush",rpoplpushcommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},
|
{"rpoplpush",rpoplpushcommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM},
|
||||||
{"sadd",saddCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},
|
{"sadd",saddCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},
|
||||||
{"srem",sremCommand,3,REDIS_CMD_BULK},
|
{"srem",sremCommand,3,REDIS_CMD_BULK},
|
||||||
{"smove",smoveCommand,4,REDIS_CMD_BULK},
|
{"smove",smoveCommand,4,REDIS_CMD_BULK},
|
||||||
@ -1038,7 +1038,7 @@ static void closeTimedoutClients(void) {
|
|||||||
} else if (c->flags & REDIS_BLOCKED) {
|
} else if (c->flags & REDIS_BLOCKED) {
|
||||||
if (c->blockingto != 0 && c->blockingto < now) {
|
if (c->blockingto != 0 && c->blockingto < now) {
|
||||||
addReply(c,shared.nullmultibulk);
|
addReply(c,shared.nullmultibulk);
|
||||||
unblockClient(c);
|
unblockClientWaitingData(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1672,14 +1672,14 @@ static void freeClient(redisClient *c) {
|
|||||||
listNode *ln;
|
listNode *ln;
|
||||||
|
|
||||||
/* Note that if the client we are freeing is blocked into a blocking
|
/* Note that if the client we are freeing is blocked into a blocking
|
||||||
* call, we have to set querybuf to NULL *before* to call unblockClient()
|
* call, we have to set querybuf to NULL *before* to call
|
||||||
* to avoid processInputBuffer() will get called. Also it is important
|
* unblockClientWaitingData() to avoid processInputBuffer() will get
|
||||||
* to remove the file events after this, because this call adds
|
* called. Also it is important to remove the file events after
|
||||||
* the READABLE event. */
|
* this, because this call adds the READABLE event. */
|
||||||
sdsfree(c->querybuf);
|
sdsfree(c->querybuf);
|
||||||
c->querybuf = NULL;
|
c->querybuf = NULL;
|
||||||
if (c->flags & REDIS_BLOCKED)
|
if (c->flags & REDIS_BLOCKED)
|
||||||
unblockClient(c);
|
unblockClientWaitingData(c);
|
||||||
|
|
||||||
aeDeleteFileEvent(server.el,c->fd,AE_READABLE);
|
aeDeleteFileEvent(server.el,c->fd,AE_READABLE);
|
||||||
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
|
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
|
||||||
@ -5951,7 +5951,7 @@ static void blockForKeys(redisClient *c, robj **keys, int numkeys, time_t timeou
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Unblock a client that's waiting in a blocking operation such as BLPOP */
|
/* Unblock a client that's waiting in a blocking operation such as BLPOP */
|
||||||
static void unblockClient(redisClient *c) {
|
static void unblockClientWaitingData(redisClient *c) {
|
||||||
dictEntry *de;
|
dictEntry *de;
|
||||||
list *l;
|
list *l;
|
||||||
int j;
|
int j;
|
||||||
@ -5975,16 +5975,17 @@ static void unblockClient(redisClient *c) {
|
|||||||
c->flags &= (~REDIS_BLOCKED);
|
c->flags &= (~REDIS_BLOCKED);
|
||||||
server.blockedclients--;
|
server.blockedclients--;
|
||||||
/* Ok now we are ready to get read events from socket, note that we
|
/* Ok now we are ready to get read events from socket, note that we
|
||||||
* can't trap errors here as it's possible that unblockClients() is
|
* can't trap errors here as it's possible that unblockClientWaitingDatas() is
|
||||||
* called from freeClient() itself, and the only thing we can do
|
* called from freeClient() itself, and the only thing we can do
|
||||||
* if we failed to register the READABLE event is to kill the client.
|
* if we failed to register the READABLE event is to kill the client.
|
||||||
* Still the following function should never fail in the real world as
|
* Still the following function should never fail in the real world as
|
||||||
* we are sure the file descriptor is sane, and we exit on out of mem. */
|
* we are sure the file descriptor is sane, and we exit on out of mem. */
|
||||||
aeCreateFileEvent(server.el, c->fd, AE_READABLE, readQueryFromClient, c);
|
aeCreateFileEvent(server.el, c->fd, AE_READABLE, readQueryFromClient, c);
|
||||||
/* As a final step we want to process data if there is some command waiting
|
/* As a final step we want to process data if there is some command waiting
|
||||||
* in the input buffer. Note that this is safe even if unblockClient()
|
* in the input buffer. Note that this is safe even if
|
||||||
* gets called from freeClient() because freeClient() will be smart
|
* unblockClientWaitingData() gets called from freeClient() because
|
||||||
* enough to call this function *after* c->querybuf was set to NULL. */
|
* freeClient() will be smart enough to call this function
|
||||||
|
* *after* c->querybuf was set to NULL. */
|
||||||
if (c->querybuf && sdslen(c->querybuf) > 0) processInputBuffer(c);
|
if (c->querybuf && sdslen(c->querybuf) > 0) processInputBuffer(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -6018,7 +6019,7 @@ static int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele) {
|
|||||||
addReplyBulkLen(receiver,ele);
|
addReplyBulkLen(receiver,ele);
|
||||||
addReply(receiver,ele);
|
addReply(receiver,ele);
|
||||||
addReply(receiver,shared.crlf);
|
addReply(receiver,shared.crlf);
|
||||||
unblockClient(receiver);
|
unblockClientWaitingData(receiver);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -7395,7 +7396,7 @@ static int vmSwapOneObject(int usethreads) {
|
|||||||
|
|
||||||
for (j = 0; j < server.dbnum; j++) {
|
for (j = 0; j < server.dbnum; j++) {
|
||||||
redisDb *db = server.db+j;
|
redisDb *db = server.db+j;
|
||||||
int maxtries = 1000;
|
int maxtries = 100;
|
||||||
|
|
||||||
if (dictSize(db->dict) == 0) continue;
|
if (dictSize(db->dict) == 0) continue;
|
||||||
for (i = 0; i < 5; i++) {
|
for (i = 0; i < 5; i++) {
|
||||||
@ -7500,9 +7501,7 @@ static void vmThreadedIOCompletedJob(aeEventLoop *el, int fd, void *privdata,
|
|||||||
int mask)
|
int mask)
|
||||||
{
|
{
|
||||||
char buf[1];
|
char buf[1];
|
||||||
int retval;
|
int retval, processed = 0, toprocess = -1, trytoswap = 1;
|
||||||
int processed = 0;
|
|
||||||
int toprocess = -1;
|
|
||||||
REDIS_NOTUSED(el);
|
REDIS_NOTUSED(el);
|
||||||
REDIS_NOTUSED(mask);
|
REDIS_NOTUSED(mask);
|
||||||
REDIS_NOTUSED(privdata);
|
REDIS_NOTUSED(privdata);
|
||||||
@ -7599,7 +7598,9 @@ static void vmThreadedIOCompletedJob(aeEventLoop *el, int fd, void *privdata,
|
|||||||
freeIOJob(j);
|
freeIOJob(j);
|
||||||
/* Put a few more swap requests in queue if we are still
|
/* Put a few more swap requests in queue if we are still
|
||||||
* out of memory */
|
* out of memory */
|
||||||
if (vmCanSwapOut() && zmalloc_used_memory() > server.vm_max_memory){
|
if (trytoswap && vmCanSwapOut() &&
|
||||||
|
zmalloc_used_memory() > server.vm_max_memory)
|
||||||
|
{
|
||||||
int more = 1;
|
int more = 1;
|
||||||
while(more) {
|
while(more) {
|
||||||
lockThreadedIO();
|
lockThreadedIO();
|
||||||
@ -7607,7 +7608,10 @@ static void vmThreadedIOCompletedJob(aeEventLoop *el, int fd, void *privdata,
|
|||||||
(unsigned) server.vm_max_threads;
|
(unsigned) server.vm_max_threads;
|
||||||
unlockThreadedIO();
|
unlockThreadedIO();
|
||||||
/* Don't waste CPU time if swappable objects are rare. */
|
/* Don't waste CPU time if swappable objects are rare. */
|
||||||
if (vmSwapOneObjectThreaded() == REDIS_ERR) break;
|
if (vmSwapOneObjectThreaded() == REDIS_ERR) {
|
||||||
|
trytoswap = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -7839,6 +7843,19 @@ static int vmSwapObjectThreaded(robj *key, robj *val, redisDb *db) {
|
|||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============ Virtual Memory - Blocking clients on missing keys =========== */
|
||||||
|
|
||||||
|
/* Is this client attempting to run a command against swapped keys?
|
||||||
|
* If so, block it ASAP, load the keys in background, then resume it.4
|
||||||
|
*
|
||||||
|
* The improtat thing about this function is that it can fail! If keys will
|
||||||
|
* still be swapped when the client is resumed, a few of key lookups will
|
||||||
|
* just block loading keys from disk. */
|
||||||
|
#if 0
|
||||||
|
static void blockClientOnSwappedKeys(redisClient *c) {
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* ================================= Debugging ============================== */
|
/* ================================= Debugging ============================== */
|
||||||
|
|
||||||
static void debugCommand(redisClient *c) {
|
static void debugCommand(redisClient *c) {
|
||||||
|
@ -20,7 +20,7 @@ array set ::redis::multibulkarg {}
|
|||||||
|
|
||||||
# Flag commands requiring last argument as a bulk write operation
|
# Flag commands requiring last argument as a bulk write operation
|
||||||
foreach redis_bulk_cmd {
|
foreach redis_bulk_cmd {
|
||||||
set setnx rpush lpush lset lrem sadd srem sismember echo getset smove zadd zrem zscore rpoplpush zincrby
|
set setnx rpush lpush lset lrem sadd srem sismember echo getset smove zadd zrem zscore zincrby
|
||||||
} {
|
} {
|
||||||
set ::redis::bulkarg($redis_bulk_cmd) {}
|
set ::redis::bulkarg($redis_bulk_cmd) {}
|
||||||
}
|
}
|
||||||
|
@ -206,7 +206,7 @@ static struct redisFunctionSym symsTable[] = {
|
|||||||
{"tryResizeHashTables",(unsigned long)tryResizeHashTables},
|
{"tryResizeHashTables",(unsigned long)tryResizeHashTables},
|
||||||
{"ttlCommand",(unsigned long)ttlCommand},
|
{"ttlCommand",(unsigned long)ttlCommand},
|
||||||
{"typeCommand",(unsigned long)typeCommand},
|
{"typeCommand",(unsigned long)typeCommand},
|
||||||
{"unblockClient",(unsigned long)unblockClient},
|
{"unblockClientWaitingData",(unsigned long)unblockClientWaitingData},
|
||||||
{"unlockThreadedIO",(unsigned long)unlockThreadedIO},
|
{"unlockThreadedIO",(unsigned long)unlockThreadedIO},
|
||||||
{"updateSlavesWaitingBgsave",(unsigned long)updateSlavesWaitingBgsave},
|
{"updateSlavesWaitingBgsave",(unsigned long)updateSlavesWaitingBgsave},
|
||||||
{"vmCanSwapOut",(unsigned long)vmCanSwapOut},
|
{"vmCanSwapOut",(unsigned long)vmCanSwapOut},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user