Skip to content

Commit 6a84054

Browse files
committed
only encode ttl on new hash object
1 parent 6a92b32 commit 6a84054

File tree

3 files changed

+37
-36
lines changed

3 files changed

+37
-36
lines changed

src/storage/redis_db.cc

+3-3
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ rocksdb::Status Database::MDel(const std::vector<Slice> &keys, uint64_t *deleted
225225

226226
batch->Delete(metadata_cf_handle_, lock_keys[i]);
227227

228-
// if delete a hash object that all of fields expired,
228+
// if delete a hash object that all of fields expired,
229229
// so this hash object should be treated as empty and should not affect the deleted_cnt.
230230
if (metadata.Type() == kRedisHash) {
231231
HashMetadata hash_metadata(false);
@@ -314,7 +314,7 @@ rocksdb::Status Database::Keys(const std::string &prefix, std::vector<std::strin
314314
if (stats) stats->n_expired++;
315315
continue;
316316
}
317-
// if a hash object that all of fields was expired,
317+
// if a hash object that all of fields was expired,
318318
// so the key should not be returned.
319319
if (metadata.Type() == kRedisHash) {
320320
HashMetadata hash_metadata(false);
@@ -409,7 +409,7 @@ rocksdb::Status Database::Scan(const std::string &cursor, uint64_t limit, const
409409

410410
if (metadata.Expired()) continue;
411411

412-
// if a hash object that all of fields was expired,
412+
// if a hash object that all of fields was expired,
413413
// so the key should not be returned.
414414
if (metadata.Type() == kRedisHash) {
415415
HashMetadata hash_metadata(false);

src/types/redis_hash.cc

+21-32
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "redis_hash.h"
2222

2323
#include <rocksdb/status.h>
24+
#include <sys/wait.h>
2425

2526
#include <algorithm>
2627
#include <cctype>
@@ -46,15 +47,15 @@ rocksdb::Status Hash::Size(const Slice &user_key, uint64_t *size) {
4647
HashMetadata metadata(false);
4748
rocksdb::Status s = GetMetadata(Database::GetOptions{}, ns_key, &metadata);
4849
if (!s.ok()) return s;
49-
// if field expiration is disabled,
50+
// if field expiration is disabled,
5051
// the size field in metadata is the length of hash
5152
if (!metadata.IsFieldExpirationEnabled()) {
5253
*size = metadata.size;
5354
return rocksdb::Status::OK();
5455
}
5556

5657
// otherwise, we have to check each field to calc the length
57-
LatestSnapShot ss(storage_);
58+
LatestSnapShot ss(storage_);
5859
std::string prefix_key = InternalKey(ns_key, "", metadata.version, storage_->IsSlotIdEncoded()).Encode();
5960
std::string next_version_prefix_key =
6061
InternalKey(ns_key, "", metadata.version + 1, storage_->IsSlotIdEncoded()).Encode();
@@ -101,6 +102,9 @@ rocksdb::Status Hash::IncrBy(const Slice &user_key, const Slice &field, int64_t
101102
HashMetadata metadata;
102103
rocksdb::Status s = GetMetadata(GetOptions{}, ns_key, &metadata);
103104
if (!s.ok() && !s.IsNotFound()) return s;
105+
if (s.IsNotFound()) {
106+
metadata.field_encoding = HashSubkeyEncoding::VALUE_WITH_TTL;
107+
}
104108

105109
uint64_t expire = 0;
106110
std::string sub_key = InternalKey(ns_key, field, metadata.version, storage_->IsSlotIdEncoded()).Encode();
@@ -156,6 +160,9 @@ rocksdb::Status Hash::IncrByFloat(const Slice &user_key, const Slice &field, dou
156160
HashMetadata metadata;
157161
rocksdb::Status s = GetMetadata(GetOptions{}, ns_key, &metadata);
158162
if (!s.ok() && !s.IsNotFound()) return s;
163+
if (s.IsNotFound()) {
164+
metadata.field_encoding = HashSubkeyEncoding::VALUE_WITH_TTL;
165+
}
159166

160167
uint64_t expire = 0;
161168
std::string sub_key = InternalKey(ns_key, field, metadata.version, storage_->IsSlotIdEncoded()).Encode();
@@ -294,6 +301,12 @@ rocksdb::Status Hash::MSet(const Slice &user_key, const std::vector<FieldValue>
294301
rocksdb::Status s = GetMetadata(GetOptions{}, ns_key, &metadata);
295302
if (!s.ok() && !s.IsNotFound()) return s;
296303

304+
// For avoid affect existing data,
305+
// we only encode ttl of field on new hash object.
306+
if (s.IsNotFound()) {
307+
metadata.field_encoding = HashSubkeyEncoding::VALUE_WITH_TTL;
308+
}
309+
297310
int added = 0;
298311
auto batch = storage_->GetWriteBatchBase();
299312
WriteBatchLogData log_data(kRedisHash);
@@ -502,6 +515,11 @@ rocksdb::Status Hash::ExpireFields(const Slice &user_key, uint64_t expire_ms, co
502515
return rocksdb::Status::OK();
503516
}
504517

518+
// we don't support encode ttl on existing hash object
519+
if (!metadata.IsFieldExpirationEnabled()) {
520+
return rocksdb::Status::NotSupported("can't expire fields on existing hash object");
521+
}
522+
505523
rocksdb::ReadOptions read_options = storage_->DefaultMultiGetOptions();
506524
read_options.snapshot = ss.GetSnapShot();
507525

@@ -565,35 +583,6 @@ rocksdb::Status Hash::ExpireFields(const Slice &user_key, uint64_t expire_ms, co
565583
}
566584
}
567585

568-
// convert rest field encoding
569-
if (!metadata.IsFieldExpirationEnabled()) {
570-
metadata.field_encoding = HashSubkeyEncoding::VALUE_WITH_TTL;
571-
572-
std::unordered_set<std::string_view> field_set;
573-
for (auto field : fields) {
574-
if (!field_set.emplace(field.ToStringView()).second) {
575-
continue;
576-
}
577-
}
578-
579-
std::string prefix_key = InternalKey(ns_key, "", metadata.version, storage_->IsSlotIdEncoded()).Encode();
580-
std::string next_version_prefix_key =
581-
InternalKey(ns_key, "", metadata.version + 1, storage_->IsSlotIdEncoded()).Encode();
582-
583-
rocksdb::Slice upper_bound(next_version_prefix_key);
584-
read_options.iterate_upper_bound = &upper_bound;
585-
586-
auto iter = util::UniqueIterator(storage_, read_options);
587-
for (iter->Seek(prefix_key); iter->Valid() && iter->key().starts_with(prefix_key); iter->Next()) {
588-
InternalKey sub_ikey(iter->key(), storage_->IsSlotIdEncoded());
589-
auto value = iter->value().ToString();
590-
if (field_set.find(sub_ikey.GetSubKey().ToStringView()) == field_set.end()) {
591-
encodeExpireToValue(&value, 0);
592-
batch->Put(sub_ikey.Encode(), value);
593-
}
594-
}
595-
}
596-
597586
std::string bytes;
598587
metadata.Encode(&bytes);
599588
batch->Put(metadata_cf_handle_, ns_key, bytes);
@@ -747,7 +736,7 @@ bool Hash::ExistValidField(const Slice &ns_key, const HashMetadata &metadata) {
747736
return true;
748737
}
749738

750-
LatestSnapShot ss(storage_);
739+
LatestSnapShot ss(storage_);
751740
std::string prefix_key = InternalKey(ns_key, "", metadata.version, storage_->IsSlotIdEncoded()).Encode();
752741
std::string next_version_prefix_key =
753742
InternalKey(ns_key, "", metadata.version + 1, storage_->IsSlotIdEncoded()).Encode();

tests/gocase/unit/type/hash/hash_test.go

+13-1
Original file line numberDiff line numberDiff line change
@@ -702,8 +702,20 @@ var testHash = func(t *testing.T, enabledRESP3 string) {
702702
rdb.Do(ctx, "HEXPIRE", "hfe-key", 1, "FIELDS", 2, "f1", "f2")
703703
time.Sleep(1 * time.Second)
704704

705-
require.Equal(t, "none", rdb.Type(ctx, "hfe-key").Val())
705+
// if a hash object than all of fields was expired,
706+
// the hash object should be treated not exist.
706707
require.Equal(t, int64(0), rdb.HLen(ctx, "hfe-key").Val())
708+
require.Equal(t, "none", rdb.Type(ctx, "hfe-key").Val())
709+
require.Equal(t, 0, rdb.Exists(ctx, "hfe-key").Val())
710+
require.Equal(t, -2, rdb.TTL(ctx, "hfe-key").Val())
711+
require.Equal(t, -2, rdb.PTTL(ctx, "hfe-key").Val())
712+
require.Equal(t, -2, rdb.ExpireTime(ctx, "hfe-key").Val())
713+
require.Equal(t, -2, rdb.PExpireTime(ctx, "hfe-key").Val())
714+
require.Equal(t, 0, rdb.ExpireAt(ctx, "hfe-key", time.Now().Add(1*time.Second)).Val())
715+
require.Equal(t, 0, rdb.PExpireAt(ctx, "hfe-key", time.Unix(time.Now().Unix()+1, 0)).Val())
716+
require.Equal(t, 0, rdb.Copy(ctx, "hfe-key", "dst", 0, true).Val())
717+
require.Equal(t, "", rdb.Dump(ctx, "hfe-key").Val())
718+
require.Equal(t, 0, rdb.Del(ctx, "hfe-key").Val())
707719
})
708720

709721
t.Run("HFE expected 0 if delete a expired field", func(t *testing.T) {

0 commit comments

Comments
 (0)