Skip to content

Commit 91d0975

Browse files
committed
Merge branch 'main' into RELEASE_6_3
2 parents aa2143f + 38310ec commit 91d0975

13 files changed

Lines changed: 144 additions & 47 deletions

File tree

COPYING

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Copyright (c) 2006-2020, Salvatore Sanfilippo
2+
Copyright (C) 2019-2021, John Sully
3+
Copyright (C) 2020-2021, EQ Alpha Technology Ltd.
4+
Copyright (C) 2022 Snap Inc.
5+
All rights reserved.
6+
7+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
8+
9+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
10+
* 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.
11+
* 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.
12+
13+
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.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ Avoid forwarding RREPLAY messages to other masters? WARNING: This setting is dan
9999

100100

101101
```
102-
scratch-file-path /path
102+
db-s3-object /path/to/bucket
103103
```
104104
If you would like KeyDB to dump and load directly to AWS S3 this option specifies the bucket. Using this option with the traditional RDB options will result in KeyDB backing up twice to both locations. If both are specified KeyDB will first attempt to load from the local dump file and if that fails load from S3. This requires the AWS CLI tools to be installed and configured which are used under the hood to transfer the data.
105105

pkg/deb/master_changelog

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
keydb (6:6.3.0-1distribution_placeholder) codename_placeholder; urgency=medium
2+
3+
* This release open sources KeyDB Enterprise features into the open source project along with PSYNC for active replication
4+
* Partial synchronization for active replication is introduced
5+
* MVCC introduced into codebase from KeyDB Enterprise
6+
* Async commands added: GET, MGET. These will see perf improvements
7+
* KEYS and SCAN commands will no longer be blocking calls
8+
* Async Rehash implemented for additional stability to perf
9+
* IStorage interface added
10+
* In-process background saving (forkless) to comply with maxmemory setting
11+
* See v6.3.0 tagged release notes on github for a detailed explanation of these changes
12+
13+
-- Ben Schermel <ben@eqalpha.com> Wed, 11 May 2022 20:00:37 +0000
14+
115
keydb (6:6.2.2-1distribution_placeholder) codename_placeholder; urgency=medium
216

317
* Acquire lock in module.cpp to fix module test break

