StreamWAL - #11
Conversation
…tion Summary: ## Problem A READ COMMITTED retry triggered by a deadlock / abort could SIGSEGV at `AfterTriggerEndSubXact` during the subsequent ROLLBACK. Stack trace: ``` Signal: SIGSEGV #0 GetMemoryChunkContext(pointer=0x0) memutils.h:141:12 (inlined) #1 pfree(pointer=0x0) mcxt.c:1500:26 (inlined) #2 AfterTriggerEndSubXact trigger.c:5657:4 #3 AbortSubTransaction xact.c:5726:3 #4 CommitTransactionCommand xact.c:3650:4 #5 CommitTransactionCommand xact.c:0 #6 yb_exec_simple_query_impl postgres.c:3023:3 (inlined finish_xact_command) #7 yb_exec_simple_query_impl postgres.c:1494:4 #8 yb_exec_simple_query_impl postgres.c:5804:2 #9 yb_exec_query_wrapper_one_attempt postgres.c:5764:3 #10 PostgresMain postgres.c:5796:3 #11 PostgresMain postgres.c:5821:2 (inlined yb_exec_simple_query) #12 PostgresMain postgres.c:6623:8 ``` ### Reading the stack - **#2 `AfterTriggerEndSubXact` (trigger.c:5657)** -- the call site is `pfree(afterTriggers.state)` in the abort branch, gated on `trans_stack[my_level].state != NULL`. - **#3-#5 `CommitTransactionCommand` -> `AbortSubTransaction`** -- called because of ROLLBACK. ### Root cause The retry path before this change ran, in order: ``` yb_restart_transaction -> YBCRestartWriteTransaction -> AfterTriggerEndXact(false) // wipes trans_stack to NULL, maxtransdepth to 0 // and afterTriggers.state to NULL -> AfterTriggerBeginXact() -> RollbackAndReleaseCurrentSubTransaction -> YbBeginInternalSubTransactionForReadCommittedStatement -> AfterTriggerBeginSubXact -> MemoryContextAlloc(8 * sizeof(AfterTriggersTransData)) // NOT AllocZero: leaves N-1 slots uninitialized -> initializes trans_stack[my_level] only ``` Any live subxact with level < `my_level` -- a user `SAVEPOINT` and the per-statement RC internal subxact above it -- now points at an uninitialized slot. ROLLBACK reads garbage as `state`, the non-NULL gate passes, and `pfree(afterTriggers.state)` faults because `EndXact` already cleared that field. ## Fix `YBCRestartWriteTransaction` now rolls back every savepoint / subtransaction before recreating the top-level write state, so the PG-side `trans_stack` is empty by the time the surgical reset wipes the after-trigger state. Removed the now-redundant `RollbackAndReleaseCurrentSubTransaction()` from the else branch of `yb_restart_transaction`. Test Plan: Jenkins Reviewers: pjain, smishra Reviewed By: pjain Subscribers: ybase, yql Differential Revision: https://phorge.dev.yugabyte.com/D54387
| for (const auto& [transaction_id, apply_op_id] : txns) { | ||
| const OpId* apply_record_op_id = &apply_op_id; | ||
| if (!apply_op_id.valid()) { | ||
| if (cdc_wall_clock_retention) { |
There was a problem hiding this comment.
Preserve CDC checkpoint barriers when wall-clock retention is enabled
When --intents_min_seconds_to_retain is nonzero, this branch takes precedence even if GetLatestCheckPointUnlocked() returns a real CDCSDK checkpoint. In a cluster with an existing checkpoint-based CDC stream that enables the new global StreamWAL retention flag, transactions older than the wall-clock window can be inserted for cleanup without checking whether info.apply_op_id is still greater than the stream checkpoint. A lagging lease/checkpoint-based consumer can therefore lose committed intents that were previously protected by the checkpoint barrier.
React with 👍/👎 — all feedback helps improve the agent.
| RPC_CHECK_AND_RETURN_ERROR( | ||
| req->has_tablet_id() && !req->tablet_id().empty(), | ||
| STATUS(InvalidArgument, "StreamWAL: tablet_id is required"), | ||
| resp->mutable_error(), CDCErrorPB::INVALID_REQUEST, context); |
There was a problem hiding this comment.
StreamWAL exposes decoded tablet changes without stream authorization
The new StreamWAL handler accepts only a caller-supplied tablet_id and from_op_id before proceeding. In this validation block it checks that those two fields are present, but it does not require a stream_id, does not look up cdc_state, and does not perform an equivalent stream/table authorization check. After this point the handler looks up the tablet, reads WAL entries via ReadReplicatedMessagesInSegmentForCDC, synthesizes streamless metadata over tablet_ptr->metadata()->GetAllColocatedTables(), decodes WAL/intents through DispatchWalOpForStreamWAL, and appends decoded CDCSDKProtoRecordPB records to the response.
The existing GetChanges path is scoped by CDC stream authorization: it requires stream_id/db_stream_id, validates that the requested tablet belongs to that stream, fetches stream metadata from master, and checks stream activity before returning data. This new RPC bypasses that control model entirely. Any client/principal that can reach the tserver CDC service only needs a tablet id to request decoded changes for that tablet. As a result, a client with tserver RPC access could call StreamWAL for a victim tablet and receive decoded row-change data for tables it has not been granted a CDC stream over. This is a broken access-control issue, CWE-862 / OWASP A01.
React with 👍/👎 — all feedback helps improve the agent.
| << "TransactionId: " << transaction_id << ", commit_ht: " << info.commit_ht; | ||
| continue; | ||
| } | ||
| set.insert(transaction_id); |
There was a problem hiding this comment.
Treat missing post-apply metadata as unsafe under retention
When the compaction filter adds a transaction but does not also see a post-apply marker, info.commit_ht remains invalid and this branch falls through to set.insert(transaction_id). That regresses the existing CDC behavior for unknown apply metadata: committed transactions whose post-apply metadata is absent due to upgrade, crash before asynchronous metadata write, or compaction not including the marker can be routed to abort cleanup even though the retention window cannot be evaluated. Once the participant has evicted the transaction and the coordinator no longer reports it, this can remove committed intents that should have been retained.
React with 👍/👎 — all feedback helps improve the agent.
| if (cdc_intents_retain_secs > 0 && best_file_max_ht.is_valid() && | ||
| best_file_max_ht != HybridTime::kMax) { | ||
| const auto now_micros = clock_->Now().GetPhysicalValueMicros(); | ||
| const auto file_micros = best_file_max_ht.GetPhysicalValueMicros(); |
There was a problem hiding this comment.
Do not delete whole intent SSTs based on intent write time
The whole-file retention gate compares the current time to best_file_max_ht, which is the file frontier/newest intent write time rather than the transaction apply time. A long-running transaction can write intents into an old SST, remain open longer than --intents_min_seconds_to_retain, and then commit. Once it is no longer a running transaction, this check can see the old file frontier as outside the window and delete the entire SST immediately, bypassing per-transaction retention logic. StreamWAL can then read the recent APPLYING WAL record but fail to read the committed intents.
React with 👍/👎 — all feedback helps improve the agent.
Overall goal
This PR adds
StreamWAL, a new per-tablet, leader-only tserver RPC that delivers fully-decoded, committed change events as a stream ofCDCSDKProtoRecordPB— the same wire format the existing CDCSDK gRPC connector already consumes.The client owns all stream state (a per-tablet
(term, index)cursor); the server registers nothing. There is nocdc_statetable, no stream IDs, no master-driven control plane, and no per-stream aggregation.The data plane reuses the existing CDCSDK decoder family unchanged: transactional
WRITE_OPs are skipped on the wire and the corresponding rows are emitted atAPPLYINGtime by reading intents from IntentsDB, sandwiched betweenBEGIN/COMMITenvelopes stamped withcommit_hybrid_time.By default StreamWAL delivers records in WAL (apply) order. It also adds an optional, per-request consistent-commit-order mode that instead delivers committed records in commit-time order, watermark-gated, via a composite (term, index, commit_ht) cursor.
StreamWALlives alongsideGetChanges; no existing CDC code path is modified. The one cross-cutting server change is a new wall-clock intent-retention mechanism (gated behind a flag, default-off) that lets a checkpoint-less consumer keep just-applied intents readable.These changes are broken into a number of sections:
Proto changes
StreamWALRPC to the existingCDCService, three new top-level messages (StreamWalRequestPB,StreamWalResponsePB,StreamWalCursorPBCDCSDKProtoRecordPB(aborted_subtxn_set,split_tablet_request)CDCErrorPB::Codevalue (INTENTS_GC_ERROR = 15).StreamWalRequestPB.consistent_commit_order(bool, default false),StreamWalCursorPB.commit_ht(the commit-time frontier), andStreamWalResponsePB.resolution_safe_time(the per-tablet resolution watermark the batch gated on).Files changed:
src/yb/cdc/cdc_service.protoStreamWAL handler and control flow
CDCServiceImpl::StreamWALhandler.ReplicateMsgs and dispatches each through the decoder, leader/safe-time resolution,INTENTS_GC_ERRORdetection, and the partial-APPLYING batch/spill logic.StreamWalDecodeContext,StreamWalIntentResumeState,StreamWalDispatchResult) live incdc_producer.h.consistent_commit_order=true, the handler forks early intoCDCServiceImpl::HandleStreamWALConsistentCommitOrderand returns; the default (WAL-order) path is left entirely untouched.Files changed:
src/yb/cdc/cdc_service.cc,src/yb/cdc/cdc_service.h,src/yb/cdc/cdc_producer.hDecoder helpers reusing the CDCSDK pipeline
DispatchWalOpForStreamWAL,DispatchApplyingForStreamWALImpl) and envelope builders (PopulateSyntheticBootstrapDDLs,PopulateStreamWalApplyingRecord,PopulateStreamWalSplitRecord,StampOpIdOnLeadingBeginForStreamWAL).Populate*decoder family (PopulateCDCSDKWriteRecord,PopulateCDCSDKIntentRecord, DDL/truncate fillers), which are reused verbatim.Files changed:
src/yb/cdc/cdcsdk_producer.ccWall-clock intent retention
--intents_min_seconds_to_retain(default0), a time-based parallel to--log_min_seconds_to_retainfor IntentsDB, so a checkpoint-less consumer can read committed-but-just-applied intents without a per-stream lease barrier.TransactionParticipant::Cleanupand the IntentsDB compaction filter now consult the commit hybrid time when deciding whether to GC a transaction's intents, andTransactionIdApplyOpIdMapintransaction.hchanges from mapping to a bareOpIdto aTransactionApplyOpIdInfostruct that also carriescommit_ht.0, every path falls through to existing behavior, so this is a no-op for non-CDC and lease-based CDC clusters.RunningTransaction::SetApplyHybridTimes(stamps commit/log HT for single-batch applies),Tablet/docdb::CountTxnReverseIndexEntriesForCDC(distinguishes a real intent-GC from a zero-intent txn to avoid spuriousINTENTS_GC_ERROR).Files changed:
src/yb/tablet/transaction_participant.cc,src/yb/docdb/docdb_compaction_filter_intents.cc,src/yb/common/transaction.h,src/yb/tablet/running_transaction.cc,src/yb/tablet/running_transaction.h,src/yb/tablet/tablet.cc,src/yb/tablet/tablet.h,src/yb/docdb/docdb.cc,src/yb/docdb/docdb.hStream-less metadata bootstrap
StreamMetadatacan be constructed for stream-id-less callers, lettingStreamWALreuse the existing decoder context without registering a stream.Files changed:
src/yb/cdc/xrepl_stream_metadata.cc,src/yb/cdc/xrepl_stream_metadata.hConsistent commit-order mode (optional, per-request)
order, gated behind the per-tablet resolution watermark, using a composite cursor: (term, index) is the WAL
re-read floor (and retention point) and commit_ht is the commit-time frontier (the server skips records with
commit_time <= commit_ht on resume). Floor + frontier together discharge ordering and dedup with zero client
state.
StreamWalConsistentOutput structs (in cdc_producer.h). It mirrors the consistent branch of GetChangesForCDCSDK but emits via the existing StreamWAL decoder dispatch and packages the composite cursor.
FLAGS_cdc_enable_consistent_recordsgflag that governs the legacy GetChanges path.Files changed:
src/yb/cdc/cdcsdk_producer.cc,src/yb/cdc/cdc_producer.h,src/yb/cdc/cdc_service.cc,Java client bindings & leader-hint failover
streamWAL(...)client methods and the request/response wrappers.TabletClient.decodegains a CDC-scoped branch that, onLEADER_NOT_READY, applies thetablet_consensus_infohint from the response to refresh the meta-cache leader pointer before retrying (RemoteTablet.applyLeaderHint), avoiding a master round-trip on leader failover mid-stream.StreamWalRequest/StreamWalResponsepairs, so existing RPC dispatch is unaffected.Files changed:
java/yb-client/src/main/java/org/yb/client/AsyncYBClient.java,YBClient.java,StreamWalRequest.java(new),StreamWalResponse.java(new),TabletClient.javaServer-side observability metrics
stream-id-less design, these attach directly to the tablet's existing metric entity (not a separate
per-stream entity like CDCSDKTabletMetrics/XClusterTabletMetrics), so they aggregate at the table level
handler_latency_yb_cdc_CDCService_StreamWAL family (plus service_request_bytes /
service_response_bytes), so no custom RPC metrics are added.
streamwal_intents_gc_errors increments on the two INTENTS_GC_ERROR return paths, and the success-path
gauge/counter updates before RespondSuccess(). No existing metric or handler is modified.
Metrics added:
streamwal_records_sent— counter (units), aggregated sum. Total decoded change records sent overStreamWAL for this tablet.
streamwal_traffic_sent— counter (bytes), aggregated sum. Total decoded record payload bytes sent overStreamWAL.
streamwal_sent_lag_micros— gauge (µs), aggregated max. Lag between the leader's safe time and thecommit time of the last record sent.
streamwal_wal_lag_index— gauge (ops), aggregated max. WAL ops between the leader tip and the readcursor (leader_tip.index − next_op_id.index).
streamwal_intent_retention_window_secs— gauge (s), aggregated max. Current--intents_min_seconds_to_retain; lets dashboards derive intent-retention headroom as window − sent_lag.
A true kMin "headroom" gauge is not expressible (only kSum/kMax aggregation functions exist), so the
window is surfaced and headroom is computed against the kMax lag downstream.
streamwal_intents_gc_errors— counter (units), aggregated sum. Count of INTENTS_GC_ERROR responses(intents GC'd before StreamWAL could read them). Must always be zero; non-zero indicates data loss.
Files changed:
src/yb/cdc/xrepl_metrics.h,src/yb/cdc/xrepl_metrics.cc,src/yb/cdc/cdc_service.cc,src/yb/cdc/stream_wal-test.cc