Skip to content

Commit 2e44dab

Browse files
committed
fix some fail path
1 parent 2fe0974 commit 2e44dab

16 files changed

Lines changed: 245 additions & 45 deletions

‎include/paimon/file_store_write.h‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ class PAIMON_EXPORT FileStoreWrite {
6464
/// independently. A future implementation will support spilling sealed segments to a
6565
/// temporary directory; currently this method only creates the in-memory segment boundary.
6666
/// Calling this method on a non-real-time writer returns an error.
67+
/// If sealing fails, the caller must recreate both the `RealtimeContext` and writer. The
68+
/// upstream must then recover input from the durable recovery offset persisted in the
69+
/// snapshot. Reusing the failed writer is unsupported.
6770
virtual Status Seal();
6871

6972
/// Compact data stored in given partition and bucket. Note that compaction process is only
@@ -112,6 +115,10 @@ class PAIMON_EXPORT FileStoreWrite {
112115
/// @param commit_identifier Identifier of this prepare-commit operation in streaming mode.
113116
/// @return Real-time commit messages with their partition-bucket offset ranges.
114117
/// @note Calling this method on a non-real-time writer or in batch mode returns an error.
118+
/// @note If preparation fails, the caller must recreate both the `RealtimeContext` and writer.
119+
/// The upstream must then recover input from the durable recovery offset persisted in the
120+
/// snapshot. The failed writer may contain partially prepared bucket state and must not
121+
/// be reused.
115122
virtual Result<std::vector<RealtimeCommitProgress>> PrepareCommitWithProgress(
116123
int64_t commit_identifier);
117124

@@ -128,6 +135,12 @@ class PAIMON_EXPORT FileStoreWrite {
128135
virtual Status RefreshCommittedSnapshot(int64_t snapshot_id);
129136

130137
virtual std::shared_ptr<Metrics> GetMetrics() const = 0;
138+
139+
/// Releases resources owned by this writer.
140+
///
141+
/// Closing a real-time writer with data not covered by a successful
142+
/// `PrepareCommitWithProgress()` returns an error and invalidates its `RealtimeContext`.
143+
/// The caller must rebuild both objects and recover input from the durable snapshot offset.
131144
virtual Status Close() = 0;
132145
};
133146

‎src/paimon/core/operation/abstract_file_store_write.cpp‎

Lines changed: 90 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "paimon/core/operation/file_system_write_restore.h"
3232
#include "paimon/core/operation/metrics/compaction_metrics.h"
3333
#include "paimon/core/operation/restore_files.h"
34+
#include "paimon/core/realtime/realtime_context_impl.h"
3435
#include "paimon/core/realtime/realtime_schema_layout.h"
3536
#include "paimon/core/schema/table_schema.h"
3637
#include "paimon/core/snapshot.h"
@@ -97,6 +98,7 @@ Status AbstractFileStoreWrite::Write(std::unique_ptr<RecordBatch>&& batch) {
9798
if (PAIMON_UNLIKELY(batch == nullptr)) {
9899
return Status::Invalid("batch is null pointer");
99100
}
101+
PAIMON_RETURN_NOT_OK(CheckRealtimeWriteUsable());
100102
// in FileStoreWrite::Create() we have checked the table kind and bucket mode, here we only
101103
// check the bucket id in batch
102104
if (options_.GetBucket() == -1) {
@@ -149,6 +151,8 @@ Status AbstractFileStoreWrite::Seal() {
149151
if (!IsRealtimeWrite()) {
150152
return FileStoreWrite::Seal();
151153
}
154+
std::lock_guard<std::mutex> seal_prepare_lock(realtime_seal_prepare_mutex_);
155+
PAIMON_RETURN_NOT_OK(CheckRealtimeWriteUsable());
152156
std::vector<std::shared_ptr<BatchWriter>> writers;
153157
{
154158
std::lock_guard<std::mutex> lock(writers_mutex_);
@@ -159,13 +163,18 @@ Status AbstractFileStoreWrite::Seal() {
159163
}
160164
}
161165
for (const std::shared_ptr<BatchWriter>& writer : writers) {
162-
PAIMON_RETURN_NOT_OK(writer->Seal());
166+
Status status = writer->Seal();
167+
if (!status.ok()) {
168+
// Earlier buckets may already be sealed, so the writer cannot safely continue.
169+
return PoisonRealtimeWrite(status);
170+
}
163171
}
164172
return Status::OK();
165173
}
166174

167175
Status AbstractFileStoreWrite::Compact(const std::map<std::string, std::string>& partition,
168176
int32_t bucket, bool full_compaction) {
177+
PAIMON_RETURN_NOT_OK(CheckRealtimeWriteUsable());
169178
PAIMON_ASSIGN_OR_RAISE(BinaryRow part, file_store_path_factory_->ToBinaryRow(partition));
170179
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<BatchWriter> writer, GetWriter(part, bucket));
171180
assert(writer);
@@ -300,10 +309,18 @@ Result<std::vector<RealtimeCommitProgress>> AbstractFileStoreWrite::PrepareCommi
300309
if (is_streaming_mode_ == false) {
301310
return Status::Invalid("PrepareCommitWithProgress requires streaming mode");
302311
}
312+
// Keep one seal/prepare attempt in flight so a failure fences every later checkpoint.
313+
std::lock_guard<std::mutex> seal_prepare_lock(realtime_seal_prepare_mutex_);
314+
PAIMON_RETURN_NOT_OK(CheckRealtimeWriteUsable());
303315
// Real-time prepare snapshots writers under a short lock so writes can continue while sealed
304316
// segments are flushed. The normal path iterates and may erase writers in place, which would
305317
// either race with concurrent writer creation or hold writers_mutex_ for the whole prepare.
306-
return PrepareRealtimeCommit();
318+
Result<std::vector<RealtimeCommitProgress>> result = PrepareRealtimeCommit();
319+
if (!result.ok()) {
320+
// Prepare may have drained an earlier bucket or partially written the failing bucket.
321+
return PoisonRealtimeWrite(result.status());
322+
}
323+
return result;
307324
}
308325

309326
Result<std::vector<RealtimeCommitProgress>> AbstractFileStoreWrite::PrepareRealtimeCommit() {
@@ -362,22 +379,91 @@ Result<std::vector<RealtimeCommitProgress>> AbstractFileStoreWrite::PrepareRealt
362379
}
363380

364381
Status AbstractFileStoreWrite::Close() {
382+
std::unique_lock<std::mutex> seal_prepare_lock(realtime_seal_prepare_mutex_, std::defer_lock);
383+
if (IsRealtimeWrite()) {
384+
seal_prepare_lock.lock();
385+
}
365386
std::lock_guard<std::mutex> lock(writers_mutex_);
387+
bool has_unprepared_realtime_data = false;
388+
Status unprepared_error = Status::OK();
389+
if (IsRealtimeWrite()) {
390+
for (const auto& [_, bucket_writers] : writers_) {
391+
for (const auto& [_, writer_container] : bucket_writers) {
392+
if (writer_container.writer->HasUnpreparedRealtimeData()) {
393+
has_unprepared_realtime_data = true;
394+
break;
395+
}
396+
}
397+
if (has_unprepared_realtime_data) {
398+
break;
399+
}
400+
}
401+
if (has_unprepared_realtime_data) {
402+
// Close cannot publish pending segments for its caller.
403+
unprepared_error = PoisonRealtimeWrite(
404+
Status::Invalid("real-time writer closed with data not covered by a successful "
405+
"PrepareCommitWithProgress"));
406+
}
407+
}
408+
Status first_error = Status::OK();
366409
for (auto& [_, bucket_writers] : writers_) {
367410
for (auto& [_, writer_container] : bucket_writers) {
368411
writer_memory_manager_->UnregisterWriter(writer_container.writer.get());
369-
PAIMON_RETURN_NOT_OK(writer_container.writer->Close());
412+
Status status = writer_container.writer->Close();
413+
if (!status.ok() && first_error.ok()) {
414+
first_error = std::move(status);
415+
}
370416
}
371417
}
372418
writers_.clear();
373419
compact_executor_->ShutdownNow();
374-
return Status::OK();
420+
if (!first_error.ok() && IsRealtimeWrite()) {
421+
return PoisonRealtimeWrite(first_error);
422+
}
423+
if (has_unprepared_realtime_data) {
424+
return unprepared_error;
425+
}
426+
return first_error;
375427
}
376428

377429
std::shared_ptr<Metrics> AbstractFileStoreWrite::GetMetrics() const {
378430
return metrics_;
379431
}
380432

433+
Status AbstractFileStoreWrite::CheckRealtimeWriteUsable() const {
434+
if (IsRealtimeWrite() && realtime_write_poisoned_.load()) {
435+
return Status::Invalid(
436+
"real-time writer cannot be reused after Seal or PrepareCommitWithProgress failed, "
437+
"or after it closed with unprepared data; recreate RealtimeContext and "
438+
"FileStoreWrite, then let upstream recover input from the durable recovery offset "
439+
"persisted in the snapshot");
440+
}
441+
std::shared_ptr<RealtimeContext> context = GetRealtimeContext();
442+
if (!context) {
443+
return Status::OK();
444+
}
445+
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<RealtimeContextImpl> context_impl,
446+
RealtimeContextImpl::Cast(context));
447+
return context_impl->CheckUsable();
448+
}
449+
450+
void AbstractFileStoreWrite::InvalidateRealtimeContext() {
451+
std::shared_ptr<RealtimeContextImpl> context =
452+
std::dynamic_pointer_cast<RealtimeContextImpl>(GetRealtimeContext());
453+
if (context) {
454+
context->Invalidate();
455+
}
456+
}
457+
458+
Status AbstractFileStoreWrite::PoisonRealtimeWrite(const Status& cause) {
459+
realtime_write_poisoned_.store(true);
460+
InvalidateRealtimeContext();
461+
return cause.WithMessage(cause.message(),
462+
"; recreate RealtimeContext and FileStoreWrite, then let upstream "
463+
"recover input from the durable recovery offset persisted in the "
464+
"snapshot");
465+
}
466+
381467
int32_t AbstractFileStoreWrite::GetDefaultBucketNum() const {
382468
return options_.GetBucket();
383469
}

‎src/paimon/core/operation/abstract_file_store_write.h‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#pragma once
2020

21+
#include <atomic>
2122
#include <cstdint>
2223
#include <limits>
2324
#include <memory>
@@ -65,6 +66,7 @@ class MemoryPool;
6566
class RecordBatch;
6667
class RestoreFiles;
6768
class IOManager;
69+
class RealtimeContext;
6870
class RealtimeSchemaLayout;
6971

7072
class AbstractFileStoreWrite : public FileStoreWrite {
@@ -126,6 +128,10 @@ class AbstractFileStoreWrite : public FileStoreWrite {
126128
return false;
127129
}
128130

131+
virtual std::shared_ptr<RealtimeContext> GetRealtimeContext() const {
132+
return nullptr;
133+
}
134+
129135
Result<std::shared_ptr<RestoreFiles>> ScanExistingFileMetas(const BinaryRow& partition,
130136
int32_t bucket) const;
131137
int32_t GetDefaultBucketNum() const;
@@ -152,18 +158,24 @@ class AbstractFileStoreWrite : public FileStoreWrite {
152158
std::shared_ptr<CompactionMetrics> compaction_metrics_;
153159

154160
private:
161+
Status CheckRealtimeWriteUsable() const;
162+
void InvalidateRealtimeContext();
163+
Status PoisonRealtimeWrite(const Status& cause);
164+
155165
Result<std::shared_ptr<BatchWriter>> GetWriter(const BinaryRow& partition, int32_t bucket);
156166
Result<std::vector<RealtimeCommitProgress>> PrepareRealtimeCommit();
157167

158168
private:
159169
std::unordered_map<BinaryRow, std::unordered_map<int32_t, WriterContainer<BatchWriter>>>
160170
writers_;
161171
std::mutex writers_mutex_;
172+
std::mutex realtime_seal_prepare_mutex_;
162173
std::mutex realtime_metrics_mutex_;
163174
bool ignore_previous_files_ = false;
164175
bool is_streaming_mode_ = false;
165176
bool ignore_num_bucket_check_ = false;
166177
bool batch_committed_ = false;
178+
std::atomic<bool> realtime_write_poisoned_{false};
167179

168180
std::shared_ptr<MetricsImpl> metrics_;
169181
std::unique_ptr<Logger> logger_;

‎src/paimon/core/operation/append_only_file_store_write.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ class AppendOnlyFileStoreWrite : public AbstractFileStoreWrite {
120120
return realtime_context_ != nullptr;
121121
}
122122

123+
std::shared_ptr<RealtimeContext> GetRealtimeContext() const override {
124+
return realtime_context_;
125+
}
126+
123127
/// @param plan_factory The active shredding write plan, or nullptr when the rewrite stays a
124128
/// plain passthrough. Resolved by the caller because
125129
/// `GetDictionaryPassthroughVetoReason` needs the same answer.

‎src/paimon/core/operation/file_store_write.cpp‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,15 @@ namespace {
6363
Status RestoreRealtimeCommittedProgress(const std::shared_ptr<RealtimeContext>& realtime_context,
6464
const std::shared_ptr<SnapshotManager>& snapshot_manager,
6565
const CoreOptions& options) {
66+
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<RealtimeContextImpl> realtime_context_impl,
67+
RealtimeContextImpl::Cast(realtime_context));
68+
PAIMON_RETURN_NOT_OK(realtime_context_impl->CheckUsable());
6669
PAIMON_ASSIGN_OR_RAISE(std::optional<Snapshot> latest_snapshot,
6770
snapshot_manager->LatestSnapshot());
6871
if (latest_snapshot) {
6972
PAIMON_ASSIGN_OR_RAISE(
7073
RealtimeOffsetMap realtime_committed_offsets,
7174
RealtimeCommitProperties::ReadOffsets(latest_snapshot, options.GetFileSystem()));
72-
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<RealtimeContextImpl> realtime_context_impl,
73-
RealtimeContextImpl::Cast(realtime_context));
7475
PAIMON_RETURN_NOT_OK(realtime_context_impl->AdvanceCommittedProgress(
7576
latest_snapshot->Id(), realtime_committed_offsets));
7677
}

‎src/paimon/core/operation/key_value_file_store_write.cpp‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,9 @@ Status KeyValueFileStoreWrite::RefreshCommittedSnapshot(int64_t snapshot_id) {
188188
}
189189

190190
Status KeyValueFileStoreWrite::Close() {
191-
PAIMON_RETURN_NOT_OK(AbstractFileStoreWrite::Close());
191+
Status writer_status = AbstractFileStoreWrite::Close();
192192
compact_manager_factory_->Close();
193-
return Status::OK();
193+
return writer_status;
194194
}
195195

196196
} // namespace paimon

‎src/paimon/core/operation/key_value_file_store_write.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ class KeyValueFileStoreWrite : public AbstractFileStoreWrite {
8787
return realtime_context_ != nullptr;
8888
}
8989

90+
std::shared_ptr<RealtimeContext> GetRealtimeContext() const override {
91+
return realtime_context_;
92+
}
93+
9094
private:
9195
bool enable_multi_thread_spill_;
9296
std::shared_ptr<RealtimeContext> realtime_context_;

‎src/paimon/core/operation/key_value_file_store_write_test.cpp‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,9 @@ TEST_F(KeyValueFileStoreWriteTest, TestRealtimePool) {
519519
const int64_t allocations_before_write = pool->allocation_count;
520520
ASSERT_OK(writer->Write(MakeBatch(realtime_schema, R"([[0, 1, "one"]])")));
521521
ASSERT_GT(pool->allocation_count, allocations_before_write);
522+
ASSERT_OK_AND_ASSIGN(std::vector<RealtimeCommitProgress> progress,
523+
writer->PrepareCommitWithProgress(/*commit_identifier=*/0));
524+
ASSERT_EQ(1, progress.size());
522525
ASSERT_OK(writer->Close());
523526
writer.reset();
524527
ASSERT_OK_AND_ASSIGN(auto retained_rows, ReadRealtimePrimaryKeyStoreRows(realtime_context));
@@ -615,6 +618,9 @@ TEST_F(KeyValueFileStoreWriteTest, TestRealtimeLimits) {
615618
ASSERT_EQ(1, read_state.views.size());
616619
ASSERT_EQ(std::optional<OffsetRange>(OffsetRange(max - 1, max)),
617620
read_state.views[0].read_view->GetOffsetRange());
621+
ASSERT_OK_AND_ASSIGN(std::vector<RealtimeCommitProgress> boundary_progress,
622+
writer->PrepareCommitWithProgress(/*commit_identifier=*/1));
623+
ASSERT_EQ(1, boundary_progress.size());
618624
ASSERT_OK(writer->Close());
619625
ASSERT_GE(snapshot_id, 1);
620626
}

‎src/paimon/core/realtime/realtime_append_only_writer.cpp‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ Status RealtimeAppendOnlyWriter::Write(std::unique_ptr<RecordBatch>&& batch) {
110110
PAIMON_RETURN_NOT_OK(
111111
realtime_store_->Write(RealtimeWriteBatch{std::move(batch), validated.offset_range}));
112112
next_offset_ = validated.offset_range.end;
113+
has_building_data_ = true;
113114
return Status::OK();
114115
}
115116

@@ -121,6 +122,7 @@ Status RealtimeAppendOnlyWriter::SealCurrentSegment() {
121122
return Status::Invalid("append real-time store sealed a null segment");
122123
}
123124
sealed_segments_.push_back(std::move(segment.value()));
125+
has_building_data_ = false;
124126
}
125127
return Status::OK();
126128
}
@@ -226,6 +228,11 @@ Status RealtimeAppendOnlyWriter::Close() {
226228
return file_writer_->Close();
227229
}
228230

231+
bool RealtimeAppendOnlyWriter::HasUnpreparedRealtimeData() const {
232+
std::lock_guard<std::mutex> lock(realtime_store_mutex_);
233+
return has_building_data_ || !sealed_segments_.empty();
234+
}
235+
229236
std::shared_ptr<Metrics> RealtimeAppendOnlyWriter::GetMetrics() const {
230237
return file_writer_->GetMetrics();
231238
}

‎src/paimon/core/realtime/realtime_append_only_writer.h‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ class RealtimeAppendOnlyWriter : public BatchWriter {
6969

7070
Status Close() override;
7171

72+
bool HasUnpreparedRealtimeData() const override;
73+
7274
std::shared_ptr<Metrics> GetMetrics() const override;
7375

7476
private:
@@ -86,7 +88,8 @@ class RealtimeAppendOnlyWriter : public BatchWriter {
8688
std::shared_ptr<RealtimeSchemaLayout> schema_layout_;
8789
std::vector<std::shared_ptr<RealtimeSegmentHandle>> sealed_segments_;
8890
int64_t next_offset_;
89-
std::mutex realtime_store_mutex_;
91+
bool has_building_data_ = false;
92+
mutable std::mutex realtime_store_mutex_;
9093
std::mutex prepare_mutex_;
9194
};
9295

0 commit comments

Comments
 (0)