src/config.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2782,7 +2782,7 @@ standardConfig configs[] = {
27822782
createBoolConfig("disable-thp", NULL, MODIFIABLE_CONFIG, g_pserver->disable_thp, 1, NULL, NULL),
27832783
createBoolConfig("cluster-allow-replica-migration", NULL, MODIFIABLE_CONFIG, g_pserver->cluster_allow_replica_migration, 1, NULL, NULL),
27842784
createBoolConfig("replica-announced", NULL, MODIFIABLE_CONFIG, g_pserver->replica_announced, 1, NULL, NULL),
2785-
createBoolConfig("enable-async-commands", NULL, MODIFIABLE_CONFIG, g_pserver->enable_async_commands, 1, NULL, NULL),
2785+
createBoolConfig("enable-async-commands", NULL, MODIFIABLE_CONFIG, g_pserver->enable_async_commands, 0, NULL, NULL),
27862786
createBoolConfig("multithread-load-enabled", NULL, MODIFIABLE_CONFIG, g_pserver->multithread_load_enabled, 0, NULL, NULL),
27872787
createBoolConfig("active-client-balancing", NULL, MODIFIABLE_CONFIG, g_pserver->active_client_balancing, 1, NULL, NULL),
27882788

src/db.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2669,6 +2669,11 @@ void redisDbPersistentData::prepOverwriteForSnapshot(char *key)
26692669
auto itr = m_pdbSnapshot->find_cached_threadsafe(key);
26702670
if (itr.key() != nullptr)
26712671
{
2672+
if (itr.val()->FExpires()) {
2673+
// Note: I'm sure we could handle this, but its too risky at the moment.
2674+
// There are known bugs doing this with expires
2675+
return;
2676+
}
26722677
sds keyNew = sdsdupshared(itr.key());
26732678
if (dictAdd(m_pdictTombstone, keyNew, (void*)dictHashKey(m_pdict, key)) != DICT_OK)
26742679
sdsfree(keyNew);
@@ -3263,10 +3268,11 @@ bool redisDbPersistentData::prefetchKeysAsync(client *c, parsed_command &command
32633268
dictEntry **table;
32643269
__atomic_load(&c->db->m_pdict->ht[iht].table, &table, __ATOMIC_RELAXED);
32653270
if (table != nullptr) {
3266-
dictEntry *de = table[hT];
3271+
dictEntry *de;
3272+
__atomic_load(&table[hT], &de, __ATOMIC_ACQUIRE);
32673273
while (de != nullptr) {
32683274
_mm_prefetch(dictGetKey(de), _MM_HINT_T2);
3269-
de = de->next;
3275+
__atomic_load(&de->next, &de, __ATOMIC_ACQUIRE);
32703276
}
32713277
}
32723278
if (!dictIsRehashing(c->db->m_pdict))

src/dict.cpp

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ int _dictInit(dict *d, dictType *type,
128128
d->pauserehash = 0;
129129
d->asyncdata = nullptr;
130130
d->refcount = 1;
131+
d->noshrink = false;
131132
return DICT_OK;
132133
}
133134

@@ -204,15 +205,15 @@ int dictMerge(dict *dst, dict *src)
204205

205206
if (dictSize(dst) == 0)
206207
{
207-
std::swap(*dst, *src);
208+
dict::swap(*dst, *src);
208209
std::swap(dst->pauserehash, src->pauserehash);
209210
return DICT_OK;
210211
}
211212

212213
size_t expectedSize = dictSize(src) + dictSize(dst);
213214
if (dictSize(src) > dictSize(dst) && src->asyncdata == nullptr && dst->asyncdata == nullptr)
214215
{
215-
std::swap(*dst, *src);
216+
dict::swap(*dst, *src);
216217
std::swap(dst->pauserehash, src->pauserehash);
217218
}
218219

@@ -402,7 +403,7 @@ int dictRehash(dict *d, int n) {
402403

403404
dictAsyncRehashCtl::dictAsyncRehashCtl(struct dict *d, dictAsyncRehashCtl *next) : dict(d), next(next) {
404405
queue.reserve(c_targetQueueSize);
405-
__atomic_fetch_add(&d->refcount, 1, __ATOMIC_RELEASE);
406+
__atomic_fetch_add(&d->refcount, 1, __ATOMIC_ACQ_REL);
406407
this->rehashIdxBase = d->rehashidx;
407408
}
408409

@@ -446,6 +447,9 @@ dictAsyncRehashCtl *dictRehashAsyncStart(dict *d, int buckets) {
446447
}
447448

448449
void dictRehashAsync(dictAsyncRehashCtl *ctl) {
450+
if (ctl->abondon.load(std::memory_order_acquire)) {
451+
ctl->hashIdx = ctl->queue.size();
452+
}
449453
for (size_t idx = ctl->hashIdx; idx < ctl->queue.size(); ++idx) {
450454
auto &wi = ctl->queue[idx];
451455
wi.hash = dictHashKey(ctl->dict, dictGetKey(wi.de));
@@ -455,6 +459,9 @@ void dictRehashAsync(dictAsyncRehashCtl *ctl) {
455459
}
456460

457461
bool dictRehashSomeAsync(dictAsyncRehashCtl *ctl, size_t hashes) {
462+
if (ctl->abondon.load(std::memory_order_acquire)) {
463+
ctl->hashIdx = ctl->queue.size();
464+
}
458465
size_t max = std::min(ctl->hashIdx + hashes, ctl->queue.size());
459466
for (; ctl->hashIdx < max; ++ctl->hashIdx) {
460467
auto &wi = ctl->queue[ctl->hashIdx];
@@ -465,6 +472,23 @@ bool dictRehashSomeAsync(dictAsyncRehashCtl *ctl, size_t hashes) {
465472
return ctl->hashIdx < ctl->queue.size();
466473
}
467474

475+
476+
void discontinueAsyncRehash(dict *d) {
477+
// We inform our async rehashers and the completion function the results are to be
478+
// abandoned. We keep the asyncdata linked in so that dictEntry's are still added
479+
// to the GC list. This is because we can't gurantee when the other threads will
480+
// stop looking at them.
481+
if (d->asyncdata != nullptr) {
482+
auto adata = d->asyncdata;
483+
while (adata != nullptr && !adata->abondon.load(std::memory_order_relaxed)) {
484+
adata->abondon = true;
485+
adata = adata->next;
486+
}
487+
if (dictIsRehashing(d))
488+
d->rehashidx = 0;
489+
}
490+
}
491+
468492
void dictCompleteRehashAsync(dictAsyncRehashCtl *ctl, bool fFree) {
469493
dict *d = ctl->dict;
470494
assert(ctl->done);
@@ -786,6 +810,8 @@ int _dictClear(dict *d, dictht *ht, void(callback)(void *)) {
786810
if (callback && (i & 65535) == 0) callback(d->privdata);
787811

788812
if ((he = ht->table[i]) == NULL) continue;
813+
dictEntry *deNull = nullptr;
814+
__atomic_store(&ht->table[i], &deNull, __ATOMIC_RELEASE);
789815
while(he) {
790816
nextHe = he->next;
791817
if (d->asyncdata && (ssize_t)i < d->rehashidx) {

src/dict.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,16 @@ struct dictAsyncRehashCtl {
110110
std::atomic<bool> abondon { false };
111111

112112
dictAsyncRehashCtl(struct dict *d, dictAsyncRehashCtl *next);
113+
dictAsyncRehashCtl(const dictAsyncRehashCtl&) = delete;
114+
dictAsyncRehashCtl(dictAsyncRehashCtl&&) = delete;
113115
~dictAsyncRehashCtl();
114116
};
115117
#else
116118
struct dictAsyncRehashCtl;
117119
#endif
118120

121+
void discontinueAsyncRehash(dict *d);
122+
119123
typedef struct dict {
120124
dictType *type;
121125
void *privdata;
@@ -125,6 +129,24 @@ typedef struct dict {
125129
dictAsyncRehashCtl *asyncdata;
126130
int16_t pauserehash; /* If >0 rehashing is paused (<0 indicates coding error) */
127131
uint8_t noshrink = false;
132+
133+
#ifdef __cplusplus
134+
dict() = default;
135+
dict(dict &) = delete; // No Copy Ctor
136+
137+
static void swap(dict& a, dict& b) {
138+
discontinueAsyncRehash(&a);
139+
discontinueAsyncRehash(&b);
140+
std::swap(a.type, b.type);
141+
std::swap(a.privdata, b.privdata);
142+
std::swap(a.ht[0], b.ht[0]);
143+
std::swap(a.ht[1], b.ht[1]);
144+
std::swap(a.rehashidx, b.rehashidx);
145+
// Never swap refcount - they are attached to the specific dict obj
146+
std::swap(a.pauserehash, b.pauserehash);
147+
std::swap(a.noshrink, b.noshrink);
148+
}
149+
#endif
128150
} dict;
129151

130152
/* If safe is set to 1 this is a safe iterator, that means, you can call

src/gc.h

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <vector>
33
#include <assert.h>
44
#include <unordered_set>
5+
#include <list>
56

67
struct ICollectable
78
{
@@ -45,14 +46,14 @@ class GarbageCollector
4546
void shutdown()
4647
{
4748
std::unique_lock<fastlock> lock(m_lock);
48-
m_vecepochs.clear();
49+
m_listepochs.clear();
4950
m_setepochOutstanding.clear();
5051
}
5152

5253
bool empty() const
5354
{
5455
std::unique_lock<fastlock> lock(m_lock);
55-
return m_vecepochs.empty();
56+
return m_listepochs.empty();
5657
}
5758

5859
void endEpoch(uint64_t epoch, bool fNoFree = false)
@@ -63,12 +64,12 @@ class GarbageCollector
6364
m_setepochOutstanding.erase(epoch);
6465
if (fNoFree)
6566
return;
66-
std::vector<EpochHolder> vecclean;
67+
std::list<EpochHolder> listclean;
6768

6869
// No outstanding epochs?
6970
if (m_setepochOutstanding.empty())
7071
{
71-
vecclean = std::move(m_vecepochs); // Everything goes!
72+
listclean = std::move(m_listepochs); // Everything goes!
7273
}
7374
else
7475
{
@@ -77,18 +78,20 @@ class GarbageCollector
7778
return; // No available epochs to free
7879

7980
// Clean any epochs available (after the lock)
80-
for (size_t iepoch = 0; iepoch < m_vecepochs.size(); ++iepoch)
81+
for (auto itr = m_listepochs.begin(); itr != m_listepochs.end(); /* itr incremented in loop*/)
8182
{
82-
auto &e = m_vecepochs[iepoch];
83+
auto &e = *itr;
84+
auto itrNext = itr;
85+
++itrNext;
8386
if (e < minepoch)
8487
{
85-
vecclean.emplace_back(std::move(e));
86-
m_vecepochs.erase(m_vecepochs.begin() + iepoch);
87-
--iepoch;
88+
listclean.emplace_back(std::move(e));
89+
m_listepochs.erase(itr);
8890
}
91+
itr = itrNext;
8992
}
9093

91-
assert(vecclean.empty() || fMinElement);
94+
assert(listclean.empty() || fMinElement);
9295
}
9396

9497
lock.unlock(); // don't hold it for the potentially long delete of vecclean
@@ -100,13 +103,13 @@ class GarbageCollector
100103
serverAssert(m_setepochOutstanding.find(epoch) != m_setepochOutstanding.end());
101104
serverAssert(sp->FWillFreeChildDebug() == false);
102105

103-
auto itr = std::find(m_vecepochs.begin(), m_vecepochs.end(), m_epochNext+1);
104-
if (itr == m_vecepochs.end())
106+
auto itr = std::find(m_listepochs.begin(), m_listepochs.end(), m_epochNext+1);
107+
if (itr == m_listepochs.end())
105108
{
106109
EpochHolder e;
107110
e.tstamp = m_epochNext+1;
108111
e.m_vecObjs.push_back(std::move(sp));
109-
m_vecepochs.emplace_back(std::move(e));
112+
m_listepochs.emplace_back(std::move(e));
110113
}
111114
else
112115
{
@@ -117,7 +120,7 @@ class GarbageCollector
117120
private:
118121
mutable fastlock m_lock { "Garbage Collector"};
119122

120-
std::vector<EpochHolder> m_vecepochs;
123+
std::list<EpochHolder> m_listepochs;
121124
std::unordered_set<uint64_t> m_setepochOutstanding;
122125
uint64_t m_epochNext = 0;
123126
};

src/networking.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1810,6 +1810,8 @@ int writeToClient(client *c, int handler_installed) {
18101810
is a replica, so only attempt to do so if that's the case. */
18111811
if (c->flags & CLIENT_SLAVE && !(c->flags & CLIENT_MONITOR) && c->replstate == SLAVE_STATE_ONLINE) {
18121812
std::unique_lock<fastlock> repl_backlog_lock (g_pserver->repl_backlog_lock);
1813+
// Ensure all writes to the repl backlog are visible
1814+
std::atomic_thread_fence(std::memory_order_acquire);
18131815

18141816
while (clientHasPendingReplies(c)) {
18151817
long long repl_end_idx = getReplIndexFromOffset(c->repl_end_off);
@@ -2077,8 +2079,6 @@ int handleClientsWithPendingWrites(int iel, int aof_state) {
20772079
* that may trigger write error or recreate handler. */
20782080
if ((flags & CLIENT_PROTECTED) && !(flags & CLIENT_SLAVE)) continue;
20792081

2080-
//std::unique_lock<decltype(c->lock)> lock(c->lock);
2081-
20822082
/* Don't write to clients that are going to be closed anyway. */
20832083
if (c->flags & CLIENT_CLOSE_ASAP) continue;
20842084

@@ -2096,6 +2096,7 @@ int handleClientsWithPendingWrites(int iel, int aof_state) {
20962096

20972097
/* If after the synchronous writes above we still have data to
20982098
* output to the client, we need to install the writable handler. */
2099+
std::unique_lock<decltype(c->lock)> lock(c->lock);
20992100
if (clientHasPendingReplies(c)) {
21002101
if (connSetWriteHandlerWithBarrier(c->conn, sendReplyToClient, ae_flags, true) == C_ERR) {
21012102
freeClientAsync(c);
@@ -2742,9 +2743,10 @@ void readQueryFromClient(connection *conn) {
27422743
parseClientCommandBuffer(c);
27432744
if (g_pserver->enable_async_commands && !serverTL->disable_async_commands && listLength(g_pserver->monitors) == 0 && (aeLockContention() || serverTL->rgdbSnapshot[c->db->id] || g_fTestMode)) {
27442745
// Frequent writers aren't good candidates for this optimization, they cause us to renew the snapshot too often
2745-
// so we exclude them unless the snapshot we need already exists
2746+
// so we exclude them unless the snapshot we need already exists.
2747+
// Note: In test mode we want to create snapshots as often as possibl to excercise them - we don't care about perf
27462748
bool fSnapshotExists = c->db->mvccLastSnapshot >= c->mvccCheckpoint;
2747-
bool fWriteTooRecent = (((getMvccTstamp() - c->mvccCheckpoint) >> MVCC_MS_SHIFT) < static_cast<uint64_t>(g_pserver->snapshot_slip)/2);
2749+
bool fWriteTooRecent = !g_fTestMode && (((getMvccTstamp() - c->mvccCheckpoint) >> MVCC_MS_SHIFT) < static_cast<uint64_t>(g_pserver->snapshot_slip)/2);
27482750

27492751
// The check below avoids running async commands if this is a frequent writer unless a snapshot is already there to service it
27502752
if (!fWriteTooRecent || fSnapshotExists) {

src/rdb.cpp

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1657,13 +1657,18 @@ int launchRdbSaveThread(pthread_t &child, rdbSaveInfo *rsi)
16571657

16581658
g_pserver->rdbThreadVars.tmpfileNum++;
16591659
g_pserver->rdbThreadVars.fRdbThreadCancel = false;
1660-
if (pthread_create(&child, NULL, rdbSaveThread, args)) {
1660+
pthread_attr_t tattr;
1661+
pthread_attr_init(&tattr);
1662+
pthread_attr_setstacksize(&tattr, 1 << 23); // 8 MB
1663+
if (pthread_create(&child, &tattr, rdbSaveThread, args)) {
1664+
pthread_attr_destroy(&tattr);
16611665
for (int idb = 0; idb < cserver.dbnum; ++idb)
16621666
g_pserver->db[idb]->endSnapshot(args->rgpdb[idb]);
16631667
args->~rdbSaveThreadArgs();
16641668
zfree(args);
16651669
return C_ERR;
16661670
}
1671+
pthread_attr_destroy(&tattr);
16671672
g_pserver->child_type = CHILD_TYPE_RDB;
16681673
}
16691674
return C_OK;
@@ -3049,7 +3054,9 @@ void rdbLoadProgressCallback(rio *r, const void *buf, size_t len) {
30493054
(r->keys_since_last_callback >= g_pserver->loading_process_events_interval_keys)))
30503055
{
30513056
rdbAsyncWorkThread *pwthread = reinterpret_cast<rdbAsyncWorkThread*>(r->chksum_arg);
3052-
bool fUpdateReplication = (g_pserver->mstime - r->last_update) > 1000;
3057+
mstime_t mstime;
3058+
__atomic_load(&g_pserver->mstime, &mstime, __ATOMIC_RELAXED);
3059+
bool fUpdateReplication = (mstime - r->last_update) > 1000;
30533060

30543061
if (fUpdateReplication) {
30553062
listIter li;
@@ -3832,7 +3839,11 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
38323839

38333840
g_pserver->rdbThreadVars.tmpfileNum++;
38343841
g_pserver->rdbThreadVars.fRdbThreadCancel = false;
3835-
if (pthread_create(&child, nullptr, rdbSaveToSlavesSocketsThread, args)) {
3842+
pthread_attr_t tattr;
3843+
pthread_attr_init(&tattr);
3844+
pthread_attr_setstacksize(&tattr, 1 << 23); // 8 MB
3845+
if (pthread_create(&child, &tattr, rdbSaveToSlavesSocketsThread, args)) {
3846+
pthread_attr_destroy(&tattr);
38363847
serverLog(LL_WARNING,"Can't save in background: fork: %s",
38373848
strerror(errno));
38383849

@@ -3858,6 +3869,7 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
38583869
closeChildInfoPipe();
38593870
return C_ERR;
38603871
}
3872+
pthread_attr_destroy(&tattr);
38613873
g_pserver->child_type = CHILD_TYPE_RDB;
38623874

38633875
serverLog(LL_NOTICE,"Background RDB transfer started");

0 commit comments

Comments
 (0)