Skip to content

Commit f757a00

Browse files
fix(logging): address commit lifecycle review feedback
1 parent f9cf129 commit f757a00

5 files changed

Lines changed: 129 additions & 52 deletions

File tree

src/iceberg/table_metadata.cc

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
#include "iceberg/exception.h"
3939
#include "iceberg/file_io.h"
4040
#include "iceberg/json_serde_internal.h"
41-
#include "iceberg/logging/log_macros.h"
4241
#include "iceberg/metrics_config.h"
4342
#include "iceberg/partition_field.h"
4443
#include "iceberg/partition_spec.h"
@@ -1107,8 +1106,6 @@ Status TableMetadataBuilder::Impl::AddSnapshot(std::shared_ptr<Snapshot> snapsho
11071106
metadata_.next_row_id += add_rows.value();
11081107
}
11091108

1110-
ICEBERG_LOG_DEBUG("Added snapshot {} (sequence number {}) to table metadata",
1111-
snapshot->snapshot_id, snapshot->sequence_number);
11121109
return {};
11131110
}
11141111

src/iceberg/test/fast_append_test.cc

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
#include "iceberg/avro/avro_register.h"
3636
#include "iceberg/constants.h"
37+
#include "iceberg/logging/log_level.h"
3738
#include "iceberg/manifest/manifest_entry.h"
3839
#include "iceberg/manifest/manifest_reader.h"
3940
#include "iceberg/manifest/manifest_writer.h"
@@ -46,6 +47,7 @@
4647
#include "iceberg/table_metadata.h"
4748
#include "iceberg/table_properties.h"
4849
#include "iceberg/test/executor.h"
50+
#include "iceberg/test/logging_test_helpers.h"
4951
#include "iceberg/test/matchers.h"
5052
#include "iceberg/test/mock_catalog.h"
5153
#include "iceberg/test/update_test_base.h"
@@ -178,6 +180,31 @@ TEST_F(FastAppendTest, AppendDataFile) {
178180
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kManifestsReplaced), "0");
179181
}
180182

