• antirez's avatar
    Replication: fix the infamous key leakage of writable slaves + EXPIRE. · 04542cff
    antirez authored
    BACKGROUND AND USE CASEj
    
    Redis slaves are normally write only, however the supprot a "writable"
    mode which is very handy when scaling reads on slaves, that actually
    need write operations in order to access data. For instance imagine
    having slaves replicating certain Sets keys from the master. When
    accessing the data on the slave, we want to peform intersections between
    such Sets values. However we don't want to intersect each time: to cache
    the intersection for some time often is a good idea.
    
    To do so, it is possible to setup a slave as a writable slave, and
    perform the intersection on the slave side, perhaps setting a TTL on the
    resulting key so that it will expire after some time.
    
    THE BUG
    
    Problem: in order to have a consistent replication, expiring of keys in
    Redis replication is up to the master, that synthesize DEL operations to
    send in the replication stream. However slaves logically expire keys
    by hiding them from read attempts from clients so that if the master did
    not promptly sent a DEL, the client still see logically expired keys
    as non existing.
    
    Because slaves don't actively expire keys by actually evicting them but
    just masking from the POV of read operations, if a key is created in a
    writable slave, and an expire is set, the key will be leaked forever:
    
    1. No DEL will be received from the master, which does not know about
    such a key at all.
    
    2. No eviction will be performed by the slave, since it needs to disable
    eviction because it's up to masters, otherwise consistency of data is
    lost.
    
    THE FIX
    
    In order to fix the problem, the slave should be able to tag keys that
    were created in the slave side and have an expire set in some way.
    
    My solution involved using an unique additional dictionary created by
    the writable slave only if needed. The dictionary is obviously keyed by
    the key name that we need to track: all the keys that are set with an
    expire directly by a client writing to the slave are tracked.
    
    The value in the dictionary is a bitmap of all the DBs where such a key
    name need to be tracked, so that we can use a single dictionary to track
    keys in all the DBs used by the slave (actually this limits the solution
    to the first 64 DBs, but the default with Redis is to use 16 DBs).
    
    This solution allows to pay both a small complexity and CPU penalty,
    which is zero when the feature is not used, actually. The slave-side
    eviction is encapsulated in code which is not coupled with the rest of
    the Redis core, if not for the hook to track the keys.
    
    TODO
    
    I'm doing the first smoke tests to see if the feature works as expected:
    so far so good. Unit tests should be added before merging into the
    4.0 branch.
    04542cff
server.h 84.7 KB