diff --git a/db/db_impl.cc b/db/db_impl.cc index ce2466a2e..a090deb76 100644 --- a/db/db_impl.cc +++ b/db/db_impl.cc @@ -2718,10 +2718,13 @@ bool DBImpl::IsSnapshotSupported() const { } const Snapshot* DBImpl::GetSnapshot() { + int64_t unix_time = 0; + env_->GetCurrentTime(&unix_time); // Ignore error + MutexLock l(&mutex_); // returns null if the underlying memtable does not support snapshot. if (!IsSnapshotSupported()) return nullptr; - return snapshots_.New(versions_->LastSequence()); + return snapshots_.New(versions_->LastSequence(), unix_time); } void DBImpl::ReleaseSnapshot(const Snapshot* s) { diff --git a/db/db_impl.h b/db/db_impl.h index c2c3969c1..189937b0f 100644 --- a/db/db_impl.h +++ b/db/db_impl.h @@ -248,6 +248,8 @@ class DBImpl : public DB { ColumnFamilyHandle* DefaultColumnFamily() const; + const SnapshotList& snapshots() const { return snapshots_; } + protected: Env* const env_; const std::string dbname_; diff --git a/db/db_test.cc b/db/db_test.cc index ccc7597a2..5d40a7b33 100644 --- a/db/db_test.cc +++ b/db/db_test.cc @@ -173,7 +173,9 @@ class SpecialEnv : public EnvWrapper { std::function* table_write_callback_; - explicit SpecialEnv(Env* base) : EnvWrapper(base), rnd_(301) { + int64_t addon_time_; + + explicit SpecialEnv(Env* base) : EnvWrapper(base), rnd_(301), addon_time_(0) { delay_sstable_sync_.store(false, std::memory_order_release); drop_writes_.store(false, std::memory_order_release); no_space_.store(false, std::memory_order_release); @@ -368,6 +370,14 @@ class SpecialEnv : public EnvWrapper { sleep_counter_.Increment(); target()->SleepForMicroseconds(micros); } + + virtual Status GetCurrentTime(int64_t* unix_time) override { + Status s = target()->GetCurrentTime(unix_time); + if (s.ok()) { + *unix_time += addon_time_; + } + return s; + } }; class DBTest { @@ -814,6 +824,19 @@ class DBTest { return result; } + uint64_t GetNumSnapshots() { + uint64_t int_num; + ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.num-snapshots", &int_num)); + return int_num; + } + + uint64_t GetTimeOldestSnapshots() { + uint64_t int_num; + ASSERT_TRUE( + dbfull()->GetIntProperty("rocksdb.oldest-snapshot-time", &int_num)); + return int_num; + } + // Return a string that contains all key,value pairs in order, // formatted like "(k1->v1)(k2->v2)". std::string Contents(int cf = 0) { @@ -5429,13 +5452,25 @@ TEST(DBTest, Snapshot) { CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); Put(0, "foo", "0v1"); Put(1, "foo", "1v1"); + const Snapshot* s1 = db_->GetSnapshot(); + ASSERT_EQ(1U, GetNumSnapshots()); + uint64_t time_snap1 = GetTimeOldestSnapshots(); + ASSERT_GT(time_snap1, 0U); Put(0, "foo", "0v2"); Put(1, "foo", "1v2"); + + env_->addon_time_++; + const Snapshot* s2 = db_->GetSnapshot(); + ASSERT_EQ(2U, GetNumSnapshots()); + ASSERT_EQ(time_snap1, GetTimeOldestSnapshots()); Put(0, "foo", "0v3"); Put(1, "foo", "1v3"); + const Snapshot* s3 = db_->GetSnapshot(); + ASSERT_EQ(3U, GetNumSnapshots()); + ASSERT_EQ(time_snap1, GetTimeOldestSnapshots()); Put(0, "foo", "0v4"); Put(1, "foo", "1v4"); @@ -5449,6 +5484,8 @@ TEST(DBTest, Snapshot) { ASSERT_EQ("1v4", Get(1, "foo")); db_->ReleaseSnapshot(s3); + ASSERT_EQ(2U, GetNumSnapshots()); + ASSERT_EQ(time_snap1, GetTimeOldestSnapshots()); ASSERT_EQ("0v1", Get(0, "foo", s1)); ASSERT_EQ("1v1", Get(1, "foo", s1)); ASSERT_EQ("0v2", Get(0, "foo", s2)); @@ -5461,8 +5498,11 @@ TEST(DBTest, Snapshot) { ASSERT_EQ("1v2", Get(1, "foo", s2)); ASSERT_EQ("0v4", Get(0, "foo")); ASSERT_EQ("1v4", Get(1, "foo")); + ASSERT_EQ(1U, GetNumSnapshots()); + ASSERT_LT(time_snap1, GetTimeOldestSnapshots()); db_->ReleaseSnapshot(s2); + ASSERT_EQ(0U, GetNumSnapshots()); ASSERT_EQ("0v4", Get(0, "foo")); ASSERT_EQ("1v4", Get(1, "foo")); } while (ChangeOptions(kSkipHashCuckoo)); diff --git a/db/internal_stats.cc b/db/internal_stats.cc index c729ef8d6..c14a03c12 100644 --- a/db/internal_stats.cc +++ b/db/internal_stats.cc @@ -136,6 +136,10 @@ DBPropertyType GetPropertyType(const Slice& property, bool* is_int_property, return kEstimatedUsageByTableReaders; } else if (in == "is-file-deletions-enabled") { return kIsFileDeletionEnabled; + } else if (in == "num-snapshots") { + return kNumSnapshots; + } else if (in == "oldest-snapshot-time") { + return kOldestSnapshotTime; } return kUnknown; } @@ -263,6 +267,12 @@ bool InternalStats::GetIntProperty(DBPropertyType property_type, cfd_->imm()->current()->GetTotalNumEntries() + vstorage->GetEstimatedActiveKeys(); return true; + case kNumSnapshots: + *value = db->snapshots().count(); + return true; + case kOldestSnapshotTime: + *value = static_cast(db->snapshots().GetOldestSnapshotTime()); + return true; #ifndef ROCKSDB_LITE case kIsFileDeletionEnabled: *value = db->IsFileDeletionsEnabled(); diff --git a/db/internal_stats.h b/db/internal_stats.h index 0c98ebcea..96c13e03b 100644 --- a/db/internal_stats.h +++ b/db/internal_stats.h @@ -46,6 +46,8 @@ enum DBPropertyType : uint32_t { kEstimatedUsageByTableReaders, // Estimated memory by table readers. kIsFileDeletionEnabled, // Equals disable_delete_obsolete_files_, // 0 means file deletions enabled + kNumSnapshots, // Number of snapshots in the system + kOldestSnapshotTime, // Unix timestamp of the first snapshot }; extern DBPropertyType GetPropertyType(const Slice& property, diff --git a/db/snapshot.h b/db/snapshot.h index 51fa556c8..45c66eabc 100644 --- a/db/snapshot.h +++ b/db/snapshot.h @@ -28,6 +28,8 @@ class SnapshotImpl : public Snapshot { SnapshotImpl* next_; SnapshotList* list_; // just for sanity checks + + int64_t unix_time_; }; class SnapshotList { @@ -36,20 +38,23 @@ class SnapshotList { list_.prev_ = &list_; list_.next_ = &list_; list_.number_ = 0xFFFFFFFFL; // placeholder marker, for debugging + count_ = 0; } bool empty() const { return list_.next_ == &list_; } SnapshotImpl* oldest() const { assert(!empty()); return list_.next_; } SnapshotImpl* newest() const { assert(!empty()); return list_.prev_; } - const SnapshotImpl* New(SequenceNumber seq) { + const SnapshotImpl* New(SequenceNumber seq, uint64_t unix_time) { SnapshotImpl* s = new SnapshotImpl; s->number_ = seq; + s->unix_time_ = unix_time; s->list_ = this; s->next_ = &list_; s->prev_ = list_.prev_; s->prev_->next_ = s; s->next_->prev_ = s; + count_++; return s; } @@ -57,6 +62,7 @@ class SnapshotList { assert(s->list_ == this); s->prev_->next_ = s->next_; s->next_->prev_ = s->prev_; + count_--; delete s; } @@ -78,9 +84,20 @@ class SnapshotList { return newest()->number_; } + int64_t GetOldestSnapshotTime() const { + if (empty()) { + return 0; + } else { + return oldest()->unix_time_; + } + } + + uint64_t count() const { return count_; } + private: // Dummy head of doubly-linked list of snapshots SnapshotImpl list_; + uint64_t count_; }; } // namespace rocksdb