183+
TEST_F(FastAppendTest, StageOnlyCommitLogNamesAddedSnapshot) {
184+
auto capturing = std::make_shared<CapturingLogger>();
185+
capturing->SetLevel(LogLevel::kTrace);
186+
ScopedDefaultLogger guard(capturing);
187+
188+
ICEBERG_UNWRAP_OR_FAIL(auto fast_append, table_->NewFastAppend());
189+
fast_append->StageOnly();
190+
fast_append->AppendFile(file_a_);
191+
ASSERT_THAT(fast_append->Commit(), IsOk());
192+
ASSERT_THAT(table_->Refresh(), IsOk());
193+
194+
ASSERT_FALSE(table_->metadata()->snapshots.empty());
195+
const auto snapshot_id = table_->metadata()->snapshots.back()->snapshot_id;
196+
bool found = false;
197+
for (const auto& record : capturing->records()) {
198+
if (record.level == LogLevel::kInfo &&
199+
record.message.find(std::format("committed snapshot {}", snapshot_id)) !=
200+
std::string::npos) {
201+
found = true;
202+
break;
203+
}
204+
}
205+
EXPECT_TRUE(found) << "expected the staged snapshot in the commit success log";
206+
}
207+
181208
TEST_F(FastAppendTest, AppendMultipleDataFiles) {
182209
std::shared_ptr<FastAppend> fast_append;
183210
ICEBERG_UNWRAP_OR_FAIL(fast_append, table_->NewFastAppend());

src/iceberg/test/table_metadata_builder_test.cc

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
#include <gmock/gmock.h>
2525
#include <gtest/gtest.h>
2626

27-
#include "iceberg/logging/log_level.h"
2827
#include "iceberg/partition_spec.h"
2928
#include "iceberg/result.h"
3029
#include "iceberg/schema.h"
@@ -34,7 +33,6 @@
3433
#include "iceberg/table_metadata.h"
3534
#include "iceberg/table_properties.h"
3635
#include "iceberg/table_update.h"
37-
#include "iceberg/test/logging_test_helpers.h"
3836
#include "iceberg/test/matchers.h"
3937
#include "iceberg/transform.h"
4038
#include "iceberg/type.h"
@@ -1187,29 +1185,6 @@ TEST(TableMetadataBuilderTest, RemoveSchemasAfterSchemaChange) {
11871185
ASSERT_THAT(builder->Build(), HasErrorMessage("Cannot remove current schema: 1"));
11881186
}
11891187

1190-
// Adding a snapshot to the builder emits a DEBUG record naming the snapshot.
1191-
TEST(TableMetadataBuilderTest, AddSnapshotEmitsDebugLog) {
1192-
auto capturing = std::make_shared<CapturingLogger>();
1193-
capturing->SetLevel(LogLevel::kTrace);
1194-
ScopedDefaultLogger guard(capturing);
1195-
1196-
auto base = CreateBaseMetadata();
1197-
auto builder = TableMetadataBuilder::BuildFrom(base.get());
1198-
builder->AddSnapshot(
1199-
std::make_shared<Snapshot>(Snapshot{.snapshot_id = 42, .sequence_number = 7}));
1200-
ICEBERG_UNWRAP_OR_FAIL(auto metadata, builder->Build());
1201-
1202-
bool found = false;
1203-
for (const auto& record : capturing->records()) {
1204-
if (record.level == LogLevel::kDebug &&
1205-
record.message.find("Added snapshot 42") != std::string::npos) {
1206-
found = true;
1207-
break;
1208-
}
1209-
}
1210-
EXPECT_TRUE(found) << "expected a DEBUG record naming the added snapshot";
1211-
}
1212-
12131188
TEST(TableMetadataBuilderTest, RemoveSnapshotRef) {
12141189
auto base = CreateBaseMetadata();
12151190
auto builder = TableMetadataBuilder::BuildFrom(base.get());

src/iceberg/test/transaction_test.cc

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
#include "iceberg/expression/expressions.h"
2323
#include "iceberg/expression/term.h"
2424
#include "iceberg/logging/log_level.h"
25+
#include "iceberg/snapshot.h"
2526
#include "iceberg/sort_order.h"
27+
#include "iceberg/table_metadata.h"
2628
#include "iceberg/test/logging_test_helpers.h"
2729
#include "iceberg/test/matchers.h"
2830
#include "iceberg/test/mock_catalog.h"
@@ -227,9 +229,68 @@ TEST_F(TransactionRetryTest, CommitRetryEmitsRetryAndSuccessLogs) {
227229
<< "expected a success INFO";
228230
}
229231

230-
// A commit that exhausts its retries emits an ERROR with the attempt count and the
231-
// final error.
232-
TEST_F(TransactionRetryTest, CommitRetryExhaustedEmitsErrorLog) {
232+
// A metadata-only retry must not attribute a snapshot committed concurrently by
233+
// another writer to this transaction.
234+
TEST_F(TransactionRetryTest, MetadataOnlyRetryDoesNotLogConcurrentSnapshot) {
235+
auto capturing = std::make_shared<CapturingLogger>();
236+
capturing->SetLevel(LogLevel::kTrace);
237+
ScopedDefaultLogger guard(capturing);
238+
239+
constexpr int64_t kConcurrentSnapshotId = 987654321;
240+
auto metadata_builder = TableMetadataBuilder::BuildFrom(mock_table_->metadata().get());
241+
auto concurrent_snapshot = std::make_shared<Snapshot>(Snapshot{
242+
.snapshot_id = kConcurrentSnapshotId,
243+
.parent_snapshot_id = mock_table_->metadata()->current_snapshot_id,
244+
.sequence_number = mock_table_->metadata()->last_sequence_number + 1,
245+
.timestamp_ms = TimePointMs{},
246+
.manifest_list = "concurrent-manifest-list.avro",
247+
.summary = {{SnapshotSummaryFields::kOperation, "append"}},
248+
});
249+
metadata_builder->SetBranchSnapshot(concurrent_snapshot,
250+
std::string(SnapshotRef::kMainBranch));
251+
ICEBERG_UNWRAP_OR_FAIL(auto concurrent_metadata, metadata_builder->Build());
252+
auto concurrent_metadata_ptr =
253+
std::shared_ptr<TableMetadata>(std::move(concurrent_metadata));
254+
const std::string concurrent_metadata_location = "concurrent.metadata.json";
255+
256+
ON_CALL(*mock_catalog_, LoadTable(::testing::_))
257+
.WillByDefault([this, concurrent_metadata_ptr, &concurrent_metadata_location](
258+
const TableIdentifier&) -> Result<std::shared_ptr<Table>> {
259+
return Table::Make(mock_table_->name(), concurrent_metadata_ptr,
260+
concurrent_metadata_location, mock_table_->io(),
261+
mock_catalog_);
262+
});
263+
264+
int update_call_count = 0;
265+
ON_CALL(*mock_catalog_, UpdateTable(::testing::_, ::testing::_, ::testing::_))
266+
.WillByDefault(
267+
[this, concurrent_metadata_ptr, &concurrent_metadata_location,
268+
&update_call_count](const TableIdentifier&,
269+
const std::vector<std::unique_ptr<TableRequirement>>&,
270+
const std::vector<std::unique_ptr<TableUpdate>>&)
271+
-> Result<std::shared_ptr<Table>> {
272+
if (++update_call_count == 1) {
273+
return CommitFailed("conflict on first attempt");
274+
}
275+
return Table::Make(mock_table_->name(), concurrent_metadata_ptr,
276+
concurrent_metadata_location, mock_table_->io(),
277+
mock_catalog_);
278+
});
279+
280+
ICEBERG_UNWRAP_OR_FAIL(auto txn, mock_table_->NewTransaction());
281+
ICEBERG_UNWRAP_OR_FAIL(auto update, txn->NewUpdateProperties());
282+
update->Set("retry.test", "value");
283+
ASSERT_THAT(update->Commit(), IsOk());
284+
ASSERT_THAT(txn->Commit(), IsOk());
285+
286+
EXPECT_FALSE(HasRecord(capturing->records(), LogLevel::kInfo,
287+
std::to_string(kConcurrentSnapshotId)))
288+
<< "metadata-only commit attributed the concurrent snapshot to itself";
289+
}
290+
291+
// A commit that exhausts its retries returns the final error without emitting a
292+
// generic ERROR log. Genuine retry attempts still emit WARN records.
293+
TEST_F(TransactionRetryTest, CommitRetryExhaustedDoesNotEmitErrorLog) {
233294
auto capturing = std::make_shared<CapturingLogger>();
234295
capturing->SetLevel(LogLevel::kTrace);
235296
ScopedDefaultLogger guard(capturing);
@@ -249,10 +310,8 @@ TEST_F(TransactionRetryTest, CommitRetryExhaustedEmitsErrorLog) {
249310
EXPECT_THAT(txn->Commit(), IsError(ErrorKind::kCommitFailed));
250311

251312
auto records = capturing->records();
252-
EXPECT_TRUE(HasRecord(records, LogLevel::kError, "failed after 5 attempt(s)"))
253-
<< "expected a final ERROR with the attempt count";
254-
EXPECT_TRUE(HasRecord(records, LogLevel::kError, "always conflicts"))
255-
<< "final ERROR should carry the last error";
313+
EXPECT_FALSE(HasRecord(records, LogLevel::kError, ""))
314+
<< "the final commit error should be propagated without a generic ERROR log";
256315
// Retries 2..5 each log a WARN.
257316
EXPECT_TRUE(
258317
HasRecord(records, LogLevel::kWarn, "Retrying transaction commit (attempt 5)"));
@@ -288,7 +347,11 @@ TEST_F(TransactionRetryTest, CommitSuccessEmitsInfoLog) {
288347
EXPECT_FALSE(HasRecord(records, LogLevel::kWarn, "Retrying transaction commit"));
289348
}
290349

291-
TEST_F(TransactionRetryTest, CommitNonRetryableErrorStopsImmediately) {
350+
TEST_F(TransactionRetryTest, CommitStateUnknownStopsImmediatelyWithoutErrorLog) {
351+
auto capturing = std::make_shared<CapturingLogger>();
352+
capturing->SetLevel(LogLevel::kTrace);
353+
ScopedDefaultLogger guard(capturing);
354+
292355
int update_call_count = 0;
293356
ON_CALL(*mock_catalog_, UpdateTable(::testing::_, ::testing::_, ::testing::_))
294357
.WillByDefault(
@@ -308,6 +371,8 @@ TEST_F(TransactionRetryTest, CommitNonRetryableErrorStopsImmediately) {
308371
auto result = txn->Commit();
309372
EXPECT_THAT(result, IsError(ErrorKind::kCommitStateUnknown));
310373
EXPECT_EQ(update_call_count, 1); // Should not retry
374+
EXPECT_FALSE(HasRecord(capturing->records(), LogLevel::kError, ""))
375+
<< "an unknown commit state must not be logged as a confirmed failure";
311376
}
312377

313378
TEST_F(TransactionRetryTest, CreateTransactionDoesNotRetry) {

src/iceberg/transaction.cc

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "iceberg/transaction.h"
2020

2121
#include <format>
22+
#include <iterator>
2223
#include <memory>
2324
#include <string>
2425

@@ -377,9 +378,6 @@ Result<std::shared_ptr<Table>> Transaction::Commit() {
377378
int32_t max_wait_ms = props.Get(TableProperties::kCommitMaxRetryWaitMs);
378379
int32_t total_timeout_ms = props.Get(TableProperties::kCommitTotalRetryTimeMs);
379380

380-
// Snapshot id before the commit, to detect whether this commit advanced it (a
381-
// data commit) versus a metadata-only commit that adds no snapshot.
382-
const int64_t base_current_snapshot_id = ctx_->table->metadata()->current_snapshot_id;
383381
bool is_first_attempt = true;
384382
int32_t attempt = 0;
385383
std::string last_error;
@@ -403,27 +401,42 @@ Result<std::shared_ptr<Table>> Transaction::Commit() {
403401
});
404402

405403
if (commit_result.has_value()) {
406-
// Name the resulting snapshot only when this commit produced one (current
407-
// snapshot advanced); metadata-only commits report a plain success.
404+
// The builder contains only changes made by the successful attempt. Inspecting
405+
// AddSnapshot changes avoids attributing a concurrent writer's snapshot to this
406+
// transaction and also detects snapshots committed with StageOnly or ToBranch.
408407
std::string detail;
409-
if (auto snapshot = commit_result.value()->metadata()->Snapshot();
410-
snapshot.has_value() &&
411-
snapshot.value()->snapshot_id != base_current_snapshot_id) {
412-
const auto& summary = snapshot.value()->summary;
413-
auto op = summary.find(SnapshotSummaryFields::kOperation);
414-
detail =
415-
std::format(": committed snapshot {} (op={})", snapshot.value()->snapshot_id,
416-
op != summary.end() ? op->second : "unknown");
408+
const auto& changes = ctx_->metadata_builder->changes();
409+
size_t added_snapshot_count = 0;
410+
for (const auto& change : changes) {
411+
added_snapshot_count += change->kind() == TableUpdate::Kind::kAddSnapshot;
412+
}
413+
if (added_snapshot_count > 0) {
414+
detail.reserve(32 + added_snapshot_count * 48);
415+
std::format_to(std::back_inserter(detail), ": committed snapshot{} ",
416+
added_snapshot_count == 1 ? "" : "s");
417+
418+
size_t appended_snapshot_count = 0;
419+
for (const auto& change : changes) {
420+
if (change->kind() != TableUpdate::Kind::kAddSnapshot) {
421+
continue;
422+
}
423+
const auto& snapshot =
424+
internal::checked_cast<const table::AddSnapshot&>(*change).snapshot();
425+
if (appended_snapshot_count++ > 0) {
426+
detail += ", ";
427+
}
428+
const auto& summary = snapshot->summary;
429+
auto op = summary.find(SnapshotSummaryFields::kOperation);
430+
std::format_to(std::back_inserter(detail), "{} (op={})", snapshot->snapshot_id,
431+
op != summary.end() ? op->second : "unknown");
432+
}
417433
}
418434
if (attempt > 1) {
419435
ICEBERG_LOG_INFO("Transaction commit succeeded after {} attempts{}", attempt,
420436
detail);
421437
} else {
422438
ICEBERG_LOG_INFO("Transaction commit succeeded{}", detail);
423439
}
424-
} else {
425-
ICEBERG_LOG_ERROR("Transaction commit failed after {} attempt(s): {}", attempt,
426-
commit_result.error().message);
427440
}
428441

429442
Result<const TableMetadata*> finalize_result =

0 commit comments

Comments
 (0)