@ -1000,6 +1000,12 @@ class TestFileOperationListener : public EventListener {
file_syncs_success_ . store ( 0 ) ;
file_truncates_ . store ( 0 ) ;
file_truncates_success_ . store ( 0 ) ;
blob_file_reads_ . store ( 0 ) ;
blob_file_writes_ . store ( 0 ) ;
blob_file_flushes_ . store ( 0 ) ;
blob_file_closes_ . store ( 0 ) ;
blob_file_syncs_ . store ( 0 ) ;
blob_file_truncates_ . store ( 0 ) ;
}
void OnFileReadFinish ( const FileOperationInfo & info ) override {
@ -1007,6 +1013,9 @@ class TestFileOperationListener : public EventListener {
if ( info . status . ok ( ) ) {
+ + file_reads_success_ ;
}
if ( EndsWith ( info . path , " .blob " ) ) {
+ + blob_file_reads_ ;
}
ReportDuration ( info ) ;
}
@ -1015,6 +1024,9 @@ class TestFileOperationListener : public EventListener {
if ( info . status . ok ( ) ) {
+ + file_writes_success_ ;
}
if ( EndsWith ( info . path , " .blob " ) ) {
+ + blob_file_writes_ ;
}
ReportDuration ( info ) ;
}
@ -1023,6 +1035,9 @@ class TestFileOperationListener : public EventListener {
if ( info . status . ok ( ) ) {
+ + file_flushes_success_ ;
}
if ( EndsWith ( info . path , " .blob " ) ) {
+ + blob_file_flushes_ ;
}
ReportDuration ( info ) ;
}
@ -1031,6 +1046,9 @@ class TestFileOperationListener : public EventListener {
if ( info . status . ok ( ) ) {
+ + file_closes_success_ ;
}
if ( EndsWith ( info . path , " .blob " ) ) {
+ + blob_file_closes_ ;
}
ReportDuration ( info ) ;
}
@ -1039,6 +1057,9 @@ class TestFileOperationListener : public EventListener {
if ( info . status . ok ( ) ) {
+ + file_syncs_success_ ;
}
if ( EndsWith ( info . path , " .blob " ) ) {
+ + blob_file_syncs_ ;
}
ReportDuration ( info ) ;
}
@ -1047,6 +1068,9 @@ class TestFileOperationListener : public EventListener {
if ( info . status . ok ( ) ) {
+ + file_truncates_success_ ;
}
if ( EndsWith ( info . path , " .blob " ) ) {
+ + blob_file_truncates_ ;
}
ReportDuration ( info ) ;
}
@ -1064,6 +1088,12 @@ class TestFileOperationListener : public EventListener {
std : : atomic < size_t > file_syncs_success_ ;
std : : atomic < size_t > file_truncates_ ;
std : : atomic < size_t > file_truncates_success_ ;
std : : atomic < size_t > blob_file_reads_ ;
std : : atomic < size_t > blob_file_writes_ ;
std : : atomic < size_t > blob_file_flushes_ ;
std : : atomic < size_t > blob_file_closes_ ;
std : : atomic < size_t > blob_file_syncs_ ;
std : : atomic < size_t > blob_file_truncates_ ;
private :
void ReportDuration ( const FileOperationInfo & info ) const {
@ -1113,6 +1143,379 @@ TEST_F(EventListenerTest, OnFileOperationTest) {
}
}
TEST_F ( EventListenerTest , OnBlobFileOperationTest ) {
Options options ;
options . env = CurrentOptions ( ) . env ;
options . create_if_missing = true ;
TestFileOperationListener * listener = new TestFileOperationListener ( ) ;
options . listeners . emplace_back ( listener ) ;
options . disable_auto_compactions = true ;
options . enable_blob_files = true ;
options . min_blob_size = 0 ;
options . enable_blob_garbage_collection = true ;
options . blob_garbage_collection_age_cutoff = 0.5 ;
DestroyAndReopen ( options ) ;
ASSERT_OK ( Put ( " Key1 " , " blob_value1 " ) ) ;
ASSERT_OK ( Put ( " Key2 " , " blob_value2 " ) ) ;
ASSERT_OK ( Put ( " Key3 " , " blob_value3 " ) ) ;
ASSERT_OK ( Put ( " Key4 " , " blob_value4 " ) ) ;
ASSERT_OK ( Flush ( ) ) ;
ASSERT_OK ( Put ( " Key3 " , " new_blob_value3 " ) ) ;
ASSERT_OK ( Put ( " Key4 " , " new_blob_value4 " ) ) ;
ASSERT_OK ( Flush ( ) ) ;
ASSERT_OK ( Put ( " Key5 " , " blob_value5 " ) ) ;
ASSERT_OK ( Put ( " Key6 " , " blob_value6 " ) ) ;
ASSERT_OK ( Flush ( ) ) ;
ASSERT_GT ( listener - > blob_file_writes_ . load ( ) , 0U ) ;
ASSERT_GT ( listener - > blob_file_flushes_ . load ( ) , 0U ) ;
Close ( ) ;
Reopen ( options ) ;
ASSERT_GT ( listener - > blob_file_closes_ . load ( ) , 0U ) ;
ASSERT_GT ( listener - > blob_file_syncs_ . load ( ) , 0U ) ;
if ( true = = options . use_direct_io_for_flush_and_compaction ) {
ASSERT_GT ( listener - > blob_file_truncates_ . load ( ) , 0U ) ;
}
}
class BlobDBJobLevelEventListenerTest : public EventListener {
public :
explicit BlobDBJobLevelEventListenerTest ( EventListenerTest * test )
: test_ ( test ) , call_count_ ( 0 ) { }
std : : shared_ptr < BlobFileMetaData > GetBlobFileMetaData (
const VersionStorageInfo : : BlobFiles & blob_files ,
uint64_t blob_file_number ) {
const auto it = blob_files . find ( blob_file_number ) ;
if ( it = = blob_files . end ( ) ) {
return nullptr ;
}
const auto & meta = it - > second ;
assert ( meta ) ;
return meta ;
}
const VersionStorageInfo : : BlobFiles & GetBlobFiles ( ) {
VersionSet * const versions = test_ - > dbfull ( ) - > TEST_GetVersionSet ( ) ;
assert ( versions ) ;
ColumnFamilyData * const cfd = versions - > GetColumnFamilySet ( ) - > GetDefault ( ) ;
EXPECT_NE ( cfd , nullptr ) ;
Version * const current = cfd - > current ( ) ;
EXPECT_NE ( current , nullptr ) ;
const VersionStorageInfo * const storage_info = current - > storage_info ( ) ;
EXPECT_NE ( storage_info , nullptr ) ;
const auto & blob_files = storage_info - > GetBlobFiles ( ) ;
return blob_files ;
}
std : : vector < std : : string > GetFlushedFiles ( ) {
std : : lock_guard < std : : mutex > lock ( mutex_ ) ;
std : : vector < std : : string > result ;
for ( const auto & fname : flushed_files_ ) {
result . push_back ( fname ) ;
}
return result ;
}
void OnFlushCompleted ( DB * /*db*/ , const FlushJobInfo & info ) override {
call_count_ + + ;
EXPECT_FALSE ( info . blob_file_addition_infos . empty ( ) ) ;
const auto & blob_files = GetBlobFiles ( ) ;
{
std : : lock_guard < std : : mutex > lock ( mutex_ ) ;
flushed_files_ . push_back ( info . file_path ) ;
}
EXPECT_EQ ( info . blob_compression_type , kNoCompression ) ;
for ( const auto & blob_file_addition_info : info . blob_file_addition_infos ) {
const auto meta = GetBlobFileMetaData (
blob_files , blob_file_addition_info . blob_file_number ) ;
EXPECT_EQ ( meta - > GetBlobFileNumber ( ) ,
blob_file_addition_info . blob_file_number ) ;
EXPECT_EQ ( meta - > GetTotalBlobBytes ( ) ,
blob_file_addition_info . total_blob_bytes ) ;
EXPECT_EQ ( meta - > GetTotalBlobCount ( ) ,
blob_file_addition_info . total_blob_count ) ;
EXPECT_FALSE ( blob_file_addition_info . blob_file_path . empty ( ) ) ;
}
}
void OnCompactionCompleted ( DB * /*db*/ , const CompactionJobInfo & ci ) override {
call_count_ + + ;
EXPECT_FALSE ( ci . blob_file_garbage_infos . empty ( ) ) ;
const auto & blob_files = GetBlobFiles ( ) ;
EXPECT_EQ ( ci . blob_compression_type , kNoCompression ) ;
for ( const auto & blob_file_addition_info : ci . blob_file_addition_infos ) {
const auto meta = GetBlobFileMetaData (
blob_files , blob_file_addition_info . blob_file_number ) ;
EXPECT_EQ ( meta - > GetBlobFileNumber ( ) ,
blob_file_addition_info . blob_file_number ) ;
EXPECT_EQ ( meta - > GetTotalBlobBytes ( ) ,
blob_file_addition_info . total_blob_bytes ) ;
EXPECT_EQ ( meta - > GetTotalBlobCount ( ) ,
blob_file_addition_info . total_blob_count ) ;
EXPECT_FALSE ( blob_file_addition_info . blob_file_path . empty ( ) ) ;
}
for ( const auto & blob_file_garbage_info : ci . blob_file_garbage_infos ) {
EXPECT_GT ( blob_file_garbage_info . blob_file_number , 0U ) ;
EXPECT_GT ( blob_file_garbage_info . garbage_blob_count , 0U ) ;
EXPECT_GT ( blob_file_garbage_info . garbage_blob_bytes , 0U ) ;
EXPECT_FALSE ( blob_file_garbage_info . blob_file_path . empty ( ) ) ;
}
}
EventListenerTest * test_ ;
uint32_t call_count_ ;
private :
std : : vector < std : : string > flushed_files_ ;
std : : mutex mutex_ ;
} ;
// Test OnFlushCompleted EventListener called for blob files
TEST_F ( EventListenerTest , BlobDBOnFlushCompleted ) {
Options options ;
options . env = CurrentOptions ( ) . env ;
options . enable_blob_files = true ;
options . create_if_missing = true ;
options . disable_auto_compactions = true ;
options . min_blob_size = 0 ;
BlobDBJobLevelEventListenerTest * blob_event_listener =
new BlobDBJobLevelEventListenerTest ( this ) ;
options . listeners . emplace_back ( blob_event_listener ) ;
DestroyAndReopen ( options ) ;
ASSERT_OK ( Put ( " Key1 " , " blob_value1 " ) ) ;
ASSERT_OK ( Put ( " Key2 " , " blob_value2 " ) ) ;
ASSERT_OK ( Flush ( ) ) ;
ASSERT_OK ( Put ( " Key3 " , " blob_value3 " ) ) ;
ASSERT_OK ( Flush ( ) ) ;
ASSERT_EQ ( Get ( " Key1 " ) , " blob_value1 " ) ;
ASSERT_EQ ( Get ( " Key2 " ) , " blob_value2 " ) ;
ASSERT_EQ ( Get ( " Key3 " ) , " blob_value3 " ) ;
ASSERT_GT ( blob_event_listener - > call_count_ , 0U ) ;
}
// Test OnCompactionCompleted EventListener called for blob files
TEST_F ( EventListenerTest , BlobDBOnCompactionCompleted ) {
Options options ;
options . env = CurrentOptions ( ) . env ;
options . enable_blob_files = true ;
options . create_if_missing = true ;
options . disable_auto_compactions = true ;
options . min_blob_size = 0 ;
BlobDBJobLevelEventListenerTest * blob_event_listener =
new BlobDBJobLevelEventListenerTest ( this ) ;
options . listeners . emplace_back ( blob_event_listener ) ;
options . enable_blob_garbage_collection = true ;
options . blob_garbage_collection_age_cutoff = 0.5 ;
DestroyAndReopen ( options ) ;
ASSERT_OK ( Put ( " Key1 " , " blob_value1 " ) ) ;
ASSERT_OK ( Put ( " Key2 " , " blob_value2 " ) ) ;
ASSERT_OK ( Put ( " Key3 " , " blob_value3 " ) ) ;
ASSERT_OK ( Put ( " Key4 " , " blob_value4 " ) ) ;
ASSERT_OK ( Flush ( ) ) ;
ASSERT_OK ( Put ( " Key3 " , " new_blob_value3 " ) ) ;
ASSERT_OK ( Put ( " Key4 " , " new_blob_value4 " ) ) ;
ASSERT_OK ( Flush ( ) ) ;
ASSERT_OK ( Put ( " Key5 " , " blob_value5 " ) ) ;
ASSERT_OK ( Put ( " Key6 " , " blob_value6 " ) ) ;
ASSERT_OK ( Flush ( ) ) ;
blob_event_listener - > call_count_ = 0 ;
constexpr Slice * begin = nullptr ;
constexpr Slice * end = nullptr ;
// On compaction, because of blob_garbage_collection_age_cutoff, it will
// delete the oldest blob file and create new blob file during compaction.
ASSERT_OK ( db_ - > CompactRange ( CompactRangeOptions ( ) , begin , end ) ) ;
// Make sure, OnCompactionCompleted is called.
ASSERT_GT ( blob_event_listener - > call_count_ , 0U ) ;
}
// Test CompactFiles calls OnCompactionCompleted EventListener for blob files
// and populate the blob files info.
TEST_F ( EventListenerTest , BlobDBCompactFiles ) {
Options options ;
options . env = CurrentOptions ( ) . env ;
options . enable_blob_files = true ;
options . create_if_missing = true ;
options . disable_auto_compactions = true ;
options . min_blob_size = 0 ;
options . enable_blob_garbage_collection = true ;
options . blob_garbage_collection_age_cutoff = 0.5 ;
BlobDBJobLevelEventListenerTest * blob_event_listener =
new BlobDBJobLevelEventListenerTest ( this ) ;
options . listeners . emplace_back ( blob_event_listener ) ;
DestroyAndReopen ( options ) ;
ASSERT_OK ( Put ( " Key1 " , " blob_value1 " ) ) ;
ASSERT_OK ( Put ( " Key2 " , " blob_value2 " ) ) ;
ASSERT_OK ( Put ( " Key3 " , " blob_value3 " ) ) ;
ASSERT_OK ( Put ( " Key4 " , " blob_value4 " ) ) ;
ASSERT_OK ( Flush ( ) ) ;
ASSERT_OK ( Put ( " Key3 " , " new_blob_value3 " ) ) ;
ASSERT_OK ( Put ( " Key4 " , " new_blob_value4 " ) ) ;
ASSERT_OK ( Flush ( ) ) ;
ASSERT_OK ( Put ( " Key5 " , " blob_value5 " ) ) ;
ASSERT_OK ( Put ( " Key6 " , " blob_value6 " ) ) ;
ASSERT_OK ( Flush ( ) ) ;
std : : vector < std : : string > output_file_names ;
CompactionJobInfo compaction_job_info ;
// On compaction, because of blob_garbage_collection_age_cutoff, it will
// delete the oldest blob file and create new blob file during compaction
// which will be populated in output_files_names.
ASSERT_OK ( dbfull ( ) - > CompactFiles (
CompactionOptions ( ) , blob_event_listener - > GetFlushedFiles ( ) , 1 , - 1 ,
& output_file_names , & compaction_job_info ) ) ;
bool is_blob_in_output = false ;
for ( const auto & file : output_file_names ) {
if ( EndsWith ( file , " .blob " ) ) {
is_blob_in_output = true ;
}
}
ASSERT_TRUE ( is_blob_in_output ) ;
for ( const auto & blob_file_addition_info :
compaction_job_info . blob_file_addition_infos ) {
EXPECT_GT ( blob_file_addition_info . blob_file_number , 0U ) ;
EXPECT_GT ( blob_file_addition_info . total_blob_bytes , 0U ) ;
EXPECT_GT ( blob_file_addition_info . total_blob_count , 0U ) ;
EXPECT_FALSE ( blob_file_addition_info . blob_file_path . empty ( ) ) ;
}
for ( const auto & blob_file_garbage_info :
compaction_job_info . blob_file_garbage_infos ) {
EXPECT_GT ( blob_file_garbage_info . blob_file_number , 0U ) ;
EXPECT_GT ( blob_file_garbage_info . garbage_blob_count , 0U ) ;
EXPECT_GT ( blob_file_garbage_info . garbage_blob_bytes , 0U ) ;
EXPECT_FALSE ( blob_file_garbage_info . blob_file_path . empty ( ) ) ;
}
}
class BlobDBFileLevelEventListener : public EventListener {
public :
BlobDBFileLevelEventListener ( ) {
files_started_ . store ( 0 ) ;
files_created_ . store ( 0 ) ;
files_deleted_ . store ( 0 ) ;
}
void OnBlobFileCreationStarted (
const BlobFileCreationBriefInfo & info ) override {
files_started_ + + ;
EXPECT_FALSE ( info . db_name . empty ( ) ) ;
EXPECT_FALSE ( info . cf_name . empty ( ) ) ;
EXPECT_FALSE ( info . file_path . empty ( ) ) ;
EXPECT_GT ( info . job_id , 0 ) ;
}
void OnBlobFileCreated ( const BlobFileCreationInfo & info ) override {
files_created_ + + ;
EXPECT_FALSE ( info . db_name . empty ( ) ) ;
EXPECT_FALSE ( info . cf_name . empty ( ) ) ;
EXPECT_FALSE ( info . file_path . empty ( ) ) ;
EXPECT_GT ( info . job_id , 0 ) ;
EXPECT_GT ( info . total_blob_count , 0U ) ;
EXPECT_GT ( info . total_blob_bytes , 0U ) ;
EXPECT_EQ ( info . file_checksum , kUnknownFileChecksum ) ;
EXPECT_EQ ( info . file_checksum_func_name , kUnknownFileChecksumFuncName ) ;
EXPECT_TRUE ( info . status . ok ( ) ) ;
}
void OnBlobFileDeleted ( const BlobFileDeletionInfo & info ) override {
files_deleted_ + + ;
EXPECT_FALSE ( info . db_name . empty ( ) ) ;
EXPECT_FALSE ( info . file_path . empty ( ) ) ;
EXPECT_GT ( info . job_id , 0 ) ;
EXPECT_TRUE ( info . status . ok ( ) ) ;
}
void CheckCounters ( ) {
EXPECT_EQ ( files_started_ , files_created_ ) ;
EXPECT_GT ( files_started_ , 0U ) ;
EXPECT_GT ( files_deleted_ , 0U ) ;
EXPECT_LT ( files_deleted_ , files_created_ ) ;
}
private :
std : : atomic < uint32_t > files_started_ ;
std : : atomic < uint32_t > files_created_ ;
std : : atomic < uint32_t > files_deleted_ ;
} ;
TEST_F ( EventListenerTest , BlobDBFileTest ) {
Options options ;
options . env = CurrentOptions ( ) . env ;
options . enable_blob_files = true ;
options . create_if_missing = true ;
options . disable_auto_compactions = true ;
options . min_blob_size = 0 ;
options . enable_blob_garbage_collection = true ;
options . blob_garbage_collection_age_cutoff = 0.5 ;
BlobDBFileLevelEventListener * blob_event_listener =
new BlobDBFileLevelEventListener ( ) ;
options . listeners . emplace_back ( blob_event_listener ) ;
DestroyAndReopen ( options ) ;
ASSERT_OK ( Put ( " Key1 " , " blob_value1 " ) ) ;
ASSERT_OK ( Put ( " Key2 " , " blob_value2 " ) ) ;
ASSERT_OK ( Put ( " Key3 " , " blob_value3 " ) ) ;
ASSERT_OK ( Put ( " Key4 " , " blob_value4 " ) ) ;
ASSERT_OK ( Flush ( ) ) ;
ASSERT_OK ( Put ( " Key3 " , " new_blob_value3 " ) ) ;
ASSERT_OK ( Put ( " Key4 " , " new_blob_value4 " ) ) ;
ASSERT_OK ( Flush ( ) ) ;
ASSERT_OK ( Put ( " Key5 " , " blob_value5 " ) ) ;
ASSERT_OK ( Put ( " Key6 " , " blob_value6 " ) ) ;
ASSERT_OK ( Flush ( ) ) ;
constexpr Slice * begin = nullptr ;
constexpr Slice * end = nullptr ;
// On compaction, because of blob_garbage_collection_age_cutoff, it will
// delete the oldest blob file and create new blob file during compaction.
ASSERT_OK ( db_ - > CompactRange ( CompactRangeOptions ( ) , begin , end ) ) ;
blob_event_listener - > CheckCounters ( ) ;
}
} // namespace ROCKSDB_NAMESPACE
# endif // ROCKSDB_LITE