Skip to content

Commit 4f1be19

Browse files
committed
fix(compaction): update compaction catalog on offline compaction
- tglogutil offline compaction now registers the compacted file in the compaction catalog (max epoch id, max blob id, pwal_0000.compacted) - without this, a later startup generated the snapshot as if no compaction had been performed, dropping remove entries and resurrecting deleted records (tsurugi-issues #1498) - detect at startup when pwal_0000.compacted exists on disk but is not registered in the catalog, and fail fast with initialization_failure instead of silently corrupting data - finalize the expected error-message substring in the offline compaction test comment
1 parent 865bafb commit 4f1be19

3 files changed

Lines changed: 50 additions & 2 deletions

File tree

src/limestone/datastore.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,44 @@ datastore::datastore(configuration const& conf) : location_(conf.data_location_)
146146
add_file(compaction_catalog_path);
147147
compaction_catalog_ = std::make_unique<compaction_catalog>(compaction_catalog::from_catalog_file(location_));
148148

149+
// Verify consistency between the compaction catalog and the WAL files.
150+
// The compacted file is loaded at startup based purely on its presence on
151+
// disk (see snapshot_impl). If it exists but is not registered in the
152+
// catalog, the snapshot would be generated as if no compaction had been
153+
// performed, dropping remove entries and resurrecting deleted records
154+
// (see tsurugi-issues #1498). Fail fast instead of silently corrupting data.
155+
// NOTE: only the single well-known compacted file is checked here. If
156+
// multiple compacted files are ever registered, this check must be extended.
157+
{
158+
boost::filesystem::path compacted_file_path = location_ / compaction_catalog::get_compacted_filename();
159+
// Use the error_code overload so that a filesystem error (permission, broken
160+
// symlink, I/O error, ...) is funneled into a limestone_exception rather than
161+
// escaping as a boost::filesystem::filesystem_error.
162+
bool compacted_file_exists = boost::filesystem::exists(compacted_file_path, error);
163+
if (error) {
164+
std::string err_msg = "failed to check existence of the compacted file '"
165+
+ compacted_file_path.string() + "': " + error.message();
166+
LOG(ERROR) << "/:limestone:config:datastore " << err_msg;
167+
throw limestone_exception(exception_type::initialization_failure, err_msg);
168+
}
169+
if (compacted_file_exists) {
170+
bool registered = false;
171+
for (auto const& info : compaction_catalog_->get_compacted_files()) {
172+
if (info.get_file_name() == compaction_catalog::get_compacted_filename()) {
173+
registered = true;
174+
break;
175+
}
176+
}
177+
if (!registered) {
178+
std::string err_msg = "compaction catalog is inconsistent: the compacted file '"
179+
+ compaction_catalog::get_compacted_filename()
180+
+ "' exists but is not registered in the catalog, log directory: " + location_.string();
181+
LOG(ERROR) << "/:limestone:config:datastore " << err_msg;
182+
throw limestone_exception(exception_type::initialization_failure, err_msg);
183+
}
184+
}
185+
}
186+
149187
epoch_file_path_ = location_ / std::string(limestone::internal::epoch_file_name);
150188
tmp_epoch_file_path_ = location_ / std::string(limestone::internal::tmp_epoch_file_name);
151189
const bool result = boost::filesystem::exists(epoch_file_path_, error);

src/limestone/dblogutil/dblogutil.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "log_entry.h"
2727
#include "limestone_exception_helper.h"
2828
#include "manifest.h"
29+
#include "compaction_catalog.h"
2930

3031
// NOLINTBEGIN(performance-avoid-endl)
3132

@@ -227,7 +228,7 @@ void compaction(dblog_scan &ds, std::optional<epoch_id_type> epoch) {
227228

228229
VLOG_LP(log_info) << "making compact pwal file to " << tmp;
229230
compaction_options options{from_dir, tmp, FLAGS_thread_num};
230-
create_compact_pwal_and_get_max_blob_id(options);
231+
blob_id_type max_blob_id = create_compact_pwal_and_get_max_blob_id(options);
231232

232233
// epoch file
233234
VLOG_LP(log_info) << "making compact epoch file to " << tmp;
@@ -247,6 +248,15 @@ void compaction(dblog_scan &ds, std::optional<epoch_id_type> epoch) {
247248
LOG_AND_THROW_IO_EXCEPTION("fclose failed", errno);
248249
}
249250

251+
// Update the compaction catalog so that the compacted file is registered.
252+
// Without this, a subsequent startup treats the directory as if no compaction
253+
// had been performed, and remove entries are dropped from the snapshot,
254+
// resurrecting deleted records (see tsurugi-issues #1498).
255+
VLOG_LP(log_info) << "updating compaction catalog in " << tmp;
256+
compaction_catalog catalog{tmp};
257+
compacted_file_info compacted_file{compaction_catalog::get_compacted_filename(), 1};
258+
catalog.update_catalog_file(ld_epoch, max_blob_id, {compacted_file}, {});
259+
250260
if (FLAGS_dry_run) {
251261
std::cout << "compaction will be successfully completed (dry-run mode)" << std::endl;
252262
VLOG_LP(log_info) << "deleting work directory " << tmp;

test/limestone/compaction/offline_compaction_test.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ TEST_F(offline_compaction_test, detects_inconsistent_compaction_catalog_at_start
174174
// Startup must fail fast because the catalog is inconsistent. Verify both that a
175175
// limestone_exception is thrown and that its message is the intended one (so that an
176176
// unrelated failure that happens to throw the same type is not mistaken for success).
177-
// TODO: replace this placeholder with the actual message once the detection is implemented.
177+
// This substring must match the message produced by datastore startup.
178178
const std::string expected_message_substr = "compaction catalog is inconsistent";
179179
try {
180180
gen_datastore();

0 commit comments

Comments
 (0)