You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug](High) Enabling binlog reduces import throughput by about 65% #65947
I searched the existing issues and found no issue reporting the same binlog import-performance regression.
Version
Reproduced on Doris commit 87d57a84729.
What's Wrong?
Enabling binlog on a table causes a significant import-performance regression under the same workload.
Binlog
Import throughput metric
Elapsed-time metric
Enabled
1,068,208.99
2,340.366
Disabled
3,085,916.86
810.132
With binlog enabled:
Import throughput drops by approximately 65.4%, to 34.6% of the binlog-disabled result.
Elapsed time increases to approximately 2.89x the binlog-disabled result.
The comparison used the same Doris build and import workload, with binlog enablement as the relevant variable.
What You Expected?
Enabling binlog has unavoidable write amplification, but it should not reduce import throughput by approximately 65% under the same workload. The binlog write path should be profiled and optimized, or the expected overhead and limiting factors should be documented.
How to Reproduce?
Prepare a table and a repeatable bulk-import workload.
Run the workload with binlog disabled and record throughput and elapsed time.
Recreate the table with the same schema and settings, enabling binlog as the relevant change.
Run the identical workload and compare throughput and elapsed time.
Observe that the binlog-enabled run reaches only about one third of the throughput and takes nearly three times as long.
Impact
The regression substantially reduces sustained ingestion capacity for workloads that require binlog, including downstream incremental-consumption scenarios.
Breakwater-GitHub-Analysis-Slot: slot_2543afc8eebe
This content is generated by AI for reference only.
Initial triage for 87d57a84729b61fcdd2e00e4e10697b46b2a82e9:
This looks like a real row-binlog write-path amplification issue, but the current report does not include enough workload detail or profiles to pin the single dominant bottleneck yet.
What the code path shows:
If the table uses binlog.enable=true with binlog.format=ROW, FE creates a separate row-binlog index/schema for streaming binlog. That schema contains the source key/visible columns, optional BEFORE columns when binlog.need_historical_value=true, and hidden TSO/LSN/op columns.
On the BE load path, VTabletWriter detects the row-binlog index and allocates one row-binlog LSN per input row through the auto-increment buffer. Those LSNs are copied through the add-block request, TabletsChannel, the memtable sidecar, and finally into the segment writer options at memtable flush time.
The rowset writer is wrapped as a GroupRowsetWriter, so each flush/build is synchronous for both the normal data rowset and the row-binlog rowset. In other words, row-binlog generation is on the import critical path, not an asynchronous post-commit task.
RowBinlogSegmentWriter::append_block() reconstructs and writes a second segment stream: it converts source columns again, writes the TSO/LSN/op columns, builds a key index, and, when historical values are requested or partial update needs full AFTER rows, performs historical-row retrieval/fill work before writing BEFORE/AFTER columns.
FE transaction commit also fetches a commit TSO for row-binlog tables. That is worth measuring, especially for workloads made of many small transactions, but for large bulk import the code suggests the main suspects are BE row-binlog segment construction, sidecar LSN handling, and optional historical-row lookup.
So the 65% throughput drop is plausible from the current implementation, but I would not classify the exact regression boundary from the provided numbers alone. The strongest code-level suspect is that the current ROW binlog implementation synchronously writes a full secondary rowset per flush and may additionally do per-row LSN copying plus historical lookup. That can directly throttle import throughput when the workload is CPU-bound in conversion/sorting/index writing or I/O-bound on the extra segment files.
Missing information needed to make this actionable:
Exact CREATE TABLE DDL, especially key type, number/types of columns, partitions/buckets/tablets, replica count, and the binlog properties: binlog.enable, binlog.format, and binlog.need_historical_value.
Exact load method and workload shape: Stream Load/Broker Load/Routine Load/Insert, total rows/bytes, file format, batch size, concurrency, transaction count, and whether both tests created the same number of rowsets/segments.
FE/BE configs that affect this path, including enable_feature_binlog, memtable flush settings, compaction settings, load parallelism, auto-increment prefetch settings, and disk/network layout.
Side-by-side BE load profiles or flamegraphs for enabled vs disabled runs, with attention to VTabletWriter, TabletsChannel::add_block, MemTableFlushExecutor, GroupRowsetWriter::flush_memtable/build_rowsets, RowBinlogSegmentWriter::append_block, OlapBlockDataConvertor, build_key_index, and historical-row retriever code if need_historical_value=true.
FE transaction timing for commit/visible/publish phases, especially TSO-fetch latency if the workload uses many small transactions.
Recommended next checks:
First confirm whether this is ROW binlog. If it is STATEMENT_AND_SNAPSHOT, this investigation should pivot to the CCR-style binlog path instead of the row-binlog writer path above.
Run the same benchmark with binlog.format=ROW and binlog.need_historical_value=false versus true. A large gap there would point to historical-row retrieval/BEFORE-column filling rather than basic secondary-rowset writing.
Compare flush count, segment count, rowset count, CPU time, and disk write bytes between the two runs. If the slowdown scales with segment/flush count, the synchronous secondary rowset build is the likely limiter.
Add or collect timing around the row-binlog writer stages before optimizing: LSN allocation/copy, source-column conversion, binlog meta-column fill, key-index build, BEFORE/AFTER historical lookup, and normal-vs-row-binlog flush/build time.
If profiles confirm the BE row-binlog writer dominates, likely optimization directions are batching/reducing LSN sidecar copies, avoiding repeated column conversion where possible, bulk-filling LSN/op columns, reducing key-index build cost for append-only cases, or decoupling row-binlog materialization from the synchronous import critical path while preserving ordering and correctness.
Activity
zclllyybb commentedon Jul 23, 2026
Breakwater-GitHub-Analysis-Slot: slot_2543afc8eebe
This content is generated by AI for reference only.
Initial triage for
87d57a84729b61fcdd2e00e4e10697b46b2a82e9:This looks like a real row-binlog write-path amplification issue, but the current report does not include enough workload detail or profiles to pin the single dominant bottleneck yet.
What the code path shows:
binlog.enable=truewithbinlog.format=ROW, FE creates a separate row-binlog index/schema for streaming binlog. That schema contains the source key/visible columns, optional BEFORE columns whenbinlog.need_historical_value=true, and hidden TSO/LSN/op columns.VTabletWriterdetects the row-binlog index and allocates one row-binlog LSN per input row through the auto-increment buffer. Those LSNs are copied through the add-block request,TabletsChannel, the memtable sidecar, and finally into the segment writer options at memtable flush time.GroupRowsetWriter, so each flush/build is synchronous for both the normal data rowset and the row-binlog rowset. In other words, row-binlog generation is on the import critical path, not an asynchronous post-commit task.RowBinlogSegmentWriter::append_block()reconstructs and writes a second segment stream: it converts source columns again, writes the TSO/LSN/op columns, builds a key index, and, when historical values are requested or partial update needs full AFTER rows, performs historical-row retrieval/fill work before writing BEFORE/AFTER columns.So the 65% throughput drop is plausible from the current implementation, but I would not classify the exact regression boundary from the provided numbers alone. The strongest code-level suspect is that the current ROW binlog implementation synchronously writes a full secondary rowset per flush and may additionally do per-row LSN copying plus historical lookup. That can directly throttle import throughput when the workload is CPU-bound in conversion/sorting/index writing or I/O-bound on the extra segment files.
Missing information needed to make this actionable:
CREATE TABLEDDL, especially key type, number/types of columns, partitions/buckets/tablets, replica count, and the binlog properties:binlog.enable,binlog.format, andbinlog.need_historical_value.enable_feature_binlog, memtable flush settings, compaction settings, load parallelism, auto-increment prefetch settings, and disk/network layout.VTabletWriter,TabletsChannel::add_block,MemTableFlushExecutor,GroupRowsetWriter::flush_memtable/build_rowsets,RowBinlogSegmentWriter::append_block,OlapBlockDataConvertor,build_key_index, and historical-row retriever code ifneed_historical_value=true.Recommended next checks:
STATEMENT_AND_SNAPSHOT, this investigation should pivot to the CCR-style binlog path instead of the row-binlog writer path above.binlog.format=ROWandbinlog.need_historical_value=falseversustrue. A large gap there would point to historical-row retrieval/BEFORE-column filling rather than basic secondary-rowset writing.