You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@doris.apache.org by ja...@apache.org on 2024/10/11 07:30:42 UTC
(doris) branch spill_and_reserve updated (f08a3bb4dc6 -> 4eb45a884ac)
This is an automated email from the ASF dual-hosted git repository.
jacktengg pushed a change to branch spill_and_reserve
in repository https://gitbox.apache.org/repos/asf/doris.git
omit f08a3bb4dc6 Use DorisVector to avoid memory usage from not being traced (#41557)
omit 0756e28d7df only spill largest task (#41518)
omit d8f61b57a72 pretty print the size info (#41516)
omit ae335e794f2 [fix]avoid too much memory in join probe operator
omit 06726961120 add reserve time and failed counter
omit dab8e72d4a4 cancel other query when memory not enough
omit a834354efb1 Opt the spill logic (#41415)
omit 8f8d19a3628 print more message during revoke (#41365)
omit 7a285e2565d support stream load (#41351)
omit 70c59001ea1 refactor logic of revoking and low memory mode in local exchange oper… (#41264)
omit 28dd5181f18 [spill](fix) fix reserve memory if enable_memory_overcommit (#41282)
omit 351669ab6e9 enable memory check by default
omit 196fa5ab902 [spill](counters) adjust spill counter level
omit 4dd37d10db7 avoid scanner run exceed memory limit (#41209)
omit a14444abcfe [refactor] refactor spill strategy (#41195)
omit 21bb82ed3e1 add load buffer limit for load buffer management (#41115)
omit 39d75d5ca0b support low memory mode for exchange, union and stream agg (#41005)
omit 5935d2f09c6 flush memtable before spill disk
omit a2079c99b0d wait for clear cache before do spill to disk
omit ca31ee7343e fix FE compile error
omit d50d9c789d0 [feature](workloadgroup) use slot num to control memory distribution among queries (#40935)
omit dc9272df9ba fix build error
omit 2a74d1c796b Avoid dead-loop when reserve failed
omit 9f01cd08e5d Use SCOPED_PEAK_MEM to replace ScopedMemTracker
omit 01bc5c82648 [fix] use faststring in Block::serialize
omit 2a103cc1623 set min reserve bytes to 1MB (#40795)
omit e346940011f add reserve memory logic in scan operator (#40719)
omit 2d2d79d6a75 fix bug
omit 8bb3e9b682a add low memory mode in scan operator (#40662)
omit 2c4c06d32f0 [fix](exchange) fix coredump of update counter is block is null
omit 6b6e6f6dd6f move paused query and handle logic to workload group manager (#40603)
omit c07189ea99a [improvement](spill) add counter for memory usage (#40572)
omit 8c6f476ad60 [fix] avoid crash when no data to revoke
omit 5c59d27fe98 [opt](spill) avoid query blocked when reserving failed (#40550)
omit cf78055a02e [opt](spill) Avoid occupying a large amount of memory in join build side
omit acdfe77cd7d 1. remove revoke memory logic in execute task 2. use memory error code when cancel query (#40426)
omit fdb2d6bb2e1 [revokememory] remove old revoke memory logic
omit e0719d54e36 [refactor](codestyle) use pointer as return value (#40396)
omit e7f1657117d updated
omit 1bbf39ecd3e updated
omit 14ea49d38a2 [feat]reserve and spill
add b0b2c0a2551 [improvement](jdbc catalog) Improve JdbcClientException to accommodate various identifier formats (#40931)
add d4b9c5edba2 [improvement](jdbc catalog) Optimize JdbcCatalog case mapping stability (#40891)
add 1b9c78a9d43 Revert "[improvement](jdbc catalog) Optimize JdbcCatalog case mapping stability" (#41476)
add 56592b31599 [case](mtmv)fix alter job case (#41326)
add ca40d2652fe [test](p2) ignore order of load counters in etl info (#41420)
add d8ee8e55cda [opt](load) print more detailed log when stream load finished (#41398)
add 421fde0203e [chore](log) log stream load put request in debug level (#41376)
add cf5a159582d [chore](file-cache) Enable file cache for cloud mode by force (#41357)
add ddc21289bd9 [improvement](show) make show cache hotspot like others (#41322)
add 44525d06803 [Chore](sink) add check for ExchangeSinkBuffer::add_block (#41473)
add 6884a686fee [improve](serde) support const column in serialize/deserialize function (#41175)
add 27236f6f774 [refine](time type) remove and deprecated datatypetimev1 (#41334)
add 970717ccac7 [Chore](case) make case test_duplicate_mv stable (#41467)
add 5da26a548fa [fix](ubsan) fix ubsan : runtime error: applying non-zero offset 16 to null pointer (#41486)
add 26bbfa13827 [fix](test)resolve table name conflict between test cases (#41492)
add bfccd0ebafc [fix](mtmv)Fix show mtmv time should wrapped in double quotation marks (#41419)
add e8fb88f9167 [opt](compile) ensure fe-core compile with language level 8 (#41417)
add ce982a959e0 [Chore](materialized-view) mow table's mv need have all key column (#41496)
add b41927833f0 [fix](Nereids) could not parse date/datetime with blank + zone (#41394)
add 2926cdb07f6 [Feat](Nereids) add numeric functions (#40744)
add f9bd4efb9e5 [fix](oracle scan) Fix performance issues caused by version judgment (#41407)
add 637da180ad7 [regressioncase](nereids)tpcds 10t plan shape for external table (#41340)
add 0c977478237 [fix](regression test) fix test_abort_txn_by_fe (#41382)
add e93587aff47 [bugfix](brpc) Should use status to generate protobuf message, because it will encoding Backend Info (#41515)
add 9b8e65b294c [chore](test)Print docker-related process information when executing kerbreos-related cases (#41517)
add 1fc6b355326 [Exec](mem) Reduce the memory usage during JOIN operations (#41388)
add 0e5fd2fdab2 [Enhancement](doris-future) Support "REGR_" aggregation functions (PART II) (#41240)
add 4b8375f346c [fix](pipeline) Make all upstream tasks runnable if all tasks finishe… (#41292)
add 76f2bb5e40b [Improvement](predicate) reduce useless check on HybridSet::find (#41498)
add 3fdd1b2f64e [Enhancement](multi-catalog) Set hdfs native client logger to glog and redirect jvm stdout/stderr logger to jni.log. (#39540)
add 80930aaa5be [chore](query err) fix cutting err msg incorrectly (#41456)
add 144f61635ee [improvement](create table) add backend details to creating table failed msg (#41463)
add 5ca03008ec2 [fix](ce) Wshorten-64-to-32-error in incr_created_tasks (#41539)
add 958d63831cd [fix](nereids)do not add delta row count to BE reported row count (#41464)
add 5fd2ef7d66c [fix](fe) Fix `PluginMgr.getActivePluginList` npt (#41466)
add bdd738279c1 [Chore](checks) Clang Tidy Preparation do not open build_cloud (#41543)
add e8a1a16f3ca [Enhancement](maxCompute)Implement compatibility with existing maxcompute catalogs from previous versions. (#41386)
add 8e3db001f21 [Enhancement](tvf)catalog tvf implements user permission checks and hides sensitive information (#41497)
add 9101ed03a4d [Enhancement](inverted index) apply inverted index when has any (#41547)
add 48cbc350acc [fix] (inverted index) disable index compaction (#41427)
add 00ec3a820ce [fix](crash) fix be crash because of int overflow (#41554)
add 401be166bbc [compile](minor) Fix unsafe type conversion (#41534)
add fa3934d3ff2 [feature](paimon)support paimon with dlf (#41247)
add bcbec25ec57 [fix](test) fix unstable test_export_external_table cases (#41523)
add 46020680a47 [ubsan](PODArray)Avoid applying non-zero offset to null pointer (#41525)
add bc9b87d2f72 [Fix](inverted index) Resolve null processing issue in arrays_overlap (#41495)
add 17422ff540b [test](inverted index) fix test case for no need read data (#41564)
add 4562b9a6821 [bugfix](arrow) Fix time zone issues and accuracy issues (#38215)
add 5197843ffd0 [fix](FoldConst)The length function returns incorrect results under FoldConst. (#40947)
add 138b0625b07 [Compile](fix) Fix mac complie BE error because codecvt_utf8_utf16 deprecated in C++17 and atomic_long not support in mac (#41569)
add 2255b186e88 [chore](scan) remove useless enable_scan_node_run_serial (#41559)
add 03aff6e2aef [Fix](inverted index) add DATEV2 and DATETIMEV2 for inverted index reader (#41565)
add 4cfd57e8c70 [Enhancement](inverted index) return OK instead of not supported in expr evaluate_inverted_index (#41567)
add c065364a905 [local exchange](fix) Fix correctness caused by local exchange (#41555)
add e8336c4e604 [opt](Nereids) use 1 instead narrowest column when do column pruning (#41548)
add 277e3b979ce [fix](glue) support glue on aws (#41084)
add 945edf8dbff [chore](dependencies)upgrade fe dependencies (#41142)
add 9b25a83b6c6 [chore](plugin)It's allowed for the plugin directory to be empty when loading plugins (#41574)
add e08811ba9a4 [fix] (inverted index) fix the error result in the query when using count on index (#41375)
add bfeb9fefc2b Revert "[fix](inverted index) Fix Session Variable Compatibility" (#39906)
add 3eb24bff707 [fix](schema scan) Finish schema scanner if limitation is reached (#41592)
add fc0279b08d4 [minor](log) Delete useless logics checking (#41589)
add 1a5c1be39c2 [ci](ga) skip scope-label (#41612)
add 35c47a8c54c [opt](blacklist) Backend should not be added to blacklist easily (#41170)
add a298d01e14d [Bug](runtime-filter) send rf when hash join build early close and add check for BloomFilterFuncBase (#41601)
add 3c5a98e2caa [enhance](parquet) support decimal256 for parquet reader (#41526)
add 5fee54319cb [bugfix](iceberg)add prefix for endpoint with s3 client (#41336)
add 2957b197302 [improve](binlog) Filter the truncated partitions (#41611)
add 7229aeac549 [refactor](wg&memtracker) using weak ptr to delete memtracker and query context automatically (#41549)
add 0d2550f3f82 [fix](parquet) fix dict page offset zero incompatible behaviour (#41506)
add 469f04ed4ec [fix](jdbc) fix Unknown command(27) (#41621)
add d1dd2d1a429 [Feature](partial update) Support flexible partial update in stream load with json files (#39756)
add 531097d9eba [fix](conn-pool) Avoid some invalid connections returned to pool (#41597)
add 52835068735 [test](copy into) add copy into regression test case (#41529)
add 047fa26046e [fix](cache) fix show cache hotspot syntax error (#41519)
add 66d88ecabaf [fix](case) ajust timeout to make case stable (#41468)
add f6dac85fc7c [chore](docker) adjust subnet in kafka.yaml.tpl to prevent conflict (#40770)
add c0749bc64de [fix](case) try to drop function before create it (#41603)
add f313f1f8c1d [opt](inverted index) Optimize sequential phrase query logic (#41432)
add b3e07156531 [Chore](runtime-filter) change bloom filter DCHECK to error status (#41648)
add f5057517716 [Feature](compatibility) make some agg function restrict (#41459)
add 4583a027ce1 [feat](Nereids) support set var in hint when parse sql (#41331)
add a205182850f [feature](nereids) extend infer predicates (#40878)
add 8f7e1062e24 [bugfix](segmentload) should remove segment from segment cache if load segment failed (#41608)
add 641c4dcdc1a [fix](array/map) fix resize impl in array/map (#41595)
add 1e8ea55efdf [chore](case) adjust db name (#41644)
add 0a26ad88857 Revert "[Improvement](sort) do not sort partitial when spill disabled(#40528)" (#41652)
add a9caf05d159 [Bug](runtime-filter) fix wrong check on BloomFilterFuncBase::merge (#41670)
add 3a85f78cb59 [fix](delete) Fix potential delete job stuck util timeout if exception happend in FE DeleteJob execution (#41672)
add 70f19d87c0c [fix](counters) fix MemoryUsage and PeakMemoryUsage counters of some operators (#41602)
add fc96ff183e8 [fix](case) fix test_schema_change_ck (#41654)
add c6bd9cbf9c3 [chore](log) Adjust log level for replaying a batch editlog cost time (#41392)
add 93323fe2b6d [fix](cluster key) Cluster key skip non vertical compact (#41413)
add 6bb30e43f34 [improve](group commit) set internal group commit timeout (#41404)
add 199148f82cc [improve](cloud) set default vaule for file_path (#41507)
add 954f98c8a74 [fix](cloud) enable_file_cache for cloud by default (#41502)
add 3b2d61d9d6a [enhance]change SpinLock to mutex for _tablets_channels_lock (#41384)
add 89b24cab455 [Test](Orc) Add tests for exception orc files (#40621)
add 975e48e2995 [opt](hive) make supported hive table error msg clearer (#41616)
add 473007d3c74 [fix](Nerieds) column prune should retain at least one column for union all (#41613)
add 5d4ab4d9f95 [enhance](catalog)Allow parallel running of insert overwrite on the external table (#41575)
add 1f3465f0d7c [bugfix](becore) use after free problem when the segment is pop (#41685)
add 3e55152ec13 [Refactor](exec) refactor schema scanner del unless code (#41540)
add af9581871dd [Improvement](shuffle) Reduce memory consumption in data stream sender (#41676)
add 6a7d6e6739a [Fix](delete) Fix some delete p2 case (#41678)
add 44fe7686582 [fix](jdbc catalog) Disable oracle scan null operator pushdown (#41563)
add de32b566630 [enhance](mtmv)Mtmv support audit log (#41499)
add 3b18b1f0004 [fix](scanner) Fix incorrect _max_thread_num in scanner context when many queries are running. (#41273)
new 4eb45a884ac [feat]reserve and spill
This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version. This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:
* -- * -- B -- O -- O -- O (f08a3bb4dc6)
\
N -- N -- N refs/heads/spill_and_reserve (4eb45a884ac)
You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.
Any revisions marked "omit" are not gone; other references still
refer to them. Any revisions marked "discard" are gone forever.
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.github/workflows/code-checks.yml | 2 +-
.github/workflows/scope-label.yml | 11 +-
be/src/agent/be_exec_version_manager.cpp | 25 +-
be/src/agent/be_exec_version_manager.h | 9 +-
be/src/cloud/cloud_rowset_writer.cpp | 5 +-
be/src/cloud/cloud_stream_load_executor.cpp | 2 +
be/src/cloud/cloud_tablet.cpp | 2 +-
be/src/cloud/pb_convert.cpp | 4 +
be/src/common/config.cpp | 11 +-
be/src/common/daemon.cpp | 1 +
be/src/exec/schema_scanner.cpp | 23 +-
be/src/exec/schema_scanner.h | 12 +-
.../schema_scanner/schema_statistics_scanner.cpp | 50 --
.../schema_scanner/schema_statistics_scanner.h | 35 --
be/src/exec/tablet_info.cpp | 68 +-
be/src/exec/tablet_info.h | 20 +-
be/src/exprs/bloom_filter_func.h | 12 +-
be/src/exprs/hybrid_set.h | 16 -
be/src/gutil/endian.h | 13 +-
be/src/http/action/stream_load.cpp | 81 ++-
be/src/http/http_common.h | 1 +
be/src/io/cache/block_file_cache_factory.h | 5 +-
be/src/io/hdfs_builder.cpp | 61 ++
be/src/olap/base_tablet.cpp | 246 +++++++-
be/src/olap/base_tablet.h | 18 +-
be/src/olap/compaction.cpp | 4 +
be/src/olap/delta_writer_v2.cpp | 2 +-
be/src/olap/in_list_predicate.h | 28 +-
be/src/olap/memtable.cpp | 205 ++++--
be/src/olap/memtable.h | 7 +-
be/src/olap/partial_update_info.cpp | 395 +++++++++++-
be/src/olap/partial_update_info.h | 96 ++-
be/src/olap/rowset/beta_rowset_writer.cpp | 2 +-
be/src/olap/rowset/beta_rowset_writer.h | 2 +-
be/src/olap/rowset/beta_rowset_writer_v2.h | 2 +-
.../inverted_index/analyzer/analyzer.cpp | 17 +
.../segment_v2/inverted_index/analyzer/analyzer.h | 6 +
.../inverted_index/query/phrase_query.cpp | 87 ++-
.../segment_v2/inverted_index/query/phrase_query.h | 14 +-
.../rowset/segment_v2/inverted_index/query/query.h | 10 +
.../rowset/segment_v2/inverted_index_reader.cpp | 23 +-
.../olap/rowset/segment_v2/inverted_index_reader.h | 2 +
be/src/olap/rowset/segment_v2/segment.cpp | 71 ++-
be/src/olap/rowset/segment_v2/segment.h | 13 +-
be/src/olap/rowset/segment_v2/segment_iterator.cpp | 9 +-
be/src/olap/rowset/segment_v2/segment_iterator.h | 5 +
be/src/olap/rowset/segment_v2/segment_writer.cpp | 363 ++++++++++-
be/src/olap/rowset/segment_v2/segment_writer.h | 25 +-
.../rowset/segment_v2/vertical_segment_writer.cpp | 436 ++++++++++++-
.../rowset/segment_v2/vertical_segment_writer.h | 26 +-
be/src/olap/rowset_builder.cpp | 15 +-
be/src/olap/segment_loader.cpp | 8 +-
be/src/olap/segment_loader.h | 12 +
be/src/olap/tablet_meta.h | 2 -
be/src/olap/tablet_schema.cpp | 9 +-
be/src/olap/tablet_schema.h | 3 +
be/src/olap/txn_manager.cpp | 6 +-
be/src/olap/utils.h | 2 +
be/src/pipeline/dependency.cpp | 2 +-
be/src/pipeline/dependency.h | 13 +-
be/src/pipeline/exec/aggregation_sink_operator.cpp | 55 +-
be/src/pipeline/exec/aggregation_sink_operator.h | 4 +-
.../pipeline/exec/aggregation_source_operator.cpp | 6 +-
be/src/pipeline/exec/analytic_sink_operator.cpp | 9 +-
be/src/pipeline/exec/analytic_sink_operator.h | 4 +-
be/src/pipeline/exec/analytic_source_operator.cpp | 3 +-
be/src/pipeline/exec/assert_num_rows_operator.cpp | 2 -
be/src/pipeline/exec/cache_source_operator.cpp | 1 -
.../distinct_streaming_aggregation_operator.cpp | 1 -
.../exec/distinct_streaming_aggregation_operator.h | 2 +-
be/src/pipeline/exec/exchange_sink_buffer.cpp | 37 +-
be/src/pipeline/exec/exchange_sink_operator.cpp | 66 +-
be/src/pipeline/exec/exchange_sink_operator.h | 2 +-
be/src/pipeline/exec/exchange_source_operator.cpp | 2 -
.../exec/group_commit_block_sink_operator.cpp | 3 -
be/src/pipeline/exec/hashjoin_build_sink.cpp | 130 ++--
be/src/pipeline/exec/hashjoin_build_sink.h | 10 +-
be/src/pipeline/exec/hashjoin_probe_operator.cpp | 8 +-
be/src/pipeline/exec/hashjoin_probe_operator.h | 2 +-
.../pipeline/exec/join/process_hash_table_probe.h | 1 -
.../exec/join/process_hash_table_probe_impl.h | 17 +-
.../pipeline/exec/memory_scratch_sink_operator.cpp | 2 +-
.../exec/multi_cast_data_stream_source.cpp | 1 -
be/src/pipeline/exec/operator.cpp | 35 +-
be/src/pipeline/exec/operator.h | 27 +-
.../pipeline/exec/partition_sort_sink_operator.cpp | 4 +-
.../exec/partition_sort_source_operator.cpp | 2 -
.../exec/partitioned_aggregation_sink_operator.cpp | 10 +-
.../exec/partitioned_aggregation_sink_operator.h | 1 -
.../exec/partitioned_hash_join_probe_operator.cpp | 9 +-
.../exec/partitioned_hash_join_probe_operator.h | 2 +-
.../exec/partitioned_hash_join_sink_operator.cpp | 6 +-
.../exec/partitioned_hash_join_sink_operator.h | 2 +-
be/src/pipeline/exec/repeat_operator.cpp | 1 -
be/src/pipeline/exec/result_sink_operator.cpp | 5 +-
be/src/pipeline/exec/scan_operator.cpp | 11 +-
be/src/pipeline/exec/scan_operator.h | 10 +-
be/src/pipeline/exec/schema_scan_operator.cpp | 3 +
be/src/pipeline/exec/sort_sink_operator.cpp | 9 +-
be/src/pipeline/exec/sort_sink_operator.h | 2 +-
be/src/pipeline/exec/spill_sort_sink_operator.cpp | 13 +-
be/src/pipeline/exec/spill_sort_sink_operator.h | 1 -
.../exec/streaming_aggregation_operator.cpp | 55 +-
.../pipeline/exec/streaming_aggregation_operator.h | 6 -
be/src/pipeline/exec/table_function_operator.cpp | 3 -
be/src/pipeline/exec/union_sink_operator.h | 6 +
be/src/pipeline/exec/union_source_operator.h | 5 +
.../local_exchange_sink_operator.cpp | 24 +-
.../local_exchange/local_exchange_sink_operator.h | 2 -
.../local_exchange_source_operator.cpp | 6 +-
be/src/pipeline/local_exchange/local_exchanger.cpp | 15 +-
be/src/pipeline/pipeline.cpp | 11 +-
be/src/pipeline/pipeline.h | 17 +-
be/src/pipeline/pipeline_fragment_context.cpp | 70 ++-
be/src/pipeline/pipeline_fragment_context.h | 3 +-
be/src/pipeline/pipeline_task.cpp | 3 +-
be/src/pipeline/pipeline_task.h | 17 +-
be/src/pipeline/task_queue.h | 2 +-
be/src/pipeline/task_scheduler.cpp | 2 +-
be/src/runtime/define_primitive_type.h | 3 +-
be/src/runtime/descriptors.h | 4 +
be/src/runtime/exec_env_init.cpp | 82 +--
be/src/runtime/fold_constant_executor.cpp | 9 +-
be/src/runtime/load_channel.cpp | 13 +-
be/src/runtime/load_channel.h | 3 +-
be/src/runtime/memory/mem_tracker.cpp | 53 --
be/src/runtime/memory/mem_tracker.h | 19 +-
be/src/runtime/memory/mem_tracker_limiter.h | 3 -
be/src/runtime/primitive_type.cpp | 8 +-
be/src/runtime/primitive_type.h | 6 -
be/src/runtime/query_context.cpp | 2 -
be/src/runtime/runtime_filter_mgr.cpp | 1 -
be/src/runtime/runtime_state.h | 5 -
.../runtime/stream_load/stream_load_executor.cpp | 8 +-
be/src/runtime/types.cpp | 9 +-
be/src/runtime/workload_group/workload_group.cpp | 84 ++-
be/src/runtime/workload_group/workload_group.h | 15 +-
.../workload_group/workload_group_manager.cpp | 7 +
.../workload_group/workload_group_manager.h | 2 +
be/src/service/internal_service.h | 8 +-
be/src/util/arrow/row_batch.cpp | 39 +-
be/src/util/arrow/row_batch.h | 11 +-
be/src/util/bit_util.h | 9 +-
.../aggregate_function_covar.cpp | 16 +-
.../aggregate_functions/aggregate_function_covar.h | 34 -
.../aggregate_function_group_array_intersect.h | 6 +-
.../aggregate_function_percentile.cpp | 62 +-
.../aggregate_function_percentile.h | 270 --------
.../aggregate_function_percentile_approx.cpp | 39 --
.../aggregate_function_percentile_approx.h | 258 --------
.../aggregate_function_regr_union.cpp | 93 +++
.../aggregate_function_regr_union.h | 216 +++++++
.../aggregate_function_simple_factory.cpp | 2 +
.../aggregate_function_stddev.cpp | 32 +-
.../aggregate_function_stddev.h | 34 -
.../aggregate_function_window_funnel.cpp | 28 +-
.../aggregate_function_window_funnel.h | 261 --------
be/src/vec/columns/column_array.cpp | 2 +
be/src/vec/columns/column_const.cpp | 15 +
be/src/vec/columns/column_const.h | 1 +
be/src/vec/columns/column_map.cpp | 3 +
be/src/vec/common/pod_array.h | 6 +-
be/src/vec/common/sort/sorter.h | 4 +-
be/src/vec/core/block.cpp | 32 +-
be/src/vec/core/block.h | 3 +
be/src/vec/data_types/data_type.cpp | 45 ++
be/src/vec/data_types/data_type.h | 8 +-
be/src/vec/data_types/data_type_agg_state.h | 3 +-
be/src/vec/data_types/data_type_array.cpp | 113 +++-
be/src/vec/data_types/data_type_array.h | 4 +-
be/src/vec/data_types/data_type_bitmap.cpp | 156 +++--
be/src/vec/data_types/data_type_bitmap.h | 4 +-
be/src/vec/data_types/data_type_decimal.cpp | 89 ++-
be/src/vec/data_types/data_type_decimal.h | 4 +-
be/src/vec/data_types/data_type_factory.cpp | 1 -
.../data_types/data_type_fixed_length_object.cpp | 149 ++++-
.../vec/data_types/data_type_fixed_length_object.h | 9 +-
be/src/vec/data_types/data_type_hll.cpp | 164 +++--
be/src/vec/data_types/data_type_hll.h | 3 +-
be/src/vec/data_types/data_type_jsonb.cpp | 3 +-
be/src/vec/data_types/data_type_jsonb.h | 3 +-
be/src/vec/data_types/data_type_map.cpp | 130 ++--
be/src/vec/data_types/data_type_map.h | 4 +-
be/src/vec/data_types/data_type_nothing.cpp | 2 +-
be/src/vec/data_types/data_type_nothing.h | 3 +-
be/src/vec/data_types/data_type_nullable.cpp | 105 ++--
be/src/vec/data_types/data_type_nullable.h | 4 +-
be/src/vec/data_types/data_type_number_base.cpp | 90 ++-
be/src/vec/data_types/data_type_number_base.h | 4 +-
be/src/vec/data_types/data_type_object.cpp | 6 +-
be/src/vec/data_types/data_type_object.h | 3 +-
be/src/vec/data_types/data_type_quantilestate.cpp | 159 +++--
be/src/vec/data_types/data_type_quantilestate.h | 4 +-
be/src/vec/data_types/data_type_string.cpp | 151 +++--
be/src/vec/data_types/data_type_string.h | 4 +-
be/src/vec/data_types/data_type_struct.cpp | 93 ++-
be/src/vec/data_types/data_type_struct.h | 3 +-
be/src/vec/data_types/data_type_time.cpp | 34 -
be/src/vec/data_types/data_type_time.h | 35 --
be/src/vec/exec/format/json/new_json_reader.cpp | 126 +++-
be/src/vec/exec/format/json/new_json_reader.h | 10 +
.../exec/format/parquet/parquet_column_convert.cpp | 5 +-
.../exec/format/parquet/parquet_column_convert.h | 19 +-
be/src/vec/exec/format/parquet/schema_desc.cpp | 2 +-
.../parquet/vparquet_column_chunk_reader.cpp | 11 +-
.../format/parquet/vparquet_column_chunk_reader.h | 1 +
.../exec/format/parquet/vparquet_column_reader.cpp | 7 +-
be/src/vec/exec/jni_connector.cpp | 5 +-
be/src/vec/exec/scan/scanner_context.cpp | 174 ++++--
be/src/vec/exec/scan/scanner_context.h | 20 +-
be/src/vec/exec/scan/scanner_scheduler.cpp | 8 +-
be/src/vec/exec/scan/scanner_scheduler.h | 6 +
be/src/vec/exec/scan/vfile_scanner.cpp | 88 ++-
be/src/vec/exec/scan/vfile_scanner.h | 9 +
be/src/vec/exec/scan/vmeta_scanner.cpp | 1 +
be/src/vec/exprs/vexpr.h | 6 +-
.../vec/functions/array/function_arrays_overlap.h | 20 +-
be/src/vec/functions/function.h | 3 +-
be/src/vec/functions/function_cast.h | 7 +-
be/src/vec/functions/function_string.h | 5 +-
be/src/vec/runtime/partitioner.cpp | 14 +-
be/src/vec/runtime/partitioner.h | 6 +-
.../vec/runtime/shared_hash_table_controller.cpp | 6 +-
be/src/vec/runtime/shared_hash_table_controller.h | 1 -
be/src/vec/runtime/vdata_stream_recvr.cpp | 8 +-
be/src/vec/runtime/vparquet_transformer.cpp | 3 +-
be/src/vec/sink/vdata_stream_sender.cpp | 10 +-
be/src/vec/sink/vdata_stream_sender.h | 2 +-
be/src/vec/sink/writer/vtablet_writer.cpp | 2 +-
be/src/vec/sink/writer/vtablet_writer_v2.cpp | 2 +-
.../inverted_index/query/phrase_query_test.cpp | 83 +++
be/test/util/bit_util_test.cpp | 19 +-
.../aggregate_functions/vec_window_funnel_test.cpp | 3 +-
be/test/vec/core/block_test.cpp | 409 +++++++++++-
be/test/vec/core/column_complex_test.cpp | 5 +-
be/test/vec/core/column_test.cpp | 105 +++-
.../serde/data_type_serde_arrow_test.cpp | 4 +-
be/test/vec/exec/parquet/parquet_thrift_test.cpp | 1 -
be/test/vec/function/function_test_util.h | 6 +-
bin/start_be.sh | 4 +-
bin/start_fe.sh | 4 +-
.../hdfs_tvf/test_parquet_decimal256.parquet | Bin 0 -> 1320 bytes
.../docker-compose/kafka/kafka.yaml.tpl | 2 +-
.../doris/common/classloader/ScannerLoader.java | 14 +
.../doris/common/jni/utils/Log4jOutputStream.java | 44 ++
.../org/apache/doris/jdbc/OracleJdbcExecutor.java | 4 +-
.../doris/maxcompute/MaxComputeColumnValue.java | 50 +-
.../doris/maxcompute/MaxComputeJniScanner.java | 15 +-
.../main/java/org/apache/doris/common/Config.java | 27 +-
fe/fe-core/pom.xml | 3 +
.../ConfigurationAWSCredentialsProvider2x.java | 50 ++
.../doris/alter/MaterializedViewHandler.java | 3 +-
.../apache/doris/alter/SchemaChangeHandler.java | 56 +-
.../org/apache/doris/alter/SchemaChangeJobV2.java | 14 +
.../org/apache/doris/analysis/AlterTableStmt.java | 17 +
.../java/org/apache/doris/analysis/Analyzer.java | 8 +-
.../java/org/apache/doris/analysis/ColumnDef.java | 9 +-
.../org/apache/doris/analysis/CreateTableStmt.java | 26 +
.../org/apache/doris/analysis/DataDescription.java | 7 +
.../apache/doris/analysis/EnableFeatureClause.java | 5 +
.../org/apache/doris/analysis/FloatLiteral.java | 3 +
.../analysis/ModifyTablePropertiesClause.java | 2 +
.../doris/analysis/ShowCacheHotSpotStmt.java | 16 +-
.../org/apache/doris/binlog/BinlogManager.java | 2 +-
.../java/org/apache/doris/binlog/DBBinlog.java | 12 +
.../apache/doris/binlog/TruncateTableRecord.java | 16 +
.../apache/doris/catalog/AggregateFunction.java | 2 +-
.../doris/catalog/BuiltinAggregateFunctions.java | 18 +-
.../main/java/org/apache/doris/catalog/Column.java | 11 +
.../main/java/org/apache/doris/catalog/Env.java | 11 +-
.../java/org/apache/doris/catalog/FunctionSet.java | 23 +
.../doris/catalog/InternalSchemaInitializer.java | 1 +
.../java/org/apache/doris/catalog/OlapTable.java | 25 +
.../main/java/org/apache/doris/catalog/Table.java | 5 +
.../java/org/apache/doris/catalog/TableIf.java | 2 +
.../org/apache/doris/catalog/TableProperty.java | 9 +
.../doris/cloud/catalog/CloudTabletRebalancer.java | 18 +-
.../apache/doris/cloud/rpc/MetaServiceClient.java | 2 +-
.../java/org/apache/doris/common/ErrorCode.java | 2 +-
.../apache/doris/common/util/ClassLoaderUtils.java | 6 +-
.../doris/common/util/DynamicPartitionUtil.java | 3 +-
.../apache/doris/common/util/ProfileManager.java | 11 +-
.../apache/doris/common/util/PropertyAnalyzer.java | 27 +
.../doris/common/util/SerializationUtils.java | 71 +++
.../org/apache/doris/datasource/CatalogMgr.java | 85 +--
.../org/apache/doris/datasource/ExternalTable.java | 6 +
.../org/apache/doris/datasource/FileGroupInfo.java | 27 +-
.../apache/doris/datasource/InternalCatalog.java | 13 +-
.../apache/doris/datasource/LoadScanProvider.java | 9 +-
.../doris/datasource/hive/HMSExternalTable.java | 12 +-
.../doris/datasource/iceberg/dlf/DLFCatalog.java | 3 +
.../jdbc/client/JdbcClientException.java | 27 +-
.../doris/datasource/jdbc/source/JdbcScanNode.java | 14 +
.../maxcompute/MaxComputeExternalCatalog.java | 151 ++++-
.../maxcompute/source/MaxComputeScanNode.java | 33 +-
.../paimon/PaimonDLFExternalCatalog.java | 54 ++
.../datasource/paimon/PaimonExternalCatalog.java | 1 +
.../paimon/PaimonExternalCatalogFactory.java | 4 +
.../datasource/paimon/PaimonExternalTable.java | 5 +-
.../datasource/property/PropertyConverter.java | 25 +-
.../property/constants/GlueProperties.java | 7 +-
.../property/constants/MCProperties.java | 10 +-
.../property/constants/PaimonProperties.java | 1 +
.../insertoverwrite/InsertOverwriteManager.java | 8 +
.../apache/doris/job/extensions/mtmv/MTMVTask.java | 43 +-
.../src/main/java/org/apache/doris/load/Load.java | 72 ++-
.../org/apache/doris/load/StreamLoadHandler.java | 9 +-
.../doris/load/routineload/RoutineLoadJob.java | 2 +-
.../java/org/apache/doris/master/MasterImpl.java | 3 +
.../java/org/apache/doris/mtmv/MTMVPlanUtil.java | 1 +
.../org/apache/doris/mtmv/MTMVRefreshSchedule.java | 3 +-
.../java/org/apache/doris/mysql/MysqlCommand.java | 4 +-
.../doris/nereids/jobs/executor/Rewriter.java | 10 +-
.../doris/nereids/parser/LogicalPlanBuilder.java | 4 +-
.../doris/nereids/properties/SelectHintSetVar.java | 30 +
.../rules/analysis/EliminateLogicalSelectHint.java | 29 +-
.../rules/implementation/AggregateStrategies.java | 101 ++-
.../doris/nereids/rules/rewrite/ColumnPruning.java | 22 +-
.../rules/rewrite/InferPredicateByReplace.java | 266 ++++++++
.../nereids/rules/rewrite/InferPredicates.java | 46 +-
.../rules/rewrite/PredicatePropagation.java | 251 --------
.../nereids/rules/rewrite/PullUpPredicates.java | 95 ++-
.../rules/rewrite/UnequalPredicateInfer.java | 576 +++++++++++++++++
.../doris/nereids/stats/StatsCalculator.java | 6 +-
.../trees/expressions/ExpressionEvaluator.java | 17 +-
.../doris/nereids/trees/expressions/Like.java | 14 +-
.../doris/nereids/trees/expressions/Not.java | 10 +
.../trees/expressions/StringRegexPredicate.java | 6 +-
.../trees/expressions/functions/BoundFunction.java | 6 +-
.../trees/expressions/functions/Function.java | 6 +-
.../expressions/functions/agg/RegrIntercept.java | 97 +++
.../trees/expressions/functions/agg/RegrSlope.java | 97 +++
.../functions/executable/ExecutableFunctions.java | 115 ----
.../functions/executable/NumericArithmetic.java | 566 ++++++++++++++++-
.../functions/executable/StringArithmetic.java | 2 +-
.../functions/scalar/ScalarFunction.java | 6 +-
.../trees/expressions/literal/DateLiteral.java | 12 +-
.../expressions/literal/DecimalV3Literal.java | 28 +
.../visitor/AggregateFunctionVisitor.java | 10 +
.../plans/commands/info/ColumnDefinition.java | 9 +
.../trees/plans/commands/info/CreateTableInfo.java | 30 +
.../insert/BatchInsertIntoTableCommand.java | 19 +-
.../trees/plans/logical/LogicalProject.java | 3 +-
.../doris/nereids/util/PredicateInferUtils.java | 179 ++++++
.../apache/doris/persist/TruncateTableInfo.java | 13 +-
.../org/apache/doris/persist/gson/GsonUtils.java | 4 +-
.../org/apache/doris/planner/FileLoadScanNode.java | 8 +-
.../org/apache/doris/planner/OlapTableSink.java | 36 +-
.../apache/doris/planner/StreamLoadPlanner.java | 45 +-
.../java/org/apache/doris/plugin/PluginMgr.java | 4 +
.../main/java/org/apache/doris/qe/Coordinator.java | 9 -
.../org/apache/doris/qe/MysqlConnectProcessor.java | 12 +
.../java/org/apache/doris/qe/SessionVariable.java | 38 +-
.../java/org/apache/doris/qe/SimpleScheduler.java | 200 +++++-
.../main/java/org/apache/doris/qe/VariableMgr.java | 2 +-
.../doris/statistics/util/StatisticsUtil.java | 1 -
.../main/java/org/apache/doris/system/Backend.java | 65 ++
.../org/apache/doris/system/SystemInfoService.java | 14 +-
.../GroupCommitTableValuedFunction.java | 9 +
.../doris/tablefunction/MetadataGenerator.java | 9 +-
.../java/org/apache/doris/task/LoadTaskInfo.java | 11 +-
.../java/org/apache/doris/task/StreamLoadTask.java | 32 +-
.../org/apache/doris/catalog/CreateTableTest.java | 6 +-
.../apache/doris/catalog/ModifyBackendTest.java | 9 +-
.../doris/common/util/SerializationUtilsTest.java | 110 ++++
.../datasource/iceberg/IcebergTransactionTest.java | 2 +-
.../datasource/jdbc/JdbcClientExceptionTest.java | 132 ++++
.../datasource/property/PropertyConverterTest.java | 41 ++
.../InsertOverwriteManagerTest.java | 34 +-
.../test/java/org/apache/doris/mtmv/MTMVTest.java | 2 +-
.../doris/nereids/jobs/joinorder/TPCHTest.java | 4 +-
.../doris/nereids/properties/UniformTest.java | 4 +
.../nereids/rules/expression/FoldConstantTest.java | 162 +++++
.../nereids/rules/rewrite/ColumnPruningTest.java | 15 +-
.../rules/rewrite/InferPredicateByReplaceTest.java | 203 ++++++
.../rules/rewrite/PredicatePropagationTest.java | 67 --
.../rules/rewrite/UnequalPredicateInferTest.java | 688 +++++++++++++++++++++
.../functions/ExecutableFunctionsTest.java | 64 --
.../trees/expressions/literal/DateLiteralTest.java | 6 +
.../trees/plans/CreateTableCommandTest.java | 5 +-
.../nereids/util/DateTimeFormatterUtilsTest.java | 26 +
.../java/org/apache/doris/policy/PolicyTest.java | 12 +-
.../org/apache/doris/qe/SessionVariablesTest.java | 8 +
.../org/apache/doris/qe/SimpleSchedulerTest.java | 32 +-
fe/pom.xml | 18 +-
gensrc/proto/descriptors.proto | 4 +-
gensrc/proto/olap_file.proto | 11 +-
gensrc/thrift/Descriptors.thrift | 4 +-
gensrc/thrift/FrontendService.thrift | 1 +
gensrc/thrift/PaloInternalService.thrift | 19 +-
gensrc/thrift/PlanNodes.thrift | 2 +
gensrc/thrift/Types.thrift | 6 +
.../data/arrow_flight_sql_p0/test_select.out | 4 +
.../unique/test_unique_table_like.out | 2 +
.../data/datatype_p0/double/test_double_nan.out | 4 -
.../data/datatype_p0/float/test_float_nan.out | 4 -
.../query/test_nested_type_with_resize.csv | 200 ++++++
.../query/test_nested_type_with_resize.out | 49 ++
.../export/test_export_external_table.out | 2 +-
.../jdbc/test_oracle_jdbc_catalog.out | 35 ++
.../external_table_p0/tvf/test_catalogs_tvf.out | 40 ++
.../tvf/test_hdfs_parquet_group0.out | Bin 22311 -> 22377 bytes
.../data/external_table_p0/tvf/test_hdfs_tvf.out | 7 +
.../iceberg/test_iceberg_dlf_catalog.out | 5 +
.../test_external_catalog_maxcompute.out | 30 +
.../maxcompute/test_max_compute_all_type.out | 26 +
.../maxcompute/test_max_compute_complex_type.out | 19 +-
.../paimon/test_paimon_dlf_catalog.out | 9 +
.../flexible/legacy/skip_delete.json | 3 +
.../legacy/test_f_delete_publish_skip_read.out | 19 +
.../data/fault_injection_p0/flexible/test1.json | 6 +
.../data/fault_injection_p0/flexible/test2.json | 7 +
.../data/fault_injection_p0/flexible/test3.json | 5 +
.../data/fault_injection_p0/flexible/test4.json | 5 +
...leixble_partial_update_publish_conflict_seq.out | 38 ++
...st_flexible_partial_update_publish_conflict.out | 19 +
.../test_partial_update_publish_conflict_seq.out | 22 +
.../test_array_contains_with_inverted_index.out | 615 ++++++++++++++++++
.../inverted_index_p0/test_count_on_index_2.out | 9 +
.../test_index_match_phrase_ordered.out | 6 +
.../mv_p0/test_duplicate_mv/test_duplicate_mv.out | 3 +-
.../data/mv_p0/test_mv_mow/test_mv_mow.out | 4 +-
.../data/nereids_hint_tpch_p0/shape/q12.out | 2 +-
.../insert_into_table/partial_update_seq_col.out | Bin 1416 -> 1123 bytes
.../eliminate_aggregate_constant.out | 96 +--
.../eliminate_join_condition.out | 8 +-
.../push_down_filter_other_condition.out | 2 +-
.../extend_infer_equal_predicate.out | 686 ++++++++++++++++++++
.../infer_predicate/infer_unequal_predicates.out | 165 +++++
.../limit_push_down/limit_push_down.out | 24 +-
.../limit_push_down/order_push_down.out | 8 +-
.../predicate_infer/infer_predicate.out | 11 +-
.../push_down_top_n_distinct_through_join.out | 2 +-
.../shape/query90.out | 36 +-
.../rf_prune/query90.out | 36 +-
.../nereids_tpcds_shape_sf100_p0/shape/query90.out | 36 +-
.../nereids_tpcds_shape_sf10t_orc/shape/query1.out | 37 ++
.../shape/query10.out | 47 ++
.../shape/query11.out | 57 ++
.../shape/query12.out | 26 +
.../shape/query13.out | 34 +
.../shape/query14.out | 162 +++++
.../shape/query15.out | 25 +
.../shape/query16.out | 34 +
.../shape/query17.out | 44 ++
.../shape/query18.out | 42 ++
.../shape/query19.out | 35 ++
.../nereids_tpcds_shape_sf10t_orc/shape/query2.out | 39 ++
.../shape/query20.out | 26 +
.../shape/query21.out | 26 +
.../shape/query22.out | 23 +
.../shape/query23.out | 82 +++
.../shape/query24.out | 52 ++
.../shape/query25.out | 43 ++
.../shape/query26.out | 31 +
.../shape/query27.out | 33 +
.../shape/query28.out | 57 ++
.../shape/query29.out | 43 ++
.../shape}/query3.out | 0
.../shape/query30.out | 41 ++
.../shape/query31.out | 65 ++
.../shape/query32.out | 25 +
.../shape/query33.out | 83 +++
.../shape/query34.out | 32 +
.../shape/query35.out | 47 ++
.../shape/query36.out | 33 +
.../shape/query37.out | 27 +
.../shape/query38.out | 52 ++
.../shape/query39.out | 33 +
.../nereids_tpcds_shape_sf10t_orc/shape/query4.out | 82 +++
.../shape/query40.out | 30 +
.../shape/query41.out | 23 +
.../shape/query42.out | 22 +
.../shape/query43.out | 22 +
.../shape/query44.out | 69 +++
.../shape/query45.out | 35 ++
.../shape/query46.out | 38 ++
.../shape/query47.out | 45 ++
.../shape/query48.out | 29 +
.../shape/query49.out | 107 ++++
.../nereids_tpcds_shape_sf10t_orc/shape/query5.out | 77 +++
.../shape/query50.out | 29 +
.../shape/query51.out | 43 ++
.../shape/query52.out | 23 +
.../shape/query53.out | 32 +
.../shape/query54.out | 74 +++
.../shape/query55.out | 23 +
.../shape/query56.out | 83 +++
.../shape}/query57.out | 0
.../shape/query58.out | 86 +++
.../shape/query59.out | 42 ++
.../nereids_tpcds_shape_sf10t_orc/shape/query6.out | 47 ++
.../shape/query60.out | 83 +++
.../shape/query61.out | 70 +++
.../shape/query62.out | 29 +
.../shape/query63.out | 32 +
.../shape/query64.out | 102 +++
.../shape/query65.out | 41 ++
.../shape/query66.out | 62 ++
.../shape/query67.out | 32 +
.../shape/query68.out | 38 ++
.../shape/query69.out | 47 ++
.../nereids_tpcds_shape_sf10t_orc/shape/query7.out | 31 +
.../shape/query70.out | 44 ++
.../shape/query71.out | 46 ++
.../shape/query72.out | 54 ++
.../shape/query73.out | 32 +
.../shape/query74.out | 57 ++
.../shape/query75.out | 78 +++
.../shape/query76.out | 47 ++
.../shape/query77.out | 101 +++
.../shape/query78.out | 57 ++
.../shape/query79.out | 32 +
.../shape}/query8.out | 0
.../shape/query80.out | 100 +++
.../shape/query81.out | 41 ++
.../shape/query82.out | 27 +
.../shape/query83.out | 80 +++
.../shape/query84.out | 31 +
.../shape/query85.out | 46 ++
.../shape/query86.out | 28 +
.../shape/query87.out | 51 ++
.../shape/query88.out | 171 +++++
.../shape/query89.out | 33 +
.../nereids_tpcds_shape_sf10t_orc/shape/query9.out | 114 ++++
.../shape/query90.out | 47 ++
.../shape/query91.out | 41 ++
.../shape/query92.out | 25 +
.../shape/query93.out | 21 +
.../shape/query94.out | 34 +
.../shape/query95.out | 43 ++
.../shape/query96.out | 25 +
.../shape/query97.out | 32 +
.../shape/query98.out | 26 +
.../shape/query99.out | 29 +
.../nostats_rf_prune/q12.out | 2 +-
.../nereids_tpch_shape_sf1000_p0/rf_prune/q12.out | 2 +-
.../runtime_filter/test_pushdown_setop.out | 2 +-
.../nereids_tpch_shape_sf1000_p0/shape/q12.out | 2 +-
.../shape_no_stats/q12.out | 2 +-
.../data/new_shapes_p0/hint_tpch/shape/q12.out | 2 +-
.../new_shapes_p0/tpcds_sf100/rf_prune/query90.out | 36 +-
.../new_shapes_p0/tpcds_sf100/shape/query90.out | 36 +-
.../new_shapes_p0/tpcds_sf1000/shape/query90.out | 36 +-
.../tpch_sf1000/nostats_rf_prune/q12.out | 2 +-
.../new_shapes_p0/tpch_sf1000/rf_prune/q12.out | 2 +-
.../runtime_filter/test_pushdown_setop.out | 2 +-
.../data/new_shapes_p0/tpch_sf1000/shape/q12.out | 2 +-
.../tpch_sf1000/shape_no_stats/q12.out | 2 +-
.../query_p0/aggregate/test_regr_intercept.out | 114 ++++
.../data/query_p0/aggregate/test_regr_slope.out | 114 ++++
.../data/query_p0/system/test_table_properties.out | 8 +-
.../unique_with_mow_c_p0/test_schema_change_ck.out | 44 +-
.../flexible/all_types/array_0.json | 100 +++
.../flexible/all_types/array_1.json | 100 +++
.../flexible/all_types/array_2.json | 100 +++
.../flexible/all_types/array_3.json | 100 +++
.../flexible/all_types/array_4.json | 100 +++
.../flexible/all_types/array_5.json | 100 +++
.../flexible/all_types/array_6.json | 100 +++
.../flexible/all_types/array_7.json | 100 +++
.../flexible/all_types/basic_0.json | 200 ++++++
.../flexible/all_types/basic_1.json | 200 ++++++
.../flexible/all_types/basic_2.json | 200 ++++++
.../flexible/all_types/basic_3.json | 200 ++++++
.../flexible/all_types/basic_4.json | 200 ++++++
.../flexible/all_types/basic_5.json | 200 ++++++
.../flexible/all_types/basic_6.json | 200 ++++++
.../flexible/all_types/basic_7.json | 200 ++++++
.../flexible/all_types/basic_8.json | 200 ++++++
.../flexible/all_types/basic_9.json | 200 ++++++
.../data/unique_with_mow_p0/flexible/autoinc1.json | 8 +
.../data/unique_with_mow_p0/flexible/autoinc2.json | 6 +
.../data/unique_with_mow_p0/flexible/delete1.json | 7 +
.../data/unique_with_mow_p0/flexible/delete2.json | 2 +
.../unique_with_mow_p0/flexible/key_missing.json | 5 +
.../unique_with_mow_p0/flexible/key_missing2.json | 6 +
.../unique_with_mow_p0/flexible/key_missing3.json | 6 +
.../flexible/legacy/complex_type/array.json | 3 +
.../flexible/legacy/complex_type/jsonb.json | 3 +
.../flexible/legacy/complex_type/map.json | 3 +
.../flexible/legacy/complex_type/struct.json | 3 +
.../flexible/legacy/complex_type/varchar.json | 2 +
.../flexible/legacy/concurrency_update1.json | 11 +
.../flexible/legacy/concurrency_update2.json | 11 +
.../flexible/legacy/concurrency_update3.json | 11 +
.../unique_with_mow_p0/flexible/legacy/data1.json | 2 +
.../flexible/legacy/schema_change/load10.json | 3 +
.../flexible/legacy/schema_change/load11.json | 3 +
.../flexible/legacy/schema_change/load12.json | 1 +
.../flexible/legacy/schema_change/load13.json | 1 +
.../flexible/legacy/schema_change/load3.json | 3 +
.../flexible/legacy/schema_change/load4.json | 3 +
.../flexible/legacy/schema_change/load5.json | 3 +
.../flexible/legacy/schema_change/load6.json | 3 +
.../flexible/legacy/schema_change/load7.json | 3 +
.../flexible/legacy/schema_change/load8.json | 3 +
.../flexible/legacy/schema_change/load9.json | 3 +
.../legacy/schema_change/load_with_new_column.json | 2 +
.../schema_change/load_without_new_column.json | 2 +
.../flexible/legacy/test_f_2pc_schema_change.out | 95 +++
.../legacy/test_f_complex_type_schema_change.out | 97 +++
.../legacy/test_f_conflict_be_restart.out} | 0
.../flexible/legacy/test_f_schema_change.out | 493 +++++++++++++++
.../data/unique_with_mow_p0/flexible/quality1.json | 3 +
.../data/unique_with_mow_p0/flexible/quality2.json | 3 +
.../data/unique_with_mow_p0/flexible/seq1.json | 14 +
.../data/unique_with_mow_p0/flexible/test1.csv | 2 +
.../data/unique_with_mow_p0/flexible/test1.json | 8 +
.../data/unique_with_mow_p0/flexible/test10.json | 33 +
.../data/unique_with_mow_p0/flexible/test11.json | 4 +
.../data/unique_with_mow_p0/flexible/test12.json | 4 +
.../data/unique_with_mow_p0/flexible/test13.json | 4 +
.../data/unique_with_mow_p0/flexible/test14.json | 4 +
.../data/unique_with_mow_p0/flexible/test2.json | 7 +
.../data/unique_with_mow_p0/flexible/test3.json | 7 +
.../data/unique_with_mow_p0/flexible/test4.json | 17 +
.../data/unique_with_mow_p0/flexible/test5.json | 21 +
.../data/unique_with_mow_p0/flexible/test6.json | 17 +
.../data/unique_with_mow_p0/flexible/test7.json | 21 +
.../data/unique_with_mow_p0/flexible/test8.json | 28 +
.../data/unique_with_mow_p0/flexible/test9.json | 3 +
.../flexible/test_f_no_simdjson.out | 77 +++
.../test_f_seq_col_no_vertical_segment_writer.out | 271 ++++++++
.../flexible/test_flexible_partial_update.out | 153 +++++
.../test_flexible_partial_update_all_types.out | 209 +++++++
.../test_flexible_partial_update_auto_inc.out | 101 +++
.../test_flexible_partial_update_delete_sign.out | 169 +++++
.../test_flexible_partial_update_filter_ratio.out | 71 +++
.../test_flexible_partial_update_property.out | 26 +
.../test_flexible_partial_update_restricts.out | 9 +
.../test_flexible_partial_update_seq_col.out | 383 ++++++++++++
.../test_partial_update_with_row_column.out | 2 +-
regression-test/framework/pom.xml | 2 +-
.../org/apache/doris/regression/Config.groovy | 7 +
.../org/apache/doris/regression/suite/Suite.groovy | 30 +-
.../doris/regression/suite/SuiteCluster.groovy | 7 +
.../suites/arrow_flight_sql_p0/test_select.groovy | 12 +
.../test_compaction_uniq_keys_row_store.groovy | 1 +
.../test_compaction_uniq_keys_row_store_ck.groovy | 1 +
.../data_model_p0/unique/test_unique_table.groovy | 2 +-
.../unique/test_unique_table_like.groovy | 5 +-
.../unique/test_unique_table_new_sequence.groovy | 3 +-
.../unique/test_unique_table_sequence.groovy | 6 +-
.../datatype_p0/double/test_double_nan.groovy | 13 +-
.../suites/datatype_p0/float/test_float_nan.groovy | 12 +-
.../query/test_nested_type_with_resize.groovy | 73 +++
.../test_delete_predicate_on_value.groovy | 4 +-
.../export/test_export_external_table.groovy | 2 +-
.../hive/test_partial_update_broker_load.groovy | 2 +-
.../jdbc/test_oracle_jdbc_catalog.groovy | 26 +
.../kerberos/test_single_hive_kerberos.groovy | 8 +
.../kerberos/test_two_hive_kerberos.groovy | 8 +
.../tvf/orc_format/test_orc_exception_files.groovy | 150 +++++
.../external_table_p0/tvf/test_catalogs_tvf.groovy | 68 +-
.../tvf/test_hdfs_parquet_group0.groovy | 5 +-
.../tvf/test_hdfs_parquet_group5.groovy | 11 -
.../external_table_p0/tvf/test_hdfs_tvf.groovy | 8 +
.../iceberg/test_iceberg_dlf_catalog.groovy | 51 ++
.../test_external_catalog_maxcompute.groovy | 47 +-
.../maxcompute/test_max_compute_all_type.groovy | 13 +-
.../test_max_compute_complex_type.groovy | 33 +-
.../paimon/test_paimon_dlf_catalog.groovy | 59 ++
.../legacy/test_f_delete_publish_skip_read.groovy | 121 ++++
...xble_partial_update_publish_conflict_seq.groovy | 143 +++++
...flexible_partial_update_publish_conflict.groovy | 117 ++++
.../test_delete_publish_skip_read.groovy | 2 +-
...test_partial_update_publish_conflict_seq.groovy | 129 ++++
regression-test/suites/insert_p0/test_jdbc.groovy | 102 +++
...ommit_http_stream_lineitem_schema_change.groovy | 2 -
...ommit_insert_into_lineitem_scheme_change.groovy | 2 -
...ommit_stream_load_lineitem_schema_change.groovy | 2 -
.../insert_p2/txn_insert_with_schema_change.groovy | 1 -
.../test_array_contains_with_inverted_index.groovy | 29 +-
.../inverted_index_p0/test_count_on_index_2.groovy | 29 +
.../test_index_match_phrase_ordered.groovy | 64 +-
.../inverted_index_p0/test_need_read_data.groovy | 4 +-
.../suites/javaudf_p0/function_meta/load.groovy | 5 +-
.../suites/load_p0/copy_into/test_copy_into.groovy | 146 +++++
.../load_p2/broker_load/test_broker_load.groovy | 5 +-
.../test_parquet_large_metadata_load.groovy | 5 +-
.../suites/mtmv_p0/test_show_create_mtmv.groovy | 23 +
.../test_duplicate_mv/test_duplicate_mv.groovy | 2 +-
.../suites/mv_p0/test_mv_mow/test_mv_mow.groovy | 2 +-
.../nereids_function_p0/scalar_function/A.groovy | 5 +-
.../fold_constant_numeric_arithmatic.groovy | 412 ++++++++++++
.../fold_constant_string_arithmatic.groovy | 1 +
.../infer_predicate/infer_predicate.groovy | 14 +-
.../partial_update_seq_col.groovy | 6 +-
.../union_const_expr_column_pruning.groovy | 23 +
.../extend_infer_equal_predicate.groovy | 357 +++++++++++
.../infer_unequal_predicates.groovy | 189 ++++++
.../union_all_compensate.groovy | 2 +-
.../test_multi_range_partition.groovy | 6 +-
.../mv/aggregate/agg_sync_mv.groovy | 4 +-
.../suites/nereids_syntax_p1/mv/load.groovy | 2 +-
.../nereids_tpcds_shape_sf10t_orc/ddl/gen_shape.py | 26 +
.../nereids_tpcds_shape_sf10t_orc/ddl/shape.tmpl | 49 ++
.../nereids_tpcds_shape_sf10t_orc/load.groovy | 662 ++++++++++++++++++++
.../shape/query1.groovy | 70 +++
.../shape/query10.groovy | 104 ++++
.../shape/query11.groovy | 126 ++++
.../shape/query12.groovy | 79 +++
.../shape/query13.groovy | 97 +++
.../shape/query14.groovy | 149 +++++
.../shape/query15.groovy | 65 ++
.../shape/query16.groovy | 76 +++
.../shape/query17.groovy | 90 +++
.../shape/query18.groovy | 79 +++
.../shape/query19.groovy | 70 +++
.../shape/query2.groovy | 105 ++++
.../shape/query20.groovy | 75 +++
.../shape/query21.groovy | 75 +++
.../shape/query22.groovy | 65 ++
.../shape/query23.groovy | 97 +++
.../shape/query24.groovy | 100 +++
.../shape/query25.groovy | 93 +++
.../shape/query26.groovy | 66 ++
.../shape/query27.groovy | 68 ++
.../shape/query28.groovy | 98 +++
.../shape/query29.groovy | 92 +++
.../shape/query3.groovy | 66 ++
.../shape/query30.groovy | 76 +++
.../shape/query31.groovy | 97 +++
.../shape/query32.groovy | 73 +++
.../shape/query33.groovy | 120 ++++
.../shape/query34.groovy | 76 +++
.../shape/query35.groovy | 103 +++
.../shape/query36.groovy | 75 +++
.../shape/query37.groovy | 62 ++
.../shape/query38.groovy | 68 ++
.../shape/query39.groovy | 73 +++
.../shape/query4.groovy | 161 +++++
.../shape/query40.groovy | 73 +++
.../shape/query41.groovy | 97 +++
.../shape/query42.groovy | 67 ++
.../shape/query43.groovy | 64 ++
.../shape/query44.groovy | 80 +++
.../shape/query45.groovy | 65 ++
.../shape/query46.groovy | 80 +++
.../shape/query47.groovy | 96 +++
.../shape/query48.groovy | 112 ++++
.../shape/query49.groovy | 174 ++++++
.../shape/query5.groovy | 173 ++++++
.../shape/query50.groovy | 104 ++++
.../shape/query51.groovy | 90 +++
.../shape/query52.groovy | 67 ++
.../shape/query53.groovy | 73 +++
.../shape/query54.groovy | 101 +++
.../shape/query55.groovy | 59 ++
.../shape/query56.groovy | 114 ++++
.../shape/query57.groovy | 93 +++
.../shape/query58.groovy | 110 ++++
.../shape/query59.groovy | 89 +++
.../shape/query6.groovy | 71 +++
.../shape/query60.groovy | 123 ++++
.../shape/query61.groovy | 89 +++
.../shape/query62.groovy | 80 +++
.../shape/query63.groovy | 74 +++
.../shape/query64.groovy | 166 +++++
.../shape/query65.groovy | 74 +++
.../shape/query66.groovy | 265 ++++++++
.../shape/query67.groovy | 89 +++
.../shape/query68.groovy | 87 +++
.../shape/query69.groovy | 92 +++
.../shape/query7.groovy | 66 ++
.../shape/query70.groovy | 83 +++
.../shape/query71.groovy | 85 +++
.../shape/query72.groovy | 74 +++
.../shape/query73.groovy | 73 +++
.../shape/query74.groovy | 106 ++++
.../shape/query75.groovy | 115 ++++
.../shape/query76.groovy | 69 +++
.../shape/query77.groovy | 153 +++++
.../shape/query78.groovy | 103 +++
.../shape/query79.groovy | 68 ++
.../shape/query8.groovy | 153 +++++
.../shape/query80.groovy | 141 +++++
.../shape/query81.groovy | 76 +++
.../shape/query82.groovy | 62 ++
.../shape/query83.groovy | 112 ++++
.../shape/query84.groovy | 66 ++
.../shape/query85.groovy | 129 ++++
.../shape/query86.groovy | 71 +++
.../shape/query87.groovy | 68 ++
.../shape/query88.groovy | 139 +++++
.../shape/query89.groovy | 73 +++
.../shape/query9.groovy | 96 +++
.../shape/query90.groovy | 67 ++
.../shape/query91.groovy | 76 +++
.../shape/query92.groovy | 75 +++
.../shape/query93.groovy | 63 ++
.../shape/query94.groovy | 74 +++
.../shape/query95.groovy | 77 +++
.../shape/query96.groovy | 61 ++
.../shape/query97.groovy | 70 +++
.../shape/query98.groovy | 78 +++
.../shape/query99.groovy | 80 +++
.../query_p0/aggregate/test_regr_intercept.groovy | 217 +++++++
.../query_p0/aggregate/test_regr_slope.groovy | 217 +++++++
.../schema_change_p0/test_abort_txn_by_be.groovy | 171 +++++
.../test_abort_txn_by_be_cloud1.groovy | 164 -----
.../test_abort_txn_by_be_cloud2.groovy | 164 -----
.../test_abort_txn_by_be_local5.groovy | 164 -----
.../test_abort_txn_by_be_local6.groovy | 164 -----
.../schema_change_p0/test_abort_txn_by_fe.groovy | 126 ++++
.../test_abort_txn_by_fe_cloud4.groovy | 104 ----
.../test_abort_txn_by_fe_local3.groovy | 104 ----
.../test_dup_schema_key_change_modify.groovy | 4 +-
.../test_uniq_delete_sign_schema_change.groovy | 14 +-
.../suites/tpch_sf100_p2/ddl/customer.sql | 1 -
.../suites/tpch_sf100_p2/ddl/lineitem.sql | 1 -
.../suites/tpch_sf100_p2/ddl/nation.sql | 1 -
.../suites/tpch_sf100_p2/ddl/orders.sql | 1 -
regression-test/suites/tpch_sf100_p2/ddl/part.sql | 1 -
.../suites/tpch_sf100_p2/ddl/partsupp.sql | 1 -
.../suites/tpch_sf100_p2/ddl/region.sql | 1 -
.../suites/tpch_sf100_p2/ddl/supplier.sql | 1 -
.../suites/tpch_sf1_p2/ddl/create_table.sql | 1 -
.../suites/tpch_sf1_p2/ddl/customer.sql | 1 -
.../suites/tpch_sf1_p2/ddl/lineitem.sql | 1 -
regression-test/suites/tpch_sf1_p2/ddl/nation.sql | 1 -
regression-test/suites/tpch_sf1_p2/ddl/orders.sql | 1 -
regression-test/suites/tpch_sf1_p2/ddl/part.sql | 1 -
.../suites/tpch_sf1_p2/ddl/partsupp.sql | 1 -
regression-test/suites/tpch_sf1_p2/ddl/region.sql | 1 -
.../suites/tpch_sf1_p2/ddl/supplier.sql | 1 -
.../test_schema_change_ck.groovy | 4 +-
.../legacy/test_f_2pc_schema_change.groovy | 179 ++++++
.../test_f_complex_type_schema_change.groovy | 168 +++++
.../legacy/test_f_conflict_be_restart.groovy | 156 +++++
.../flexible/legacy/test_f_schema_change.groovy | 245 ++++++++
.../flexible/test_f_no_simdjson.groovy | 166 +++++
...est_f_seq_col_no_vertical_segment_writer.groovy | 289 +++++++++
.../flexible/test_flexible_partial_update.groovy | 159 +++++
.../test_flexible_partial_update_all_types.groovy | 196 ++++++
.../test_flexible_partial_update_auto_inc.groovy | 96 +++
...test_flexible_partial_update_delete_sign.groovy | 118 ++++
...est_flexible_partial_update_filter_ratio.groovy | 199 ++++++
.../test_flexible_partial_update_property.groovy | 93 +++
.../test_flexible_partial_update_restricts.groovy | 407 ++++++++++++
.../test_flexible_partial_update_seq_col.groovy | 351 +++++++++++
.../test_partial_update_parallel.groovy | 7 +-
.../test_partial_update_with_row_column.groovy | 3 +-
.../test_mow_enable_sequence_col.groovy | 4 +-
845 files changed, 38757 insertions(+), 5037 deletions(-)
delete mode 100644 be/src/exec/schema_scanner/schema_statistics_scanner.cpp
delete mode 100644 be/src/exec/schema_scanner/schema_statistics_scanner.h
delete mode 100644 be/src/runtime/memory/mem_tracker.cpp
delete mode 100644 be/src/vec/aggregate_functions/aggregate_function_percentile_approx.cpp
delete mode 100644 be/src/vec/aggregate_functions/aggregate_function_percentile_approx.h
create mode 100644 be/src/vec/aggregate_functions/aggregate_function_regr_union.cpp
create mode 100644 be/src/vec/aggregate_functions/aggregate_function_regr_union.h
create mode 100644 be/test/olap/rowset/segment_v2/inverted_index/query/phrase_query_test.cpp
create mode 100644 docker/thirdparties/docker-compose/hive/scripts/preinstalled_data/hdfs_tvf/test_parquet_decimal256.parquet
create mode 100644 fe/be-java-extensions/java-common/src/main/java/org/apache/doris/common/jni/utils/Log4jOutputStream.java
create mode 100644 fe/fe-core/src/main/java/com/amazonaws/glue/catalog/credentials/ConfigurationAWSCredentialsProvider2x.java
create mode 100644 fe/fe-core/src/main/java/org/apache/doris/common/util/SerializationUtils.java
create mode 100644 fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonDLFExternalCatalog.java
create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/InferPredicateByReplace.java
delete mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PredicatePropagation.java
create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/UnequalPredicateInfer.java
create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/agg/RegrIntercept.java
create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/agg/RegrSlope.java
delete mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/ExecutableFunctions.java
create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/util/PredicateInferUtils.java
create mode 100644 fe/fe-core/src/test/java/org/apache/doris/common/util/SerializationUtilsTest.java
create mode 100644 fe/fe-core/src/test/java/org/apache/doris/datasource/jdbc/JdbcClientExceptionTest.java
create mode 100644 fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/InferPredicateByReplaceTest.java
delete mode 100644 fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PredicatePropagationTest.java
create mode 100644 fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/UnequalPredicateInferTest.java
delete mode 100644 fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/ExecutableFunctionsTest.java
create mode 100644 regression-test/data/datatype_p0/nested_types/query/test_nested_type_with_resize.csv
create mode 100644 regression-test/data/datatype_p0/nested_types/query/test_nested_type_with_resize.out
create mode 100644 regression-test/data/external_table_p2/iceberg/test_iceberg_dlf_catalog.out
create mode 100644 regression-test/data/external_table_p2/paimon/test_paimon_dlf_catalog.out
create mode 100644 regression-test/data/fault_injection_p0/flexible/legacy/skip_delete.json
create mode 100644 regression-test/data/fault_injection_p0/flexible/legacy/test_f_delete_publish_skip_read.out
create mode 100644 regression-test/data/fault_injection_p0/flexible/test1.json
create mode 100644 regression-test/data/fault_injection_p0/flexible/test2.json
create mode 100644 regression-test/data/fault_injection_p0/flexible/test3.json
create mode 100644 regression-test/data/fault_injection_p0/flexible/test4.json
create mode 100644 regression-test/data/fault_injection_p0/flexible/test_fleixble_partial_update_publish_conflict_seq.out
create mode 100644 regression-test/data/fault_injection_p0/flexible/test_flexible_partial_update_publish_conflict.out
create mode 100644 regression-test/data/fault_injection_p0/partial_update/test_partial_update_publish_conflict_seq.out
create mode 100644 regression-test/data/nereids_rules_p0/infer_predicate/extend_infer_equal_predicate.out
create mode 100644 regression-test/data/nereids_rules_p0/infer_predicate/infer_unequal_predicates.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query1.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query10.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query11.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query12.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query13.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query14.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query15.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query16.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query17.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query18.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query19.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query2.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query20.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query21.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query22.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query23.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query24.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query25.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query26.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query27.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query28.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query29.out
copy regression-test/data/{nereids_tpcds_shape_sf100_p0/noStatsRfPrune => nereids_tpcds_shape_sf10t_orc/shape}/query3.out (100%)
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query30.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query31.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query32.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query33.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query34.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query35.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query36.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query37.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query38.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query39.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query4.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query40.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query41.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query42.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query43.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query44.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query45.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query46.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query47.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query48.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query49.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query5.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query50.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query51.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query52.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query53.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query54.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query55.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query56.out
copy regression-test/data/{nereids_tpcds_shape_sf100_p0/no_stats_shape => nereids_tpcds_shape_sf10t_orc/shape}/query57.out (100%)
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query58.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query59.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query6.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query60.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query61.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query62.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query63.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query64.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query65.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query66.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query67.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query68.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query69.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query7.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query70.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query71.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query72.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query73.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query74.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query75.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query76.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query77.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query78.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query79.out
copy regression-test/data/{nereids_tpcds_shape_sf1000_p0/bs_downgrade_shape => nereids_tpcds_shape_sf10t_orc/shape}/query8.out (100%)
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query80.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query81.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query82.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query83.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query84.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query85.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query86.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query87.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query88.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query89.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query9.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query90.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query91.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query92.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query93.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query94.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query95.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query96.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query97.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query98.out
create mode 100644 regression-test/data/nereids_tpcds_shape_sf10t_orc/shape/query99.out
create mode 100644 regression-test/data/query_p0/aggregate/test_regr_intercept.out
create mode 100644 regression-test/data/query_p0/aggregate/test_regr_slope.out
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/all_types/array_0.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/all_types/array_1.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/all_types/array_2.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/all_types/array_3.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/all_types/array_4.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/all_types/array_5.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/all_types/array_6.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/all_types/array_7.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/all_types/basic_0.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/all_types/basic_1.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/all_types/basic_2.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/all_types/basic_3.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/all_types/basic_4.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/all_types/basic_5.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/all_types/basic_6.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/all_types/basic_7.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/all_types/basic_8.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/all_types/basic_9.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/autoinc1.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/autoinc2.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/delete1.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/delete2.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/key_missing.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/key_missing2.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/key_missing3.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/complex_type/array.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/complex_type/jsonb.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/complex_type/map.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/complex_type/struct.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/complex_type/varchar.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/concurrency_update1.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/concurrency_update2.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/concurrency_update3.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/data1.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/schema_change/load10.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/schema_change/load11.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/schema_change/load12.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/schema_change/load13.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/schema_change/load3.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/schema_change/load4.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/schema_change/load5.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/schema_change/load6.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/schema_change/load7.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/schema_change/load8.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/schema_change/load9.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/schema_change/load_with_new_column.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/schema_change/load_without_new_column.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/test_f_2pc_schema_change.out
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/test_f_complex_type_schema_change.out
copy regression-test/data/{unique_with_mow_c_p0/partial_update/test_partial_update_conflict_be_restart.out => unique_with_mow_p0/flexible/legacy/test_f_conflict_be_restart.out} (100%)
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/legacy/test_f_schema_change.out
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/quality1.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/quality2.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/seq1.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test1.csv
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test1.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test10.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test11.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test12.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test13.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test14.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test2.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test3.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test4.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test5.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test6.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test7.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test8.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test9.json
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test_f_no_simdjson.out
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test_f_seq_col_no_vertical_segment_writer.out
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test_flexible_partial_update.out
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test_flexible_partial_update_all_types.out
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test_flexible_partial_update_auto_inc.out
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test_flexible_partial_update_delete_sign.out
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test_flexible_partial_update_filter_ratio.out
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test_flexible_partial_update_property.out
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test_flexible_partial_update_restricts.out
create mode 100644 regression-test/data/unique_with_mow_p0/flexible/test_flexible_partial_update_seq_col.out
create mode 100644 regression-test/suites/datatype_p0/nested_types/query/test_nested_type_with_resize.groovy
create mode 100644 regression-test/suites/external_table_p0/tvf/orc_format/test_orc_exception_files.groovy
create mode 100644 regression-test/suites/external_table_p2/iceberg/test_iceberg_dlf_catalog.groovy
create mode 100644 regression-test/suites/external_table_p2/paimon/test_paimon_dlf_catalog.groovy
create mode 100644 regression-test/suites/fault_injection_p0/flexible/legacy/test_f_delete_publish_skip_read.groovy
create mode 100644 regression-test/suites/fault_injection_p0/flexible/test_fleixble_partial_update_publish_conflict_seq.groovy
create mode 100644 regression-test/suites/fault_injection_p0/flexible/test_flexible_partial_update_publish_conflict.groovy
create mode 100644 regression-test/suites/fault_injection_p0/partial_update/test_partial_update_publish_conflict_seq.groovy
create mode 100644 regression-test/suites/insert_p0/test_jdbc.groovy
create mode 100644 regression-test/suites/load_p0/copy_into/test_copy_into.groovy
create mode 100644 regression-test/suites/nereids_p0/expression/fold_constant/fold_constant_numeric_arithmatic.groovy
create mode 100644 regression-test/suites/nereids_rules_p0/column_pruning/union_const_expr_column_pruning.groovy
create mode 100644 regression-test/suites/nereids_rules_p0/infer_predicate/extend_infer_equal_predicate.groovy
create mode 100644 regression-test/suites/nereids_rules_p0/infer_predicate/infer_unequal_predicates.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/ddl/gen_shape.py
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/ddl/shape.tmpl
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/load.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query1.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query10.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query11.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query12.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query13.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query14.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query15.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query16.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query17.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query18.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query19.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query2.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query20.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query21.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query22.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query23.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query24.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query25.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query26.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query27.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query28.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query29.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query3.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query30.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query31.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query32.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query33.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query34.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query35.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query36.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query37.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query38.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query39.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query4.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query40.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query41.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query42.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query43.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query44.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query45.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query46.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query47.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query48.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query49.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query5.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query50.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query51.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query52.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query53.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query54.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query55.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query56.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query57.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query58.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query59.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query6.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query60.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query61.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query62.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query63.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query64.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query65.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query66.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query67.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query68.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query69.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query7.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query70.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query71.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query72.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query73.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query74.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query75.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query76.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query77.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query78.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query79.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query8.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query80.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query81.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query82.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query83.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query84.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query85.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query86.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query87.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query88.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query89.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query9.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query90.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query91.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query92.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query93.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query94.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query95.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query96.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query97.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query98.groovy
create mode 100644 regression-test/suites/nereids_tpcds_shape_sf10t_orc/shape/query99.groovy
create mode 100644 regression-test/suites/query_p0/aggregate/test_regr_intercept.groovy
create mode 100644 regression-test/suites/query_p0/aggregate/test_regr_slope.groovy
create mode 100644 regression-test/suites/schema_change_p0/test_abort_txn_by_be.groovy
delete mode 100644 regression-test/suites/schema_change_p0/test_abort_txn_by_be_cloud1.groovy
delete mode 100644 regression-test/suites/schema_change_p0/test_abort_txn_by_be_cloud2.groovy
delete mode 100644 regression-test/suites/schema_change_p0/test_abort_txn_by_be_local5.groovy
delete mode 100644 regression-test/suites/schema_change_p0/test_abort_txn_by_be_local6.groovy
create mode 100644 regression-test/suites/schema_change_p0/test_abort_txn_by_fe.groovy
delete mode 100644 regression-test/suites/schema_change_p0/test_abort_txn_by_fe_cloud4.groovy
delete mode 100644 regression-test/suites/schema_change_p0/test_abort_txn_by_fe_local3.groovy
create mode 100644 regression-test/suites/unique_with_mow_p0/flexible/legacy/test_f_2pc_schema_change.groovy
create mode 100644 regression-test/suites/unique_with_mow_p0/flexible/legacy/test_f_complex_type_schema_change.groovy
create mode 100644 regression-test/suites/unique_with_mow_p0/flexible/legacy/test_f_conflict_be_restart.groovy
create mode 100644 regression-test/suites/unique_with_mow_p0/flexible/legacy/test_f_schema_change.groovy
create mode 100644 regression-test/suites/unique_with_mow_p0/flexible/test_f_no_simdjson.groovy
create mode 100644 regression-test/suites/unique_with_mow_p0/flexible/test_f_seq_col_no_vertical_segment_writer.groovy
create mode 100644 regression-test/suites/unique_with_mow_p0/flexible/test_flexible_partial_update.groovy
create mode 100644 regression-test/suites/unique_with_mow_p0/flexible/test_flexible_partial_update_all_types.groovy
create mode 100644 regression-test/suites/unique_with_mow_p0/flexible/test_flexible_partial_update_auto_inc.groovy
create mode 100644 regression-test/suites/unique_with_mow_p0/flexible/test_flexible_partial_update_delete_sign.groovy
create mode 100644 regression-test/suites/unique_with_mow_p0/flexible/test_flexible_partial_update_filter_ratio.groovy
create mode 100644 regression-test/suites/unique_with_mow_p0/flexible/test_flexible_partial_update_property.groovy
create mode 100644 regression-test/suites/unique_with_mow_p0/flexible/test_flexible_partial_update_restricts.groovy
create mode 100644 regression-test/suites/unique_with_mow_p0/flexible/test_flexible_partial_update_seq_col.groovy
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org
(doris) 01/01: [feat]reserve and spill
Posted by ja...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
jacktengg pushed a commit to branch spill_and_reserve
in repository https://gitbox.apache.org/repos/asf/doris.git
commit 4eb45a884ac6fc14a60dadf1afc707b9f706f937
Author: Jerry Hu <mr...@gmail.com>
AuthorDate: Sat Aug 24 21:49:42 2024 +0800
[feat]reserve and spill
---
be/src/agent/workload_group_listener.cpp | 4 +
be/src/clucene | 2 +-
be/src/common/config.cpp | 4 +-
be/src/common/daemon.cpp | 14 +-
be/src/common/status.h | 10 +
be/src/olap/memtable_memory_limiter.cpp | 32 +-
be/src/olap/memtable_memory_limiter.h | 6 +-
be/src/olap/memtable_writer.h | 8 +
be/src/pipeline/common/agg_utils.h | 32 +-
be/src/pipeline/dependency.h | 24 +-
be/src/pipeline/exec/aggregation_sink_operator.cpp | 54 +-
be/src/pipeline/exec/aggregation_sink_operator.h | 9 +
.../pipeline/exec/aggregation_source_operator.cpp | 167 ++++---
be/src/pipeline/exec/aggregation_source_operator.h | 8 +-
be/src/pipeline/exec/analytic_sink_operator.cpp | 10 +
be/src/pipeline/exec/analytic_sink_operator.h | 4 +
be/src/pipeline/exec/analytic_source_operator.cpp | 4 +-
be/src/pipeline/exec/assert_num_rows_operator.cpp | 4 +-
be/src/pipeline/exec/data_queue.h | 2 +
be/src/pipeline/exec/datagen_operator.cpp | 4 +-
.../distinct_streaming_aggregation_operator.cpp | 7 +-
be/src/pipeline/exec/exchange_sink_buffer.cpp | 9 +-
be/src/pipeline/exec/exchange_sink_buffer.h | 26 +-
be/src/pipeline/exec/exchange_sink_operator.cpp | 12 +-
be/src/pipeline/exec/exchange_source_operator.cpp | 3 +-
be/src/pipeline/exec/hashjoin_build_sink.cpp | 52 +-
be/src/pipeline/exec/hashjoin_build_sink.h | 8 +
be/src/pipeline/exec/hashjoin_probe_operator.cpp | 14 +-
be/src/pipeline/exec/hashjoin_probe_operator.h | 2 +
.../pipeline/exec/join/process_hash_table_probe.h | 3 +-
.../exec/join/process_hash_table_probe_impl.h | 3 +-
.../exec/multi_cast_data_stream_source.cpp | 4 +-
.../exec/nested_loop_join_probe_operator.cpp | 8 +-
be/src/pipeline/exec/operator.cpp | 21 +-
be/src/pipeline/exec/operator.h | 154 ++++--
.../pipeline/exec/partition_sort_sink_operator.cpp | 13 +
.../pipeline/exec/partition_sort_sink_operator.h | 2 +
.../exec/partition_sort_source_operator.cpp | 14 +-
.../exec/partitioned_aggregation_sink_operator.cpp | 92 +++-
.../exec/partitioned_aggregation_sink_operator.h | 41 +-
.../partitioned_aggregation_source_operator.cpp | 118 +++--
.../exec/partitioned_aggregation_source_operator.h | 11 +-
.../exec/partitioned_hash_join_probe_operator.cpp | 324 ++++++++----
.../exec/partitioned_hash_join_probe_operator.h | 26 +-
.../exec/partitioned_hash_join_sink_operator.cpp | 232 +++++++--
.../exec/partitioned_hash_join_sink_operator.h | 18 +-
be/src/pipeline/exec/repeat_operator.cpp | 9 +-
be/src/pipeline/exec/scan_operator.cpp | 25 +
be/src/pipeline/exec/scan_operator.h | 2 +
be/src/pipeline/exec/schema_scan_operator.cpp | 4 +-
be/src/pipeline/exec/select_operator.h | 3 +-
be/src/pipeline/exec/set_probe_sink_operator.cpp | 7 +
be/src/pipeline/exec/set_probe_sink_operator.h | 4 +
be/src/pipeline/exec/set_sink_operator.cpp | 21 +
be/src/pipeline/exec/set_sink_operator.h | 2 +
be/src/pipeline/exec/set_source_operator.cpp | 5 +-
be/src/pipeline/exec/sort_source_operator.cpp | 2 +
be/src/pipeline/exec/spill_sort_sink_operator.cpp | 38 +-
be/src/pipeline/exec/spill_sort_sink_operator.h | 7 +-
.../pipeline/exec/spill_sort_source_operator.cpp | 35 +-
be/src/pipeline/exec/spill_sort_source_operator.h | 3 +-
be/src/pipeline/exec/spill_utils.h | 48 ++
.../exec/streaming_aggregation_operator.cpp | 16 +-
.../pipeline/exec/streaming_aggregation_operator.h | 1 +
be/src/pipeline/exec/table_function_operator.cpp | 3 +-
be/src/pipeline/exec/union_sink_operator.cpp | 3 +
be/src/pipeline/exec/union_source_operator.cpp | 8 +-
.../local_exchange_sink_operator.cpp | 8 +-
be/src/pipeline/local_exchange/local_exchanger.cpp | 37 +-
be/src/pipeline/pipeline_fragment_context.cpp | 56 +++
be/src/pipeline/pipeline_fragment_context.h | 6 +
be/src/pipeline/pipeline_task.cpp | 182 ++++---
be/src/pipeline/pipeline_task.h | 46 +-
be/src/pipeline/task_scheduler.cpp | 19 +-
be/src/pipeline/task_scheduler.h | 11 +-
be/src/runtime/fragment_mgr.cpp | 3 +-
be/src/runtime/memory/global_memory_arbitrator.cpp | 6 +
be/src/runtime/memory/global_memory_arbitrator.h | 6 +
be/src/runtime/memory/mem_tracker_limiter.cpp | 4 +-
be/src/runtime/memory/mem_tracker_limiter.h | 41 +-
be/src/runtime/memory/memory_reclamation.cpp | 2 +-
be/src/runtime/memory/thread_mem_tracker_mgr.h | 42 +-
be/src/runtime/query_context.cpp | 176 ++++++-
be/src/runtime/query_context.h | 146 +++++-
be/src/runtime/runtime_state.h | 12 +-
be/src/runtime/thread_context.cpp | 16 +-
be/src/runtime/workload_group/workload_group.cpp | 82 +++-
be/src/runtime/workload_group/workload_group.h | 66 ++-
.../workload_group/workload_group_manager.cpp | 541 +++++++++++++++++++--
.../workload_group/workload_group_manager.h | 55 +++
be/src/util/mem_info.cpp | 6 +-
be/src/vec/columns/column.h | 29 +-
be/src/vec/columns/column_const.h | 5 +-
be/src/vec/columns/column_decimal.cpp | 9 +-
be/src/vec/columns/column_decimal.h | 9 +-
be/src/vec/columns/column_nullable.cpp | 4 +-
be/src/vec/columns/column_nullable.h | 6 +-
be/src/vec/columns/column_object.h | 9 +-
be/src/vec/columns/column_string.cpp | 11 +-
be/src/vec/columns/column_string.h | 9 +-
be/src/vec/columns/column_vector.cpp | 9 +-
be/src/vec/columns/column_vector.h | 9 +-
be/src/vec/common/custom_allocator.h | 2 +-
be/src/vec/common/hash_table/hash_map_context.h | 32 +-
be/src/vec/common/hash_table/hash_table.h | 12 +
be/src/vec/common/hash_table/join_hash_table.h | 11 +-
be/src/vec/common/hash_table/ph_hash_map.h | 9 +
be/src/vec/common/hash_table/string_hash_table.h | 26 +
be/src/vec/exec/scan/scanner_context.cpp | 72 ++-
be/src/vec/exec/scan/scanner_context.h | 15 +-
be/src/vec/exec/scan/scanner_scheduler.cpp | 20 +-
be/src/vec/exprs/vectorized_fn_call.cpp | 18 +
be/src/vec/exprs/vectorized_fn_call.h | 2 +
be/src/vec/exprs/vexpr.cpp | 18 +
be/src/vec/exprs/vexpr.h | 2 +
be/src/vec/exprs/vexpr_context.cpp | 46 +-
be/src/vec/exprs/vexpr_context.h | 14 +
be/src/vec/exprs/vin_predicate.cpp | 20 +
be/src/vec/exprs/vin_predicate.h | 1 +
be/src/vec/exprs/vslot_ref.h | 2 +
be/src/vec/runtime/vdata_stream_recvr.cpp | 3 +
be/src/vec/runtime/vdata_stream_recvr.h | 4 +-
be/src/vec/sink/vdata_stream_sender.cpp | 7 +-
be/src/vec/spill/spill_stream.h | 8 +-
be/src/vec/spill/spill_writer.cpp | 8 +
be/src/vec/spill/spill_writer.h | 13 +-
be/test/runtime/memory/mem_tracker_test.cpp | 6 -
.../main/java/org/apache/doris/qe/Coordinator.java | 2 +-
.../java/org/apache/doris/qe/SessionVariable.java | 34 ++
.../doris/resource/workloadgroup/QueryQueue.java | 39 +-
.../doris/resource/workloadgroup/QueueToken.java | 8 +-
.../resource/workloadgroup/WorkloadGroup.java | 5 +
gensrc/thrift/BackendService.thrift | 1 +
gensrc/thrift/PaloInternalService.thrift | 8 +-
134 files changed, 3172 insertions(+), 839 deletions(-)
diff --git a/be/src/agent/workload_group_listener.cpp b/be/src/agent/workload_group_listener.cpp
index f0f57869f25..acf5f799c81 100644
--- a/be/src/agent/workload_group_listener.cpp
+++ b/be/src/agent/workload_group_listener.cpp
@@ -17,6 +17,8 @@
#include "agent/workload_group_listener.h"
+#include <thrift/protocol/TDebugProtocol.h>
+
#include "runtime/workload_group/workload_group.h"
#include "runtime/workload_group/workload_group_manager.h"
#include "util/mem_info.h"
@@ -32,6 +34,8 @@ void WorkloadGroupListener::handle_topic_info(const std::vector<TopicInfo>& topi
if (!topic_info.__isset.workload_group_info) {
continue;
}
+ LOG(INFO) << "Received publish workload group info request: "
+ << apache::thrift::ThriftDebugString(topic_info).c_str();
is_set_workload_group_info = true;
// 1 parse topic info to group info
diff --git a/be/src/clucene b/be/src/clucene
index 5e9566ab364..c5d02a7e411 160000
--- a/be/src/clucene
+++ b/be/src/clucene
@@ -1 +1 @@
-Subproject commit 5e9566ab364d71b64c436ee46e5c848eed0ab7f7
+Subproject commit c5d02a7e41194b02444be6d684e3aeb4ff1b5595
diff --git a/be/src/common/config.cpp b/be/src/common/config.cpp
index e805da4e62a..712812aee3b 100644
--- a/be/src/common/config.cpp
+++ b/be/src/common/config.cpp
@@ -96,7 +96,7 @@ DEFINE_String(mem_limit, "90%");
DEFINE_Double(soft_mem_limit_frac, "0.9");
// Cache capacity reduce mem limit as a fraction of soft mem limit.
-DEFINE_mDouble(cache_capacity_reduce_mem_limit_frac, "0.6");
+DEFINE_mDouble(cache_capacity_reduce_mem_limit_frac, "0.7");
// Schema change memory limit as a fraction of soft memory limit.
DEFINE_Double(schema_change_mem_limit_frac, "0.6");
@@ -151,7 +151,7 @@ DEFINE_mInt64(stacktrace_in_alloc_large_memory_bytes, "2147483648");
DEFINE_mInt64(crash_in_alloc_large_memory_bytes, "-1");
// If memory tracker value is inaccurate, BE will crash. usually used in test environments, default value is false.
-DEFINE_mBool(crash_in_memory_tracker_inaccurate, "false");
+DEFINE_mBool(crash_in_memory_tracker_inaccurate, "true");
// default is true. if any memory tracking in Orphan mem tracker will report error.
// !! not modify the default value of this conf!! otherwise memory errors cannot be detected in time.
diff --git a/be/src/common/daemon.cpp b/be/src/common/daemon.cpp
index 27fbfb71d7f..68a8369ee74 100644
--- a/be/src/common/daemon.cpp
+++ b/be/src/common/daemon.cpp
@@ -299,15 +299,18 @@ void Daemon::memory_maintenance_thread() {
doris::ExecEnv::GetInstance()->workload_group_mgr()->do_sweep();
doris::ExecEnv::GetInstance()->workload_group_mgr()->refresh_wg_weighted_memory_limit();
- // step 7. Analyze blocking queries.
+ // step 7: handle paused queries(caused by memory insufficient)
+ doris::ExecEnv::GetInstance()->workload_group_mgr()->handle_paused_queries();
+
+ // step 8. Analyze blocking queries.
// TODO sort the operators that can spill, wake up the pipeline task spill
// or continue execution according to certain rules or cancel query.
- // step 8. Flush memtable
+ // step 9. Flush memtable
doris::GlobalMemoryArbitrator::notify_memtable_memory_refresh();
// TODO notify flush memtable
- // step 9. Jemalloc purge all arena dirty pages
+ // step 10. Jemalloc purge all arena dirty pages
je_purge_dirty_pages();
}
}
@@ -485,7 +488,9 @@ void Daemon::cache_adjust_capacity_thread() {
doris::GlobalMemoryArbitrator::cache_adjust_capacity_cv.wait_for(
l, std::chrono::seconds(1));
}
- double adjust_weighted = GlobalMemoryArbitrator::last_cache_capacity_adjust_weighted;
+ double adjust_weighted = std::min<double>(
+ GlobalMemoryArbitrator::last_cache_capacity_adjust_weighted,
+ GlobalMemoryArbitrator::last_wg_trigger_cache_capacity_adjust_weighted);
if (_stop_background_threads_latch.count() == 0) {
break;
}
@@ -500,6 +505,7 @@ void Daemon::cache_adjust_capacity_thread() {
LOG(INFO) << fmt::format(
"[MemoryGC] refresh cache capacity end, free memory {}, details: {}",
PrettyPrinter::print(freed_mem, TUnit::BYTES), ss.str());
+ GlobalMemoryArbitrator::last_affected_cache_capacity_adjust_weighted = adjust_weighted;
doris::GlobalMemoryArbitrator::cache_adjust_capacity_notify.store(
false, std::memory_order_relaxed);
} while (true);
diff --git a/be/src/common/status.h b/be/src/common/status.h
index e95b9343167..a4610d963b8 100644
--- a/be/src/common/status.h
+++ b/be/src/common/status.h
@@ -130,6 +130,9 @@ namespace ErrorCode {
E(BAD_CAST, -254, true); \
E(ARITHMETIC_OVERFLOW_ERRROR, -255, false); \
E(PERMISSION_DENIED, -256, false); \
+ E(QUERY_MEMORY_EXCEEDED, -257, false); \
+ E(WORKLOAD_GROUP_MEMORY_EXCEEDED, -258, false); \
+ E(PROCESS_MEMORY_EXCEEDED, -259, false); \
E(CE_CMD_PARAMS_ERROR, -300, true); \
E(CE_BUFFER_TOO_SMALL, -301, true); \
E(CE_CMD_NOT_VALID, -302, true); \
@@ -379,6 +382,11 @@ public:
_code = rhs._code;
if (rhs._err_msg) {
_err_msg = std::make_unique<ErrMsg>(*rhs._err_msg);
+ } else {
+ // If rhs error msg is empty, then should also clear current error msg
+ // For example, if rhs is OK and current status is error, then copy to current
+ // status, should clear current error message.
+ _err_msg.reset();
}
return *this;
}
@@ -388,6 +396,8 @@ public:
_code = rhs._code;
if (rhs._err_msg) {
_err_msg = std::move(rhs._err_msg);
+ } else {
+ _err_msg.reset();
}
return *this;
}
diff --git a/be/src/olap/memtable_memory_limiter.cpp b/be/src/olap/memtable_memory_limiter.cpp
index 1cb6c0c8e2d..7a220ef87ac 100644
--- a/be/src/olap/memtable_memory_limiter.cpp
+++ b/be/src/olap/memtable_memory_limiter.cpp
@@ -22,6 +22,7 @@
#include "common/config.h"
#include "olap/memtable.h"
#include "olap/memtable_writer.h"
+#include "runtime/workload_group/workload_group_manager.h"
#include "util/doris_metrics.h"
#include "util/mem_info.h"
#include "util/metrics.h"
@@ -139,7 +140,7 @@ void MemTableMemoryLimiter::handle_memtable_flush() {
<< ", active: " << PrettyPrinter::print_bytes(_active_mem_usage)
<< ", queue: " << PrettyPrinter::print_bytes(_queue_mem_usage)
<< ", flush: " << PrettyPrinter::print_bytes(_flush_mem_usage);
- _flush_active_memtables(need_flush);
+ _flush_active_memtables(0, need_flush);
}
} while (_hard_limit_reached());
g_memtable_memory_limit_waiting_threads << -1;
@@ -149,7 +150,13 @@ void MemTableMemoryLimiter::handle_memtable_flush() {
LOG(INFO) << "waited " << time_ms << " ms for memtable memory limit";
}
-void MemTableMemoryLimiter::_flush_active_memtables(int64_t need_flush) {
+void MemTableMemoryLimiter::flush_workload_group_memtables(uint64_t wg_id,
+ int64_t need_flush_bytes) {
+ std::unique_lock<std::mutex> l(_lock);
+ _flush_active_memtables(wg_id, need_flush_bytes);
+}
+
+void MemTableMemoryLimiter::_flush_active_memtables(uint64_t wg_id, int64_t need_flush) {
if (need_flush <= 0) {
return;
}
@@ -181,6 +188,10 @@ void MemTableMemoryLimiter::_flush_active_memtables(int64_t need_flush) {
if (w == nullptr) {
continue;
}
+ // If wg id is specified, but wg id not match, then not need flush
+ if (wg_id != 0 && w->workload_group_id() != wg_id) {
+ continue;
+ }
int64_t mem = w->active_memtable_mem_consumption();
if (mem < sort_mem * 0.9) {
// if the memtable writer just got flushed, don't flush it again
@@ -234,17 +245,30 @@ void MemTableMemoryLimiter::_refresh_mem_tracker() {
_flush_mem_usage = 0;
_queue_mem_usage = 0;
_active_mem_usage = 0;
+ std::map<uint64_t, doris::MemtableUsage> wg_mem_usages;
_active_writers.clear();
for (auto it = _writers.begin(); it != _writers.end();) {
if (auto writer = it->lock()) {
+ if (wg_mem_usages.find(writer->workload_group_id()) == wg_mem_usages.end()) {
+ wg_mem_usages.insert({writer->workload_group_id(), {0, 0, 0}});
+ }
+ auto& wg_mem_usage = wg_mem_usages.find(writer->workload_group_id())->second;
+
// The memtable is currently used by writer to insert blocks.
auto active_usage = writer->active_memtable_mem_consumption();
+ wg_mem_usage.active_mem_usage += active_usage;
_active_mem_usage += active_usage;
if (active_usage > 0) {
_active_writers.push_back(writer);
}
- _flush_mem_usage += writer->mem_consumption(MemType::FLUSH);
- _queue_mem_usage += writer->mem_consumption(MemType::WRITE_FINISHED);
+
+ auto flush_usage = writer->mem_consumption(MemType::FLUSH);
+ wg_mem_usage.flush_mem_usage += flush_usage;
+ _flush_mem_usage += flush_usage;
+
+ auto write_usage = writer->mem_consumption(MemType::WRITE_FINISHED);
+ wg_mem_usage.queue_mem_usage += write_usage;
+ _queue_mem_usage += write_usage;
++it;
} else {
*it = std::move(_writers.back());
diff --git a/be/src/olap/memtable_memory_limiter.h b/be/src/olap/memtable_memory_limiter.h
index 1e32cb165e4..143edaa8fe5 100644
--- a/be/src/olap/memtable_memory_limiter.h
+++ b/be/src/olap/memtable_memory_limiter.h
@@ -39,8 +39,12 @@ public:
// check if the total mem consumption exceeds limit.
// If yes, it will flush memtable to try to reduce memory consumption.
+ // Every write operation will call this API to check if need flush memtable OR hang
+ // when memory is not available.
void handle_memtable_flush();
+ void flush_workload_group_memtables(uint64_t wg_id, int64_t need_flush_bytes);
+
void register_writer(std::weak_ptr<MemTableWriter> writer);
void refresh_mem_tracker();
@@ -57,7 +61,7 @@ private:
bool _hard_limit_reached();
bool _load_usage_low();
int64_t _need_flush();
- void _flush_active_memtables(int64_t need_flush);
+ void _flush_active_memtables(uint64_t wg_id, int64_t need_flush);
void _refresh_mem_tracker();
std::mutex _lock;
diff --git a/be/src/olap/memtable_writer.h b/be/src/olap/memtable_writer.h
index ec44348b4a9..ae06a04e86d 100644
--- a/be/src/olap/memtable_writer.h
+++ b/be/src/olap/memtable_writer.h
@@ -106,6 +106,14 @@ public:
uint64_t flush_running_count() const;
+ uint64_t workload_group_id() const {
+ auto wg = _query_thread_context.wg_wptr.lock();
+ if (wg != nullptr) {
+ return wg->id();
+ }
+ return 0;
+ }
+
private:
// push a full memtable to flush executor
Status _flush_memtable_async();
diff --git a/be/src/pipeline/common/agg_utils.h b/be/src/pipeline/common/agg_utils.h
index e0435954b8b..6b18e42b3b3 100644
--- a/be/src/pipeline/common/agg_utils.h
+++ b/be/src/pipeline/common/agg_utils.h
@@ -210,7 +210,7 @@ public:
}
*reinterpret_cast<KeyType*>(_current_keys) = key;
- auto aggregate_data = _current_agg_data;
+ auto* aggregate_data = _current_agg_data;
++_total_count;
++_index_in_sub_container;
_current_agg_data += _size_of_aggregate_states;
@@ -275,15 +275,37 @@ public:
using IteratorBase<ConstIterator, true>::IteratorBase;
};
- ConstIterator begin() const { return ConstIterator(this, 0); }
+ ConstIterator begin() const { return {this, 0}; }
ConstIterator cbegin() const { return begin(); }
- Iterator begin() { return Iterator(this, 0); }
+ Iterator begin() { return {this, 0}; }
- ConstIterator end() const { return ConstIterator(this, _total_count); }
+ ConstIterator end() const { return {this, _total_count}; }
ConstIterator cend() const { return end(); }
- Iterator end() { return Iterator(this, _total_count); }
+ Iterator end() { return {this, _total_count}; }
+
+ [[nodiscard]] uint32_t total_count() const { return _total_count; }
+
+ size_t estimate_memory(size_t rows) const {
+ bool need_to_expand = false;
+ if (_total_count == 0) {
+ need_to_expand = true;
+ } else if ((_index_in_sub_container + rows) > SUB_CONTAINER_CAPACITY) {
+ need_to_expand = true;
+ rows -= (SUB_CONTAINER_CAPACITY - _index_in_sub_container);
+ }
+
+ if (!need_to_expand) {
+ return 0;
+ }
+
+ size_t count = (rows + SUB_CONTAINER_CAPACITY - 1) / SUB_CONTAINER_CAPACITY;
+ size_t size = _size_of_key * SUB_CONTAINER_CAPACITY;
+ size += _size_of_aggregate_states * SUB_CONTAINER_CAPACITY;
+ size *= count;
+ return size;
+ }
void init_once() {
if (_inited) {
diff --git a/be/src/pipeline/dependency.h b/be/src/pipeline/dependency.h
index 63fa8ac8c79..3cf1cbff9fa 100644
--- a/be/src/pipeline/dependency.h
+++ b/be/src/pipeline/dependency.h
@@ -26,6 +26,7 @@
#include <thread>
#include <utility>
+#include "common/config.h"
#include "common/logging.h"
#include "concurrentqueue.h"
#include "gutil/integral_types.h"
@@ -449,7 +450,8 @@ struct PartitionedAggSharedState : public BasicSharedState,
std::deque<std::shared_ptr<AggSpillPartition>> spill_partitions;
size_t get_partition_index(size_t hash_value) const {
- return (hash_value >> (32 - partition_count_bits)) & max_partition_index;
+ // return (hash_value >> (32 - partition_count_bits)) & max_partition_index;
+ return hash_value % partition_count;
}
};
@@ -826,6 +828,7 @@ public:
std::unique_ptr<ExchangerBase> exchanger {};
std::vector<RuntimeProfile::Counter*> mem_counters;
std::atomic<int64_t> mem_usage = 0;
+ size_t _buffer_mem_limit = config::local_exchange_buffer_mem_limit;
// We need to make sure to add mem_usage first and then enqueue, otherwise sub mem_usage may cause negative mem_usage during concurrent dequeue.
std::mutex le_lock;
virtual void create_dependencies(int local_exchange_id) {
@@ -871,7 +874,7 @@ public:
}
virtual void add_total_mem_usage(size_t delta, int channel_id) {
- if (mem_usage.fetch_add(delta) + delta > config::local_exchange_buffer_mem_limit) {
+ if (mem_usage.fetch_add(delta) + delta > _buffer_mem_limit) {
sink_deps.front()->block();
}
}
@@ -880,10 +883,15 @@ public:
auto prev_usage = mem_usage.fetch_sub(delta);
DCHECK_GE(prev_usage - delta, 0) << "prev_usage: " << prev_usage << " delta: " << delta
<< " channel_id: " << channel_id;
- if (prev_usage - delta <= config::local_exchange_buffer_mem_limit) {
+ if (prev_usage - delta <= _buffer_mem_limit) {
sink_deps.front()->set_ready();
}
}
+
+ virtual void set_low_memory_mode() {
+ _buffer_mem_limit =
+ std::min<int64_t>(config::local_exchange_buffer_mem_limit, 10 * 1024 * 1024);
+ }
};
struct LocalMergeExchangeSharedState : public LocalExchangeSharedState {
@@ -929,6 +937,14 @@ struct LocalMergeExchangeSharedState : public LocalExchangeSharedState {
source_deps[channel_id]->set_ready();
}
+ void set_low_memory_mode() override {
+ _buffer_mem_limit =
+ std::min<int64_t>(config::local_exchange_buffer_mem_limit, 10 * 1024 * 1024);
+ DCHECK(!_queues_mem_usage.empty());
+ _each_queue_limit =
+ std::max<int64_t>(64 * 1024, _buffer_mem_limit / _queues_mem_usage.size());
+ }
+
Dependency* get_sink_dep_by_channel_id(int channel_id) override {
return sink_deps[channel_id].get();
}
@@ -939,7 +955,7 @@ struct LocalMergeExchangeSharedState : public LocalExchangeSharedState {
private:
std::vector<std::atomic_int64_t> _queues_mem_usage;
- const int64_t _each_queue_limit;
+ int64_t _each_queue_limit;
};
} // namespace doris::pipeline
diff --git a/be/src/pipeline/exec/aggregation_sink_operator.cpp b/be/src/pipeline/exec/aggregation_sink_operator.cpp
index 444891b9c5e..bcbf83f6290 100644
--- a/be/src/pipeline/exec/aggregation_sink_operator.cpp
+++ b/be/src/pipeline/exec/aggregation_sink_operator.cpp
@@ -23,6 +23,8 @@
#include "common/status.h"
#include "pipeline/exec/operator.h"
#include "runtime/primitive_type.h"
+#include "runtime/thread_context.h"
+#include "util/runtime_profile.h"
#include "vec/common/hash_table/hash.h"
#include "vec/exprs/vectorized_agg_fn.h"
@@ -75,6 +77,9 @@ Status AggSinkLocalState::init(RuntimeState* state, LocalSinkStateInfo& info) {
_hash_table_input_counter = ADD_COUNTER(Base::profile(), "HashTableInputCount", TUnit::UNIT);
_max_row_size_counter = ADD_COUNTER(Base::profile(), "MaxRowSizeInBytes", TUnit::UNIT);
+ _memory_usage_container = ADD_COUNTER(profile(), "MemoryUsageContainer", TUnit::BYTES);
+ _memory_usage_arena = ADD_COUNTER(profile(), "MemoryUsageArena", TUnit::BYTES);
+
return Status::OK();
}
@@ -176,6 +181,8 @@ Status AggSinkLocalState::_create_agg_status(vectorized::AggregateDataPtr data)
Status AggSinkLocalState::_execute_without_key(vectorized::Block* block) {
DCHECK(_agg_data->without_key != nullptr);
SCOPED_TIMER(_build_timer);
+ _memory_usage_last_executing = 0;
+ SCOPED_PEAK_MEM(&_memory_usage_last_executing);
for (int i = 0; i < Base::_shared_state->aggregate_evaluators.size(); ++i) {
RETURN_IF_ERROR(Base::_shared_state->aggregate_evaluators[i]->execute_single_add(
block,
@@ -187,6 +194,8 @@ Status AggSinkLocalState::_execute_without_key(vectorized::Block* block) {
}
Status AggSinkLocalState::_merge_with_serialized_key(vectorized::Block* block) {
+ _memory_usage_last_executing = 0;
+ SCOPED_PEAK_MEM(&_memory_usage_last_executing);
if (_shared_state->reach_limit) {
return _merge_with_serialized_key_helper<true, false>(block);
} else {
@@ -227,17 +236,21 @@ void AggSinkLocalState::_update_memusage_with_serialized_key() {
},
[&](auto& agg_method) -> void {
auto& data = *agg_method.hash_table;
- int64_t arena_memory_usage =
- _agg_arena_pool->size() +
+ int64_t memory_usage_arena = _agg_arena_pool->size();
+ int64_t memory_usage_container =
_shared_state->aggregate_data_container->memory_usage();
int64_t hash_table_memory_usage = data.get_buffer_size_in_bytes();
- COUNTER_SET(_memory_used_counter,
- arena_memory_usage + hash_table_memory_usage);
- COUNTER_SET(_peak_memory_usage_counter, _memory_used_counter->value());
-
- COUNTER_SET(_serialize_key_arena_memory_usage, arena_memory_usage);
+ COUNTER_SET(_memory_usage_arena, memory_usage_arena);
+ COUNTER_SET(_memory_usage_container, memory_usage_container);
COUNTER_SET(_hash_table_memory_usage, hash_table_memory_usage);
+ COUNTER_SET(_serialize_key_arena_memory_usage,
+ memory_usage_arena + memory_usage_container);
+
+ COUNTER_SET(_memory_used_counter, memory_usage_arena +
+ memory_usage_container +
+ hash_table_memory_usage);
+ COUNTER_SET(_peak_memory_usage_counter, _memory_used_counter->value());
}},
_agg_data->method_variant);
}
@@ -387,6 +400,9 @@ Status AggSinkLocalState::_merge_with_serialized_key_helper(vectorized::Block* b
Status AggSinkLocalState::_merge_without_key(vectorized::Block* block) {
SCOPED_TIMER(_merge_timer);
DCHECK(_agg_data->without_key != nullptr);
+
+ _memory_usage_last_executing = 0;
+ SCOPED_PEAK_MEM(&_memory_usage_last_executing);
for (int i = 0; i < Base::_shared_state->aggregate_evaluators.size(); ++i) {
if (Base::_shared_state->aggregate_evaluators[i]->is_merge()) {
int col_id = AggSharedState::get_slot_column_id(
@@ -423,6 +439,8 @@ void AggSinkLocalState::_update_memusage_without_key() {
}
Status AggSinkLocalState::_execute_with_serialized_key(vectorized::Block* block) {
+ _memory_usage_last_executing = 0;
+ SCOPED_PEAK_MEM(&_memory_usage_last_executing);
if (_shared_state->reach_limit) {
return _execute_with_serialized_key_helper<true>(block);
} else {
@@ -700,6 +718,22 @@ Status AggSinkLocalState::_init_hash_method(const vectorized::VExprContextSPtrs&
return Status::OK();
}
+size_t AggSinkLocalState::get_reserve_mem_size(RuntimeState* state) const {
+ size_t size_to_reserve = std::visit(
+ [&](auto&& arg) -> size_t {
+ using HashTableCtxType = std::decay_t<decltype(arg)>;
+ if constexpr (std::is_same_v<HashTableCtxType, std::monostate>) {
+ return 0;
+ } else {
+ return arg.hash_table->estimate_memory(state->batch_size());
+ }
+ },
+ _agg_data->method_variant);
+
+ size_to_reserve += _memory_usage_last_executing;
+ return size_to_reserve;
+}
+
AggSinkOperatorX::AggSinkOperatorX(ObjectPool* pool, int operator_id, const TPlanNode& tnode,
const DescriptorTbl& descs, bool require_bucket_distribution)
: DataSinkOperatorX<AggSinkLocalState>(operator_id, tnode.node_id),
@@ -853,9 +887,15 @@ Status AggSinkOperatorX::reset_hash_table(RuntimeState* state) {
auto& ss = *local_state.Base::_shared_state;
RETURN_IF_ERROR(ss.reset_hash_table());
local_state._agg_arena_pool = ss.agg_arena_pool.get();
+ local_state._serialize_key_arena_memory_usage->set((int64_t)0);
return Status::OK();
}
+size_t AggSinkOperatorX::get_reserve_mem_size(RuntimeState* state) {
+ auto& local_state = get_local_state(state);
+ return local_state.get_reserve_mem_size(state);
+}
+
Status AggSinkLocalState::close(RuntimeState* state, Status exec_status) {
SCOPED_TIMER(Base::exec_time_counter());
SCOPED_TIMER(Base::_close_timer);
diff --git a/be/src/pipeline/exec/aggregation_sink_operator.h b/be/src/pipeline/exec/aggregation_sink_operator.h
index 087ac05f9f2..477df890b4f 100644
--- a/be/src/pipeline/exec/aggregation_sink_operator.h
+++ b/be/src/pipeline/exec/aggregation_sink_operator.h
@@ -21,6 +21,7 @@
#include "pipeline/exec/operator.h"
#include "runtime/exec_env.h"
+#include "util/runtime_profile.h"
namespace doris::pipeline {
@@ -96,6 +97,8 @@ protected:
Status _create_agg_status(vectorized::AggregateDataPtr data);
size_t _memory_usage() const;
+ size_t get_reserve_mem_size(RuntimeState* state) const;
+
RuntimeProfile::Counter* _hash_table_compute_timer = nullptr;
RuntimeProfile::Counter* _hash_table_emplace_timer = nullptr;
RuntimeProfile::Counter* _hash_table_limit_compute_timer = nullptr;
@@ -110,6 +113,8 @@ protected:
RuntimeProfile::Counter* _hash_table_memory_usage = nullptr;
RuntimeProfile::Counter* _hash_table_size_counter = nullptr;
RuntimeProfile::Counter* _serialize_key_arena_memory_usage = nullptr;
+ RuntimeProfile::Counter* _memory_usage_container = nullptr;
+ RuntimeProfile::Counter* _memory_usage_arena = nullptr;
bool _should_limit_output = false;
@@ -123,6 +128,8 @@ protected:
std::unique_ptr<vectorized::Arena> _agg_profile_arena;
std::unique_ptr<ExecutorBase> _executor = nullptr;
+
+ int64_t _memory_usage_last_executing = 0;
};
class AggSinkOperatorX final : public DataSinkOperatorX<AggSinkLocalState> {
@@ -163,6 +170,8 @@ public:
Status reset_hash_table(RuntimeState* state);
+ size_t get_reserve_mem_size(RuntimeState* state) override;
+
using DataSinkOperatorX<AggSinkLocalState>::node_id;
using DataSinkOperatorX<AggSinkLocalState>::operator_id;
using DataSinkOperatorX<AggSinkLocalState>::get_local_state;
diff --git a/be/src/pipeline/exec/aggregation_source_operator.cpp b/be/src/pipeline/exec/aggregation_source_operator.cpp
index bc2d83a2782..ba87db34097 100644
--- a/be/src/pipeline/exec/aggregation_source_operator.cpp
+++ b/be/src/pipeline/exec/aggregation_source_operator.cpp
@@ -22,18 +22,15 @@
#include "common/exception.h"
#include "pipeline/exec/operator.h"
+#include "runtime/thread_context.h"
+#include "util/runtime_profile.h"
#include "vec/exprs/vectorized_agg_fn.h"
+#include "vec/exprs/vexpr_fwd.h"
namespace doris::pipeline {
#include "common/compile_check_begin.h"
-AggLocalState::AggLocalState(RuntimeState* state, OperatorXBase* parent)
- : Base(state, parent),
- _get_results_timer(nullptr),
- _serialize_result_timer(nullptr),
- _hash_table_iterate_timer(nullptr),
- _insert_keys_to_column_timer(nullptr),
- _serialize_data_timer(nullptr) {}
+AggLocalState::AggLocalState(RuntimeState* state, OperatorXBase* parent) : Base(state, parent) {}
Status AggLocalState::init(RuntimeState* state, LocalStateInfo& info) {
RETURN_IF_ERROR(Base::init(state, info));
@@ -49,28 +46,40 @@ Status AggLocalState::init(RuntimeState* state, LocalStateInfo& info) {
_deserialize_data_timer = ADD_TIMER(Base::profile(), "DeserializeAndMergeTime");
_hash_table_compute_timer = ADD_TIMER(Base::profile(), "HashTableComputeTime");
_hash_table_emplace_timer = ADD_TIMER(Base::profile(), "HashTableEmplaceTime");
- _hash_table_input_counter = ADD_COUNTER(Base::profile(), "HashTableInputCount", TUnit::UNIT);
+ _hash_table_input_counter =
+ ADD_COUNTER_WITH_LEVEL(Base::profile(), "HashTableInputCount", TUnit::UNIT, 1);
+ _hash_table_memory_usage =
+ ADD_COUNTER_WITH_LEVEL(Base::profile(), "MemoryUsageHashTable", TUnit::BYTES, 1);
+ _hash_table_size_counter =
+ ADD_COUNTER_WITH_LEVEL(Base::profile(), "HashTableSize", TUnit::UNIT, 1);
+
+ _memory_usage_container = ADD_COUNTER(profile(), "MemoryUsageContainer", TUnit::BYTES);
+ _memory_usage_arena = ADD_COUNTER(profile(), "MemoryUsageArena", TUnit::BYTES);
auto& p = _parent->template cast<AggSourceOperatorX>();
if (p._without_key) {
if (p._needs_finalize) {
- _executor.get_result = std::bind<Status>(&AggLocalState::_get_without_key_result, this,
- std::placeholders::_1, std::placeholders::_2,
- std::placeholders::_3);
+ _executor.get_result = [this](RuntimeState* state, vectorized::Block* block,
+ bool* eos) {
+ return _get_without_key_result(state, block, eos);
+ };
} else {
- _executor.get_result = std::bind<Status>(&AggLocalState::_serialize_without_key, this,
- std::placeholders::_1, std::placeholders::_2,
- std::placeholders::_3);
+ _executor.get_result = [this](RuntimeState* state, vectorized::Block* block,
+ bool* eos) {
+ return _serialize_without_key(state, block, eos);
+ };
}
} else {
if (p._needs_finalize) {
- _executor.get_result = std::bind<Status>(
- &AggLocalState::_get_with_serialized_key_result, this, std::placeholders::_1,
- std::placeholders::_2, std::placeholders::_3);
+ _executor.get_result = [this](RuntimeState* state, vectorized::Block* block,
+ bool* eos) {
+ return _get_with_serialized_key_result(state, block, eos);
+ };
} else {
- _executor.get_result = std::bind<Status>(
- &AggLocalState::_serialize_with_serialized_key_result, this,
- std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
+ _executor.get_result = [this](RuntimeState* state, vectorized::Block* block,
+ bool* eos) {
+ return _serialize_with_serialized_key_result(state, block, eos);
+ };
}
}
@@ -446,11 +455,11 @@ AggSourceOperatorX::AggSourceOperatorX(ObjectPool* pool, const TPlanNode& tnode,
Status AggSourceOperatorX::get_block(RuntimeState* state, vectorized::Block* block, bool* eos) {
auto& local_state = get_local_state(state);
SCOPED_TIMER(local_state.exec_time_counter());
+ SCOPED_PEAK_MEM(&local_state._estimate_memory_usage);
RETURN_IF_ERROR(local_state._executor.get_result(state, block, eos));
local_state.make_nullable_output_key(block);
// dispose the having clause, should not be execute in prestreaming agg
- RETURN_IF_ERROR(vectorized::VExprContext::filter_block(local_state._conjuncts, block,
- block->columns()));
+ RETURN_IF_ERROR(local_state.filter_block(local_state._conjuncts, block, block->columns()));
local_state.do_agg_limit(block, eos);
return Status::OK();
}
@@ -484,6 +493,7 @@ void AggLocalState::make_nullable_output_key(vectorized::Block* block) {
template <bool limit>
Status AggLocalState::merge_with_serialized_key_helper(vectorized::Block* block) {
SCOPED_TIMER(_merge_timer);
+ SCOPED_PEAK_MEM(&_estimate_memory_usage);
size_t key_size = Base::_shared_state->probe_expr_ctxs.size();
vectorized::ColumnRawPtrs key_columns(key_size);
@@ -571,6 +581,21 @@ template Status AggSourceOperatorX::merge_with_serialized_key_helper<true>(
template Status AggSourceOperatorX::merge_with_serialized_key_helper<false>(
RuntimeState* state, vectorized::Block* block);
+size_t AggSourceOperatorX::get_estimated_memory_size_for_merging(RuntimeState* state,
+ size_t rows) const {
+ auto& local_state = get_local_state(state);
+ size_t size = std::visit(
+ vectorized::Overload {
+ [&](std::monostate& arg) -> size_t {
+ throw doris::Exception(ErrorCode::INTERNAL_ERROR, "uninited hash table");
+ return 0;
+ },
+ [&](auto& agg_method) { return agg_method.hash_table->estimate_memory(rows); }},
+ local_state._shared_state->agg_data->method_variant);
+ size += local_state._shared_state->aggregate_data_container->estimate_memory(rows);
+ return size;
+}
+
size_t AggLocalState::_get_hash_table_size() {
return std::visit(
vectorized::Overload {[&](std::monostate& arg) -> size_t {
@@ -585,49 +610,61 @@ size_t AggLocalState::_get_hash_table_size() {
void AggLocalState::_emplace_into_hash_table(vectorized::AggregateDataPtr* places,
vectorized::ColumnRawPtrs& key_columns,
size_t num_rows) {
- std::visit(vectorized::Overload {
- [&](std::monostate& arg) -> void {
- throw doris::Exception(ErrorCode::INTERNAL_ERROR, "uninited hash table");
- },
- [&](auto& agg_method) -> void {
- SCOPED_TIMER(_hash_table_compute_timer);
- using HashMethodType = std::decay_t<decltype(agg_method)>;
- using AggState = typename HashMethodType::State;
- AggState state(key_columns);
- agg_method.init_serialized_keys(key_columns, num_rows);
-
- auto creator = [this](const auto& ctor, auto& key, auto& origin) {
- HashMethodType::try_presis_key_and_origin(
- key, origin, *_shared_state->agg_arena_pool);
- auto mapped =
- Base::_shared_state->aggregate_data_container->append_data(
- origin);
- auto st = _create_agg_status(mapped);
- if (!st) {
- throw Exception(st.code(), st.to_string());
- }
- ctor(key, mapped);
- };
-
- auto creator_for_null_key = [&](auto& mapped) {
- mapped = _shared_state->agg_arena_pool->aligned_alloc(
- _shared_state->total_size_of_aggregate_states,
- _shared_state->align_aggregate_states);
- auto st = _create_agg_status(mapped);
- if (!st) {
- throw Exception(st.code(), st.to_string());
- }
- };
-
- SCOPED_TIMER(_hash_table_emplace_timer);
- for (size_t i = 0; i < num_rows; ++i) {
- places[i] = agg_method.lazy_emplace(state, i, creator,
- creator_for_null_key);
- }
-
- COUNTER_UPDATE(_hash_table_input_counter, num_rows);
- }},
- _shared_state->agg_data->method_variant);
+ std::visit(
+ vectorized::Overload {
+ [&](std::monostate& arg) -> void {
+ throw doris::Exception(ErrorCode::INTERNAL_ERROR, "uninited hash table");
+ },
+ [&](auto& agg_method) -> void {
+ SCOPED_TIMER(_hash_table_compute_timer);
+ using HashMethodType = std::decay_t<decltype(agg_method)>;
+ using AggState = typename HashMethodType::State;
+ AggState state(key_columns);
+ agg_method.init_serialized_keys(key_columns, num_rows);
+
+ auto creator = [this](const auto& ctor, auto& key, auto& origin) {
+ HashMethodType::try_presis_key_and_origin(
+ key, origin, *_shared_state->agg_arena_pool);
+ auto mapped =
+ Base::_shared_state->aggregate_data_container->append_data(
+ origin);
+ auto st = _create_agg_status(mapped);
+ if (!st) {
+ throw Exception(st.code(), st.to_string());
+ }
+ ctor(key, mapped);
+ };
+
+ auto creator_for_null_key = [&](auto& mapped) {
+ mapped = _shared_state->agg_arena_pool->aligned_alloc(
+ _shared_state->total_size_of_aggregate_states,
+ _shared_state->align_aggregate_states);
+ auto st = _create_agg_status(mapped);
+ if (!st) {
+ throw Exception(st.code(), st.to_string());
+ }
+ };
+
+ SCOPED_TIMER(_hash_table_emplace_timer);
+ for (size_t i = 0; i < num_rows; ++i) {
+ places[i] = agg_method.lazy_emplace(state, i, creator,
+ creator_for_null_key);
+ }
+
+ COUNTER_UPDATE(_hash_table_input_counter, num_rows);
+ COUNTER_SET(_hash_table_memory_usage,
+ static_cast<int64_t>(
+ agg_method.hash_table->get_buffer_size_in_bytes()));
+ COUNTER_SET(_hash_table_size_counter,
+ static_cast<int64_t>(agg_method.hash_table->size()));
+ COUNTER_SET(
+ _memory_usage_container,
+ static_cast<int64_t>(
+ _shared_state->aggregate_data_container->memory_usage()));
+ COUNTER_SET(_memory_usage_arena,
+ static_cast<int64_t>(_shared_state->agg_arena_pool->size()));
+ }},
+ _shared_state->agg_data->method_variant);
}
void AggLocalState::_find_in_hash_table(vectorized::AggregateDataPtr* places,
diff --git a/be/src/pipeline/exec/aggregation_source_operator.h b/be/src/pipeline/exec/aggregation_source_operator.h
index a3824a381eb..09b03a5aab1 100644
--- a/be/src/pipeline/exec/aggregation_source_operator.h
+++ b/be/src/pipeline/exec/aggregation_source_operator.h
@@ -82,8 +82,12 @@ protected:
RuntimeProfile::Counter* _hash_table_compute_timer = nullptr;
RuntimeProfile::Counter* _hash_table_emplace_timer = nullptr;
RuntimeProfile::Counter* _hash_table_input_counter = nullptr;
+ RuntimeProfile::Counter* _hash_table_size_counter = nullptr;
+ RuntimeProfile::Counter* _hash_table_memory_usage = nullptr;
RuntimeProfile::Counter* _merge_timer = nullptr;
RuntimeProfile::Counter* _deserialize_data_timer = nullptr;
+ RuntimeProfile::Counter* _memory_usage_container = nullptr;
+ RuntimeProfile::Counter* _memory_usage_arena = nullptr;
using vectorized_get_result =
std::function<Status(RuntimeState* state, vectorized::Block* block, bool* eos)>;
@@ -100,7 +104,7 @@ public:
using Base = OperatorX<AggLocalState>;
AggSourceOperatorX(ObjectPool* pool, const TPlanNode& tnode, int operator_id,
const DescriptorTbl& descs);
- ~AggSourceOperatorX() = default;
+ ~AggSourceOperatorX() override = default;
Status get_block(RuntimeState* state, vectorized::Block* block, bool* eos) override;
@@ -109,6 +113,8 @@ public:
template <bool limit>
Status merge_with_serialized_key_helper(RuntimeState* state, vectorized::Block* block);
+ size_t get_estimated_memory_size_for_merging(RuntimeState* state, size_t rows) const;
+
private:
friend class AggLocalState;
diff --git a/be/src/pipeline/exec/analytic_sink_operator.cpp b/be/src/pipeline/exec/analytic_sink_operator.cpp
index 1da5c1f7c35..e11ffe0c48a 100644
--- a/be/src/pipeline/exec/analytic_sink_operator.cpp
+++ b/be/src/pipeline/exec/analytic_sink_operator.cpp
@@ -21,6 +21,7 @@
#include <string>
#include "pipeline/exec/operator.h"
+#include "runtime/runtime_state.h"
#include "vec/exprs/vectorized_agg_fn.h"
namespace doris::pipeline {
@@ -263,6 +264,10 @@ Status AnalyticSinkOperatorX::sink(doris::RuntimeState* state, vectorized::Block
auto& local_state = get_local_state(state);
SCOPED_TIMER(local_state.exec_time_counter());
COUNTER_UPDATE(local_state.rows_input_counter(), (int64_t)input_block->rows());
+
+ local_state._reserve_mem_size = 0;
+ SCOPED_PEAK_MEM(&local_state._reserve_mem_size);
+
local_state._shared_state->input_eos = eos;
if (local_state._shared_state->input_eos && input_block->rows() == 0) {
local_state._dependency->set_ready_to_read();
@@ -327,6 +332,11 @@ Status AnalyticSinkOperatorX::sink(doris::RuntimeState* state, vectorized::Block
return Status::OK();
}
+size_t AnalyticSinkOperatorX::get_reserve_mem_size(RuntimeState* state) {
+ auto& local_state = get_local_state(state);
+ return local_state._reserve_mem_size;
+}
+
Status AnalyticSinkOperatorX::_insert_range_column(vectorized::Block* block,
const vectorized::VExprContextSPtr& expr,
vectorized::IColumn* dst_column, size_t length) {
diff --git a/be/src/pipeline/exec/analytic_sink_operator.h b/be/src/pipeline/exec/analytic_sink_operator.h
index 97c873a076f..dee7ee27c30 100644
--- a/be/src/pipeline/exec/analytic_sink_operator.h
+++ b/be/src/pipeline/exec/analytic_sink_operator.h
@@ -61,6 +61,8 @@ private:
RuntimeProfile::Counter* _blocks_memory_usage = nullptr;
std::vector<vectorized::VExprContextSPtrs> _agg_expr_ctxs;
+
+ int64_t _reserve_mem_size = 0;
};
class AnalyticSinkOperatorX final : public DataSinkOperatorX<AnalyticSinkLocalState> {
@@ -93,6 +95,8 @@ public:
return !_partition_by_eq_expr_ctxs.empty() && _order_by_eq_expr_ctxs.empty();
}
+ size_t get_reserve_mem_size(RuntimeState* state) override;
+
private:
Status _insert_range_column(vectorized::Block* block, const vectorized::VExprContextSPtr& expr,
vectorized::IColumn* dst_column, size_t length);
diff --git a/be/src/pipeline/exec/analytic_source_operator.cpp b/be/src/pipeline/exec/analytic_source_operator.cpp
index 134a0ad82d7..8a1f3bc2c09 100644
--- a/be/src/pipeline/exec/analytic_source_operator.cpp
+++ b/be/src/pipeline/exec/analytic_source_operator.cpp
@@ -514,6 +514,7 @@ Status AnalyticSourceOperatorX::get_block(RuntimeState* state, vectorized::Block
bool* eos) {
auto& local_state = get_local_state(state);
SCOPED_TIMER(local_state.exec_time_counter());
+ SCOPED_PEAK_MEM(&local_state._estimate_memory_usage);
if (local_state._shared_state->input_eos &&
(local_state._output_block_index == local_state._shared_state->input_blocks.size() ||
local_state._shared_state->input_total_rows == 0)) {
@@ -541,8 +542,7 @@ Status AnalyticSourceOperatorX::get_block(RuntimeState* state, vectorized::Block
}
}
RETURN_IF_ERROR(local_state.output_current_block(block));
- RETURN_IF_ERROR(vectorized::VExprContext::filter_block(local_state._conjuncts, block,
- block->columns()));
+ RETURN_IF_ERROR(local_state.filter_block(local_state._conjuncts, block, block->columns()));
local_state.reached_limit(block, eos);
return Status::OK();
}
diff --git a/be/src/pipeline/exec/assert_num_rows_operator.cpp b/be/src/pipeline/exec/assert_num_rows_operator.cpp
index 71688ff530d..7cc81640ec8 100644
--- a/be/src/pipeline/exec/assert_num_rows_operator.cpp
+++ b/be/src/pipeline/exec/assert_num_rows_operator.cpp
@@ -42,6 +42,7 @@ Status AssertNumRowsOperatorX::pull(doris::RuntimeState* state, vectorized::Bloc
bool* eos) {
auto& local_state = get_local_state(state);
SCOPED_TIMER(local_state.exec_time_counter());
+ SCOPED_PEAK_MEM(&local_state.estimate_memory_usage());
local_state.add_num_rows_returned(block->rows());
int64_t num_rows_returned = local_state.num_rows_returned();
bool assert_res = false;
@@ -114,8 +115,7 @@ Status AssertNumRowsOperatorX::pull(doris::RuntimeState* state, vectorized::Bloc
return Status::Cancelled("Expected {} {} to be returned by expression {}",
to_string_lambda(_assertion), _desired_num_rows, _subquery_string);
}
- RETURN_IF_ERROR(vectorized::VExprContext::filter_block(local_state._conjuncts, block,
- block->columns()));
+ RETURN_IF_ERROR(local_state.filter_block(local_state._conjuncts, block, block->columns()));
return Status::OK();
}
diff --git a/be/src/pipeline/exec/data_queue.h b/be/src/pipeline/exec/data_queue.h
index f5bd84cc278..d8a22888d9b 100644
--- a/be/src/pipeline/exec/data_queue.h
+++ b/be/src/pipeline/exec/data_queue.h
@@ -72,6 +72,8 @@ public:
void set_max_blocks_in_sub_queue(int64_t max_blocks) { _max_blocks_in_sub_queue = max_blocks; }
+ void set_low_memory_mode() { _max_blocks_in_sub_queue = 1; }
+
private:
std::vector<std::unique_ptr<std::mutex>> _queue_blocks_lock;
std::vector<std::deque<std::unique_ptr<vectorized::Block>>> _queue_blocks;
diff --git a/be/src/pipeline/exec/datagen_operator.cpp b/be/src/pipeline/exec/datagen_operator.cpp
index 93b3d058154..bfd1085e535 100644
--- a/be/src/pipeline/exec/datagen_operator.cpp
+++ b/be/src/pipeline/exec/datagen_operator.cpp
@@ -68,9 +68,9 @@ Status DataGenSourceOperatorX::get_block(RuntimeState* state, vectorized::Block*
RETURN_IF_CANCELLED(state);
auto& local_state = get_local_state(state);
SCOPED_TIMER(local_state.exec_time_counter());
+ SCOPED_PEAK_MEM(&local_state.estimate_memory_usage());
Status res = local_state._table_func->get_next(state, block, eos);
- RETURN_IF_ERROR(vectorized::VExprContext::filter_block(local_state._conjuncts, block,
- block->columns()));
+ RETURN_IF_ERROR(local_state.filter_block(local_state._conjuncts, block, block->columns()));
local_state.reached_limit(block, eos);
return res;
}
diff --git a/be/src/pipeline/exec/distinct_streaming_aggregation_operator.cpp b/be/src/pipeline/exec/distinct_streaming_aggregation_operator.cpp
index ed4a39f10e2..79347064048 100644
--- a/be/src/pipeline/exec/distinct_streaming_aggregation_operator.cpp
+++ b/be/src/pipeline/exec/distinct_streaming_aggregation_operator.cpp
@@ -201,6 +201,10 @@ Status DistinctStreamingAggLocalState::_distinct_pre_agg_with_serialized_key(
int rows = in_block->rows();
_distinct_row.clear();
_distinct_row.reserve(rows);
+ if (_parent->cast<DistinctStreamingAggOperatorX>()._is_streaming_preagg &&
+ state()->get_query_ctx()->low_memory_mode()) {
+ _stop_emplace_flag = true;
+ }
if (!_stop_emplace_flag) {
_emplace_into_hash_table_to_distinct(_distinct_row, key_columns, rows);
@@ -456,8 +460,7 @@ Status DistinctStreamingAggOperatorX::pull(RuntimeState* state, vectorized::Bloc
local_state._make_nullable_output_key(block);
if (!_is_streaming_preagg) {
// dispose the having clause, should not be execute in prestreaming agg
- RETURN_IF_ERROR(vectorized::VExprContext::filter_block(local_state._conjuncts, block,
- block->columns()));
+ RETURN_IF_ERROR(local_state.filter_block(local_state._conjuncts, block, block->columns()));
}
local_state.add_num_rows_returned(block->rows());
// If the limit is not reached, it is important to ensure that _aggregated_block is empty
diff --git a/be/src/pipeline/exec/exchange_sink_buffer.cpp b/be/src/pipeline/exec/exchange_sink_buffer.cpp
index ff06bc37e5d..f0f66774092 100644
--- a/be/src/pipeline/exec/exchange_sink_buffer.cpp
+++ b/be/src/pipeline/exec/exchange_sink_buffer.cpp
@@ -66,8 +66,8 @@ void BroadcastPBlockHolderMemLimiter::acquire(BroadcastPBlockHolder& holder) {
auto size = holder._pblock->column_values().size();
_total_queue_buffer_size += size;
_total_queue_blocks_count++;
- if (_total_queue_buffer_size >= config::exchg_node_buffer_size_bytes ||
- _total_queue_blocks_count >= config::num_broadcast_buffer) {
+ if (_total_queue_buffer_size >= _total_queue_buffer_size_limit ||
+ _total_queue_blocks_count >= _total_queue_blocks_count_limit) {
_broadcast_dependency->block();
}
}
@@ -88,7 +88,7 @@ void BroadcastPBlockHolderMemLimiter::release(const BroadcastPBlockHolder& holde
namespace pipeline {
ExchangeSinkBuffer::ExchangeSinkBuffer(PUniqueId query_id, PlanNodeId dest_node_id, int send_id,
- int be_number, RuntimeState* state,
+ PlanNodeId node_id, int be_number, RuntimeState* state,
ExchangeSinkLocalState* parent)
: HasTaskExecutionCtx(state),
_queue_capacity(0),
@@ -96,6 +96,7 @@ ExchangeSinkBuffer::ExchangeSinkBuffer(PUniqueId query_id, PlanNodeId dest_node_
_query_id(query_id),
_dest_node_id(dest_node_id),
_sender_id(send_id),
+ _node_id(node_id),
_be_number(be_number),
_state(state),
_context(state->get_query_ctx()),
@@ -429,6 +430,8 @@ void ExchangeSinkBuffer::_ended(InstanceLoId id) {
}
void ExchangeSinkBuffer::_failed(InstanceLoId id, const std::string& err) {
+ LOG(INFO) << "send rpc failed, instance id: " << id << ", _dest_node_id: " << _dest_node_id
+ << ",_sender_id: " << _sender_id << ", node id: " << _node_id << ", err: " << err;
_is_finishing = true;
_context->cancel(Status::Cancelled(err));
std::unique_lock<std::mutex> lock(*_instance_to_package_queue_mutex[id]);
diff --git a/be/src/pipeline/exec/exchange_sink_buffer.h b/be/src/pipeline/exec/exchange_sink_buffer.h
index 2d30a492a0d..23ba2220bec 100644
--- a/be/src/pipeline/exec/exchange_sink_buffer.h
+++ b/be/src/pipeline/exec/exchange_sink_buffer.h
@@ -22,7 +22,6 @@
#include <gen_cpp/internal_service.pb.h>
#include <gen_cpp/types.pb.h>
#include <parallel_hashmap/phmap.h>
-#include <stdint.h>
#include <atomic>
#include <list>
@@ -84,14 +83,23 @@ class BroadcastPBlockHolderMemLimiter
public:
BroadcastPBlockHolderMemLimiter() = delete;
- BroadcastPBlockHolderMemLimiter(std::shared_ptr<pipeline::Dependency>& broadcast_dependency) {
+ BroadcastPBlockHolderMemLimiter(std::shared_ptr<pipeline::Dependency>& broadcast_dependency)
+ : _total_queue_buffer_size_limit(config::exchg_node_buffer_size_bytes),
+ _total_queue_blocks_count_limit(config::num_broadcast_buffer) {
_broadcast_dependency = broadcast_dependency;
}
+ void set_low_memory_mode() {
+ _total_queue_buffer_size_limit = 1024 * 1024;
+ _total_queue_blocks_count_limit = 1;
+ }
+
void acquire(BroadcastPBlockHolder& holder);
void release(const BroadcastPBlockHolder& holder);
private:
+ std::atomic_int64_t _total_queue_buffer_size_limit {0};
+ std::atomic_int64_t _total_queue_blocks_count_limit {0};
std::atomic_int64_t _total_queue_buffer_size {0};
std::atomic_int64_t _total_queue_blocks_count {0};
std::shared_ptr<pipeline::Dependency> _broadcast_dependency;
@@ -177,8 +185,8 @@ struct ExchangeRpcContext {
// Each ExchangeSinkOperator have one ExchangeSinkBuffer
class ExchangeSinkBuffer final : public HasTaskExecutionCtx {
public:
- ExchangeSinkBuffer(PUniqueId query_id, PlanNodeId dest_node_id, int send_id, int be_number,
- RuntimeState* state, ExchangeSinkLocalState* parent);
+ ExchangeSinkBuffer(PUniqueId query_id, PlanNodeId dest_node_id, int send_id, PlanNodeId node_id,
+ int be_number, RuntimeState* state, ExchangeSinkLocalState* parent);
~ExchangeSinkBuffer() override = default;
void register_sink(TUniqueId);
@@ -194,15 +202,13 @@ public:
_finish_dependency = finish_dependency;
}
- void set_broadcast_dependency(std::shared_ptr<Dependency> broadcast_dependency) {
- _broadcast_dependency = broadcast_dependency;
- }
-
void set_should_stop() {
_should_stop = true;
_set_ready_to_finish(_busy_channels == 0);
}
+ void set_low_memory_mode() { _queue_capacity = 1; }
+
private:
friend class ExchangeSinkLocalState;
void _set_ready_to_finish(bool all_done);
@@ -212,7 +218,7 @@ private:
// store data in non-broadcast shuffle
phmap::flat_hash_map<InstanceLoId, std::queue<TransmitInfo, std::list<TransmitInfo>>>
_instance_to_package_queue;
- size_t _queue_capacity;
+ std::atomic<size_t> _queue_capacity;
// store data in broadcast shuffle
phmap::flat_hash_map<InstanceLoId,
std::queue<BroadcastTransmitInfo, std::list<BroadcastTransmitInfo>>>
@@ -235,6 +241,7 @@ private:
PlanNodeId _dest_node_id;
// Sender instance id, unique within a fragment. StreamSender save the variable
int _sender_id;
+ PlanNodeId _node_id;
int _be_number;
std::atomic<int64_t> _rpc_count = 0;
RuntimeState* _state = nullptr;
@@ -254,7 +261,6 @@ private:
std::atomic<int> _total_queue_size = 0;
std::shared_ptr<Dependency> _queue_dependency = nullptr;
std::shared_ptr<Dependency> _finish_dependency = nullptr;
- std::shared_ptr<Dependency> _broadcast_dependency = nullptr;
std::atomic<bool> _should_stop = false;
ExchangeSinkLocalState* _parent = nullptr;
};
diff --git a/be/src/pipeline/exec/exchange_sink_operator.cpp b/be/src/pipeline/exec/exchange_sink_operator.cpp
index 78606d37530..ca80a41cad4 100644
--- a/be/src/pipeline/exec/exchange_sink_operator.cpp
+++ b/be/src/pipeline/exec/exchange_sink_operator.cpp
@@ -134,7 +134,8 @@ Status ExchangeSinkLocalState::open(RuntimeState* state) {
if (!only_local_exchange) {
_sink_buffer = std::make_unique<ExchangeSinkBuffer>(id, p._dest_node_id, _sender_id,
- _state->be_number(), state, this);
+ _parent->node_id(), _state->be_number(),
+ state, this);
register_channels(_sink_buffer.get());
_queue_dependency = Dependency::create_shared(_parent->operator_id(), _parent->node_id(),
"ExchangeSinkQueueDependency", true);
@@ -146,7 +147,6 @@ Status ExchangeSinkLocalState::open(RuntimeState* state) {
!only_local_exchange) {
_broadcast_dependency = Dependency::create_shared(
_parent->operator_id(), _parent->node_id(), "BroadcastDependency", true);
- _sink_buffer->set_broadcast_dependency(_broadcast_dependency);
_broadcast_pb_mem_limiter =
vectorized::BroadcastPBlockHolderMemLimiter::create_shared(_broadcast_dependency);
} else if (local_size > 0) {
@@ -381,6 +381,11 @@ Status ExchangeSinkOperatorX::sink(RuntimeState* state, vectorized::Block* block
local_state._memory_used_counter->value());
});
+ // When `local_state.only_local_exchange` the `sink_buffer` is nullptr.
+ if (state->get_query_ctx()->low_memory_mode() && local_state._sink_buffer != nullptr) {
+ local_state._sink_buffer->set_low_memory_mode();
+ }
+
if (_part_type == TPartitionType::UNPARTITIONED || local_state.channels.size() == 1) {
// 1. serialize depends on it is not local exchange
// 2. send block
@@ -418,6 +423,9 @@ Status ExchangeSinkOperatorX::sink(RuntimeState* state, vectorized::Block* block
block_holder->reset_block();
}
+ if (state->get_query_ctx()->low_memory_mode()) {
+ local_state._broadcast_pb_mem_limiter->set_low_memory_mode();
+ }
local_state._broadcast_pb_mem_limiter->acquire(*block_holder);
size_t idx = 0;
diff --git a/be/src/pipeline/exec/exchange_source_operator.cpp b/be/src/pipeline/exec/exchange_source_operator.cpp
index ca5194e7077..ff25a252cca 100644
--- a/be/src/pipeline/exec/exchange_source_operator.cpp
+++ b/be/src/pipeline/exec/exchange_source_operator.cpp
@@ -148,8 +148,7 @@ Status ExchangeSourceOperatorX::get_block(RuntimeState* state, vectorized::Block
return Status::OK();
}
auto status = local_state.stream_recvr->get_next(block, eos);
- RETURN_IF_ERROR(doris::vectorized::VExprContext::filter_block(local_state.conjuncts(), block,
- block->columns()));
+ RETURN_IF_ERROR(local_state.filter_block(local_state.conjuncts(), block, block->columns()));
// In vsortrunmerger, it will set eos=true, and block not empty
// so that eos==true, could not make sure that block not have valid data
if (!*eos || block->rows() > 0) {
diff --git a/be/src/pipeline/exec/hashjoin_build_sink.cpp b/be/src/pipeline/exec/hashjoin_build_sink.cpp
index 39039dc1885..7f8f2da35cb 100644
--- a/be/src/pipeline/exec/hashjoin_build_sink.cpp
+++ b/be/src/pipeline/exec/hashjoin_build_sink.cpp
@@ -110,6 +110,40 @@ Status HashJoinBuildSinkLocalState::open(RuntimeState* state) {
return Status::OK();
}
+size_t HashJoinBuildSinkLocalState::get_reserve_mem_size(RuntimeState* state) {
+ if (!_should_build_hash_table) {
+ return 0;
+ }
+
+ if (_shared_state->build_block) {
+ return 0;
+ }
+
+ size_t size_to_reserve = 0;
+
+ if (!_build_side_mutable_block.empty()) {
+ const auto bytes = _build_side_mutable_block.bytes();
+ const auto allocated_bytes = _build_side_mutable_block.allocated_bytes();
+ if (allocated_bytes != 0 && ((bytes * 100) / allocated_bytes) >= 85) {
+ size_to_reserve += bytes;
+ }
+ }
+
+ const size_t rows = _build_side_mutable_block.rows() + state->batch_size();
+ size_t bucket_size = JoinHashTable<StringRef>::calc_bucket_size(rows);
+
+ size_to_reserve += bucket_size * sizeof(uint32_t); // JoinHashTable::first
+ size_to_reserve += rows * sizeof(uint32_t); // JoinHashTable::next
+
+ auto& p = _parent->cast<HashJoinBuildSinkOperatorX>();
+ if (p._join_op == TJoinOp::FULL_OUTER_JOIN || p._join_op == TJoinOp::RIGHT_OUTER_JOIN ||
+ p._join_op == TJoinOp::RIGHT_ANTI_JOIN || p._join_op == TJoinOp::RIGHT_SEMI_JOIN) {
+ size_to_reserve += rows * sizeof(uint8_t); // JoinHashTable::visited
+ }
+ size_to_reserve += _evaluate_mem_usage;
+ return size_to_reserve;
+}
+
Status HashJoinBuildSinkLocalState::close(RuntimeState* state, Status exec_status) {
if (_closed) {
return Status::OK();
@@ -187,6 +221,7 @@ Status HashJoinBuildSinkLocalState::_do_evaluate(vectorized::Block& block,
vectorized::VExprContextSPtrs& exprs,
RuntimeProfile::Counter& expr_call_timer,
std::vector<int>& res_col_ids) {
+ auto origin_size = block.allocated_bytes();
for (size_t i = 0; i < exprs.size(); ++i) {
int result_col_id = -1;
// execute build column
@@ -200,6 +235,8 @@ Status HashJoinBuildSinkLocalState::_do_evaluate(vectorized::Block& block,
block.get_by_position(result_col_id).column->convert_to_full_column_if_const();
res_col_ids[i] = result_col_id;
}
+
+ _evaluate_mem_usage = block.allocated_bytes() - origin_size;
return Status::OK();
}
@@ -261,6 +298,10 @@ Status HashJoinBuildSinkLocalState::process_build_block(RuntimeState* state,
if (UNLIKELY(rows == 0)) {
return Status::OK();
}
+
+ LOG(INFO) << "build block rows: " << block.rows() << ", columns count: " << block.columns()
+ << ", bytes/allocated_bytes: " << block.bytes() << "/" << block.allocated_bytes();
+
COUNTER_UPDATE(_build_rows_counter, rows);
block.replace_if_overflow();
@@ -327,7 +368,6 @@ Status HashJoinBuildSinkLocalState::process_build_block(RuntimeState* state,
vectorized::make_bool_variant(_build_side_ignore_null),
vectorized::make_bool_variant(p._short_circuit_for_null_in_build_side),
vectorized::make_bool_variant((p._have_other_join_conjunct)));
-
return st;
}
@@ -655,4 +695,14 @@ Status HashJoinBuildSinkOperatorX::sink(RuntimeState* state, vectorized::Block*
return Status::OK();
}
+size_t HashJoinBuildSinkOperatorX::get_reserve_mem_size(RuntimeState* state) {
+ auto& local_state = get_local_state(state);
+ return local_state.get_reserve_mem_size(state);
+}
+
+size_t HashJoinBuildSinkOperatorX::get_memory_usage(RuntimeState* state) const {
+ auto& local_state = get_local_state(state);
+ return local_state._memory_used_counter->value();
+}
+
} // namespace doris::pipeline
diff --git a/be/src/pipeline/exec/hashjoin_build_sink.h b/be/src/pipeline/exec/hashjoin_build_sink.h
index 2b0390d7596..0ced9a77207 100644
--- a/be/src/pipeline/exec/hashjoin_build_sink.h
+++ b/be/src/pipeline/exec/hashjoin_build_sink.h
@@ -54,6 +54,8 @@ public:
Status close(RuntimeState* state, Status exec_status) override;
+ [[nodiscard]] size_t get_reserve_mem_size(RuntimeState* state);
+
protected:
void _hash_table_init(RuntimeState* state);
void _set_build_ignore_flag(vectorized::Block& block, const std::vector<int>& res_col_ids);
@@ -75,6 +77,8 @@ protected:
bool _should_build_hash_table = true;
+ size_t _evaluate_mem_usage = 0;
+
size_t _build_side_rows = 0;
vectorized::MutableBlock _build_side_mutable_block;
@@ -120,6 +124,10 @@ public:
Status sink(RuntimeState* state, vectorized::Block* in_block, bool eos) override;
+ size_t get_reserve_mem_size(RuntimeState* state) override;
+
+ [[nodiscard]] size_t get_memory_usage(RuntimeState* state) const;
+
bool should_dry_run(RuntimeState* state) override {
return _is_broadcast_join && !state->get_sink_local_state()
->cast<HashJoinBuildSinkLocalState>()
diff --git a/be/src/pipeline/exec/hashjoin_probe_operator.cpp b/be/src/pipeline/exec/hashjoin_probe_operator.cpp
index fc1b1cccb1c..ed173662d96 100644
--- a/be/src/pipeline/exec/hashjoin_probe_operator.cpp
+++ b/be/src/pipeline/exec/hashjoin_probe_operator.cpp
@@ -345,6 +345,7 @@ Status HashJoinProbeOperatorX::pull(doris::RuntimeState* state, vectorized::Bloc
return st;
}
+ local_state._estimate_memory_usage += temp_block.allocated_bytes();
RETURN_IF_ERROR(
local_state.filter_data_and_build_output(state, output_block, eos, &temp_block));
// Here make _join_block release the columns' ptr
@@ -443,8 +444,7 @@ Status HashJoinProbeLocalState::filter_data_and_build_output(RuntimeState* state
}
{
SCOPED_TIMER(_join_filter_timer);
- RETURN_IF_ERROR(vectorized::VExprContext::filter_block(_conjuncts, temp_block,
- temp_block->columns()));
+ RETURN_IF_ERROR(filter_block(_conjuncts, temp_block, temp_block->columns()));
}
RETURN_IF_ERROR(_build_output_block(temp_block, output_block, false));
@@ -485,8 +485,12 @@ Status HashJoinProbeOperatorX::push(RuntimeState* state, vectorized::Block* inpu
auto& local_state = get_local_state(state);
local_state.prepare_for_next();
local_state._probe_eos = eos;
- if (input_block->rows() > 0) {
- COUNTER_UPDATE(local_state._probe_rows_counter, input_block->rows());
+
+ const auto rows = input_block->rows();
+ size_t origin_size = input_block->allocated_bytes();
+
+ if (rows > 0) {
+ COUNTER_UPDATE(local_state._probe_rows_counter, rows);
std::vector<int> res_col_ids(local_state._probe_expr_ctxs.size());
RETURN_IF_ERROR(_do_evaluate(*input_block, local_state._probe_expr_ctxs,
*local_state._probe_expr_call_timer, res_col_ids));
@@ -497,6 +501,8 @@ Status HashJoinProbeOperatorX::push(RuntimeState* state, vectorized::Block* inpu
RETURN_IF_ERROR(local_state._extract_join_column(*input_block, res_col_ids));
+ local_state._estimate_memory_usage += (input_block->allocated_bytes() - origin_size);
+
if (&local_state._probe_block != input_block) {
input_block->swap(local_state._probe_block);
COUNTER_SET(local_state._memory_used_counter,
diff --git a/be/src/pipeline/exec/hashjoin_probe_operator.h b/be/src/pipeline/exec/hashjoin_probe_operator.h
index dde9c00dfe4..3d22dbf2ad3 100644
--- a/be/src/pipeline/exec/hashjoin_probe_operator.h
+++ b/be/src/pipeline/exec/hashjoin_probe_operator.h
@@ -116,6 +116,8 @@ private:
std::unique_ptr<HashTableCtxVariants> _process_hashtable_ctx_variants =
std::make_unique<HashTableCtxVariants>();
+ ssize_t _estimated_mem_in_push = -1;
+
RuntimeProfile::Counter* _probe_expr_call_timer = nullptr;
RuntimeProfile::Counter* _probe_next_timer = nullptr;
RuntimeProfile::Counter* _probe_side_output_timer = nullptr;
diff --git a/be/src/pipeline/exec/join/process_hash_table_probe.h b/be/src/pipeline/exec/join/process_hash_table_probe.h
index bf4a4d5763c..9649ba83574 100644
--- a/be/src/pipeline/exec/join/process_hash_table_probe.h
+++ b/be/src/pipeline/exec/join/process_hash_table_probe.h
@@ -23,6 +23,7 @@
#include "vec/columns/column.h"
#include "vec/columns/columns_number.h"
#include "vec/common/arena.h"
+#include "vec/common/custom_allocator.h"
namespace doris {
namespace vectorized {
@@ -73,7 +74,7 @@ struct ProcessHashTableProbe {
// each matching join column need to be processed by other join conjunct. so the struct of mutable block
// and output block may be different
// The output result is determined by the other join conjunct result and same_to_prev struct
- Status do_other_join_conjuncts(vectorized::Block* output_block, std::vector<uint8_t>& visited,
+ Status do_other_join_conjuncts(vectorized::Block* output_block, DorisVector<uint8_t>& visited,
bool has_null_in_build_side);
template <bool with_other_conjuncts>
diff --git a/be/src/pipeline/exec/join/process_hash_table_probe_impl.h b/be/src/pipeline/exec/join/process_hash_table_probe_impl.h
index 8bee9fc14ae..b431344055e 100644
--- a/be/src/pipeline/exec/join/process_hash_table_probe_impl.h
+++ b/be/src/pipeline/exec/join/process_hash_table_probe_impl.h
@@ -26,6 +26,7 @@
#include "util/simd/bits.h"
#include "vec/columns/column_filter_helper.h"
#include "vec/columns/column_nullable.h"
+#include "vec/common/custom_allocator.h"
#include "vec/exprs/vexpr_context.h"
namespace doris::pipeline {
@@ -494,7 +495,7 @@ Status ProcessHashTableProbe<JoinOpType>::do_mark_join_conjuncts(vectorized::Blo
template <int JoinOpType>
Status ProcessHashTableProbe<JoinOpType>::do_other_join_conjuncts(vectorized::Block* output_block,
- std::vector<uint8_t>& visited,
+ DorisVector<uint8_t>& visited,
bool has_null_in_build_side) {
// dispose the other join conjunct exec
auto row_count = output_block->rows();
diff --git a/be/src/pipeline/exec/multi_cast_data_stream_source.cpp b/be/src/pipeline/exec/multi_cast_data_stream_source.cpp
index 49dc384a4e7..dd9d2ce45eb 100644
--- a/be/src/pipeline/exec/multi_cast_data_stream_source.cpp
+++ b/be/src/pipeline/exec/multi_cast_data_stream_source.cpp
@@ -90,8 +90,8 @@ Status MultiCastDataStreamerSourceOperatorX::get_block(RuntimeState* state,
output_block, eos));
if (!local_state._conjuncts.empty()) {
- RETURN_IF_ERROR(vectorized::VExprContext::filter_block(local_state._conjuncts, output_block,
- output_block->columns()));
+ RETURN_IF_ERROR(local_state.filter_block(local_state._conjuncts, output_block,
+ output_block->columns()));
}
if (!local_state._output_expr_contexts.empty() && output_block->rows() > 0) {
diff --git a/be/src/pipeline/exec/nested_loop_join_probe_operator.cpp b/be/src/pipeline/exec/nested_loop_join_probe_operator.cpp
index 9546ed8df56..0a1795544dd 100644
--- a/be/src/pipeline/exec/nested_loop_join_probe_operator.cpp
+++ b/be/src/pipeline/exec/nested_loop_join_probe_operator.cpp
@@ -466,6 +466,7 @@ Status NestedLoopJoinProbeOperatorX::push(doris::RuntimeState* state, vectorized
bool eos) const {
auto& local_state = get_local_state(state);
COUNTER_UPDATE(local_state._probe_rows_counter, block->rows());
+ SCOPED_PEAK_MEM(&local_state.estimate_memory_usage());
local_state._cur_probe_row_visited_flags.resize(block->rows());
std::fill(local_state._cur_probe_row_visited_flags.begin(),
local_state._cur_probe_row_visited_flags.end(), 0);
@@ -492,10 +493,12 @@ Status NestedLoopJoinProbeOperatorX::pull(RuntimeState* state, vectorized::Block
bool* eos) const {
auto& local_state = get_local_state(state);
if (_is_output_left_side_only) {
+ SCOPED_PEAK_MEM(&local_state._estimate_memory_usage);
RETURN_IF_ERROR(local_state._build_output_block(local_state._child_block.get(), block));
*eos = local_state._shared_state->left_side_eos;
local_state._need_more_input_data = !local_state._shared_state->left_side_eos;
} else {
+ SCOPED_PEAK_MEM(&local_state._estimate_memory_usage);
*eos = ((_match_all_build || _is_right_semi_anti)
? local_state._output_null_idx_build_side ==
local_state._shared_state->build_blocks.size() &&
@@ -511,8 +514,8 @@ Status NestedLoopJoinProbeOperatorX::pull(RuntimeState* state, vectorized::Block
local_state.add_tuple_is_null_column(&tmp_block);
{
SCOPED_TIMER(local_state._join_filter_timer);
- RETURN_IF_ERROR(vectorized::VExprContext::filter_block(
- local_state._conjuncts, &tmp_block, tmp_block.columns()));
+ RETURN_IF_ERROR(local_state.filter_block(local_state._conjuncts, &tmp_block,
+ tmp_block.columns()));
}
RETURN_IF_ERROR(local_state._build_output_block(&tmp_block, block, false));
local_state._reset_tuple_is_null_column();
@@ -528,6 +531,7 @@ Status NestedLoopJoinProbeOperatorX::pull(RuntimeState* state, vectorized::Block
state, join_op_variants);
};
SCOPED_TIMER(local_state._loop_join_timer);
+ SCOPED_PEAK_MEM(&local_state._estimate_memory_usage);
RETURN_IF_ERROR(std::visit(
func, local_state._shared_state->join_op_variants,
vectorized::make_bool_variant(_match_all_build || _is_right_semi_anti),
diff --git a/be/src/pipeline/exec/operator.cpp b/be/src/pipeline/exec/operator.cpp
index 5a13fdcbd84..2be50ac6e40 100644
--- a/be/src/pipeline/exec/operator.cpp
+++ b/be/src/pipeline/exec/operator.cpp
@@ -242,6 +242,14 @@ void PipelineXLocalStateBase::clear_origin_block() {
_origin_block.clear_column_data(_parent->intermediate_row_desc().num_materialized_slots());
}
+Status PipelineXLocalStateBase::filter_block(const vectorized::VExprContextSPtrs& expr_contexts,
+ vectorized::Block* block, size_t column_to_keep) {
+ RETURN_IF_ERROR(vectorized::VExprContext::filter_block(expr_contexts, block, column_to_keep));
+
+ _estimate_memory_usage += vectorized::VExprContext::get_memory_usage(expr_contexts);
+ return Status::OK();
+}
+
Status OperatorXBase::do_projections(RuntimeState* state, vectorized::Block* origin_block,
vectorized::Block* output_block) const {
auto* local_state = state->get_local_state(operator_id());
@@ -254,11 +262,14 @@ Status OperatorXBase::do_projections(RuntimeState* state, vectorized::Block* ori
vectorized::Block input_block = *origin_block;
std::vector<int> result_column_ids;
+ size_t bytes_usage = 0;
for (const auto& projections : local_state->_intermediate_projections) {
result_column_ids.resize(projections.size());
for (int i = 0; i < projections.size(); i++) {
RETURN_IF_ERROR(projections[i]->execute(&input_block, &result_column_ids[i]));
}
+
+ bytes_usage += input_block.allocated_bytes();
input_block.shuffle_columns(result_column_ids);
}
@@ -269,12 +280,14 @@ Status OperatorXBase::do_projections(RuntimeState* state, vectorized::Block* ori
auto& null_column = reinterpret_cast<vectorized::ColumnNullable&>(*to);
null_column.get_nested_column().insert_range_from(*from, 0, rows);
null_column.get_null_map_column().get_data().resize_fill(rows, 0);
+ bytes_usage += null_column.allocated_bytes();
} else {
to = make_nullable(from, false)->assume_mutable();
}
} else {
if (_keep_origin || !from->is_exclusive()) {
to->insert_range_from(*from, 0, rows);
+ bytes_usage += from->allocated_bytes();
} else {
to = from->assume_mutable();
}
@@ -287,18 +300,24 @@ Status OperatorXBase::do_projections(RuntimeState* state, vectorized::Block* ori
*_output_row_descriptor);
if (rows != 0) {
auto& mutable_columns = mutable_block.mutable_columns();
+ const size_t origin_columns_count = input_block.columns();
DCHECK(mutable_columns.size() == local_state->_projections.size());
for (int i = 0; i < mutable_columns.size(); ++i) {
auto result_column_id = -1;
RETURN_IF_ERROR(local_state->_projections[i]->execute(&input_block, &result_column_id));
auto column_ptr = input_block.get_by_position(result_column_id)
.column->convert_to_full_column_if_const();
+ if (result_column_id >= origin_columns_count) {
+ bytes_usage += column_ptr->allocated_bytes();
+ }
insert_column_datas(mutable_columns[i], column_ptr, rows);
}
DCHECK(mutable_block.rows() == rows);
output_block->set_columns(std::move(mutable_columns));
}
+ local_state->_estimate_memory_usage += bytes_usage;
+
return Status::OK();
}
@@ -374,7 +393,7 @@ std::string DataSinkOperatorXBase::debug_string(RuntimeState* state, int indenta
Status DataSinkOperatorXBase::init(const TDataSink& tsink) {
std::string op_name = "UNKNOWN_SINK";
- std::map<int, const char*>::const_iterator it = _TDataSinkType_VALUES_TO_NAMES.find(tsink.type);
+ auto it = _TDataSinkType_VALUES_TO_NAMES.find(tsink.type);
if (it != _TDataSinkType_VALUES_TO_NAMES.end()) {
op_name = it->second;
diff --git a/be/src/pipeline/exec/operator.h b/be/src/pipeline/exec/operator.h
index 9c26341d698..639a19d9aac 100644
--- a/be/src/pipeline/exec/operator.h
+++ b/be/src/pipeline/exec/operator.h
@@ -20,6 +20,7 @@
#include <fmt/format.h>
#include <glog/logging.h>
+#include <atomic>
#include <cstdint>
#include <functional>
#include <memory>
@@ -31,9 +32,12 @@
#include "common/status.h"
#include "pipeline/dependency.h"
#include "pipeline/exec/operator.h"
+#include "pipeline/exec/spill_utils.h"
#include "pipeline/local_exchange/local_exchanger.h"
+#include "runtime/memory/mem_tracker.h"
#include "runtime/query_context.h"
#include "runtime/runtime_state.h"
+#include "runtime/thread_context.h"
#include "util/runtime_profile.h"
#include "vec/core/block.h"
#include "vec/runtime/vdata_stream_recvr.h"
@@ -105,7 +109,16 @@ public:
virtual size_t revocable_mem_size(RuntimeState* state) const { return 0; }
- virtual Status revoke_memory(RuntimeState* state) { return Status::OK(); }
+ // If this method is not overwrite by child, its default value is 1MB
+ [[nodiscard]] virtual size_t get_reserve_mem_size(RuntimeState* state) {
+ return state->minimum_operator_memory_required_bytes();
+ }
+
+ virtual Status revoke_memory(RuntimeState* state,
+ const std::shared_ptr<SpillContext>& spill_context) {
+ return Status::OK();
+ }
+
[[nodiscard]] virtual bool require_data_distribution() const { return false; }
OperatorPtr child() { return _child; }
[[nodiscard]] bool followed_by_shuffled_operator() const {
@@ -180,6 +193,15 @@ public:
std::shared_ptr<QueryStatistics> get_query_statistics_ptr() { return _query_statistics; }
+ Status filter_block(const vectorized::VExprContextSPtrs& expr_contexts,
+ vectorized::Block* block, size_t column_to_keep);
+
+ int64_t& estimate_memory_usage() { return _estimate_memory_usage; }
+
+ void reset_estimate_memory_usage() { _estimate_memory_usage = 0; }
+
+ bool low_memory_mode() { return _state->get_query_ctx()->low_memory_mode(); }
+
protected:
friend class OperatorXBase;
template <typename LocalStateType>
@@ -187,6 +209,7 @@ protected:
ObjectPool* _pool = nullptr;
int64_t _num_rows_returned {0};
+ int64_t _estimate_memory_usage {0};
std::unique_ptr<RuntimeProfile> _runtime_profile;
@@ -269,25 +292,21 @@ public:
Status init(RuntimeState* state, LocalStateInfo& info) override {
RETURN_IF_ERROR(PipelineXLocalState<SharedStateArg>::init(state, info));
- _spill_counters = ADD_LABEL_COUNTER_WITH_LEVEL(Base::profile(), "Spill", 1);
- _spill_recover_time =
- ADD_CHILD_TIMER_WITH_LEVEL(Base::profile(), "SpillRecoverTime", "Spill", 1);
- _spill_read_data_time =
- ADD_CHILD_TIMER_WITH_LEVEL(Base::profile(), "SpillReadDataTime", "Spill", 1);
- _spill_deserialize_time =
- ADD_CHILD_TIMER_WITH_LEVEL(Base::profile(), "SpillDeserializeTime", "Spill", 1);
- _spill_read_bytes = ADD_CHILD_COUNTER_WITH_LEVEL(Base::profile(), "SpillReadDataSize",
- TUnit::BYTES, "Spill", 1);
+ _spill_timer = ADD_TIMER_WITH_LEVEL(Base::profile(), "SpillTime", 1);
+ _spill_recover_time = ADD_TIMER_WITH_LEVEL(Base::profile(), "SpillRecoverTime", 1);
+ _spill_read_data_time = ADD_TIMER_WITH_LEVEL(Base::profile(), "SpillReadDataTime", 1);
+ _spill_deserialize_time = ADD_TIMER_WITH_LEVEL(Base::profile(), "SpillDeserializeTime", 1);
+ _spill_read_bytes =
+ ADD_COUNTER_WITH_LEVEL(Base::profile(), "SpillReadDataSize", TUnit::BYTES, 1);
_spill_wait_in_queue_timer =
- ADD_CHILD_TIMER_WITH_LEVEL(Base::profile(), "SpillWaitInQueueTime", "Spill", 1);
+ ADD_TIMER_WITH_LEVEL(Base::profile(), "SpillWaitInQueueTime", 1);
_spill_write_wait_io_timer =
- ADD_CHILD_TIMER_WITH_LEVEL(Base::profile(), "SpillWriteWaitIOTime", "Spill", 1);
- _spill_read_wait_io_timer =
- ADD_CHILD_TIMER_WITH_LEVEL(Base::profile(), "SpillReadWaitIOTime", "Spill", 1);
+ ADD_TIMER_WITH_LEVEL(Base::profile(), "SpillWriteWaitIOTime", 1);
+ _spill_read_wait_io_timer = ADD_TIMER_WITH_LEVEL(Base::profile(), "SpillReadWaitIOTime", 1);
return Status::OK();
}
- RuntimeProfile::Counter* _spill_counters = nullptr;
+ RuntimeProfile::Counter* _spill_timer = nullptr;
RuntimeProfile::Counter* _spill_recover_time;
RuntimeProfile::Counter* _spill_read_data_time;
RuntimeProfile::Counter* _spill_deserialize_time;
@@ -342,6 +361,7 @@ public:
RuntimeProfile::Counter* exec_time_counter() { return _exec_timer; }
RuntimeProfile::Counter* memory_used_counter() { return _memory_used_counter; }
RuntimeProfile::Counter* peak_memory_usage_counter() { return _peak_memory_usage_counter; }
+
virtual std::vector<Dependency*> dependencies() const { return {nullptr}; }
// override in exchange sink , AsyncWriterSink
@@ -415,6 +435,7 @@ public:
protected:
Dependency* _dependency = nullptr;
+ std::shared_ptr<Dependency> _spill_dependency;
SharedStateType* _shared_state = nullptr;
private:
@@ -425,13 +446,13 @@ private:
class DataSinkOperatorXBase : public OperatorBase {
public:
DataSinkOperatorXBase(const int operator_id, const int node_id)
- : OperatorBase(), _operator_id(operator_id), _node_id(node_id), _dests_id({1}) {}
+ : _operator_id(operator_id), _node_id(node_id), _dests_id({1}) {}
DataSinkOperatorXBase(const int operator_id, const int node_id, const int dest_id)
- : OperatorBase(), _operator_id(operator_id), _node_id(node_id), _dests_id({dest_id}) {}
+ : _operator_id(operator_id), _node_id(node_id), _dests_id({dest_id}) {}
DataSinkOperatorXBase(const int operator_id, const int node_id, std::vector<int>& sources)
- : OperatorBase(), _operator_id(operator_id), _node_id(node_id), _dests_id(sources) {}
+ : _operator_id(operator_id), _node_id(node_id), _dests_id(sources) {}
~DataSinkOperatorXBase() override = default;
@@ -510,6 +531,11 @@ public:
[[nodiscard]] std::string get_name() const override { return _name; }
+ [[nodiscard]] virtual bool try_reserve_memory(RuntimeState* state, vectorized::Block* block,
+ bool eos) {
+ return true;
+ }
+
virtual bool should_dry_run(RuntimeState* state) { return false; }
protected:
@@ -562,26 +588,50 @@ public:
Status init(RuntimeState* state, LocalSinkStateInfo& info) override {
RETURN_IF_ERROR(Base::init(state, info));
- _spill_counters = ADD_LABEL_COUNTER_WITH_LEVEL(Base::profile(), "Spill", 1);
- _spill_timer = ADD_CHILD_TIMER_WITH_LEVEL(Base::profile(), "SpillTime", "Spill", 1);
+ _spill_timer = ADD_TIMER_WITH_LEVEL(Base::profile(), "SpillTime", 1);
_spill_serialize_block_timer =
- ADD_CHILD_TIMER_WITH_LEVEL(Base::profile(), "SpillSerializeBlockTime", "Spill", 1);
- _spill_write_disk_timer =
- ADD_CHILD_TIMER_WITH_LEVEL(Base::profile(), "SpillWriteDiskTime", "Spill", 1);
- _spill_data_size = ADD_CHILD_COUNTER_WITH_LEVEL(Base::profile(), "SpillWriteDataSize",
- TUnit::BYTES, "Spill", 1);
- _spill_block_count = ADD_CHILD_COUNTER_WITH_LEVEL(Base::profile(), "SpillWriteBlockCount",
- TUnit::UNIT, "Spill", 1);
+ ADD_TIMER_WITH_LEVEL(Base::profile(), "SpillSerializeBlockTime", 1);
+ _spill_write_disk_timer = ADD_TIMER_WITH_LEVEL(Base::profile(), "SpillWriteDiskTime", 1);
+ _spill_data_size =
+ ADD_COUNTER_WITH_LEVEL(Base::profile(), "SpillWriteDataSize", TUnit::BYTES, 1);
+ _spill_block_count =
+ ADD_COUNTER_WITH_LEVEL(Base::profile(), "SpillWriteBlockCount", TUnit::UNIT, 1);
_spill_wait_in_queue_timer =
- ADD_CHILD_TIMER_WITH_LEVEL(Base::profile(), "SpillWaitInQueueTime", "Spill", 1);
+ ADD_TIMER_WITH_LEVEL(Base::profile(), "SpillWaitInQueueTime", 1);
_spill_write_wait_io_timer =
- ADD_CHILD_TIMER_WITH_LEVEL(Base::profile(), "SpillWriteWaitIOTime", "Spill", 1);
- _spill_read_wait_io_timer =
- ADD_CHILD_TIMER_WITH_LEVEL(Base::profile(), "SpillReadWaitIOTime", "Spill", 1);
+ ADD_TIMER_WITH_LEVEL(Base::profile(), "SpillWriteWaitIOTime", 1);
+ _spill_read_wait_io_timer = ADD_TIMER_WITH_LEVEL(Base::profile(), "SpillReadWaitIOTime", 1);
+ _spill_max_rows_of_partition =
+ ADD_COUNTER_WITH_LEVEL(Base::profile(), "SpillMaxRowsOfPartition", TUnit::UNIT, 1);
+ _spill_min_rows_of_partition =
+ ADD_COUNTER_WITH_LEVEL(Base::profile(), "SpillMinRowsOfPartition", TUnit::UNIT, 1);
return Status::OK();
}
- RuntimeProfile::Counter* _spill_counters = nullptr;
+ std::vector<Dependency*> dependencies() const override {
+ auto dependencies = Base::dependencies();
+ return dependencies;
+ }
+
+ void update_max_min_rows_counter() {
+ int64_t max_rows = 0;
+ int64_t min_rows = std::numeric_limits<int64_t>::max();
+
+ for (auto rows : _rows_in_partitions) {
+ if (rows > max_rows) {
+ max_rows = rows;
+ }
+ if (rows < min_rows) {
+ min_rows = rows;
+ }
+ }
+
+ COUNTER_SET(_spill_max_rows_of_partition, max_rows);
+ COUNTER_SET(_spill_min_rows_of_partition, min_rows);
+ }
+
+ std::vector<int64_t> _rows_in_partitions;
+
RuntimeProfile::Counter* _spill_timer = nullptr;
RuntimeProfile::Counter* _spill_serialize_block_timer = nullptr;
RuntimeProfile::Counter* _spill_write_disk_timer = nullptr;
@@ -590,6 +640,8 @@ public:
RuntimeProfile::Counter* _spill_wait_in_queue_timer = nullptr;
RuntimeProfile::Counter* _spill_write_wait_io_timer = nullptr;
RuntimeProfile::Counter* _spill_read_wait_io_timer = nullptr;
+ RuntimeProfile::Counter* _spill_max_rows_of_partition = nullptr;
+ RuntimeProfile::Counter* _spill_min_rows_of_partition = nullptr;
};
class OperatorXBase : public OperatorBase {
@@ -683,6 +735,18 @@ public:
}
}
+ size_t revocable_mem_size(RuntimeState* state) const override {
+ return (_child and !is_source()) ? _child->revocable_mem_size(state) : 0;
+ }
+
+ Status revoke_memory(RuntimeState* state,
+ const std::shared_ptr<SpillContext>& spill_context) override {
+ if (_child and !is_source()) {
+ return _child->revoke_memory(state, spill_context);
+ }
+ return Status::OK();
+ }
+
virtual std::string debug_string(int indentation_level = 0) const;
virtual std::string debug_string(RuntimeState* state, int indentation_level = 0) const;
@@ -736,6 +800,8 @@ public:
void set_parallel_tasks(int parallel_tasks) { _parallel_tasks = parallel_tasks; }
int parallel_tasks() const { return _parallel_tasks; }
+ virtual void reset_reserve_mem_size(RuntimeState* state) {}
+
protected:
template <typename Dependency>
friend class PipelineXLocalState;
@@ -767,6 +833,7 @@ protected:
int64_t _limit; // -1: no limit
uint32_t _debug_point_count = 0;
+ std::atomic_uint32_t _bytes_per_row = 0;
std::string _op_name;
bool _ignore_data_distribution = false;
@@ -792,6 +859,29 @@ public:
[[nodiscard]] LocalState& get_local_state(RuntimeState* state) const {
return state->get_local_state(operator_id())->template cast<LocalState>();
}
+
+ size_t get_reserve_mem_size(RuntimeState* state) override {
+ auto& local_state = get_local_state(state);
+ auto estimated_size = local_state.estimate_memory_usage();
+ if (estimated_size < state->minimum_operator_memory_required_bytes()) {
+ estimated_size = state->minimum_operator_memory_required_bytes();
+ }
+ if (!is_source() && _child) {
+ auto child_reserve_size = _child->get_reserve_mem_size(state);
+ estimated_size +=
+ std::max(state->minimum_operator_memory_required_bytes(), child_reserve_size);
+ }
+ return estimated_size;
+ }
+
+ void reset_reserve_mem_size(RuntimeState* state) override {
+ auto& local_state = get_local_state(state);
+ local_state.reset_estimate_memory_usage();
+
+ if (!is_source() && _child) {
+ _child->reset_reserve_mem_size(state);
+ }
+ }
};
/**
diff --git a/be/src/pipeline/exec/partition_sort_sink_operator.cpp b/be/src/pipeline/exec/partition_sort_sink_operator.cpp
index eba9e8472d3..f62d5d13600 100644
--- a/be/src/pipeline/exec/partition_sort_sink_operator.cpp
+++ b/be/src/pipeline/exec/partition_sort_sink_operator.cpp
@@ -249,6 +249,19 @@ Status PartitionSortSinkOperatorX::_split_block_by_partition(
return Status::OK();
}
+size_t PartitionSortSinkOperatorX::get_reserve_mem_size(RuntimeState* state) {
+ auto& local_state = get_local_state(state);
+ auto rows = state->batch_size();
+ size_t reserve_mem_size = std::visit(
+ vectorized::Overload {[&](std::monostate&) -> size_t { return 0; },
+ [&](auto& agg_method) -> size_t {
+ return agg_method.hash_table->estimate_memory(rows);
+ }},
+ local_state._partitioned_data->method_variant);
+ reserve_mem_size += rows * sizeof(size_t); // hash values
+ return reserve_mem_size;
+}
+
Status PartitionSortSinkOperatorX::_emplace_into_hash_table(
const vectorized::ColumnRawPtrs& key_columns, vectorized::Block* input_block,
PartitionSortSinkLocalState& local_state, bool eos) {
diff --git a/be/src/pipeline/exec/partition_sort_sink_operator.h b/be/src/pipeline/exec/partition_sort_sink_operator.h
index f16df509dca..5eccb6ac9ca 100644
--- a/be/src/pipeline/exec/partition_sort_sink_operator.h
+++ b/be/src/pipeline/exec/partition_sort_sink_operator.h
@@ -274,6 +274,8 @@ public:
return {ExchangeType::PASSTHROUGH};
}
+ size_t get_reserve_mem_size(RuntimeState* state) override;
+
private:
friend class PartitionSortSinkLocalState;
ObjectPool* _pool = nullptr;
diff --git a/be/src/pipeline/exec/partition_sort_source_operator.cpp b/be/src/pipeline/exec/partition_sort_source_operator.cpp
index 6d355477ab8..970e4c1ac6e 100644
--- a/be/src/pipeline/exec/partition_sort_source_operator.cpp
+++ b/be/src/pipeline/exec/partition_sort_source_operator.cpp
@@ -42,18 +42,18 @@ Status PartitionSortSourceOperatorX::get_block(RuntimeState* state, vectorized::
output_block->clear_column_data();
{
std::lock_guard<std::mutex> lock(local_state._shared_state->buffer_mutex);
- if (local_state._shared_state->blocks_buffer.empty() == false) {
+ if (!local_state._shared_state->blocks_buffer.empty()) {
local_state._shared_state->blocks_buffer.front().swap(*output_block);
local_state._shared_state->blocks_buffer.pop();
//if buffer have no data and sink not eos, block reading and wait for signal again
- RETURN_IF_ERROR(vectorized::VExprContext::filter_block(
- local_state._conjuncts, output_block, output_block->columns()));
+ RETURN_IF_ERROR(local_state.filter_block(local_state._conjuncts, output_block,
+ output_block->columns()));
if (local_state._shared_state->blocks_buffer.empty() &&
- local_state._shared_state->sink_eos == false) {
+ !local_state._shared_state->sink_eos) {
// add this mutex to check, as in some case maybe is doing block(), and the sink is doing set eos.
// so have to hold mutex to set block(), avoid to sink have set eos and set ready, but here set block() by mistake
std::unique_lock<std::mutex> lc(local_state._shared_state->sink_eos_lock);
- if (local_state._shared_state->sink_eos == false) {
+ if (!local_state._shared_state->sink_eos) {
local_state._dependency->block();
}
}
@@ -70,8 +70,8 @@ Status PartitionSortSourceOperatorX::get_block(RuntimeState* state, vectorized::
// if we move the _blocks_buffer output at last(behind 286 line),
// it's maybe eos but not output all data: when _blocks_buffer.empty() and _can_read = false (this: _sort_idx && _partition_sorts.size() are 0)
RETURN_IF_ERROR(get_sorted_block(state, output_block, local_state));
- RETURN_IF_ERROR(vectorized::VExprContext::filter_block(local_state._conjuncts, output_block,
- output_block->columns()));
+ RETURN_IF_ERROR(local_state.filter_block(local_state._conjuncts, output_block,
+ output_block->columns()));
{
std::lock_guard<std::mutex> lock(local_state._shared_state->buffer_mutex);
diff --git a/be/src/pipeline/exec/partitioned_aggregation_sink_operator.cpp b/be/src/pipeline/exec/partitioned_aggregation_sink_operator.cpp
index ab0a43f4a63..f84a3636800 100644
--- a/be/src/pipeline/exec/partitioned_aggregation_sink_operator.cpp
+++ b/be/src/pipeline/exec/partitioned_aggregation_sink_operator.cpp
@@ -18,12 +18,17 @@
#include "partitioned_aggregation_sink_operator.h"
#include <cstdint>
+#include <limits>
#include <memory>
#include "aggregation_sink_operator.h"
#include "common/status.h"
+#include "pipeline/dependency.h"
#include "pipeline/exec/spill_utils.h"
+#include "pipeline/pipeline_task.h"
#include "runtime/fragment_mgr.h"
+#include "util/runtime_profile.h"
+#include "vec/spill/spill_stream.h"
#include "vec/spill/spill_stream_manager.h"
namespace doris::pipeline {
@@ -32,7 +37,7 @@ PartitionedAggSinkLocalState::PartitionedAggSinkLocalState(DataSinkOperatorXBase
: Base(parent, state) {
_finish_dependency =
std::make_shared<Dependency>(parent->operator_id(), parent->node_id(),
- parent->get_name() + "_SPILL_DEPENDENCY", true);
+ parent->get_name() + "_FINISH_DEPENDENCY", true);
}
Status PartitionedAggSinkLocalState::init(doris::RuntimeState* state,
@@ -58,6 +63,12 @@ Status PartitionedAggSinkLocalState::init(doris::RuntimeState* state,
value_columns_.emplace_back(aggregate_evaluator->function()->create_serialize_column());
}
+ _rows_in_partitions.assign(Base::_shared_state->partition_count, 0);
+
+ _spill_dependency = Dependency::create_shared(parent.operator_id(), parent.node_id(),
+ "AggSinkSpillDependency", true);
+ state->get_task()->add_spill_dependency(_spill_dependency.get());
+
_finish_dependency->block();
return Status::OK();
}
@@ -96,10 +107,16 @@ void PartitionedAggSinkLocalState::_init_counters() {
_hash_table_emplace_timer = ADD_TIMER(Base::profile(), "HashTableEmplaceTime");
_hash_table_input_counter = ADD_COUNTER(Base::profile(), "HashTableInputCount", TUnit::UNIT);
_max_row_size_counter = ADD_COUNTER(Base::profile(), "MaxRowSizeInBytes", TUnit::UNIT);
+ _memory_usage_container =
+ ADD_COUNTER_WITH_LEVEL(Base::profile(), "MemoryUsageContainer", TUnit::BYTES, 1);
+ _memory_usage_arena =
+ ADD_COUNTER_WITH_LEVEL(Base::profile(), "MemoryUsageArena", TUnit::BYTES, 1);
+ _memory_usage_reserved =
+ ADD_COUNTER_WITH_LEVEL(Base::profile(), "MemoryUsageReserved", TUnit::BYTES, 1);
COUNTER_SET(_max_row_size_counter, (int64_t)0);
_spill_serialize_hash_table_timer =
- ADD_CHILD_TIMER_WITH_LEVEL(Base::profile(), "SpillSerializeHashTableTime", "Spill", 1);
+ ADD_TIMER_WITH_LEVEL(Base::profile(), "SpillSerializeHashTableTime", 1);
}
#define UPDATE_PROFILE(counter, name) \
do { \
@@ -122,6 +139,10 @@ void PartitionedAggSinkLocalState::update_profile(RuntimeProfile* child_profile)
UPDATE_PROFILE(_hash_table_emplace_timer, "HashTableEmplaceTime");
UPDATE_PROFILE(_hash_table_input_counter, "HashTableInputCount");
UPDATE_PROFILE(_max_row_size_counter, "MaxRowSizeInBytes");
+ UPDATE_PROFILE(_memory_usage_container, "MemoryUsageContainer");
+ UPDATE_PROFILE(_memory_usage_arena, "MemoryUsageArena");
+
+ update_max_min_rows_counter();
}
PartitionedAggSinkOperatorX::PartitionedAggSinkOperatorX(ObjectPool* pool, int operator_id,
@@ -166,7 +187,7 @@ Status PartitionedAggSinkOperatorX::sink(doris::RuntimeState* state, vectorized:
if (eos) {
if (local_state._shared_state->is_spilled) {
if (revocable_mem_size(state) > 0) {
- RETURN_IF_ERROR(revoke_memory(state));
+ RETURN_IF_ERROR(revoke_memory(state, nullptr));
} else {
for (auto& partition : local_state._shared_state->spill_partitions) {
RETURN_IF_ERROR(partition->finish_current_spilling(eos));
@@ -178,6 +199,10 @@ Status PartitionedAggSinkOperatorX::sink(doris::RuntimeState* state, vectorized:
local_state._dependency->set_ready_to_read();
local_state._finish_dependency->set_ready();
}
+ } else if (local_state._shared_state->is_spilled) {
+ if (revocable_mem_size(state) >= vectorized::SpillStream::MAX_SPILL_WRITE_BATCH_MEM) {
+ return revoke_memory(state, nullptr);
+ }
}
if (local_state._runtime_state) {
auto* sink_local_state = local_state._runtime_state->get_sink_local_state();
@@ -185,9 +210,10 @@ Status PartitionedAggSinkOperatorX::sink(doris::RuntimeState* state, vectorized:
}
return Status::OK();
}
-Status PartitionedAggSinkOperatorX::revoke_memory(RuntimeState* state) {
+Status PartitionedAggSinkOperatorX::revoke_memory(
+ RuntimeState* state, const std::shared_ptr<SpillContext>& spill_context) {
auto& local_state = get_local_state(state);
- return local_state.revoke_memory(state);
+ return local_state.revoke_memory(state, spill_context);
}
size_t PartitionedAggSinkOperatorX::revocable_mem_size(RuntimeState* state) const {
@@ -228,9 +254,20 @@ Status PartitionedAggSinkLocalState::setup_in_memory_agg_op(RuntimeState* state)
return sink_local_state->open(state);
}
-Status PartitionedAggSinkLocalState::revoke_memory(RuntimeState* state) {
+size_t PartitionedAggSinkOperatorX::get_reserve_mem_size(RuntimeState* state) {
+ auto& local_state = get_local_state(state);
+ auto* runtime_state = local_state._runtime_state.get();
+ auto size = _agg_sink_operator->get_reserve_mem_size(runtime_state);
+ COUNTER_SET(local_state._memory_usage_reserved, int64_t(size));
+ return size;
+}
+
+Status PartitionedAggSinkLocalState::revoke_memory(
+ RuntimeState* state, const std::shared_ptr<SpillContext>& spill_context) {
+ const auto size_to_revoke = _parent->revocable_mem_size(state);
VLOG_DEBUG << "query " << print_id(state->query_id()) << " agg node "
- << Base::_parent->node_id() << " revoke_memory"
+ << Base::_parent->node_id()
+ << " revoke_memory, size: " << _parent->revocable_mem_size(state)
<< ", eos: " << _eos;
RETURN_IF_ERROR(Base::_shared_state->sink_status);
if (!_shared_state->is_spilled) {
@@ -240,14 +277,14 @@ Status PartitionedAggSinkLocalState::revoke_memory(RuntimeState* state) {
// TODO: spill thread may set_ready before the task::execute thread put the task to blocked state
if (!_eos) {
- Base::_dependency->Dependency::block();
+ Base::_spill_dependency->Dependency::block();
}
auto& parent = Base::_parent->template cast<Parent>();
Status status;
Defer defer {[&]() {
if (!status.ok()) {
if (!_eos) {
- Base::_dependency->Dependency::set_ready();
+ Base::_spill_dependency->Dependency::set_ready();
}
}
}};
@@ -262,9 +299,10 @@ Status PartitionedAggSinkLocalState::revoke_memory(RuntimeState* state) {
return status;
});
+ state->get_query_ctx()->increase_revoking_tasks_count();
auto spill_runnable = std::make_shared<SpillRunnable>(
state, _shared_state->shared_from_this(),
- [this, &parent, state, query_id, submit_timer] {
+ [this, &parent, state, query_id, size_to_revoke, spill_context, submit_timer] {
DBUG_EXECUTE_IF("fault_inject::partitioned_agg_sink::revoke_memory_cancel", {
auto st = Status::InternalError(
"fault_inject partitioned_agg_sink "
@@ -285,30 +323,34 @@ Status PartitionedAggSinkLocalState::revoke_memory(RuntimeState* state) {
_shared_state->close();
} else {
VLOG_DEBUG << "query " << print_id(query_id) << " agg node "
- << Base::_parent->node_id() << " revoke_memory finish"
- << ", eos: " << _eos;
+ << Base::_parent->node_id() << " revoke_memory finish, size: "
+ << _parent->revocable_mem_size(state) << ", eos: " << _eos;
}
if (_eos) {
Base::_dependency->set_ready_to_read();
_finish_dependency->set_ready();
- } else {
- Base::_dependency->Dependency::set_ready();
+ }
+ state->get_query_ctx()->decrease_revoking_tasks_count();
+ Base::_spill_dependency->Dependency::set_ready();
+
+ if (spill_context) {
+ spill_context->on_task_finished();
}
}};
auto* runtime_state = _runtime_state.get();
auto* agg_data = parent._agg_sink_operator->get_agg_data(runtime_state);
- Base::_shared_state->sink_status =
- std::visit(vectorized::Overload {
- [&](std::monostate& arg) -> Status {
- return Status::InternalError("Unit hash table");
- },
- [&](auto& agg_method) -> Status {
- auto& hash_table = *agg_method.hash_table;
- RETURN_IF_CATCH_EXCEPTION(return _spill_hash_table(
- state, agg_method, hash_table, _eos));
- }},
- agg_data->method_variant);
+ Base::_shared_state->sink_status = std::visit(
+ vectorized::Overload {
+ [&](std::monostate& arg) -> Status {
+ return Status::InternalError("Unit hash table");
+ },
+ [&](auto& agg_method) -> Status {
+ auto& hash_table = *agg_method.hash_table;
+ RETURN_IF_CATCH_EXCEPTION(return _spill_hash_table(
+ state, agg_method, hash_table, size_to_revoke, _eos));
+ }},
+ agg_data->method_variant);
RETURN_IF_ERROR(Base::_shared_state->sink_status);
Base::_shared_state->sink_status =
parent._agg_sink_operator->reset_hash_table(runtime_state);
diff --git a/be/src/pipeline/exec/partitioned_aggregation_sink_operator.h b/be/src/pipeline/exec/partitioned_aggregation_sink_operator.h
index 6b3a74c83df..1124ba48c35 100644
--- a/be/src/pipeline/exec/partitioned_aggregation_sink_operator.h
+++ b/be/src/pipeline/exec/partitioned_aggregation_sink_operator.h
@@ -16,10 +16,15 @@
// under the License.
#pragma once
+#include <limits>
+#include <memory>
+
#include "aggregation_sink_operator.h"
+#include "pipeline/dependency.h"
#include "pipeline/exec/operator.h"
#include "vec/exprs/vectorized_agg_fn.h"
#include "vec/exprs/vexpr.h"
+#include "vec/spill/spill_stream.h"
#include "vec/spill/spill_stream_manager.h"
namespace doris::pipeline {
@@ -41,7 +46,7 @@ public:
Status close(RuntimeState* state, Status exec_status) override;
Dependency* finishdependency() override { return _finish_dependency.get(); }
- Status revoke_memory(RuntimeState* state);
+ Status revoke_memory(RuntimeState* state, const std::shared_ptr<SpillContext>& spill_context);
Status setup_in_memory_agg_op(RuntimeState* state);
@@ -54,7 +59,7 @@ public:
};
template <typename HashTableCtxType, typename HashTableType>
Status _spill_hash_table(RuntimeState* state, HashTableCtxType& context,
- HashTableType& hash_table, bool eos) {
+ HashTableType& hash_table, const size_t size_to_revoke, bool eos) {
Status status;
Defer defer {[&]() {
if (!status.ok()) {
@@ -66,8 +71,22 @@ public:
Base::_shared_state->in_mem_shared_state->aggregate_data_container->init_once();
- static int spill_batch_rows = 4096;
- int row_count = 0;
+ const auto total_rows =
+ Base::_shared_state->in_mem_shared_state->aggregate_data_container->total_count();
+
+ const size_t size_to_revoke_ = std::max<size_t>(size_to_revoke, 1);
+
+ // `spill_batch_rows` will be between 4k and 1M
+ // and each block to spill will not be larger than 32MB(`MAX_SPILL_WRITE_BATCH_MEM`)
+ const auto spill_batch_rows = std::min<size_t>(
+ 1024 * 1024,
+ std::max<size_t>(4096, vectorized::SpillStream::MAX_SPILL_WRITE_BATCH_MEM *
+ total_rows / size_to_revoke_));
+
+ VLOG_DEBUG << "query: " << print_id(state->query_id()) << ", node: " << _parent->node_id()
+ << ", spill_batch_rows: " << spill_batch_rows << ", total rows: " << total_rows
+ << ", size_to_revoke: " << size_to_revoke;
+ size_t row_count = 0;
std::vector<TmpSpillInfo<typename HashTableType::key_type>> spill_infos(
Base::_shared_state->partition_count);
@@ -84,6 +103,7 @@ public:
for (int i = 0; i < Base::_shared_state->partition_count && !state->is_cancelled();
++i) {
if (spill_infos[i].keys_.size() >= spill_batch_rows) {
+ _rows_in_partitions[i] += spill_infos[i].keys_.size();
status = _spill_partition(
state, context, Base::_shared_state->spill_partitions[i],
spill_infos[i].keys_, spill_infos[i].values_, nullptr, false);
@@ -99,6 +119,7 @@ public:
auto spill_null_key_data =
(hash_null_key_data && i == Base::_shared_state->partition_count - 1);
if (spill_infos[i].keys_.size() > 0 || spill_null_key_data) {
+ _rows_in_partitions[i] += spill_infos[i].keys_.size();
status = _spill_partition(state, context, Base::_shared_state->spill_partitions[i],
spill_infos[i].keys_, spill_infos[i].values_,
spill_null_key_data
@@ -132,7 +153,7 @@ public:
spill_stream->set_write_counters(Base::_spill_serialize_block_timer,
Base::_spill_block_count, Base::_spill_data_size,
Base::_spill_write_disk_timer,
- Base::_spill_write_wait_io_timer);
+ Base::_spill_write_wait_io_timer, memory_used_counter());
status = to_block(context, keys, values, null_key_data);
RETURN_IF_ERROR(status);
@@ -281,6 +302,9 @@ public:
RuntimeProfile::Counter* _deserialize_data_timer = nullptr;
RuntimeProfile::Counter* _max_row_size_counter = nullptr;
RuntimeProfile::Counter* _hash_table_memory_usage = nullptr;
+ RuntimeProfile::Counter* _memory_usage_container = nullptr;
+ RuntimeProfile::Counter* _memory_usage_arena = nullptr;
+ RuntimeProfile::Counter* _memory_usage_reserved = nullptr;
RuntimeProfile::HighWaterMarkCounter* _serialize_key_arena_memory_usage = nullptr;
RuntimeProfile::Counter* _spill_serialize_hash_table_timer = nullptr;
@@ -319,12 +343,15 @@ public:
}
size_t revocable_mem_size(RuntimeState* state) const override;
- Status revoke_memory(RuntimeState* state) override;
+ Status revoke_memory(RuntimeState* state,
+ const std::shared_ptr<SpillContext>& spill_context) override;
+
+ size_t get_reserve_mem_size(RuntimeState* state) override;
private:
friend class PartitionedAggSinkLocalState;
std::unique_ptr<AggSinkOperatorX> _agg_sink_operator;
- size_t _spill_partition_count_bits = 4;
+ size_t _spill_partition_count_bits = 5;
};
} // namespace doris::pipeline
\ No newline at end of file
diff --git a/be/src/pipeline/exec/partitioned_aggregation_source_operator.cpp b/be/src/pipeline/exec/partitioned_aggregation_source_operator.cpp
index 48df5587198..c6a6c09f01b 100644
--- a/be/src/pipeline/exec/partitioned_aggregation_source_operator.cpp
+++ b/be/src/pipeline/exec/partitioned_aggregation_source_operator.cpp
@@ -24,8 +24,10 @@
#include "common/status.h"
#include "pipeline/exec/operator.h"
#include "pipeline/exec/spill_utils.h"
+#include "pipeline/pipeline_task.h"
#include "runtime/fragment_mgr.h"
#include "util/runtime_profile.h"
+#include "vec/spill/spill_stream.h"
#include "vec/spill/spill_stream_manager.h"
namespace doris::pipeline {
@@ -38,6 +40,10 @@ Status PartitionedAggLocalState::init(RuntimeState* state, LocalStateInfo& info)
SCOPED_TIMER(exec_time_counter());
SCOPED_TIMER(_init_timer);
_init_counters();
+ _spill_dependency = Dependency::create_shared(_parent->operator_id(), _parent->node_id(),
+ "AggSourceSpillDependency", true);
+ state->get_task()->add_spill_dependency(_spill_dependency.get());
+
return Status::OK();
}
@@ -59,13 +65,20 @@ void PartitionedAggLocalState::_init_counters() {
_hash_table_iterate_timer = ADD_TIMER(profile(), "HashTableIterateTime");
_insert_keys_to_column_timer = ADD_TIMER(profile(), "InsertKeysToColumnTime");
_serialize_data_timer = ADD_TIMER(profile(), "SerializeDataTime");
- _hash_table_size_counter = ADD_COUNTER(profile(), "HashTableSize", TUnit::UNIT);
+ _hash_table_size_counter = ADD_COUNTER_WITH_LEVEL(profile(), "HashTableSize", TUnit::UNIT, 1);
_merge_timer = ADD_TIMER(profile(), "MergeTime");
_deserialize_data_timer = ADD_TIMER(profile(), "DeserializeAndMergeTime");
_hash_table_compute_timer = ADD_TIMER(profile(), "HashTableComputeTime");
_hash_table_emplace_timer = ADD_TIMER(profile(), "HashTableEmplaceTime");
- _hash_table_input_counter = ADD_COUNTER(profile(), "HashTableInputCount", TUnit::UNIT);
+ _hash_table_input_counter =
+ ADD_COUNTER_WITH_LEVEL(profile(), "HashTableInputCount", TUnit::UNIT, 1);
+ _hash_table_memory_usage =
+ ADD_COUNTER_WITH_LEVEL(profile(), "HashTableMemoryUsage", TUnit::BYTES, 1);
+
+ _memory_usage_container =
+ ADD_COUNTER_WITH_LEVEL(profile(), "MemoryUsageContainer", TUnit::BYTES, 1);
+ _memory_usage_arena = ADD_COUNTER_WITH_LEVEL(profile(), "MemoryUsageArena", TUnit::BYTES, 1);
}
#define UPDATE_PROFILE(counter, name) \
@@ -83,6 +96,9 @@ void PartitionedAggLocalState::update_profile(RuntimeProfile* child_profile) {
UPDATE_PROFILE(_insert_keys_to_column_timer, "InsertKeysToColumnTime");
UPDATE_PROFILE(_serialize_data_timer, "SerializeDataTime");
UPDATE_PROFILE(_hash_table_size_counter, "HashTableSize");
+ UPDATE_PROFILE(_hash_table_memory_usage, "HashTableMemoryUsage");
+ UPDATE_PROFILE(_memory_usage_container, "MemoryUsageContainer");
+ UPDATE_PROFILE(_memory_usage_arena, "MemoryUsageArena");
}
Status PartitionedAggLocalState::close(RuntimeState* state) {
@@ -131,18 +147,37 @@ Status PartitionedAggSourceOperatorX::get_block(RuntimeState* state, vectorized:
SCOPED_TIMER(local_state.exec_time_counter());
RETURN_IF_ERROR(local_state._status);
- if (local_state._shared_state->is_spilled) {
- local_state._status = local_state.initiate_merge_spill_partition_agg_data(state);
- RETURN_IF_ERROR(local_state._status);
-
- /// When `_is_merging` is true means we are reading spilled data and merging the data into hash table.
- if (local_state._is_merging) {
+ if (local_state._shared_state->is_spilled &&
+ local_state._need_to_merge_data_for_current_partition) {
+ if (local_state._blocks.empty() && !local_state._current_partition_eos) {
+ bool has_recovering_data = false;
+ RETURN_IF_ERROR(local_state.recover_blocks_from_disk(state, has_recovering_data));
+ *eos = !has_recovering_data;
return Status::OK();
+ } else if (!local_state._blocks.empty()) {
+ size_t merged_rows = 0;
+ while (!local_state._blocks.empty()) {
+ auto block = std::move(local_state._blocks.front());
+ merged_rows += block.rows();
+ local_state._blocks.erase(local_state._blocks.begin());
+ RETURN_IF_ERROR(_agg_source_operator->merge_with_serialized_key_helper<false>(
+ local_state._runtime_state.get(), &block));
+ }
+ local_state._estimate_memory_usage +=
+ _agg_source_operator->get_estimated_memory_size_for_merging(
+ local_state._runtime_state.get(), merged_rows);
+
+ if (!local_state._current_partition_eos) {
+ return Status::OK();
+ }
}
+
+ local_state._need_to_merge_data_for_current_partition = false;
}
// not spilled in sink or current partition still has data
auto* runtime_state = local_state._runtime_state.get();
+ local_state._shared_state->in_mem_shared_state->aggregate_data_container->init_once();
local_state._status = _agg_source_operator->get_block(runtime_state, block, eos);
RETURN_IF_ERROR(local_state._status);
if (local_state._runtime_state) {
@@ -153,6 +188,9 @@ Status PartitionedAggSourceOperatorX::get_block(RuntimeState* state, vectorized:
if (*eos) {
if (local_state._shared_state->is_spilled &&
!local_state._shared_state->spill_partitions.empty()) {
+ local_state._current_partition_eos = false;
+ local_state._need_to_merge_data_for_current_partition = true;
+ RETURN_IF_ERROR(local_state._shared_state->in_mem_shared_state->reset_hash_table());
*eos = false;
}
}
@@ -186,46 +224,34 @@ Status PartitionedAggLocalState::setup_in_memory_agg_op(RuntimeState* state) {
return source_local_state->open(state);
}
-Status PartitionedAggLocalState::initiate_merge_spill_partition_agg_data(RuntimeState* state) {
- DCHECK(!_is_merging);
- Base::_shared_state->in_mem_shared_state->aggregate_data_container->init_once();
- if (Base::_shared_state->in_mem_shared_state->aggregate_data_container->iterator !=
- Base::_shared_state->in_mem_shared_state->aggregate_data_container->end() ||
- _shared_state->spill_partitions.empty()) {
+Status PartitionedAggLocalState::recover_blocks_from_disk(RuntimeState* state, bool& has_data) {
+ const auto query_id = state->query_id();
+ MonotonicStopWatch submit_timer;
+ submit_timer.start();
+
+ if (_shared_state->spill_partitions.empty()) {
+ _shared_state->close();
+ has_data = false;
return Status::OK();
}
- _is_merging = true;
- VLOG_DEBUG << "query " << print_id(state->query_id()) << " agg node " << _parent->node_id()
- << " merge spilled agg data";
-
- RETURN_IF_ERROR(Base::_shared_state->in_mem_shared_state->reset_hash_table());
- _dependency->Dependency::block();
-
- auto query_id = state->query_id();
-
- MonotonicStopWatch submit_timer;
- submit_timer.start();
+ has_data = true;
auto spill_func = [this, state, query_id, submit_timer] {
_spill_wait_in_queue_timer->update(submit_timer.elapsed_time());
+ MonotonicStopWatch execution_timer;
+ execution_timer.start();
Defer defer {[&]() {
if (!_status.ok() || state->is_cancelled()) {
if (!_status.ok()) {
LOG(WARNING) << "query " << print_id(query_id) << " agg node "
- << _parent->node_id()
- << " merge spilled agg data error: " << _status;
+ << _parent->node_id() << " recover agg data error: " << _status;
}
_shared_state->close();
- } else if (_shared_state->spill_partitions.empty()) {
- VLOG_DEBUG << "query " << print_id(query_id) << " agg node " << _parent->node_id()
- << " merge spilled agg data finish";
}
- Base::_shared_state->in_mem_shared_state->aggregate_data_container->init_once();
- _is_merging = false;
- _dependency->Dependency::set_ready();
+ _spill_dependency->Dependency::set_ready();
}};
bool has_agg_data = false;
- auto& parent = Base::_parent->template cast<Parent>();
+ size_t accumulated_blocks_size = 0;
while (!state->is_cancelled() && !has_agg_data &&
!_shared_state->spill_partitions.empty()) {
for (auto& stream : _shared_state->spill_partitions[0]->spill_streams_) {
@@ -251,18 +277,23 @@ Status PartitionedAggLocalState::initiate_merge_spill_partition_agg_data(Runtime
if (!block.empty()) {
has_agg_data = true;
- _status = parent._agg_source_operator
- ->merge_with_serialized_key_helper<false>(
- _runtime_state.get(), &block);
- RETURN_IF_ERROR(_status);
+ accumulated_blocks_size += block.allocated_bytes();
+ _blocks.emplace_back(std::move(block));
+
+ if (accumulated_blocks_size >=
+ vectorized::SpillStream::MAX_SPILL_WRITE_BATCH_MEM) {
+ break;
+ }
}
}
- (void)ExecEnv::GetInstance()->spill_stream_mgr()->delete_spill_stream(stream);
+
+ _current_partition_eos = eos;
+
+ if (_current_partition_eos) {
+ (void)ExecEnv::GetInstance()->spill_stream_mgr()->delete_spill_stream(stream);
+ _shared_state->spill_partitions.pop_front();
+ }
}
- _shared_state->spill_partitions.pop_front();
- }
- if (_shared_state->spill_partitions.empty()) {
- _shared_state->close();
}
return _status;
};
@@ -287,6 +318,7 @@ Status PartitionedAggLocalState::initiate_merge_spill_partition_agg_data(Runtime
return Status::Error<INTERNAL_ERROR>(
"fault_inject partitioned_agg_source submit_func failed");
});
+ _spill_dependency->block();
return ExecEnv::GetInstance()->spill_stream_mgr()->get_spill_io_thread_pool()->submit(
std::make_shared<SpillRunnable>(state, _shared_state->shared_from_this(),
exception_catch_func));
diff --git a/be/src/pipeline/exec/partitioned_aggregation_source_operator.h b/be/src/pipeline/exec/partitioned_aggregation_source_operator.h
index edae99c716a..53e683ada20 100644
--- a/be/src/pipeline/exec/partitioned_aggregation_source_operator.h
+++ b/be/src/pipeline/exec/partitioned_aggregation_source_operator.h
@@ -41,7 +41,7 @@ public:
Status open(RuntimeState* state) override;
Status close(RuntimeState* state) override;
- Status initiate_merge_spill_partition_agg_data(RuntimeState* state);
+ Status recover_blocks_from_disk(RuntimeState* state, bool& has_data);
Status setup_in_memory_agg_op(RuntimeState* state);
void update_profile(RuntimeProfile* child_profile);
@@ -57,7 +57,10 @@ protected:
std::unique_ptr<std::promise<Status>> _spill_merge_promise;
std::future<Status> _spill_merge_future;
bool _current_partition_eos = true;
- bool _is_merging = false;
+ bool _need_to_merge_data_for_current_partition = true;
+
+ std::shared_ptr<Dependency> _spill_dependency;
+ std::vector<vectorized::Block> _blocks;
std::unique_ptr<RuntimeProfile> _internal_runtime_profile;
RuntimeProfile::Counter* _get_results_timer = nullptr;
@@ -72,6 +75,10 @@ protected:
RuntimeProfile::Counter* _hash_table_compute_timer = nullptr;
RuntimeProfile::Counter* _hash_table_emplace_timer = nullptr;
RuntimeProfile::Counter* _hash_table_input_counter = nullptr;
+ RuntimeProfile::Counter* _hash_table_memory_usage = nullptr;
+ RuntimeProfile::Counter* _memory_usage_container = nullptr;
+ RuntimeProfile::Counter* _memory_usage_arena = nullptr;
+ RuntimeProfile::Counter* _memory_usage_reserved = nullptr;
};
class AggSourceOperatorX;
class PartitionedAggSourceOperatorX : public OperatorX<PartitionedAggLocalState> {
diff --git a/be/src/pipeline/exec/partitioned_hash_join_probe_operator.cpp b/be/src/pipeline/exec/partitioned_hash_join_probe_operator.cpp
index 0e56acc1c57..bb65f098151 100644
--- a/be/src/pipeline/exec/partitioned_hash_join_probe_operator.cpp
+++ b/be/src/pipeline/exec/partitioned_hash_join_probe_operator.cpp
@@ -17,9 +17,19 @@
#include "partitioned_hash_join_probe_operator.h"
+#include <gen_cpp/Metrics_types.h>
+#include <glog/logging.h>
+
+#include <algorithm>
+#include <utility>
+
+#include "common/status.h"
#include "pipeline/pipeline_task.h"
#include "runtime/fragment_mgr.h"
#include "util/mem_info.h"
+#include "util/runtime_profile.h"
+#include "vec/core/block.h"
+#include "vec/spill/spill_stream.h"
#include "vec/spill/spill_stream_manager.h"
namespace doris::pipeline {
@@ -39,32 +49,36 @@ Status PartitionedHashJoinProbeLocalState::init(RuntimeState* state, LocalStateI
_partitioned_blocks.resize(p._partition_count);
_probe_spilling_streams.resize(p._partition_count);
- _spill_and_partition_label = ADD_LABEL_COUNTER(profile(), "Partition");
- _partition_timer = ADD_CHILD_TIMER(profile(), "PartitionTime", "Partition");
- _partition_shuffle_timer = ADD_CHILD_TIMER(profile(), "PartitionShuffleTime", "Partition");
- _spill_build_rows = ADD_CHILD_COUNTER(profile(), "SpillBuildRows", TUnit::UNIT, "Spill");
- _spill_build_timer = ADD_CHILD_TIMER_WITH_LEVEL(profile(), "SpillBuildTime", "Spill", 1);
- _recovery_build_rows = ADD_CHILD_COUNTER(profile(), "RecoveryBuildRows", TUnit::UNIT, "Spill");
- _recovery_build_timer = ADD_CHILD_TIMER_WITH_LEVEL(profile(), "RecoveryBuildTime", "Spill", 1);
- _spill_probe_rows = ADD_CHILD_COUNTER(profile(), "SpillProbeRows", TUnit::UNIT, "Spill");
- _recovery_probe_rows = ADD_CHILD_COUNTER(profile(), "RecoveryProbeRows", TUnit::UNIT, "Spill");
- _spill_build_blocks = ADD_CHILD_COUNTER(profile(), "SpillBuildBlocks", TUnit::UNIT, "Spill");
- _recovery_build_blocks =
- ADD_CHILD_COUNTER(profile(), "RecoveryBuildBlocks", TUnit::UNIT, "Spill");
- _spill_probe_blocks = ADD_CHILD_COUNTER(profile(), "SpillProbeBlocks", TUnit::UNIT, "Spill");
- _spill_probe_timer = ADD_CHILD_TIMER_WITH_LEVEL(profile(), "SpillProbeTime", "Spill", 1);
- _recovery_probe_blocks =
- ADD_CHILD_COUNTER(profile(), "RecoveryProbeBlocks", TUnit::UNIT, "Spill");
- _recovery_probe_timer = ADD_CHILD_TIMER_WITH_LEVEL(profile(), "RecoveryProbeTime", "Spill", 1);
+ _spill_dependency = Dependency::create_shared(_parent->operator_id(), _parent->node_id(),
+ "HashJoinProbeSpillDependency", true);
+ state->get_task()->add_spill_dependency(_spill_dependency.get());
+
+ _partition_timer = ADD_TIMER(profile(), "SpillPartitionTime");
+ _partition_shuffle_timer = ADD_TIMER(profile(), "SpillPartitionShuffleTime");
+ _spill_build_rows = ADD_COUNTER(profile(), "SpillBuildRows", TUnit::UNIT);
+ _spill_build_timer = ADD_TIMER_WITH_LEVEL(profile(), "SpillBuildTime", 1);
+ _recovery_build_rows = ADD_COUNTER(profile(), "SpillRecoveryBuildRows", TUnit::UNIT);
+ _recovery_build_timer = ADD_TIMER_WITH_LEVEL(profile(), "SpillRecoveryBuildTime", 1);
+ _spill_probe_rows = ADD_COUNTER(profile(), "SpillProbeRows", TUnit::UNIT);
+ _recovery_probe_rows = ADD_COUNTER(profile(), "SpillRecoveryProbeRows", TUnit::UNIT);
+ _spill_build_blocks = ADD_COUNTER(profile(), "SpillBuildBlocks", TUnit::UNIT);
+ _recovery_build_blocks = ADD_COUNTER(profile(), "SpillRecoveryBuildBlocks", TUnit::UNIT);
+ _spill_probe_blocks = ADD_COUNTER(profile(), "SpillProbeBlocks", TUnit::UNIT);
+ _spill_probe_timer = ADD_TIMER_WITH_LEVEL(profile(), "SpillProbeTime", 1);
+ _recovery_probe_blocks = ADD_COUNTER(profile(), "SpillRecoveryProbeBlocks", TUnit::UNIT);
+ _recovery_probe_timer = ADD_TIMER_WITH_LEVEL(profile(), "SpillRecoveryProbeTime", 1);
+ _memory_usage_reserved =
+ ADD_COUNTER_WITH_LEVEL(profile(), "MemoryUsageReserved", TUnit::UNIT, 1);
+
+ _probe_blocks_bytes = ADD_COUNTER_WITH_LEVEL(profile(), "ProbeBlocksBytes", TUnit::BYTES, 1);
_spill_serialize_block_timer =
- ADD_CHILD_TIMER_WITH_LEVEL(Base::profile(), "SpillSerializeBlockTime", "Spill", 1);
- _spill_write_disk_timer =
- ADD_CHILD_TIMER_WITH_LEVEL(Base::profile(), "SpillWriteDiskTime", "Spill", 1);
- _spill_data_size = ADD_CHILD_COUNTER_WITH_LEVEL(Base::profile(), "SpillWriteDataSize",
- TUnit::BYTES, "Spill", 1);
- _spill_block_count = ADD_CHILD_COUNTER_WITH_LEVEL(Base::profile(), "SpillWriteBlockCount",
- TUnit::UNIT, "Spill", 1);
+ ADD_TIMER_WITH_LEVEL(Base::profile(), "SpillSerializeBlockTime", 1);
+ _spill_write_disk_timer = ADD_TIMER_WITH_LEVEL(Base::profile(), "SpillWriteDiskTime", 1);
+ _spill_data_size =
+ ADD_COUNTER_WITH_LEVEL(Base::profile(), "SpillWriteDataSize", TUnit::BYTES, 1);
+ _spill_block_count =
+ ADD_COUNTER_WITH_LEVEL(Base::profile(), "SpillWriteBlockCount", TUnit::UNIT, 1);
// Build phase
_build_phase_label = ADD_LABEL_COUNTER(profile(), "BuildPhase");
@@ -80,6 +94,10 @@ Status PartitionedHashJoinProbeLocalState::init(RuntimeState* state, LocalStateI
_build_expr_call_timer = ADD_CHILD_TIMER(profile(), "BuildExprCallTime", "BuildPhase");
_build_side_compute_hash_timer =
ADD_CHILD_TIMER(profile(), "BuildSideHashComputingTime", "BuildPhase");
+
+ _hash_table_memory_usage =
+ ADD_COUNTER_WITH_LEVEL(profile(), "MemoryUsageHashTable", TUnit::BYTES, 1);
+
_allocate_resource_timer = ADD_CHILD_TIMER(profile(), "AllocateResourceTime", "BuildPhase");
// Probe phase
@@ -156,25 +174,36 @@ Status PartitionedHashJoinProbeLocalState::close(RuntimeState* state) {
return Status::OK();
}
-Status PartitionedHashJoinProbeLocalState::spill_probe_blocks(RuntimeState* state) {
+Status PartitionedHashJoinProbeLocalState::spill_probe_blocks(
+ RuntimeState* state, const std::shared_ptr<SpillContext>& spill_context) {
auto* spill_io_pool = ExecEnv::GetInstance()->spill_stream_mgr()->get_spill_io_thread_pool();
auto query_id = state->query_id();
MonotonicStopWatch submit_timer;
submit_timer.start();
+
auto spill_func = [query_id, state, submit_timer, this] {
_spill_wait_in_queue_timer->update(submit_timer.elapsed_time());
SCOPED_TIMER(_spill_probe_timer);
+ size_t not_revoked_size = 0;
auto& p = _parent->cast<PartitionedHashJoinProbeOperatorX>();
for (uint32_t partition_index = 0; partition_index != p._partition_count;
++partition_index) {
auto& blocks = _probe_blocks[partition_index];
auto& partitioned_block = _partitioned_blocks[partition_index];
- if (partitioned_block && partitioned_block->allocated_bytes() >=
- vectorized::SpillStream::MIN_SPILL_WRITE_BATCH_MEM) {
- blocks.emplace_back(partitioned_block->to_block());
- partitioned_block.reset();
+ if (partitioned_block) {
+ const auto size = partitioned_block->allocated_bytes();
+ if (size >= vectorized::SpillStream::MIN_SPILL_WRITE_BATCH_MEM) {
+ blocks.emplace_back(partitioned_block->to_block());
+ partitioned_block.reset();
+ } else {
+ not_revoked_size += size;
+ }
+ }
+
+ if (blocks.empty()) {
+ continue;
}
auto& spilling_stream = _probe_spilling_streams[partition_index];
@@ -186,28 +215,46 @@ Status PartitionedHashJoinProbeLocalState::spill_probe_blocks(RuntimeState* stat
RETURN_IF_ERROR(spilling_stream->prepare_spill());
spilling_stream->set_write_counters(
_spill_serialize_block_timer, _spill_block_count, _spill_data_size,
- _spill_write_disk_timer, _spill_write_wait_io_timer);
+ _spill_write_disk_timer, _spill_write_wait_io_timer, memory_used_counter());
}
- COUNTER_UPDATE(_spill_probe_blocks, blocks.size());
+ auto merged_block = vectorized::MutableBlock::create_unique(blocks[0].clone_empty());
while (!blocks.empty() && !state->is_cancelled()) {
auto block = std::move(blocks.back());
blocks.pop_back();
+
+ RETURN_IF_ERROR(merged_block->merge(std::move(block)));
DBUG_EXECUTE_IF("fault_inject::partitioned_hash_join_probe::spill_probe_blocks", {
return Status::Error<INTERNAL_ERROR>(
"fault_inject partitioned_hash_join_probe spill_probe_blocks failed");
});
- RETURN_IF_ERROR(spilling_stream->spill_block(state, block, false));
- COUNTER_UPDATE(_spill_probe_rows, block.rows());
+
+ if (merged_block->allocated_bytes() >=
+ vectorized::SpillStream::MAX_SPILL_WRITE_BATCH_MEM) {
+ COUNTER_UPDATE(_spill_probe_rows, merged_block->rows());
+ RETURN_IF_ERROR(
+ spilling_stream->spill_block(state, merged_block->to_block(), false));
+ COUNTER_UPDATE(_spill_probe_blocks, 1);
+ }
+ }
+
+ if (!merged_block->empty()) {
+ COUNTER_UPDATE(_spill_probe_rows, merged_block->rows());
+ RETURN_IF_ERROR(
+ spilling_stream->spill_block(state, merged_block->to_block(), false));
+ COUNTER_UPDATE(_spill_probe_blocks, 1);
}
}
+
+ COUNTER_SET(_probe_blocks_bytes, int64_t(not_revoked_size));
VLOG_DEBUG << "query: " << print_id(query_id)
<< " hash probe revoke done, node: " << p.node_id()
<< ", task: " << state->task_id();
return Status::OK();
};
- auto exception_catch_func = [query_id, spill_func, this]() {
+ auto exception_catch_func = [query_id, spill_func, spill_context, this]() {
+ SCOPED_TIMER(_spill_timer);
DBUG_EXECUTE_IF("fault_inject::partitioned_hash_join_probe::spill_probe_blocks_cancel", {
ExecEnv::GetInstance()->fragment_mgr()->cancel_query(
query_id, Status::InternalError("fault_inject partitioned_hash_join_probe "
@@ -221,10 +268,16 @@ Status PartitionedHashJoinProbeLocalState::spill_probe_blocks(RuntimeState* stat
_spill_status_ok = false;
_spill_status = std::move(status);
}
- _dependency->set_ready();
+ _spill_dependency->set_ready();
+ if (spill_context) {
+ spill_context->on_non_sink_task_finished();
+ }
};
- _dependency->block();
+ if (spill_context) {
+ spill_context->on_non_sink_task_started();
+ }
+ _spill_dependency->block();
DBUG_EXECUTE_IF("fault_inject::partitioned_hash_join_probe::spill_probe_blocks_submit_func", {
return Status::Error<INTERNAL_ERROR>(
"fault_inject partitioned_hash_join_probe spill_probe_blocks submit_func failed");
@@ -236,13 +289,6 @@ Status PartitionedHashJoinProbeLocalState::spill_probe_blocks(RuntimeState* stat
}
Status PartitionedHashJoinProbeLocalState::finish_spilling(uint32_t partition_index) {
- auto& build_spilling_stream = _shared_state->spilled_streams[partition_index];
- if (build_spilling_stream) {
- RETURN_IF_ERROR(build_spilling_stream->spill_eof());
- build_spilling_stream->set_read_counters(_spill_read_data_time, _spill_deserialize_time,
- _spill_read_bytes, _spill_read_wait_io_timer);
- }
-
auto& probe_spilling_stream = _probe_spilling_streams[partition_index];
if (probe_spilling_stream) {
@@ -254,24 +300,20 @@ Status PartitionedHashJoinProbeLocalState::finish_spilling(uint32_t partition_in
return Status::OK();
}
-Status PartitionedHashJoinProbeLocalState::recovery_build_blocks_from_disk(RuntimeState* state,
- uint32_t partition_index,
- bool& has_data) {
+Status PartitionedHashJoinProbeLocalState::recover_build_blocks_from_disk(RuntimeState* state,
+ uint32_t partition_index,
+ bool& has_data) {
VLOG_DEBUG << "query: " << print_id(state->query_id()) << ", node: " << _parent->node_id()
<< ", task id: " << state->task_id() << ", partition: " << partition_index
- << " recovery_build_blocks_from_disk";
+ << " recover_build_blocks_from_disk";
auto& spilled_stream = _shared_state->spilled_streams[partition_index];
has_data = false;
if (!spilled_stream) {
return Status::OK();
}
- auto& mutable_block = _shared_state->partitioned_build_blocks[partition_index];
- if (!mutable_block) {
- ExecEnv::GetInstance()->spill_stream_mgr()->delete_spill_stream(spilled_stream);
- spilled_stream.reset();
- return Status::OK();
- }
+ spilled_stream->set_read_counters(_spill_read_data_time, _spill_deserialize_time,
+ _spill_read_bytes, _spill_read_wait_io_timer);
std::weak_ptr<PartitionedHashJoinSharedState> shared_state_holder =
_shared_state->shared_from_this();
@@ -281,8 +323,8 @@ Status PartitionedHashJoinProbeLocalState::recovery_build_blocks_from_disk(Runti
MonotonicStopWatch submit_timer;
submit_timer.start();
- auto read_func = [this, query_id, state, spilled_stream = spilled_stream, &mutable_block,
- shared_state_holder, submit_timer, partition_index] {
+ auto read_func = [this, query_id, state, spilled_stream = spilled_stream, shared_state_holder,
+ submit_timer, partition_index] {
auto shared_state_sptr = shared_state_holder.lock();
if (!shared_state_sptr || state->is_cancelled()) {
LOG(INFO) << "query: " << print_id(query_id)
@@ -324,24 +366,31 @@ Status PartitionedHashJoinProbeLocalState::recovery_build_blocks_from_disk(Runti
break;
}
- if (mutable_block->empty()) {
- *mutable_block = std::move(block);
+ if (!_recovered_build_block) {
+ _recovered_build_block = vectorized::MutableBlock::create_unique(std::move(block));
} else {
- DCHECK_EQ(mutable_block->columns(), block.columns());
- st = mutable_block->merge(std::move(block));
+ DCHECK_EQ(_recovered_build_block->columns(), block.columns());
+ st = _recovered_build_block->merge(std::move(block));
if (!st.ok()) {
_spill_status_ok = false;
_spill_status = std::move(st);
break;
}
}
+
+ if (_recovered_build_block->allocated_bytes() >=
+ vectorized::SpillStream::MAX_SPILL_WRITE_BATCH_MEM) {
+ break;
+ }
}
- ExecEnv::GetInstance()->spill_stream_mgr()->delete_spill_stream(spilled_stream);
- shared_state_sptr->spilled_streams[partition_index].reset();
- VLOG_DEBUG << "query: " << print_id(state->query_id()) << ", node: " << _parent->node_id()
- << ", task id: " << state->task_id() << ", partition: " << partition_index
- << ", recovery build data done";
+ if (eos) {
+ ExecEnv::GetInstance()->spill_stream_mgr()->delete_spill_stream(spilled_stream);
+ shared_state_sptr->spilled_streams[partition_index].reset();
+ VLOG_DEBUG << "query: " << print_id(state->query_id())
+ << ", node: " << _parent->node_id() << ", task id: " << state->task_id()
+ << ", partition: " << partition_index;
+ }
};
auto exception_catch_func = [read_func, query_id, this]() {
@@ -361,12 +410,12 @@ Status PartitionedHashJoinProbeLocalState::recovery_build_blocks_from_disk(Runti
_spill_status_ok = false;
_spill_status = std::move(status);
}
- _dependency->set_ready();
+ _spill_dependency->set_ready();
};
auto* spill_io_pool = ExecEnv::GetInstance()->spill_stream_mgr()->get_spill_io_thread_pool();
has_data = true;
- _dependency->block();
+ _spill_dependency->block();
{
auto* pipeline_task = state->get_task();
if (pipeline_task) {
@@ -388,7 +437,7 @@ Status PartitionedHashJoinProbeLocalState::recovery_build_blocks_from_disk(Runti
exception_catch_func);
VLOG_DEBUG << "query: " << print_id(state->query_id()) << ", node: " << _parent->node_id()
<< ", task id: " << state->task_id() << ", partition: " << partition_index
- << " recovery_build_blocks_from_disk submit func";
+ << " recover_build_blocks_from_disk submit func";
return spill_io_pool->submit(std::move(spill_runnable));
}
@@ -414,9 +463,9 @@ std::string PartitionedHashJoinProbeLocalState::debug_string(int indentation_lev
return fmt::to_string(debug_string_buffer);
}
-Status PartitionedHashJoinProbeLocalState::recovery_probe_blocks_from_disk(RuntimeState* state,
- uint32_t partition_index,
- bool& has_data) {
+Status PartitionedHashJoinProbeLocalState::recover_probe_blocks_from_disk(RuntimeState* state,
+ uint32_t partition_index,
+ bool& has_data) {
auto& spilled_stream = _probe_spilling_streams[partition_index];
has_data = false;
if (!spilled_stream) {
@@ -440,18 +489,24 @@ Status PartitionedHashJoinProbeLocalState::recovery_probe_blocks_from_disk(Runti
st = Status::Error<INTERNAL_ERROR>(
"fault_inject partitioned_hash_join_probe recover_probe_blocks failed");
});
- if (st.ok()) {
+
+ size_t read_size = 0;
+ while (!eos && !_state->is_cancelled() && st.ok()) {
st = spilled_stream->read_next_block_sync(&block, &eos);
- }
- if (!st.ok()) {
- _spill_status_ok = false;
- _spill_status = std::move(st);
- } else {
- COUNTER_UPDATE(_recovery_probe_rows, block.rows());
- COUNTER_UPDATE(_recovery_probe_blocks, 1);
- blocks.emplace_back(std::move(block));
- }
+ if (!st.ok()) {
+ _spill_status_ok = false;
+ _spill_status = std::move(st);
+ } else {
+ COUNTER_UPDATE(_recovery_probe_rows, block.rows());
+ COUNTER_UPDATE(_recovery_probe_blocks, 1);
+ read_size += block.allocated_bytes();
+ blocks.emplace_back(std::move(block));
+ }
+ if (read_size >= vectorized::SpillStream::MAX_SPILL_WRITE_BATCH_MEM) {
+ break;
+ }
+ }
if (eos) {
VLOG_DEBUG << "query: " << print_id(query_id)
<< ", recovery probe data done: " << spilled_stream->get_spill_dir();
@@ -477,12 +532,12 @@ Status PartitionedHashJoinProbeLocalState::recovery_probe_blocks_from_disk(Runti
_spill_status_ok = false;
_spill_status = std::move(status);
}
- _dependency->set_ready();
+ _spill_dependency->set_ready();
};
auto* spill_io_pool = ExecEnv::GetInstance()->spill_stream_mgr()->get_spill_io_thread_pool();
DCHECK(spill_io_pool != nullptr);
- _dependency->block();
+ _spill_dependency->block();
has_data = true;
DBUG_EXECUTE_IF("fault_inject::partitioned_hash_join_probe::recovery_probe_blocks_submit_func",
{
@@ -567,6 +622,7 @@ Status PartitionedHashJoinProbeOperatorX::push(RuntimeState* state, vectorized::
}
SCOPED_TIMER(local_state._partition_shuffle_timer);
+ int64_t bytes_of_blocks = 0;
for (uint32_t i = 0; i != _partition_count; ++i) {
const auto count = partition_indexes[i].size();
if (UNLIKELY(count == 0)) {
@@ -584,9 +640,17 @@ Status PartitionedHashJoinProbeOperatorX::push(RuntimeState* state, vectorized::
(eos && partitioned_blocks[i]->rows() > 0)) {
local_state._probe_blocks[i].emplace_back(partitioned_blocks[i]->to_block());
partitioned_blocks[i].reset();
+ } else {
+ bytes_of_blocks += partitioned_blocks[i]->allocated_bytes();
+ }
+
+ for (auto& block : local_state._probe_blocks[i]) {
+ bytes_of_blocks += block.allocated_bytes();
}
}
+ COUNTER_SET(local_state._probe_blocks_bytes, bytes_of_blocks);
+
return Status::OK();
}
@@ -596,6 +660,12 @@ Status PartitionedHashJoinProbeOperatorX::_setup_internal_operator_for_non_spill
local_state._runtime_state = std::move(local_state._shared_state->inner_runtime_state);
local_state._in_mem_shared_state_sptr =
std::move(local_state._shared_state->inner_shared_state);
+
+ auto* sink_state = local_state._runtime_state->get_sink_local_state();
+ if (sink_state != nullptr) {
+ COUNTER_SET(local_state._hash_table_memory_usage,
+ sink_state->profile()->get_counter("MemoryUsageHashTable")->value());
+ }
return Status::OK();
}
@@ -654,11 +724,17 @@ Status PartitionedHashJoinProbeOperatorX::_setup_internal_operators(
return Status::Error<INTERNAL_ERROR>(
"fault_inject partitioned_hash_join_probe sink failed");
});
+
RETURN_IF_ERROR(_inner_sink_operator->sink(local_state._runtime_state.get(), &block, true));
VLOG_DEBUG << "query: " << print_id(state->query_id())
<< ", internal build operator finished, node id: " << node_id()
<< ", task id: " << state->task_id()
- << ", partition: " << local_state._partition_cursor;
+ << ", partition: " << local_state._partition_cursor << "rows: " << block.rows()
+ << ", usage: "
+ << _inner_sink_operator->get_memory_usage(local_state._runtime_state.get());
+
+ COUNTER_SET(local_state._hash_table_memory_usage,
+ sink_local_state->profile()->get_counter("MemoryUsageHashTable")->value());
return Status::OK();
}
@@ -672,14 +748,28 @@ Status PartitionedHashJoinProbeOperatorX::pull(doris::RuntimeState* state,
const auto partition_index = local_state._partition_cursor;
auto& probe_blocks = local_state._probe_blocks[partition_index];
+
+ if (local_state._recovered_build_block && !local_state._recovered_build_block->empty()) {
+ local_state._estimate_memory_usage += local_state._recovered_build_block->allocated_bytes();
+ auto& mutable_block = local_state._shared_state->partitioned_build_blocks[partition_index];
+ if (!mutable_block) {
+ mutable_block = std::move(local_state._recovered_build_block);
+ } else {
+ RETURN_IF_ERROR(mutable_block->merge(local_state._recovered_build_block->to_block()));
+ local_state._recovered_build_block.reset();
+ }
+ }
+
if (local_state._need_to_setup_internal_operators) {
- *eos = false;
bool has_data = false;
- RETURN_IF_ERROR(local_state.recovery_build_blocks_from_disk(
+ RETURN_IF_ERROR(local_state.recover_build_blocks_from_disk(
state, local_state._partition_cursor, has_data));
if (has_data) {
return Status::OK();
}
+
+ *eos = false;
+ RETURN_IF_ERROR(local_state.finish_spilling(partition_index));
RETURN_IF_ERROR(_setup_internal_operators(local_state, state));
local_state._need_to_setup_internal_operators = false;
auto& mutable_block = local_state._partitioned_blocks[partition_index];
@@ -694,7 +784,7 @@ Status PartitionedHashJoinProbeOperatorX::pull(doris::RuntimeState* state,
*eos = false;
bool has_data = false;
RETURN_IF_ERROR(
- local_state.recovery_probe_blocks_from_disk(state, partition_index, has_data));
+ local_state.recover_probe_blocks_from_disk(state, partition_index, has_data));
if (!has_data) {
vectorized::Block block;
RETURN_IF_ERROR(_inner_probe_operator->push(runtime_state, &block, true));
@@ -726,7 +816,6 @@ Status PartitionedHashJoinProbeOperatorX::pull(doris::RuntimeState* state,
if (local_state._partition_cursor == _partition_count) {
*eos = true;
} else {
- RETURN_IF_ERROR(local_state.finish_spilling(local_state._partition_cursor));
local_state._need_to_setup_internal_operators = true;
}
}
@@ -747,6 +836,22 @@ bool PartitionedHashJoinProbeOperatorX::need_more_input_data(RuntimeState* state
size_t PartitionedHashJoinProbeOperatorX::revocable_mem_size(RuntimeState* state) const {
auto& local_state = get_local_state(state);
+ if (local_state._child_eos) {
+ return 0;
+ }
+
+ auto revocable_size = _revocable_mem_size(state, true);
+ if (_child) {
+ revocable_size += _child->revocable_mem_size(state);
+ }
+ return revocable_size;
+}
+
+size_t PartitionedHashJoinProbeOperatorX::_revocable_mem_size(RuntimeState* state,
+ bool force) const {
+ const auto spill_size_threshold = force ? vectorized::SpillStream::MIN_SPILL_WRITE_BATCH_MEM
+ : vectorized::SpillStream::MAX_SPILL_WRITE_BATCH_MEM;
+ auto& local_state = get_local_state(state);
size_t mem_size = 0;
auto& probe_blocks = local_state._probe_blocks;
for (uint32_t i = 0; i < _partition_count; ++i) {
@@ -757,7 +862,7 @@ size_t PartitionedHashJoinProbeOperatorX::revocable_mem_size(RuntimeState* state
auto& partitioned_block = local_state._partitioned_blocks[i];
if (partitioned_block) {
auto block_bytes = partitioned_block->allocated_bytes();
- if (block_bytes >= vectorized::SpillStream::MIN_SPILL_WRITE_BATCH_MEM) {
+ if (block_bytes >= spill_size_threshold) {
mem_size += block_bytes;
}
}
@@ -765,6 +870,27 @@ size_t PartitionedHashJoinProbeOperatorX::revocable_mem_size(RuntimeState* state
return mem_size;
}
+Status PartitionedHashJoinProbeOperatorX::revoke_memory(
+ RuntimeState* state, const std::shared_ptr<SpillContext>& spill_context) {
+ auto& local_state = get_local_state(state);
+ VLOG_DEBUG << "query: " << print_id(state->query_id()) << ", hash probe node: " << node_id()
+ << ", task: " << state->task_id() << ", child eos: " << local_state._child_eos;
+
+ if (local_state._child_eos) {
+ VLOG_DEBUG << "query: " << print_id(state->query_id()) << ", hash probe node: " << node_id()
+ << ", task: " << state->task_id() << ", child eos: " << local_state._child_eos
+ << ", will not revoke size: " << revocable_mem_size(state);
+ return Status::OK();
+ }
+
+ RETURN_IF_ERROR(local_state.spill_probe_blocks(state, spill_context));
+
+ if (_child) {
+ return _child->revoke_memory(state, spill_context);
+ }
+ return Status::OK();
+}
+
Status PartitionedHashJoinProbeOperatorX::_revoke_memory(RuntimeState* state) {
auto& local_state = get_local_state(state);
VLOG_DEBUG << "query: " << print_id(state->query_id()) << ", hash probe node: " << node_id()
@@ -776,11 +902,8 @@ Status PartitionedHashJoinProbeOperatorX::_revoke_memory(RuntimeState* state) {
bool PartitionedHashJoinProbeOperatorX::_should_revoke_memory(RuntimeState* state) const {
auto& local_state = get_local_state(state);
- const auto revocable_size = revocable_mem_size(state);
- if (PipelineTask::should_revoke_memory(state, revocable_size)) {
- return true;
- }
if (local_state._shared_state->need_to_spill) {
+ const auto revocable_size = _revocable_mem_size(state);
const auto min_revocable_size = state->min_revocable_mem();
return revocable_size > min_revocable_size;
}
@@ -814,18 +937,16 @@ Status PartitionedHashJoinProbeOperatorX::get_block(RuntimeState* state, vectori
}
});
#endif
- if (need_more_input_data(state)) {
- if (need_to_spill && _should_revoke_memory(state)) {
- return _revoke_memory(state);
- }
+ Defer defer([&]() {
+ COUNTER_SET(local_state._memory_usage_reserved,
+ int64_t(local_state.estimate_memory_usage()));
+ });
+
+ if (need_more_input_data(state)) {
RETURN_IF_ERROR(_child->get_block_after_projects(state, local_state._child_block.get(),
&local_state._child_eos));
- if (need_to_spill && local_state._child_eos) {
- RETURN_IF_ERROR(local_state.finish_spilling(0));
- }
-
if (local_state._child_block->rows() == 0 && !local_state._child_eos) {
return Status::OK();
}
@@ -834,6 +955,9 @@ Status PartitionedHashJoinProbeOperatorX::get_block(RuntimeState* state, vectori
if (need_to_spill) {
SCOPED_TIMER(local_state.exec_time_counter());
RETURN_IF_ERROR(push(state, local_state._child_block.get(), local_state._child_eos));
+ if (_should_revoke_memory(state)) {
+ return _revoke_memory(state);
+ }
} else {
if (UNLIKELY(!local_state._runtime_state)) {
RETURN_IF_ERROR(_setup_internal_operator_for_non_spill(local_state, state));
diff --git a/be/src/pipeline/exec/partitioned_hash_join_probe_operator.h b/be/src/pipeline/exec/partitioned_hash_join_probe_operator.h
index 3aab11f62d8..0ffa446f181 100644
--- a/be/src/pipeline/exec/partitioned_hash_join_probe_operator.h
+++ b/be/src/pipeline/exec/partitioned_hash_join_probe_operator.h
@@ -18,9 +18,11 @@
#pragma once
#include <cstdint>
+#include <memory>
#include "common/status.h"
#include "operator.h"
+#include "pipeline/dependency.h"
#include "pipeline/exec/hashjoin_build_sink.h"
#include "pipeline/exec/hashjoin_probe_operator.h"
#include "pipeline/exec/join_build_sink_operator.h"
@@ -45,12 +47,13 @@ public:
Status open(RuntimeState* state) override;
Status close(RuntimeState* state) override;
- Status spill_probe_blocks(RuntimeState* state);
+ Status spill_probe_blocks(RuntimeState* state,
+ const std::shared_ptr<SpillContext>& spill_context = nullptr);
- Status recovery_build_blocks_from_disk(RuntimeState* state, uint32_t partition_index,
- bool& has_data);
- Status recovery_probe_blocks_from_disk(RuntimeState* state, uint32_t partition_index,
- bool& has_data);
+ Status recover_build_blocks_from_disk(RuntimeState* state, uint32_t partition_index,
+ bool& has_data);
+ Status recover_probe_blocks_from_disk(RuntimeState* state, uint32_t partition_index,
+ bool& has_data);
Status finish_spilling(uint32_t partition_index);
@@ -78,6 +81,7 @@ private:
std::atomic<bool> _spill_status_ok {true};
std::vector<std::unique_ptr<vectorized::MutableBlock>> _partitioned_blocks;
+ std::unique_ptr<vectorized::MutableBlock> _recovered_build_block;
std::map<uint32_t, std::vector<vectorized::Block>> _probe_blocks;
std::vector<vectorized::SpillStreamSPtr> _probe_spilling_streams;
@@ -88,7 +92,8 @@ private:
bool _need_to_setup_internal_operators {true};
- RuntimeProfile::Counter* _spill_and_partition_label = nullptr;
+ std::shared_ptr<Dependency> _spill_dependency;
+
RuntimeProfile::Counter* _partition_timer = nullptr;
RuntimeProfile::Counter* _partition_shuffle_timer = nullptr;
RuntimeProfile::Counter* _spill_build_rows = nullptr;
@@ -120,6 +125,9 @@ private:
RuntimeProfile::Counter* _build_side_compute_hash_timer = nullptr;
RuntimeProfile::Counter* _build_side_merge_block_timer = nullptr;
+ RuntimeProfile::Counter* _hash_table_memory_usage = nullptr;
+ RuntimeProfile::Counter* _probe_blocks_bytes = nullptr;
+
RuntimeProfile::Counter* _allocate_resource_timer = nullptr;
RuntimeProfile::Counter* _probe_phase_label = nullptr;
@@ -135,6 +143,7 @@ private:
RuntimeProfile::Counter* _probe_rows_counter = nullptr;
RuntimeProfile::Counter* _join_filter_timer = nullptr;
RuntimeProfile::Counter* _build_output_block_timer = nullptr;
+ RuntimeProfile::Counter* _memory_usage_reserved = nullptr;
};
class PartitionedHashJoinProbeOperatorX final
@@ -183,9 +192,14 @@ public:
return _inner_probe_operator->require_data_distribution();
}
+ Status revoke_memory(RuntimeState* state,
+ const std::shared_ptr<SpillContext>& spill_context) override;
+
private:
Status _revoke_memory(RuntimeState* state);
+ size_t _revocable_mem_size(RuntimeState* state, bool force = false) const;
+
friend class PartitionedHashJoinProbeLocalState;
[[nodiscard]] Status _setup_internal_operators(PartitionedHashJoinProbeLocalState& local_state,
diff --git a/be/src/pipeline/exec/partitioned_hash_join_sink_operator.cpp b/be/src/pipeline/exec/partitioned_hash_join_sink_operator.cpp
index ce7198aa4f5..b02c7ee0971 100644
--- a/be/src/pipeline/exec/partitioned_hash_join_sink_operator.cpp
+++ b/be/src/pipeline/exec/partitioned_hash_join_sink_operator.cpp
@@ -17,9 +17,19 @@
#include "partitioned_hash_join_sink_operator.h"
+#include <glog/logging.h>
+
+#include <algorithm>
+#include <memory>
+
+#include "common/logging.h"
#include "pipeline/exec/operator.h"
+#include "pipeline/exec/spill_utils.h"
+#include "pipeline/pipeline_task.h"
#include "runtime/fragment_mgr.h"
#include "util/mem_info.h"
+#include "util/runtime_profile.h"
+#include "vec/spill/spill_stream.h"
#include "vec/spill/spill_stream_manager.h"
namespace doris::pipeline {
@@ -33,12 +43,20 @@ Status PartitionedHashJoinSinkLocalState::init(doris::RuntimeState* state,
_shared_state->partitioned_build_blocks.resize(p._partition_count);
_shared_state->spilled_streams.resize(p._partition_count);
+ _rows_in_partitions.assign(p._partition_count, 0);
+
+ _spill_dependency = Dependency::create_shared(_parent->operator_id(), _parent->node_id(),
+ "HashJoinBuildSpillDependency", true);
+ state->get_task()->add_spill_dependency(_spill_dependency.get());
+
_internal_runtime_profile.reset(new RuntimeProfile("internal_profile"));
- _partition_timer = ADD_CHILD_TIMER_WITH_LEVEL(profile(), "PartitionTime", "Spill", 1);
- _partition_shuffle_timer =
- ADD_CHILD_TIMER_WITH_LEVEL(profile(), "PartitionShuffleTime", "Spill", 1);
- _spill_build_timer = ADD_CHILD_TIMER_WITH_LEVEL(profile(), "SpillBuildTime", "Spill", 1);
+ _partition_timer = ADD_TIMER_WITH_LEVEL(profile(), "SpillPartitionTime", 1);
+ _partition_shuffle_timer = ADD_TIMER_WITH_LEVEL(profile(), "SpillPartitionShuffleTime", 1);
+ _spill_build_timer = ADD_TIMER_WITH_LEVEL(profile(), "SpillBuildTime", 1);
+ _in_mem_rows_counter = ADD_COUNTER_WITH_LEVEL(profile(), "SpillInMemRow", TUnit::UNIT, 1);
+ _memory_usage_reserved =
+ ADD_COUNTER_WITH_LEVEL(profile(), "MemoryUsageReserved", TUnit::BYTES, 1);
return Status::OK();
}
@@ -57,7 +75,7 @@ Status PartitionedHashJoinSinkLocalState::open(RuntimeState* state) {
RETURN_IF_ERROR(spilling_stream->prepare_spill());
spilling_stream->set_write_counters(_spill_serialize_block_timer, _spill_block_count,
_spill_data_size, _spill_write_disk_timer,
- _spill_write_wait_io_timer);
+ _spill_write_wait_io_timer, memory_used_counter());
}
return p._partitioner->clone(state, _partitioner);
}
@@ -76,9 +94,9 @@ size_t PartitionedHashJoinSinkLocalState::revocable_mem_size(RuntimeState* state
/// If no need to spill, all rows were sunk into the `_inner_sink_operator` without partitioned.
if (!_shared_state->need_to_spill) {
if (_shared_state->inner_shared_state) {
- auto inner_sink_state_ = _shared_state->inner_runtime_state->get_sink_local_state();
+ auto* inner_sink_state_ = _shared_state->inner_runtime_state->get_sink_local_state();
if (inner_sink_state_) {
- auto inner_sink_state =
+ auto* inner_sink_state =
assert_cast<HashJoinBuildSinkLocalState*>(inner_sink_state_);
return inner_sink_state->_build_blocks_memory_usage->value();
}
@@ -99,29 +117,53 @@ size_t PartitionedHashJoinSinkLocalState::revocable_mem_size(RuntimeState* state
return mem_size;
}
-Status PartitionedHashJoinSinkLocalState::_revoke_unpartitioned_block(RuntimeState* state) {
+size_t PartitionedHashJoinSinkLocalState::get_reserve_mem_size(RuntimeState* state) {
+ size_t size_to_reserve = 0;
+ auto& p = _parent->cast<PartitionedHashJoinSinkOperatorX>();
+ if (_shared_state->need_to_spill) {
+ size_to_reserve = p._partition_count * vectorized::SpillStream::MIN_SPILL_WRITE_BATCH_MEM;
+ } else {
+ if (_shared_state->inner_runtime_state) {
+ size_to_reserve = p._inner_sink_operator->get_reserve_mem_size(
+ _shared_state->inner_runtime_state.get());
+ }
+ }
+
+ COUNTER_SET(_memory_usage_reserved, int64_t(size_to_reserve));
+ return size_to_reserve;
+}
+
+Status PartitionedHashJoinSinkLocalState::_revoke_unpartitioned_block(
+ RuntimeState* state, const std::shared_ptr<SpillContext>& spill_context) {
auto& p = _parent->cast<PartitionedHashJoinSinkOperatorX>();
_shared_state->inner_shared_state->hash_table_variants.reset();
auto row_desc = p._child->row_desc();
const auto num_slots = row_desc.num_slots();
vectorized::Block build_block;
- auto inner_sink_state_ = _shared_state->inner_runtime_state->get_sink_local_state();
+ size_t block_old_mem = 0;
+ auto* inner_sink_state_ = _shared_state->inner_runtime_state->get_sink_local_state();
if (inner_sink_state_) {
- auto inner_sink_state = assert_cast<HashJoinBuildSinkLocalState*>(inner_sink_state_);
+ auto* inner_sink_state = assert_cast<HashJoinBuildSinkLocalState*>(inner_sink_state_);
build_block = inner_sink_state->_build_side_mutable_block.to_block();
+ block_old_mem = build_block.allocated_bytes();
}
if (build_block.rows() <= 1) {
LOG(WARNING) << "has no data to revoke, node: " << _parent->node_id()
<< ", task: " << state->task_id();
+ if (spill_context) {
+ spill_context->on_task_finished();
+ }
return Status::OK();
}
if (build_block.columns() > num_slots) {
build_block.erase(num_slots);
+ memory_used_counter()->update(build_block.allocated_bytes() - block_old_mem);
}
auto spill_func = [build_block = std::move(build_block), state, this]() mutable {
+ Defer defer {[&]() { memory_used_counter()->set((int64_t)revocable_mem_size(state)); }};
auto& p = _parent->cast<PartitionedHashJoinSinkOperatorX>();
auto& partitioned_blocks = _shared_state->partitioned_build_blocks;
std::vector<std::vector<uint32_t>> partitions_indexes(p._partition_count);
@@ -133,13 +175,14 @@ Status PartitionedHashJoinSinkLocalState::_revoke_unpartitioned_block(RuntimeSta
auto flush_rows = [&state, this](std::unique_ptr<vectorized::MutableBlock>& partition_block,
vectorized::SpillStreamSPtr& spilling_stream) {
auto block = partition_block->to_block();
+ Defer defer {[&]() { memory_used_counter()->update(-block.allocated_bytes()); }};
auto status = spilling_stream->spill_block(state, block, false);
if (!status.ok()) {
std::unique_lock<std::mutex> lock(_spill_lock);
_spill_status = status;
_spill_status_ok = false;
- _dependency->set_ready();
+ _spill_dependency->set_ready();
return false;
}
return true;
@@ -155,6 +198,9 @@ Status PartitionedHashJoinSinkLocalState::_revoke_unpartitioned_block(RuntimeSta
sub_block.get_by_position(i).column =
build_block.get_by_position(i).column->cut(offset, this_run);
}
+ auto sub_blocks_memory_usage = sub_block.allocated_bytes();
+ memory_used_counter()->update(sub_blocks_memory_usage);
+ Defer defer {[&]() { memory_used_counter()->update(-sub_blocks_memory_usage); }};
offset += this_run;
const auto is_last_block = offset == total_rows;
@@ -179,15 +225,17 @@ Status PartitionedHashJoinSinkLocalState::_revoke_unpartitioned_block(RuntimeSta
partition_block =
vectorized::MutableBlock::create_unique(build_block.clone_empty());
}
+ auto old_mem = partition_block->allocated_bytes();
{
SCOPED_TIMER(_partition_shuffle_timer);
Status st = partition_block->add_rows(&sub_block, begin, end);
+ memory_used_counter()->update(partition_block->allocated_bytes() - old_mem);
if (!st.ok()) {
std::unique_lock<std::mutex> lock(_spill_lock);
_spill_status = st;
_spill_status_ok = false;
- _dependency->set_ready();
+ _spill_dependency->set_ready();
return;
}
partitions_indexes[partition_idx].clear();
@@ -199,14 +247,16 @@ Status PartitionedHashJoinSinkLocalState::_revoke_unpartitioned_block(RuntimeSta
}
partition_block =
vectorized::MutableBlock::create_unique(build_block.clone_empty());
+ memory_used_counter()->update(partition_block->allocated_bytes());
}
}
}
- _dependency->set_ready();
+ _spill_dependency->set_ready();
};
- auto exception_catch_func = [spill_func, this]() mutable {
+ auto exception_catch_func = [spill_func, spill_context, this]() mutable {
+ SCOPED_TIMER(_spill_timer);
auto status = [&]() {
RETURN_IF_CATCH_EXCEPTION(spill_func());
return Status::OK();
@@ -216,7 +266,11 @@ Status PartitionedHashJoinSinkLocalState::_revoke_unpartitioned_block(RuntimeSta
std::unique_lock<std::mutex> lock(_spill_lock);
_spill_status = status;
_spill_status_ok = false;
- _dependency->set_ready();
+ _spill_dependency->set_ready();
+ }
+
+ if (spill_context) {
+ spill_context->on_task_finished();
}
};
@@ -225,7 +279,7 @@ Status PartitionedHashJoinSinkLocalState::_revoke_unpartitioned_block(RuntimeSta
auto* thread_pool = ExecEnv::GetInstance()->spill_stream_mgr()->get_spill_io_thread_pool();
- _dependency->block();
+ _spill_dependency->block();
DBUG_EXECUTE_IF(
"fault_inject::partitioned_hash_join_sink::revoke_unpartitioned_block_submit_func", {
return Status::Error<INTERNAL_ERROR>(
@@ -235,15 +289,18 @@ Status PartitionedHashJoinSinkLocalState::_revoke_unpartitioned_block(RuntimeSta
return thread_pool->submit(std::move(spill_runnable));
}
-Status PartitionedHashJoinSinkLocalState::revoke_memory(RuntimeState* state) {
- LOG(INFO) << "hash join sink " << _parent->node_id() << " revoke_memory"
- << ", eos: " << _child_eos;
+Status PartitionedHashJoinSinkLocalState::revoke_memory(
+ RuntimeState* state, const std::shared_ptr<SpillContext>& spill_context) {
+ VLOG_DEBUG << "query: " << print_id(state->query_id()) << ", task: " << state->task_id()
+ << " hash join sink " << _parent->node_id() << " revoke_memory"
+ << ", eos: " << _child_eos;
DCHECK_EQ(_spilling_streams_count, 0);
+ CHECK_EQ(_spill_dependency->is_blocked_by(nullptr), nullptr);
if (!_shared_state->need_to_spill) {
profile()->add_info_string("Spilled", "true");
_shared_state->need_to_spill = true;
- return _revoke_unpartitioned_block(state);
+ return _revoke_unpartitioned_block(state, spill_context);
}
_spilling_streams_count = _shared_state->partitioned_build_blocks.size();
@@ -274,10 +331,12 @@ Status PartitionedHashJoinSinkLocalState::revoke_memory(RuntimeState* state) {
st = Status::Error<INTERNAL_ERROR>(
"fault_inject partitioned_hash_join_sink revoke_memory submit_func failed");
});
-
+ // For every stream, the task counter is increased +1
+ // so that when a stream finished, it should desc -1
+ state->get_query_ctx()->increase_revoking_tasks_count();
auto spill_runnable = std::make_shared<SpillRunnable>(
state, _shared_state->shared_from_this(),
- [this, query_id, spilling_stream, i, submit_timer] {
+ [this, query_id, spilling_stream, i, submit_timer, spill_context] {
DBUG_EXECUTE_IF(
"fault_inject::partitioned_hash_join_sink::revoke_memory_cancel", {
ExecEnv::GetInstance()->fragment_mgr()->cancel_query(
@@ -290,13 +349,15 @@ Status PartitionedHashJoinSinkLocalState::revoke_memory(RuntimeState* state) {
SCOPED_TIMER(_spill_build_timer);
auto status = [&]() {
- RETURN_IF_CATCH_EXCEPTION(_spill_to_disk(i, spilling_stream));
+ RETURN_IF_CATCH_EXCEPTION(
+ _spill_to_disk(i, spilling_stream, spill_context));
return Status::OK();
}();
- if (!status.OK()) {
+ _state->get_query_ctx()->decrease_revoking_tasks_count();
+ if (!status.ok()) {
std::unique_lock<std::mutex> lock(_spill_lock);
- _dependency->set_ready();
+ _spill_dependency->set_ready();
_spill_status_ok = false;
_spill_status = std::move(status);
}
@@ -311,14 +372,33 @@ Status PartitionedHashJoinSinkLocalState::revoke_memory(RuntimeState* state) {
}
}
+ std::unique_lock<std::mutex> lock(_spill_lock);
if (_spilling_streams_count > 0) {
- std::unique_lock<std::mutex> lock(_spill_lock);
- if (_spilling_streams_count > 0) {
- _dependency->block();
- } else if (_child_eos) {
- LOG(INFO) << "hash join sink " << _parent->node_id() << " set_ready_to_read"
- << ", task id: " << state->task_id();
- _dependency->set_ready_to_read();
+ _spill_dependency->block();
+ } else if (_child_eos) {
+ VLOG_DEBUG << "query:" << print_id(state->query_id()) << ", hash join sink "
+ << _parent->node_id() << " set_ready_to_read"
+ << ", task id: " << state->task_id();
+ std::for_each(_shared_state->partitioned_build_blocks.begin(),
+ _shared_state->partitioned_build_blocks.end(), [&](auto& block) {
+ if (block) {
+ COUNTER_UPDATE(_in_mem_rows_counter, block->rows());
+ }
+ });
+ RETURN_IF_ERROR(_finish_spilling());
+ _dependency->set_ready_to_read();
+
+ if (spill_context) {
+ spill_context->on_task_finished();
+ }
+ }
+ return Status::OK();
+}
+
+Status PartitionedHashJoinSinkLocalState::_finish_spilling() {
+ for (auto& stream : _shared_state->spilled_streams) {
+ if (stream) {
+ RETURN_IF_ERROR(stream->spill_eof());
}
}
return Status::OK();
@@ -331,6 +411,7 @@ Status PartitionedHashJoinSinkLocalState::_partition_block(RuntimeState* state,
if (!rows) {
return Status::OK();
}
+ Defer defer {[&]() { memory_used_counter()->set((int64_t)revocable_mem_size(state)); }};
{
/// TODO: DO NOT execute build exprs twice(when partition and building hash table)
SCOPED_TIMER(_partition_timer);
@@ -359,18 +440,25 @@ Status PartitionedHashJoinSinkLocalState::_partition_block(RuntimeState* state,
}
RETURN_IF_ERROR(partitioned_blocks[i]->add_rows(in_block, partition_indexes[i].data(),
partition_indexes[i].data() + count));
+ _rows_in_partitions[i] += count;
}
+ update_max_min_rows_counter();
+
return Status::OK();
}
void PartitionedHashJoinSinkLocalState::_spill_to_disk(
- uint32_t partition_index, const vectorized::SpillStreamSPtr& spilling_stream) {
+ uint32_t partition_index, const vectorized::SpillStreamSPtr& spilling_stream,
+ const std::shared_ptr<SpillContext>& spill_context) {
auto& partitioned_block = _shared_state->partitioned_build_blocks[partition_index];
if (_spill_status_ok) {
auto block = partitioned_block->to_block();
+ auto block_mem_usage = block.allocated_bytes();
+ Defer defer {[&]() { memory_used_counter()->update(-block_mem_usage); }};
partitioned_block = vectorized::MutableBlock::create_unique(block.clone_empty());
+ memory_used_counter()->update(partitioned_block->allocated_bytes());
auto st = spilling_stream->spill_block(state(), block, false);
if (!st.ok()) {
_spill_status_ok = false;
@@ -379,17 +467,31 @@ void PartitionedHashJoinSinkLocalState::_spill_to_disk(
}
}
+ VLOG_DEBUG << "query: " << print_id(_state->query_id()) << ", task: " << _state->task_id()
+ << ", join sink " << _parent->node_id() << " revoke done";
auto num = _spilling_streams_count.fetch_sub(1);
DCHECK_GE(_spilling_streams_count, 0);
if (num == 1) {
std::unique_lock<std::mutex> lock(_spill_lock);
- _dependency->set_ready();
+ _spill_dependency->set_ready();
if (_child_eos) {
- LOG(INFO) << "hash join sink " << _parent->node_id() << " set_ready_to_read"
- << ", task id: " << state()->task_id();
+ VLOG_DEBUG << "query:" << print_id(this->state()->query_id()) << ", hash join sink "
+ << _parent->node_id() << " set_ready_to_read"
+ << ", task id: " << state()->task_id();
+ std::for_each(_shared_state->partitioned_build_blocks.begin(),
+ _shared_state->partitioned_build_blocks.end(), [&](auto& block) {
+ if (block) {
+ COUNTER_UPDATE(_in_mem_rows_counter, block->rows());
+ }
+ });
+ _spill_status = _finish_spilling();
_dependency->set_ready_to_read();
}
+
+ if (spill_context) {
+ spill_context->on_task_finished();
+ }
}
}
@@ -477,6 +579,7 @@ Status PartitionedHashJoinSinkOperatorX::_setup_internal_operator(RuntimeState*
Status PartitionedHashJoinSinkOperatorX::sink(RuntimeState* state, vectorized::Block* in_block,
bool eos) {
auto& local_state = get_local_state(state);
+ CHECK_EQ(local_state._spill_dependency->is_blocked_by(nullptr), nullptr);
local_state.inc_running_big_mem_op_num(state);
SCOPED_TIMER(local_state.exec_time_counter());
if (!local_state._spill_status_ok) {
@@ -491,10 +594,13 @@ Status PartitionedHashJoinSinkOperatorX::sink(RuntimeState* state, vectorized::B
const auto need_to_spill = local_state._shared_state->need_to_spill;
if (rows == 0) {
if (eos) {
- LOG(INFO) << "hash join sink " << node_id() << " sink eos, set_ready_to_read"
- << ", task id: " << state->task_id();
+ VLOG_DEBUG << "query: " << print_id(state->query_id()) << ", hash join sink "
+ << node_id() << " sink eos, set_ready_to_read"
+ << ", task id: " << state->task_id() << ", need spill: " << need_to_spill;
- if (!need_to_spill) {
+ if (need_to_spill) {
+ return revoke_memory(state, nullptr);
+ } else {
if (UNLIKELY(!local_state._shared_state->inner_runtime_state)) {
RETURN_IF_ERROR(_setup_internal_operator(state));
}
@@ -503,9 +609,25 @@ Status PartitionedHashJoinSinkOperatorX::sink(RuntimeState* state, vectorized::B
"fault_inject partitioned_hash_join_sink "
"sink_eos failed");
});
+ Defer defer {[&]() {
+ local_state.memory_used_counter()->set(
+ (int64_t)local_state.revocable_mem_size(state));
+ }};
RETURN_IF_ERROR(_inner_sink_operator->sink(
local_state._shared_state->inner_runtime_state.get(), in_block, eos));
+ VLOG_DEBUG << "hash join sink " << node_id() << " sink eos, set_ready_to_read"
+ << ", task id: " << state->task_id() << ", nonspill build usage: "
+ << _inner_sink_operator->get_memory_usage(
+ local_state._shared_state->inner_runtime_state.get());
}
+
+ std::for_each(local_state._shared_state->partitioned_build_blocks.begin(),
+ local_state._shared_state->partitioned_build_blocks.end(),
+ [&](auto& block) {
+ if (block) {
+ COUNTER_UPDATE(local_state._in_mem_rows_counter, block->rows());
+ }
+ });
local_state._dependency->set_ready_to_read();
}
return Status::OK();
@@ -514,10 +636,10 @@ Status PartitionedHashJoinSinkOperatorX::sink(RuntimeState* state, vectorized::B
COUNTER_UPDATE(local_state.rows_input_counter(), (int64_t)in_block->rows());
if (need_to_spill) {
RETURN_IF_ERROR(local_state._partition_block(state, in_block, 0, rows));
-
- const auto revocable_size = revocable_mem_size(state);
- if (revocable_size > state->min_revocable_mem()) {
- return local_state.revoke_memory(state);
+ if (eos) {
+ return revoke_memory(state, nullptr);
+ } else if (revocable_mem_size(state) > vectorized::SpillStream::MAX_SPILL_WRITE_BATCH_MEM) {
+ return revoke_memory(state, nullptr);
}
} else {
if (UNLIKELY(!local_state._shared_state->inner_runtime_state)) {
@@ -528,14 +650,18 @@ Status PartitionedHashJoinSinkOperatorX::sink(RuntimeState* state, vectorized::B
"fault_inject partitioned_hash_join_sink "
"sink failed");
});
+ Defer defer {[&]() {
+ local_state.memory_used_counter()->set((int64_t)local_state.revocable_mem_size(state));
+ }};
RETURN_IF_ERROR(_inner_sink_operator->sink(
local_state._shared_state->inner_runtime_state.get(), in_block, eos));
- }
- if (eos) {
- LOG(INFO) << "hash join sink " << node_id() << " sink eos, set_ready_to_read"
- << ", task id: " << state->task_id();
- local_state._dependency->set_ready_to_read();
+ if (eos) {
+ VLOG_DEBUG << "query: " << print_id(state->query_id()) << ", hash join sink "
+ << node_id() << " sink eos, set_ready_to_read"
+ << ", task id: " << state->task_id();
+ local_state._dependency->set_ready_to_read();
+ }
}
return Status::OK();
@@ -547,10 +673,16 @@ size_t PartitionedHashJoinSinkOperatorX::revocable_mem_size(RuntimeState* state)
return local_state.revocable_mem_size(state);
}
-Status PartitionedHashJoinSinkOperatorX::revoke_memory(RuntimeState* state) {
+Status PartitionedHashJoinSinkOperatorX::revoke_memory(
+ RuntimeState* state, const std::shared_ptr<SpillContext>& spill_context) {
auto& local_state = get_local_state(state);
SCOPED_TIMER(local_state.exec_time_counter());
- return local_state.revoke_memory(state);
+ return local_state.revoke_memory(state, spill_context);
+}
+
+size_t PartitionedHashJoinSinkOperatorX::get_reserve_mem_size(RuntimeState* state) {
+ auto& local_state = get_local_state(state);
+ return local_state.get_reserve_mem_size(state);
}
} // namespace doris::pipeline
diff --git a/be/src/pipeline/exec/partitioned_hash_join_sink_operator.h b/be/src/pipeline/exec/partitioned_hash_join_sink_operator.h
index c768d7518b9..d8cb67ca08d 100644
--- a/be/src/pipeline/exec/partitioned_hash_join_sink_operator.h
+++ b/be/src/pipeline/exec/partitioned_hash_join_sink_operator.h
@@ -43,20 +43,25 @@ public:
Status init(RuntimeState* state, LocalSinkStateInfo& info) override;
Status open(RuntimeState* state) override;
Status close(RuntimeState* state, Status exec_status) override;
- Status revoke_memory(RuntimeState* state);
+ Status revoke_memory(RuntimeState* state, const std::shared_ptr<SpillContext>& spill_context);
size_t revocable_mem_size(RuntimeState* state) const;
+ [[nodiscard]] size_t get_reserve_mem_size(RuntimeState* state);
protected:
PartitionedHashJoinSinkLocalState(DataSinkOperatorXBase* parent, RuntimeState* state)
: PipelineXSpillSinkLocalState<PartitionedHashJoinSharedState>(parent, state) {}
void _spill_to_disk(uint32_t partition_index,
- const vectorized::SpillStreamSPtr& spilling_stream);
+ const vectorized::SpillStreamSPtr& spilling_stream,
+ const std::shared_ptr<SpillContext>& spill_context);
Status _partition_block(RuntimeState* state, vectorized::Block* in_block, size_t begin,
size_t end);
- Status _revoke_unpartitioned_block(RuntimeState* state);
+ Status _revoke_unpartitioned_block(RuntimeState* state,
+ const std::shared_ptr<SpillContext>& spill_context);
+
+ Status _finish_spilling();
friend class PartitionedHashJoinSinkOperatorX;
@@ -76,6 +81,8 @@ protected:
RuntimeProfile::Counter* _partition_timer = nullptr;
RuntimeProfile::Counter* _partition_shuffle_timer = nullptr;
RuntimeProfile::Counter* _spill_build_timer = nullptr;
+ RuntimeProfile::Counter* _in_mem_rows_counter = nullptr;
+ RuntimeProfile::Counter* _memory_usage_reserved = nullptr;
};
class PartitionedHashJoinSinkOperatorX
@@ -100,7 +107,10 @@ public:
size_t revocable_mem_size(RuntimeState* state) const override;
- Status revoke_memory(RuntimeState* state) override;
+ Status revoke_memory(RuntimeState* state,
+ const std::shared_ptr<SpillContext>& spill_context) override;
+
+ size_t get_reserve_mem_size(RuntimeState* state) override;
DataDistribution required_data_distribution() const override {
if (_join_op == TJoinOp::NULL_AWARE_LEFT_ANTI_JOIN) {
diff --git a/be/src/pipeline/exec/repeat_operator.cpp b/be/src/pipeline/exec/repeat_operator.cpp
index dba4f27af7c..8cc92568694 100644
--- a/be/src/pipeline/exec/repeat_operator.cpp
+++ b/be/src/pipeline/exec/repeat_operator.cpp
@@ -171,6 +171,7 @@ Status RepeatOperatorX::push(RuntimeState* state, vectorized::Block* input_block
auto& _expr_ctxs = local_state._expr_ctxs;
DCHECK(!_intermediate_block || _intermediate_block->rows() == 0);
if (input_block->rows() > 0) {
+ SCOPED_PEAK_MEM(&local_state._estimate_memory_usage);
_intermediate_block = vectorized::Block::create_unique();
for (auto& expr : _expr_ctxs) {
@@ -196,6 +197,9 @@ Status RepeatOperatorX::pull(doris::RuntimeState* state, vectorized::Block* outp
auto& _child_eos = local_state._child_eos;
auto& _intermediate_block = local_state._intermediate_block;
RETURN_IF_CANCELLED(state);
+
+ SCOPED_PEAK_MEM(&local_state._estimate_memory_usage);
+
DCHECK(_repeat_id_idx >= 0);
for (const std::vector<int64_t>& v : _grouping_list) {
DCHECK(_repeat_id_idx <= (int)v.size());
@@ -227,8 +231,9 @@ Status RepeatOperatorX::pull(doris::RuntimeState* state, vectorized::Block* outp
}
_child_block.clear_column_data(_child->row_desc().num_materialized_slots());
}
- RETURN_IF_ERROR(vectorized::VExprContext::filter_block(local_state._conjuncts, output_block,
- output_block->columns()));
+
+ RETURN_IF_ERROR(local_state.filter_block(local_state._conjuncts, output_block,
+ output_block->columns()));
*eos = _child_eos && _child_block.rows() == 0;
local_state.reached_limit(output_block, eos);
return Status::OK();
diff --git a/be/src/pipeline/exec/scan_operator.cpp b/be/src/pipeline/exec/scan_operator.cpp
index b7dc78bf8f1..3cfa55c89fc 100644
--- a/be/src/pipeline/exec/scan_operator.cpp
+++ b/be/src/pipeline/exec/scan_operator.cpp
@@ -1287,6 +1287,31 @@ Status ScanOperatorX<LocalStateType>::get_block(RuntimeState* state, vectorized:
return Status::OK();
}
+template <typename LocalStateType>
+size_t ScanOperatorX<LocalStateType>::get_reserve_mem_size(RuntimeState* state) {
+ auto& local_state = get_local_state(state);
+ if (local_state.low_memory_mode()) {
+ return local_state._scanner_ctx->low_memory_mode_scan_bytes_per_scanner() *
+ local_state._scanner_ctx->low_memory_mode_scanners();
+ } else {
+ if (local_state._scanner_peak_memory_usage->value() > 0) {
+ // It is only a safty check, to avoid some counter not right.
+ if (local_state._scanner_peak_memory_usage->value() >
+ local_state._scanner_ctx->block_memory_usage()) {
+ return local_state._scanner_peak_memory_usage->value() -
+ local_state._scanner_ctx->block_memory_usage();
+ } else {
+ return config::doris_scanner_row_bytes;
+ }
+ } else {
+ // If the scan operator is first time to run, then we think it will occupy doris_scanner_row_bytes.
+ // It maybe a little smaller than actual usage.
+ return config::doris_scanner_row_bytes;
+ // return local_state._scanner_ctx->max_bytes_in_queue();
+ }
+ }
+}
+
template class ScanOperatorX<OlapScanLocalState>;
template class ScanLocalState<OlapScanLocalState>;
template class ScanOperatorX<JDBCScanLocalState>;
diff --git a/be/src/pipeline/exec/scan_operator.h b/be/src/pipeline/exec/scan_operator.h
index 2b0d82ee788..9865a067b63 100644
--- a/be/src/pipeline/exec/scan_operator.h
+++ b/be/src/pipeline/exec/scan_operator.h
@@ -373,6 +373,8 @@ public:
[[nodiscard]] virtual bool is_file_scan_operator() const { return false; }
+ [[nodiscard]] size_t get_reserve_mem_size(RuntimeState* state) override;
+
const std::vector<TRuntimeFilterDesc>& runtime_filter_descs() override {
return _runtime_filter_descs;
}
diff --git a/be/src/pipeline/exec/schema_scan_operator.cpp b/be/src/pipeline/exec/schema_scan_operator.cpp
index 006ecf8ad82..7a3b7f6efaa 100644
--- a/be/src/pipeline/exec/schema_scan_operator.cpp
+++ b/be/src/pipeline/exec/schema_scan_operator.cpp
@@ -259,8 +259,8 @@ Status SchemaScanOperatorX::get_block(RuntimeState* state, vectorized::Block* bl
*src_block.get_by_name(dest_slot_desc->col_name()).column, 0,
src_block.rows());
}
- RETURN_IF_ERROR(vectorized::VExprContext::filter_block(
- local_state._conjuncts, block, _dest_tuple_desc->slots().size()));
+ RETURN_IF_ERROR(local_state.filter_block(local_state._conjuncts, block,
+ _dest_tuple_desc->slots().size()));
src_block.clear();
}
} while (block->rows() == 0 && !*eos);
diff --git a/be/src/pipeline/exec/select_operator.h b/be/src/pipeline/exec/select_operator.h
index 5370cd9e293..e5e57e57203 100644
--- a/be/src/pipeline/exec/select_operator.h
+++ b/be/src/pipeline/exec/select_operator.h
@@ -46,8 +46,7 @@ public:
auto& local_state = get_local_state(state);
SCOPED_TIMER(local_state.exec_time_counter());
RETURN_IF_CANCELLED(state);
- RETURN_IF_ERROR(vectorized::VExprContext::filter_block(local_state._conjuncts, block,
- block->columns()));
+ RETURN_IF_ERROR(local_state.filter_block(local_state._conjuncts, block, block->columns()));
local_state.reached_limit(block, eos);
return Status::OK();
}
diff --git a/be/src/pipeline/exec/set_probe_sink_operator.cpp b/be/src/pipeline/exec/set_probe_sink_operator.cpp
index 955f956f60d..7e62892f516 100644
--- a/be/src/pipeline/exec/set_probe_sink_operator.cpp
+++ b/be/src/pipeline/exec/set_probe_sink_operator.cpp
@@ -68,6 +68,7 @@ Status SetProbeSinkOperatorX<is_intersect>::sink(RuntimeState* state, vectorized
auto& local_state = get_local_state(state);
SCOPED_TIMER(local_state.exec_time_counter());
COUNTER_UPDATE(local_state.rows_input_counter(), (int64_t)in_block->rows());
+ SCOPED_PEAK_MEM(&local_state._estimate_memory_usage);
auto probe_rows = in_block->rows();
if (probe_rows > 0) {
@@ -194,6 +195,12 @@ void SetProbeSinkOperatorX<is_intersect>::_finalize_probe(
}
}
+template <bool is_intersect>
+size_t SetProbeSinkOperatorX<is_intersect>::get_reserve_mem_size(RuntimeState* state) {
+ auto& local_state = get_local_state(state);
+ return local_state._estimate_memory_usage;
+}
+
template <bool is_intersect>
void SetProbeSinkOperatorX<is_intersect>::_refresh_hash_table(
SetProbeSinkLocalState<is_intersect>& local_state) {
diff --git a/be/src/pipeline/exec/set_probe_sink_operator.h b/be/src/pipeline/exec/set_probe_sink_operator.h
index ab53f5358c2..542f6652dd4 100644
--- a/be/src/pipeline/exec/set_probe_sink_operator.h
+++ b/be/src/pipeline/exec/set_probe_sink_operator.h
@@ -55,6 +55,8 @@ private:
template <class HashTableContext, bool is_intersected>
friend struct vectorized::HashTableProbe;
+ int64_t _estimate_memory_usage = 0;
+
//record insert column id during probe
std::vector<uint16_t> _probe_column_inserted_id;
vectorized::ColumnRawPtrs _probe_columns;
@@ -100,6 +102,8 @@ public:
std::shared_ptr<BasicSharedState> create_shared_state() const override { return nullptr; }
+ size_t get_reserve_mem_size(RuntimeState* state) override;
+
private:
void _finalize_probe(SetProbeSinkLocalState<is_intersect>& local_state);
Status _extract_probe_column(SetProbeSinkLocalState<is_intersect>& local_state,
diff --git a/be/src/pipeline/exec/set_sink_operator.cpp b/be/src/pipeline/exec/set_sink_operator.cpp
index 38667293d48..e15cecd22ed 100644
--- a/be/src/pipeline/exec/set_sink_operator.cpp
+++ b/be/src/pipeline/exec/set_sink_operator.cpp
@@ -206,6 +206,27 @@ Status SetSinkOperatorX<is_intersect>::init(const TPlanNode& tnode, RuntimeState
return Status::OK();
}
+template <bool is_intersect>
+size_t SetSinkOperatorX<is_intersect>::get_reserve_mem_size(RuntimeState* state) {
+ auto& local_state = get_local_state(state);
+ size_t size_to_reserve = std::visit(
+ [&](auto&& arg) -> size_t {
+ using HashTableCtxType = std::decay_t<decltype(arg)>;
+ if constexpr (std::is_same_v<HashTableCtxType, std::monostate>) {
+ return 0;
+ } else {
+ return arg.hash_table->estimate_memory(state->batch_size());
+ }
+ },
+ *local_state._shared_state->hash_table_variants);
+
+ size_to_reserve += local_state._mutable_block.allocated_bytes();
+ for (auto& _child_expr : _child_exprs) {
+ size_to_reserve += _child_expr->root()->estimate_memory(state->batch_size());
+ }
+ return size_to_reserve;
+}
+
template <bool is_intersect>
Status SetSinkOperatorX<is_intersect>::open(RuntimeState* state) {
RETURN_IF_ERROR(Base::open(state));
diff --git a/be/src/pipeline/exec/set_sink_operator.h b/be/src/pipeline/exec/set_sink_operator.h
index 1c08eddc141..be600ff6fd5 100644
--- a/be/src/pipeline/exec/set_sink_operator.h
+++ b/be/src/pipeline/exec/set_sink_operator.h
@@ -95,6 +95,8 @@ public:
}
bool require_shuffled_data_distribution() const override { return true; }
+ size_t get_reserve_mem_size(RuntimeState* state) override;
+
private:
template <class HashTableContext, bool is_intersected>
friend struct HashTableBuild;
diff --git a/be/src/pipeline/exec/set_source_operator.cpp b/be/src/pipeline/exec/set_source_operator.cpp
index 554a58caf14..d1c42ae3c08 100644
--- a/be/src/pipeline/exec/set_source_operator.cpp
+++ b/be/src/pipeline/exec/set_source_operator.cpp
@@ -74,6 +74,8 @@ Status SetSourceOperatorX<is_intersect>::get_block(RuntimeState* state, vectoriz
RETURN_IF_CANCELLED(state);
auto& local_state = get_local_state(state);
SCOPED_TIMER(local_state.exec_time_counter());
+ SCOPED_PEAK_MEM(&local_state._estimate_memory_usage);
+
_create_mutable_cols(local_state, block);
auto st = std::visit(
[&](auto&& arg) -> Status {
@@ -88,8 +90,7 @@ Status SetSourceOperatorX<is_intersect>::get_block(RuntimeState* state, vectoriz
},
*local_state._shared_state->hash_table_variants);
RETURN_IF_ERROR(st);
- RETURN_IF_ERROR(vectorized::VExprContext::filter_block(local_state._conjuncts, block,
- block->columns()));
+ RETURN_IF_ERROR(local_state.filter_block(local_state._conjuncts, block, block->columns()));
local_state.reached_limit(block, eos);
return Status::OK();
}
diff --git a/be/src/pipeline/exec/sort_source_operator.cpp b/be/src/pipeline/exec/sort_source_operator.cpp
index 02a99e183c8..30335904442 100644
--- a/be/src/pipeline/exec/sort_source_operator.cpp
+++ b/be/src/pipeline/exec/sort_source_operator.cpp
@@ -53,6 +53,8 @@ Status SortSourceOperatorX::open(RuntimeState* state) {
Status SortSourceOperatorX::get_block(RuntimeState* state, vectorized::Block* block, bool* eos) {
auto& local_state = get_local_state(state);
SCOPED_TIMER(local_state.exec_time_counter());
+ SCOPED_PEAK_MEM(&local_state._estimate_memory_usage);
+
RETURN_IF_ERROR(local_state._shared_state->sorter->get_next(state, block, eos));
local_state.reached_limit(block, eos);
return Status::OK();
diff --git a/be/src/pipeline/exec/spill_sort_sink_operator.cpp b/be/src/pipeline/exec/spill_sort_sink_operator.cpp
index 267bcc83aad..38e7a173197 100644
--- a/be/src/pipeline/exec/spill_sort_sink_operator.cpp
+++ b/be/src/pipeline/exec/spill_sort_sink_operator.cpp
@@ -19,6 +19,7 @@
#include "pipeline/exec/sort_sink_operator.h"
#include "pipeline/exec/spill_utils.h"
+#include "pipeline/pipeline_task.h"
#include "runtime/fragment_mgr.h"
#include "vec/spill/spill_stream_manager.h"
@@ -38,6 +39,10 @@ Status SpillSortSinkLocalState::init(doris::RuntimeState* state,
_init_counters();
+ _spill_dependency = Dependency::create_shared(_parent->operator_id(), _parent->node_id(),
+ "SortSinkSpillDependency", true);
+ state->get_task()->add_spill_dependency(_spill_dependency.get());
+
RETURN_IF_ERROR(setup_in_memory_sort_op(state));
Base::_shared_state->in_mem_shared_state->sorter->set_enable_spill();
@@ -53,11 +58,9 @@ void SpillSortSinkLocalState::_init_counters() {
_sort_blocks_memory_usage =
ADD_COUNTER_WITH_LEVEL(_profile, "MemoryUsageSortBlocks", TUnit::BYTES, 1);
- _spill_merge_sort_timer =
- ADD_CHILD_TIMER_WITH_LEVEL(_profile, "SpillMergeSortTime", "Spill", 1);
+ _spill_merge_sort_timer = ADD_TIMER_WITH_LEVEL(_profile, "SpillMergeSortTime", 1);
- _spill_wait_in_queue_timer =
- ADD_CHILD_TIMER_WITH_LEVEL(profile(), "SpillWaitInQueueTime", "Spill", 1);
+ _spill_wait_in_queue_timer = ADD_TIMER_WITH_LEVEL(profile(), "SpillWaitInQueueTime", 1);
}
#define UPDATE_PROFILE(counter, name) \
do { \
@@ -129,9 +132,10 @@ Status SpillSortSinkOperatorX::open(RuntimeState* state) {
return _sort_sink_operator->open(state);
}
-Status SpillSortSinkOperatorX::revoke_memory(RuntimeState* state) {
+Status SpillSortSinkOperatorX::revoke_memory(RuntimeState* state,
+ const std::shared_ptr<SpillContext>& spill_context) {
auto& local_state = get_local_state(state);
- return local_state.revoke_memory(state);
+ return local_state.revoke_memory(state, spill_context);
}
size_t SpillSortSinkOperatorX::revocable_mem_size(RuntimeState* state) const {
@@ -165,7 +169,7 @@ Status SpillSortSinkOperatorX::sink(doris::RuntimeState* state, vectorized::Bloc
if (eos) {
if (local_state._shared_state->is_spilled) {
if (revocable_mem_size(state) > 0) {
- RETURN_IF_ERROR(revoke_memory(state));
+ RETURN_IF_ERROR(revoke_memory(state, nullptr));
} else {
local_state._dependency->set_ready_to_read();
local_state._finish_dependency->set_ready();
@@ -180,7 +184,8 @@ Status SpillSortSinkOperatorX::sink(doris::RuntimeState* state, vectorized::Bloc
return Status::OK();
}
-Status SpillSortSinkLocalState::revoke_memory(RuntimeState* state) {
+Status SpillSortSinkLocalState::revoke_memory(RuntimeState* state,
+ const std::shared_ptr<SpillContext>& spill_context) {
if (!_shared_state->is_spilled) {
_shared_state->is_spilled = true;
profile()->add_info_string("Spilled", "true");
@@ -199,7 +204,7 @@ Status SpillSortSinkLocalState::revoke_memory(RuntimeState* state) {
_spilling_stream->set_write_counters(
Base::_spill_serialize_block_timer, Base::_spill_block_count, Base::_spill_data_size,
- Base::_spill_write_disk_timer, Base::_spill_write_wait_io_timer);
+ Base::_spill_write_disk_timer, Base::_spill_write_wait_io_timer, memory_used_counter());
status = _spilling_stream->prepare_spill();
RETURN_IF_ERROR(status);
@@ -209,7 +214,7 @@ Status SpillSortSinkLocalState::revoke_memory(RuntimeState* state) {
// TODO: spill thread may set_ready before the task::execute thread put the task to blocked state
if (!_eos) {
- Base::_dependency->Dependency::block();
+ Base::_spill_dependency->Dependency::block();
}
auto query_id = state->query_id();
@@ -236,11 +241,12 @@ Status SpillSortSinkLocalState::revoke_memory(RuntimeState* state) {
}
_spilling_stream.reset();
+ state->get_query_ctx()->decrease_revoking_tasks_count();
if (_eos) {
_dependency->set_ready_to_read();
_finish_dependency->set_ready();
} else {
- _dependency->Dependency::set_ready();
+ _spill_dependency->Dependency::set_ready();
}
}};
@@ -273,7 +279,7 @@ Status SpillSortSinkLocalState::revoke_memory(RuntimeState* state) {
return Status::OK();
};
- auto exception_catch_func = [this, query_id, spill_func]() {
+ auto exception_catch_func = [this, query_id, spill_context, spill_func]() {
DBUG_EXECUTE_IF("fault_inject::spill_sort_sink::revoke_memory_cancel", {
ExecEnv::GetInstance()->fragment_mgr()->cancel_query(
query_id, Status::InternalError("fault_inject spill_sort_sink "
@@ -284,6 +290,10 @@ Status SpillSortSinkLocalState::revoke_memory(RuntimeState* state) {
_shared_state->sink_status = [&]() {
RETURN_IF_CATCH_EXCEPTION({ return spill_func(); });
}();
+
+ if (spill_context) {
+ spill_context->on_task_finished();
+ }
};
DBUG_EXECUTE_IF("fault_inject::spill_sort_sink::revoke_memory_submit_func", {
@@ -292,15 +302,17 @@ Status SpillSortSinkLocalState::revoke_memory(RuntimeState* state) {
"revoke_memory submit_func failed");
});
if (status.ok()) {
+ state->get_query_ctx()->increase_revoking_tasks_count();
status = ExecEnv::GetInstance()->spill_stream_mgr()->get_spill_io_thread_pool()->submit(
std::make_shared<SpillRunnable>(state, _shared_state->shared_from_this(),
exception_catch_func));
}
if (!status.ok()) {
if (!_eos) {
- Base::_dependency->Dependency::set_ready();
+ Base::_spill_dependency->Dependency::set_ready();
}
}
return status;
}
+
} // namespace doris::pipeline
\ No newline at end of file
diff --git a/be/src/pipeline/exec/spill_sort_sink_operator.h b/be/src/pipeline/exec/spill_sort_sink_operator.h
index 2c820d9fa09..7a361199239 100644
--- a/be/src/pipeline/exec/spill_sort_sink_operator.h
+++ b/be/src/pipeline/exec/spill_sort_sink_operator.h
@@ -17,6 +17,8 @@
#pragma once
+#include <memory>
+
#include "operator.h"
#include "sort_sink_operator.h"
@@ -38,7 +40,7 @@ public:
Dependency* finishdependency() override { return _finish_dependency.get(); }
Status setup_in_memory_sort_op(RuntimeState* state);
- Status revoke_memory(RuntimeState* state);
+ Status revoke_memory(RuntimeState* state, const std::shared_ptr<SpillContext>& spill_context);
private:
void _init_counters();
@@ -85,7 +87,8 @@ public:
size_t revocable_mem_size(RuntimeState* state) const override;
- Status revoke_memory(RuntimeState* state) override;
+ Status revoke_memory(RuntimeState* state,
+ const std::shared_ptr<SpillContext>& spill_context) override;
using DataSinkOperatorX<LocalStateType>::node_id;
using DataSinkOperatorX<LocalStateType>::operator_id;
diff --git a/be/src/pipeline/exec/spill_sort_source_operator.cpp b/be/src/pipeline/exec/spill_sort_source_operator.cpp
index e766cb27168..fe1356381b2 100644
--- a/be/src/pipeline/exec/spill_sort_source_operator.cpp
+++ b/be/src/pipeline/exec/spill_sort_source_operator.cpp
@@ -17,8 +17,11 @@
#include "spill_sort_source_operator.h"
+#include <glog/logging.h>
+
#include "common/status.h"
#include "pipeline/exec/spill_utils.h"
+#include "pipeline/pipeline_task.h"
#include "runtime/fragment_mgr.h"
#include "sort_source_operator.h"
#include "util/runtime_profile.h"
@@ -35,20 +38,21 @@ Status SpillSortLocalState::init(RuntimeState* state, LocalStateInfo& info) {
RETURN_IF_ERROR(Base::init(state, info));
SCOPED_TIMER(exec_time_counter());
SCOPED_TIMER(_init_timer);
+
+ _spill_dependency = Dependency::create_shared(_parent->operator_id(), _parent->node_id(),
+ "SortSourceSpillDependency", true);
+ state->get_task()->add_spill_dependency(_spill_dependency.get());
+
_internal_runtime_profile = std::make_unique<RuntimeProfile>("internal_profile");
- _spill_timer = ADD_CHILD_TIMER_WITH_LEVEL(Base::profile(), "SpillMergeSortTime", "Spill", 1);
- _spill_merge_sort_timer =
- ADD_CHILD_TIMER_WITH_LEVEL(Base::profile(), "SpillMergeSortTime", "Spill", 1);
+ _spill_merge_sort_timer = ADD_TIMER_WITH_LEVEL(Base::profile(), "SpillMergeSortTime", 1);
_spill_serialize_block_timer =
- ADD_CHILD_TIMER_WITH_LEVEL(Base::profile(), "SpillSerializeBlockTime", "Spill", 1);
- _spill_write_disk_timer =
- ADD_CHILD_TIMER_WITH_LEVEL(Base::profile(), "SpillWriteDiskTime", "Spill", 1);
- _spill_data_size = ADD_CHILD_COUNTER_WITH_LEVEL(Base::profile(), "SpillWriteDataSize",
- TUnit::BYTES, "Spill", 1);
- _spill_block_count = ADD_CHILD_COUNTER_WITH_LEVEL(Base::profile(), "SpillWriteBlockCount",
- TUnit::UNIT, "Spill", 1);
- _spill_wait_in_queue_timer =
- ADD_CHILD_TIMER_WITH_LEVEL(profile(), "SpillWaitInQueueTime", "Spill", 1);
+ ADD_TIMER_WITH_LEVEL(Base::profile(), "SpillSerializeBlockTime", 1);
+ _spill_write_disk_timer = ADD_TIMER_WITH_LEVEL(Base::profile(), "SpillWriteDiskTime", 1);
+ _spill_data_size =
+ ADD_COUNTER_WITH_LEVEL(Base::profile(), "SpillWriteDataSize", TUnit::BYTES, 1);
+ _spill_block_count =
+ ADD_COUNTER_WITH_LEVEL(Base::profile(), "SpillWriteBlockCount", TUnit::UNIT, 1);
+ _spill_wait_in_queue_timer = ADD_TIMER_WITH_LEVEL(profile(), "SpillWaitInQueueTime", 1);
return Status::OK();
}
@@ -58,6 +62,7 @@ Status SpillSortLocalState::open(RuntimeState* state) {
if (_opened) {
return Status::OK();
}
+
RETURN_IF_ERROR(setup_in_memory_sort_op(state));
return Base::open(state);
}
@@ -77,7 +82,7 @@ Status SpillSortLocalState::initiate_merge_sort_spill_streams(RuntimeState* stat
auto& parent = Base::_parent->template cast<Parent>();
VLOG_DEBUG << "query " << print_id(state->query_id()) << " sort node " << _parent->node_id()
<< " merge spill data";
- _dependency->Dependency::block();
+ _spill_dependency->Dependency::block();
auto query_id = state->query_id();
@@ -102,7 +107,7 @@ Status SpillSortLocalState::initiate_merge_sort_spill_streams(RuntimeState* stat
VLOG_DEBUG << "query " << print_id(query_id) << " sort node " << _parent->node_id()
<< " merge spill data finish";
}
- _dependency->Dependency::set_ready();
+ _spill_dependency->Dependency::set_ready();
}};
vectorized::Block merge_sorted_block;
vectorized::SpillStreamSPtr tmp_stream;
@@ -139,7 +144,7 @@ Status SpillSortLocalState::initiate_merge_sort_spill_streams(RuntimeState* stat
bool eos = false;
tmp_stream->set_write_counters(_spill_serialize_block_timer, _spill_block_count,
_spill_data_size, _spill_write_disk_timer,
- _spill_write_wait_io_timer);
+ _spill_write_wait_io_timer, memory_used_counter());
while (!eos && !state->is_cancelled()) {
merge_sorted_block.clear_column_data();
{
diff --git a/be/src/pipeline/exec/spill_sort_source_operator.h b/be/src/pipeline/exec/spill_sort_source_operator.h
index 66d05e739d8..ca984e352fc 100644
--- a/be/src/pipeline/exec/spill_sort_source_operator.h
+++ b/be/src/pipeline/exec/spill_sort_source_operator.h
@@ -60,9 +60,10 @@ protected:
std::vector<vectorized::SpillStreamSPtr> _current_merging_streams;
std::unique_ptr<vectorized::VSortedRunMerger> _merger;
+ std::shared_ptr<Dependency> _spill_dependency;
+
std::unique_ptr<RuntimeProfile> _internal_runtime_profile;
// counters for spill merge sort
- RuntimeProfile::Counter* _spill_timer = nullptr;
RuntimeProfile::Counter* _spill_merge_sort_timer = nullptr;
RuntimeProfile::Counter* _spill_serialize_block_timer = nullptr;
RuntimeProfile::Counter* _spill_write_disk_timer = nullptr;
diff --git a/be/src/pipeline/exec/spill_utils.h b/be/src/pipeline/exec/spill_utils.h
index 925e7df44e6..d2b157463ae 100644
--- a/be/src/pipeline/exec/spill_utils.h
+++ b/be/src/pipeline/exec/spill_utils.h
@@ -17,6 +17,13 @@
#pragma once
+#include <gen_cpp/Types_types.h>
+#include <glog/logging.h>
+
+#include <atomic>
+#include <functional>
+#include <utility>
+
#include "runtime/memory/mem_tracker_limiter.h"
#include "runtime/query_context.h"
#include "runtime/runtime_state.h"
@@ -28,6 +35,47 @@
namespace doris::pipeline {
using SpillPartitionerType = vectorized::Crc32HashPartitioner<vectorized::SpillPartitionChannelIds>;
+struct SpillContext {
+ std::atomic_int running_tasks_count;
+ TUniqueId query_id;
+ std::function<void(SpillContext*)> all_tasks_finished_callback;
+
+ SpillContext(int running_tasks_count_, TUniqueId query_id_,
+ std::function<void(SpillContext*)> all_tasks_finished_callback_)
+ : running_tasks_count(running_tasks_count_),
+ query_id(std::move(query_id_)),
+ all_tasks_finished_callback(std::move(all_tasks_finished_callback_)) {}
+
+ ~SpillContext() {
+ LOG_IF(WARNING, running_tasks_count.load() != 0)
+ << "query: " << print_id(query_id)
+ << " not all spill tasks finished, remaining tasks: " << running_tasks_count.load();
+
+ LOG_IF(WARNING, _running_non_sink_tasks_count.load() != 0)
+ << "query: " << print_id(query_id)
+ << " not all spill tasks(non sink tasks) finished, remaining tasks: "
+ << _running_non_sink_tasks_count.load();
+ }
+
+ void on_task_finished() {
+ auto count = running_tasks_count.fetch_sub(1);
+ if (count == 1 && _running_non_sink_tasks_count.load() == 0) {
+ all_tasks_finished_callback(this);
+ }
+ }
+
+ void on_non_sink_task_started() { _running_non_sink_tasks_count.fetch_add(1); }
+ void on_non_sink_task_finished() {
+ const auto count = _running_non_sink_tasks_count.fetch_sub(1);
+ if (count == 1 && running_tasks_count.load() == 0) {
+ all_tasks_finished_callback(this);
+ }
+ }
+
+private:
+ std::atomic_int _running_non_sink_tasks_count {0};
+};
+
class SpillRunnable : public Runnable {
public:
SpillRunnable(RuntimeState* state, const std::shared_ptr<BasicSharedState>& shared_state,
diff --git a/be/src/pipeline/exec/streaming_aggregation_operator.cpp b/be/src/pipeline/exec/streaming_aggregation_operator.cpp
index b7da212f019..e127565b15f 100644
--- a/be/src/pipeline/exec/streaming_aggregation_operator.cpp
+++ b/be/src/pipeline/exec/streaming_aggregation_operator.cpp
@@ -505,8 +505,15 @@ Status StreamingAggLocalState::_init_hash_method(const vectorized::VExprContextS
return Status::OK();
}
+void StreamingAggLocalState::set_low_memory_mode() {
+ auto& p = Base::_parent->template cast<StreamingAggOperatorX>();
+ p._spill_streaming_agg_mem_limit = 1024 * 1024;
+}
Status StreamingAggLocalState::do_pre_agg(vectorized::Block* input_block,
vectorized::Block* output_block) {
+ if (state()->get_query_ctx()->low_memory_mode()) {
+ set_low_memory_mode();
+ }
RETURN_IF_ERROR(_pre_agg_with_serialized_key(input_block, output_block));
// pre stream agg need use _num_row_return to decide whether to do pre stream agg
@@ -634,8 +641,7 @@ Status StreamingAggLocalState::_pre_agg_with_serialized_key(doris::vectorized::B
// to avoid wasting memory.
// But for fixed hash map, it never need to expand
bool ret_flag = false;
- const auto spill_streaming_agg_mem_limit =
- _parent->cast<StreamingAggOperatorX>()._spill_streaming_agg_mem_limit;
+ const auto spill_streaming_agg_mem_limit = p._spill_streaming_agg_mem_limit;
const bool used_too_much_memory =
spill_streaming_agg_mem_limit > 0 && _memory_usage() > spill_streaming_agg_mem_limit;
RETURN_IF_ERROR(std::visit(
@@ -1269,14 +1275,14 @@ Status StreamingAggLocalState::close(RuntimeState* state) {
Status StreamingAggOperatorX::pull(RuntimeState* state, vectorized::Block* block, bool* eos) const {
auto& local_state = get_local_state(state);
+ SCOPED_PEAK_MEM(&local_state._estimate_memory_usage);
if (!local_state._pre_aggregated_block->empty()) {
local_state._pre_aggregated_block->swap(*block);
} else {
RETURN_IF_ERROR(local_state._executor->get_result(&local_state, state, block, eos));
local_state.make_nullable_output_key(block);
// dispose the having clause, should not be execute in prestreaming agg
- RETURN_IF_ERROR(vectorized::VExprContext::filter_block(local_state._conjuncts, block,
- block->columns()));
+ RETURN_IF_ERROR(local_state.filter_block(local_state._conjuncts, block, block->columns()));
}
local_state.reached_limit(block, eos);
@@ -1286,6 +1292,8 @@ Status StreamingAggOperatorX::pull(RuntimeState* state, vectorized::Block* block
Status StreamingAggOperatorX::push(RuntimeState* state, vectorized::Block* in_block,
bool eos) const {
auto& local_state = get_local_state(state);
+ SCOPED_PEAK_MEM(&local_state._estimate_memory_usage);
+
local_state._input_num_rows += in_block->rows();
if (in_block->rows() > 0) {
RETURN_IF_ERROR(local_state.do_pre_agg(in_block, local_state._pre_aggregated_block.get()));
diff --git a/be/src/pipeline/exec/streaming_aggregation_operator.h b/be/src/pipeline/exec/streaming_aggregation_operator.h
index 9a84b694635..e65bd521482 100644
--- a/be/src/pipeline/exec/streaming_aggregation_operator.h
+++ b/be/src/pipeline/exec/streaming_aggregation_operator.h
@@ -46,6 +46,7 @@ public:
Status close(RuntimeState* state) override;
Status do_pre_agg(vectorized::Block* input_block, vectorized::Block* output_block);
void make_nullable_output_key(vectorized::Block* block);
+ void set_low_memory_mode();
private:
friend class StreamingAggOperatorX;
diff --git a/be/src/pipeline/exec/table_function_operator.cpp b/be/src/pipeline/exec/table_function_operator.cpp
index 38e69f7cb0e..455845dc034 100644
--- a/be/src/pipeline/exec/table_function_operator.cpp
+++ b/be/src/pipeline/exec/table_function_operator.cpp
@@ -198,8 +198,7 @@ Status TableFunctionLocalState::get_expanded_block(RuntimeState* state,
}
// 3. eval conjuncts
- RETURN_IF_ERROR(vectorized::VExprContext::filter_block(_conjuncts, output_block,
- output_block->columns()));
+ RETURN_IF_ERROR(filter_block(_conjuncts, output_block, output_block->columns()));
*eos = _child_eos && _cur_child_offset == -1;
return Status::OK();
diff --git a/be/src/pipeline/exec/union_sink_operator.cpp b/be/src/pipeline/exec/union_sink_operator.cpp
index 288fc131037..b0de194a06b 100644
--- a/be/src/pipeline/exec/union_sink_operator.cpp
+++ b/be/src/pipeline/exec/union_sink_operator.cpp
@@ -87,6 +87,9 @@ Status UnionSinkOperatorX::open(RuntimeState* state) {
Status UnionSinkOperatorX::sink(RuntimeState* state, vectorized::Block* in_block, bool eos) {
auto& local_state = get_local_state(state);
+ if (state->get_query_ctx()->low_memory_mode()) {
+ local_state._shared_state->data_queue.set_low_memory_mode();
+ }
SCOPED_TIMER(local_state.exec_time_counter());
COUNTER_UPDATE(local_state.rows_input_counter(), (int64_t)in_block->rows());
if (local_state._output_block == nullptr) {
diff --git a/be/src/pipeline/exec/union_source_operator.cpp b/be/src/pipeline/exec/union_source_operator.cpp
index ecaaf22922b..942135453b4 100644
--- a/be/src/pipeline/exec/union_source_operator.cpp
+++ b/be/src/pipeline/exec/union_source_operator.cpp
@@ -130,7 +130,10 @@ Status UnionSourceOperatorX::get_block(RuntimeState* state, vectorized::Block* b
}
block->swap(*output_block);
output_block->clear_column_data(_row_descriptor.num_materialized_slots());
- local_state._shared_state->data_queue.push_free_block(std::move(output_block), child_idx);
+ if (!state->get_query_ctx()->low_memory_mode()) {
+ local_state._shared_state->data_queue.push_free_block(std::move(output_block),
+ child_idx);
+ }
}
local_state.reached_limit(block, eos);
return Status::OK();
@@ -140,6 +143,9 @@ Status UnionSourceOperatorX::get_next_const(RuntimeState* state, vectorized::Blo
DCHECK_EQ(state->per_fragment_instance_idx(), 0);
auto& local_state = state->get_local_state(operator_id())->cast<UnionSourceLocalState>();
DCHECK_LT(local_state._const_expr_list_idx, _const_expr_lists.size());
+
+ SCOPED_PEAK_MEM(&local_state._estimate_memory_usage);
+
auto& _const_expr_list_idx = local_state._const_expr_list_idx;
vectorized::MutableBlock mblock =
vectorized::VectorizedUtils::build_mutable_mem_reuse_block(block, _row_descriptor);
diff --git a/be/src/pipeline/local_exchange/local_exchange_sink_operator.cpp b/be/src/pipeline/local_exchange/local_exchange_sink_operator.cpp
index 55d81f449e7..9cba09794d9 100644
--- a/be/src/pipeline/local_exchange/local_exchange_sink_operator.cpp
+++ b/be/src/pipeline/local_exchange/local_exchange_sink_operator.cpp
@@ -28,7 +28,7 @@ LocalExchangeSinkLocalState::~LocalExchangeSinkLocalState() = default;
std::vector<Dependency*> LocalExchangeSinkLocalState::dependencies() const {
auto deps = Base::dependencies();
- auto dep = _shared_state->get_sink_dep_by_channel_id(_channel_id);
+ auto* dep = _shared_state->get_sink_dep_by_channel_id(_channel_id);
if (dep != nullptr) {
deps.push_back(dep);
}
@@ -120,7 +120,13 @@ Status LocalExchangeSinkOperatorX::sink(RuntimeState* state, vectorized::Block*
auto& local_state = get_local_state(state);
SCOPED_TIMER(local_state.exec_time_counter());
COUNTER_UPDATE(local_state.rows_input_counter(), (int64_t)in_block->rows());
+
+ if (state->get_query_ctx()->low_memory_mode()) {
+ local_state._shared_state->set_low_memory_mode();
+ }
+
RETURN_IF_ERROR(local_state._exchanger->sink(state, in_block, eos, local_state));
+ // local_state._peak_memory_usage_counter->set(local_state._mem_tracker->peak_consumption());
// If all exchange sources ended due to limit reached, current task should also finish
if (local_state._exchanger->_running_source_operators == 0) {
diff --git a/be/src/pipeline/local_exchange/local_exchanger.cpp b/be/src/pipeline/local_exchange/local_exchanger.cpp
index 34b7fb503b5..90a78830e26 100644
--- a/be/src/pipeline/local_exchange/local_exchanger.cpp
+++ b/be/src/pipeline/local_exchange/local_exchanger.cpp
@@ -43,9 +43,9 @@ void Exchanger<BlockType>::_enqueue_data_and_set_ready(int channel_id,
allocated_bytes = block->data_block.allocated_bytes();
}
std::unique_lock l(_m);
- local_state._shared_state->add_mem_usage(channel_id, allocated_bytes,
- !std::is_same_v<PartitionedBlock, BlockType> &&
- !std::is_same_v<BroadcastBlock, BlockType>);
+ constexpr bool update_mem_usage = !std::is_same_v<PartitionedBlock, BlockType> &&
+ !std::is_same_v<BroadcastBlock, BlockType>;
+ local_state._shared_state->add_mem_usage(channel_id, allocated_bytes, update_mem_usage);
if (_data_queue[channel_id].enqueue(std::move(block))) {
local_state._shared_state->set_ready_to_read(channel_id);
} else {
@@ -77,11 +77,11 @@ bool Exchanger<BlockType>::_dequeue_data(LocalExchangeSourceLocalState& local_st
if (_data_queue[channel_id].try_dequeue(block)) {
if constexpr (std::is_same_v<PartitionedBlock, BlockType> ||
std::is_same_v<BroadcastBlock, BlockType>) {
- local_state._shared_state->sub_mem_usage(channel_id,
- block.first->data_block.allocated_bytes());
+ const auto bytes = block.first->data_block.allocated_bytes();
+ local_state._shared_state->sub_mem_usage(channel_id, bytes);
} else {
- local_state._shared_state->sub_mem_usage(channel_id,
- block->data_block.allocated_bytes());
+ const auto bytes = block->data_block.allocated_bytes();
+ local_state._shared_state->sub_mem_usage(channel_id, bytes);
data_block->swap(block->data_block);
block->unref(local_state._shared_state, data_block->allocated_bytes(), channel_id);
DCHECK_EQ(block->ref_value(), 0);
@@ -127,6 +127,8 @@ Status ShuffleExchanger::sink(RuntimeState* state, vectorized::Block* in_block,
in_block, local_state));
}
+ local_state._memory_used_counter->set(local_state._shared_state->mem_usage);
+
return Status::OK();
}
@@ -199,8 +201,9 @@ Status ShuffleExchanger::_split_rows(RuntimeState* state, const uint32_t* __rest
if (new_block_wrapper->data_block.empty()) {
return Status::OK();
}
- local_state._shared_state->add_total_mem_usage(new_block_wrapper->data_block.allocated_bytes(),
- local_state._channel_id);
+
+ const auto block_bytes = new_block_wrapper->data_block.allocated_bytes();
+ local_state._shared_state->add_total_mem_usage(block_bytes, local_state._channel_id);
auto bucket_seq_to_instance_idx =
local_state._parent->cast<LocalExchangeSinkOperatorX>()._bucket_seq_to_instance_idx;
if (get_type() == ExchangeType::HASH_SHUFFLE) {
@@ -271,6 +274,8 @@ Status PassthroughExchanger::sink(RuntimeState* state, vectorized::Block* in_blo
auto channel_id = (local_state._channel_id++) % _num_partitions;
_enqueue_data_and_set_ready(channel_id, local_state, std::move(wrapper));
+ local_state._memory_used_counter->set(local_state._shared_state->mem_usage);
+
return Status::OK();
}
@@ -315,6 +320,8 @@ Status PassToOneExchanger::sink(RuntimeState* state, vectorized::Block* in_block
BlockWrapperSPtr wrapper = BlockWrapper::create_shared(std::move(new_block));
_enqueue_data_and_set_ready(0, local_state, std::move(wrapper));
+ local_state._memory_used_counter->set(local_state._shared_state->mem_usage);
+
return Status::OK();
}
@@ -345,6 +352,8 @@ Status LocalMergeSortExchanger::sink(RuntimeState* state, vectorized::Block* in_
if (eos) {
local_state._shared_state->source_deps[local_state._channel_id]->set_always_ready();
}
+
+ local_state._memory_used_counter->set(local_state._shared_state->mem_usage);
return Status::OK();
}
@@ -423,8 +432,8 @@ Status BroadcastExchanger::sink(RuntimeState* state, vectorized::Block* in_block
}
new_block.swap(*in_block);
auto wrapper = BlockWrapper::create_shared(std::move(new_block));
- local_state._shared_state->add_total_mem_usage(wrapper->data_block.allocated_bytes(),
- local_state._channel_id);
+ const auto block_bytes = wrapper->data_block.allocated_bytes();
+ local_state._shared_state->add_total_mem_usage(block_bytes, local_state._channel_id);
wrapper->ref(_num_partitions);
for (size_t i = 0; i < _num_partitions; i++) {
_enqueue_data_and_set_ready(i, local_state, {wrapper, {0, wrapper->data_block.rows()}});
@@ -474,6 +483,7 @@ Status AdaptivePassthroughExchanger::_passthrough_sink(RuntimeState* state,
_enqueue_data_and_set_ready(channel_id, local_state,
BlockWrapper::create_shared(std::move(new_block)));
+ local_state._memory_used_counter->set(local_state._shared_state->mem_usage);
return Status::OK();
}
@@ -493,7 +503,10 @@ Status AdaptivePassthroughExchanger::_shuffle_sink(RuntimeState* state, vectoriz
std::iota(channel_ids.begin() + i, channel_ids.end(), 0);
}
}
- return _split_rows(state, channel_ids.data(), block, local_state);
+
+ RETURN_IF_ERROR(_split_rows(state, channel_ids.data(), block, local_state));
+ local_state._memory_used_counter->set(local_state._shared_state->mem_usage);
+ return Status::OK();
}
Status AdaptivePassthroughExchanger::_split_rows(RuntimeState* state,
diff --git a/be/src/pipeline/pipeline_fragment_context.cpp b/be/src/pipeline/pipeline_fragment_context.cpp
index 2d283652945..28b648994ba 100644
--- a/be/src/pipeline/pipeline_fragment_context.cpp
+++ b/be/src/pipeline/pipeline_fragment_context.cpp
@@ -22,6 +22,7 @@
#include <gen_cpp/PlanNodes_types.h>
#include <pthread.h>
+#include <algorithm>
#include <cstdlib>
// IWYU pragma: no_include <bits/chrono.h>
#include <fmt/format.h>
@@ -191,6 +192,9 @@ void PipelineFragmentContext::cancel(const Status reason) {
debug_string());
}
+ if (reason.is<ErrorCode::MEM_LIMIT_EXCEEDED>() || reason.is<ErrorCode::MEM_ALLOC_FAILED>()) {
+ print_profile("cancel pipeline, reason: " + reason.to_string());
+ }
_query_ctx->cancel(reason, _fragment_id);
if (reason.is<ErrorCode::LIMIT_REACH>()) {
_is_report_on_cancel = false;
@@ -1714,6 +1718,21 @@ Status PipelineFragmentContext::submit() {
}
}
+void PipelineFragmentContext::print_profile(const std::string& extra_info) {
+ if (_runtime_state->enable_profile()) {
+ std::stringstream ss;
+ for (auto runtime_profile_ptr : _runtime_state->pipeline_id_to_profile()) {
+ runtime_profile_ptr->pretty_print(&ss);
+ }
+
+ if (_runtime_state->load_channel_profile()) {
+ _runtime_state->load_channel_profile()->pretty_print(&ss);
+ }
+
+ LOG_INFO("Query {} fragment {} {}, profile, {}", print_id(this->_query_id),
+ this->_fragment_id, extra_info, ss.str());
+ }
+}
// If all pipeline tasks binded to the fragment instance are finished, then we could
// close the fragment instance.
void PipelineFragmentContext::_close_fragment_instance() {
@@ -1817,6 +1836,43 @@ Status PipelineFragmentContext::send_report(bool done) {
req, std::dynamic_pointer_cast<PipelineFragmentContext>(shared_from_this()));
}
+size_t PipelineFragmentContext::get_revocable_size(bool* has_running_task) const {
+ size_t res = 0;
+ // _tasks will be cleared during ~PipelineFragmentContext, so that it's safe
+ // here to traverse the vector.
+ for (const auto& task_instances : _tasks) {
+ for (const auto& task : task_instances) {
+ if (task->is_running() || task->is_revoking()) {
+ LOG_EVERY_N(INFO, 50) << "query: " << print_id(_query_id)
+ << " is running, task: " << (void*)task.get()
+ << ", task->is_revoking(): " << task->is_revoking() << ", "
+ << task->is_running();
+ *has_running_task = true;
+ return 0;
+ }
+
+ size_t revocable_size = task->get_revocable_size();
+ if (revocable_size > _runtime_state->min_revocable_mem()) {
+ res += revocable_size;
+ }
+ }
+ }
+ return res;
+}
+
+std::vector<PipelineTask*> PipelineFragmentContext::get_revocable_tasks() const {
+ std::vector<PipelineTask*> revocable_tasks;
+ for (const auto& task_instances : _tasks) {
+ for (const auto& task : task_instances) {
+ size_t revocable_size_ = task->get_revocable_size();
+ if (revocable_size_ > _runtime_state->min_revocable_mem()) {
+ revocable_tasks.emplace_back(task.get());
+ }
+ }
+ }
+ return revocable_tasks;
+}
+
std::string PipelineFragmentContext::debug_string() {
fmt::memory_buffer debug_string_buffer;
fmt::format_to(debug_string_buffer, "PipelineFragmentContext Info:\n");
diff --git a/be/src/pipeline/pipeline_fragment_context.h b/be/src/pipeline/pipeline_fragment_context.h
index bcef1271b60..9c2ed36b919 100644
--- a/be/src/pipeline/pipeline_fragment_context.h
+++ b/be/src/pipeline/pipeline_fragment_context.h
@@ -69,6 +69,8 @@ public:
~PipelineFragmentContext();
+ void print_profile(const std::string& extra_info);
+
std::vector<std::shared_ptr<TRuntimeProfileTree>> collect_realtime_profile() const;
std::shared_ptr<TRuntimeProfileTree> collect_realtime_load_channel_profile() const;
@@ -115,6 +117,10 @@ public:
[[nodiscard]] int next_sink_operator_id() { return _sink_operator_id--; }
+ [[nodiscard]] size_t get_revocable_size(bool* has_running_task) const;
+
+ [[nodiscard]] std::vector<PipelineTask*> get_revocable_tasks() const;
+
void instance_ids(std::vector<TUniqueId>& ins_ids) const {
ins_ids.resize(_fragment_instance_ids.size());
for (size_t i = 0; i < _fragment_instance_ids.size(); i++) {
diff --git a/be/src/pipeline/pipeline_task.cpp b/be/src/pipeline/pipeline_task.cpp
index 35d09f48509..289dd6b9ce2 100644
--- a/be/src/pipeline/pipeline_task.cpp
+++ b/be/src/pipeline/pipeline_task.cpp
@@ -20,24 +20,30 @@
#include <fmt/format.h>
#include <gen_cpp/Metrics_types.h>
#include <glog/logging.h>
-#include <stddef.h>
+#include <cstddef>
#include <ostream>
#include <vector>
#include "common/status.h"
+#include "pipeline/dependency.h"
#include "pipeline/exec/operator.h"
#include "pipeline/exec/scan_operator.h"
#include "pipeline/pipeline.h"
#include "pipeline/pipeline_fragment_context.h"
#include "pipeline/task_queue.h"
+#include "pipeline/task_scheduler.h"
#include "runtime/descriptors.h"
+#include "runtime/exec_env.h"
#include "runtime/query_context.h"
#include "runtime/thread_context.h"
+#include "runtime/workload_group/workload_group_manager.h"
#include "util/container_util.hpp"
#include "util/defer_op.h"
#include "util/mem_info.h"
#include "util/runtime_profile.h"
+#include "util/uid_util.h"
+#include "vec/spill/spill_stream.h"
namespace doris {
class RuntimeState;
@@ -64,7 +70,9 @@ PipelineTask::PipelineTask(
_sink(pipeline->sink_shared_pointer()),
_le_state_map(std::move(le_state_map)),
_task_idx(task_idx),
- _execution_dep(state->get_query_ctx()->get_execution_dependency()) {
+ _execution_dep(state->get_query_ctx()->get_execution_dependency()),
+ _memory_sufficient_dependency(
+ state->get_query_ctx()->get_memory_sufficient_dependency()) {
_pipeline_task_watcher.start();
auto shared_state = _sink->create_shared_state();
@@ -186,6 +194,9 @@ void PipelineTask::_init_profile() {
_schedule_counts = ADD_COUNTER(_task_profile, "NumScheduleTimes", TUnit::UNIT);
_yield_counts = ADD_COUNTER(_task_profile, "NumYieldTimes", TUnit::UNIT);
_core_change_times = ADD_COUNTER(_task_profile, "CoreChangeTimes", TUnit::UNIT);
+ _memory_reserve_times = ADD_COUNTER(_task_profile, "MemoryReserveTimes", TUnit::UNIT);
+ _memory_reserve_failed_times =
+ ADD_COUNTER(_task_profile, "MemoryReserveFailedTimes", TUnit::UNIT);
}
void PipelineTask::_fresh_profile_counter() {
@@ -241,6 +252,20 @@ bool PipelineTask::_wait_to_start() {
}
bool PipelineTask::_is_blocked() {
+ for (auto* spill_dependency : _spill_dependencies) {
+ _blocked_dep = spill_dependency->is_blocked_by(this);
+ if (_blocked_dep != nullptr) {
+ _blocked_dep->start_watcher();
+ return true;
+ }
+ }
+
+ _blocked_dep = _memory_sufficient_dependency->is_blocked_by(this);
+ if (_blocked_dep != nullptr) {
+ _blocked_dep->start_watcher();
+ return true;
+ }
+
// `_dry_run = true` means we do not need data from source operator.
if (!_dry_run) {
for (int i = _read_dependencies.size() - 1; i >= 0; i--) {
@@ -254,16 +279,15 @@ bool PipelineTask::_is_blocked() {
}
// If all dependencies are ready for this operator, we can execute this task if no datum is needed from upstream operators.
if (!_operators[i]->need_more_input_data(_state)) {
- if (VLOG_DEBUG_IS_ON) {
- VLOG_DEBUG << "query: " << print_id(_state->query_id())
- << ", task id: " << _index << ", operator " << i
- << " not need_more_input_data";
- }
+ // if (VLOG_DEBUG_IS_ON) {
+ // VLOG_DEBUG << "query: " << print_id(_state->query_id())
+ // << ", task id: " << _index << ", operator " << i
+ // << " not need_more_input_data";
+ // }
break;
}
}
}
-
for (auto* op_dep : _write_dependencies) {
_blocked_dep = op_dep->is_blocked_by(this);
if (_blocked_dep != nullptr) {
@@ -311,6 +335,8 @@ Status PipelineTask::execute(bool* eos) {
RETURN_IF_ERROR(_open());
}
+ const auto query_id = _state->query_id();
+
while (!_fragment_context->is_canceled()) {
if (_is_blocked()) {
return Status::OK();
@@ -330,25 +356,63 @@ Status PipelineTask::execute(bool* eos) {
_block->clear_column_data(_root->row_desc().num_materialized_slots());
auto* block = _block.get();
- auto sink_revocable_mem_size = _sink->revocable_mem_size(_state);
- if (should_revoke_memory(_state, sink_revocable_mem_size)) {
- RETURN_IF_ERROR(_sink->revoke_memory(_state));
- continue;
- }
*eos = _eos;
DBUG_EXECUTE_IF("fault_inject::PipelineXTask::executing", {
Status status =
Status::Error<INTERNAL_ERROR>("fault_inject pipeline_task executing failed");
return status;
});
+
+ DEFER_RELEASE_RESERVED();
+ // Every loop should check if memory is not enough.
+ _state->get_query_ctx()->update_low_memory_mode();
+
// `_dry_run` means sink operator need no more data
// `_sink->is_finished(_state)` means sink operator should be finished
+ int64_t reserve_size = 0;
+ bool has_enough_memory = true;
if (_dry_run || _sink->is_finished(_state)) {
*eos = true;
_eos = true;
} else {
SCOPED_TIMER(_get_block_timer);
_get_block_counter->update(1);
+ size_t sink_reserve_size = _sink->get_reserve_mem_size(_state);
+ sink_reserve_size =
+ std::max(sink_reserve_size, _state->minimum_operator_memory_required_bytes());
+ reserve_size = _root->get_reserve_mem_size(_state) + sink_reserve_size;
+ _root->reset_reserve_mem_size(_state);
+
+ auto workload_group = _state->get_query_ctx()->workload_group();
+ if (workload_group && reserve_size > 0) {
+ auto st = thread_context()->try_reserve_memory(reserve_size);
+
+ COUNTER_UPDATE(_memory_reserve_times, 1);
+ if (!st.ok()) {
+ COUNTER_UPDATE(_memory_reserve_failed_times, 1);
+ LOG(INFO) << "query: " << print_id(query_id) << ", try to reserve: "
+ << PrettyPrinter::print(reserve_size, TUnit::BYTES)
+ << "(sink reserve size:("
+ << PrettyPrinter::print(sink_reserve_size, TUnit::BYTES)
+ << "), sink name: " << _sink->get_name()
+ << ", node id: " << _sink->node_id() << " failed: " << st.to_string()
+ << ", debug info: " << GlobalMemoryArbitrator::process_mem_log_str();
+
+ _state->get_query_ctx()->update_paused_reason(st);
+ _state->get_query_ctx()->set_low_memory_mode();
+ bool is_high_wartermark = false;
+ bool is_low_wartermark = false;
+ workload_group->check_mem_used(&is_low_wartermark, &is_high_wartermark);
+ if (is_low_wartermark || is_high_wartermark) {
+ _memory_sufficient_dependency->block();
+ ExecEnv::GetInstance()->workload_group_mgr()->add_paused_query(
+ _state->get_query_ctx()->shared_from_this(), reserve_size);
+ continue;
+ }
+ has_enough_memory = false;
+ }
+ }
+
RETURN_IF_ERROR(_root->get_block_after_projects(_state, block, eos));
}
@@ -372,67 +436,22 @@ Status PipelineTask::execute(bool* eos) {
return Status::OK();
}
}
- }
-
- static_cast<void>(get_task_queue()->push_back(this));
- return Status::OK();
-}
-bool PipelineTask::should_revoke_memory(RuntimeState* state, int64_t revocable_mem_bytes) {
- auto* query_ctx = state->get_query_ctx();
- auto wg = query_ctx->workload_group();
- if (!wg) {
- LOG_ONCE(INFO) << "no workload group for query " << print_id(state->query_id());
- return false;
- }
- const auto min_revocable_mem_bytes = state->min_revocable_mem();
+ if (!has_enough_memory) {
+ COUNTER_UPDATE(_yield_counts, 1);
- if (UNLIKELY(state->enable_force_spill())) {
- if (revocable_mem_bytes >= min_revocable_mem_bytes) {
- LOG_ONCE(INFO) << "spill force, query: " << print_id(state->query_id());
- return true;
+ LOG(INFO) << "query: " << print_id(query_id) << ", task: " << (void*)this
+ << ", insufficient memory. reserve_size: "
+ << PrettyPrinter::print(reserve_size, TUnit::BYTES);
+ _memory_sufficient_dependency->block();
+ ExecEnv::GetInstance()->workload_group_mgr()->add_paused_query(
+ _state->get_query_ctx()->shared_from_this(), reserve_size);
+ break;
}
}
- bool is_wg_mem_low_water_mark = false;
- bool is_wg_mem_high_water_mark = false;
- wg->check_mem_used(&is_wg_mem_low_water_mark, &is_wg_mem_high_water_mark);
- if (is_wg_mem_high_water_mark) {
- if (revocable_mem_bytes > min_revocable_mem_bytes) {
- VLOG_DEBUG << "query " << print_id(state->query_id())
- << " revoke memory, hight water mark";
- return true;
- }
- return false;
- } else if (is_wg_mem_low_water_mark) {
- int64_t spill_threshold = query_ctx->spill_threshold();
- int64_t memory_usage = query_ctx->query_mem_tracker->consumption();
- if (spill_threshold == 0 || memory_usage < spill_threshold) {
- return false;
- }
- auto big_memory_operator_num = query_ctx->get_running_big_mem_op_num();
- DCHECK(big_memory_operator_num >= 0);
- int64_t mem_limit_of_op;
- if (0 == big_memory_operator_num) {
- return false;
- } else {
- mem_limit_of_op = spill_threshold / big_memory_operator_num;
- }
-
- LOG_EVERY_T(INFO, 1) << "query " << print_id(state->query_id())
- << " revoke memory, low water mark, revocable_mem_bytes: "
- << PrettyPrinter::print_bytes(revocable_mem_bytes)
- << ", mem_limit_of_op: " << PrettyPrinter::print_bytes(mem_limit_of_op)
- << ", min_revocable_mem_bytes: "
- << PrettyPrinter::print_bytes(min_revocable_mem_bytes)
- << ", memory_usage: " << PrettyPrinter::print_bytes(memory_usage)
- << ", spill_threshold: " << PrettyPrinter::print_bytes(spill_threshold)
- << ", big_memory_operator_num: " << big_memory_operator_num;
- return (revocable_mem_bytes > mem_limit_of_op ||
- revocable_mem_bytes > min_revocable_mem_bytes);
- } else {
- return false;
- }
+ static_cast<void>(get_task_queue()->push_back(this));
+ return Status::OK();
}
void PipelineTask::finalize() {
@@ -506,6 +525,9 @@ std::string PipelineTask::debug_string() {
}
}
+ fmt::format_to(debug_string_buffer, "{}. {}\n", i,
+ _memory_sufficient_dependency->debug_string(i++));
+
fmt::format_to(debug_string_buffer, "Write Dependency Information: \n");
for (size_t j = 0; j < _write_dependencies.size(); j++, i++) {
fmt::format_to(debug_string_buffer, "{}. {}\n", i,
@@ -526,6 +548,28 @@ std::string PipelineTask::debug_string() {
return fmt::to_string(debug_string_buffer);
}
+size_t PipelineTask::get_revocable_size() const {
+ if (_running || _eos) {
+ return 0;
+ }
+
+ return _sink->revocable_mem_size(_state) + _root->revocable_mem_size(_state);
+}
+
+Status PipelineTask::revoke_memory(const std::shared_ptr<SpillContext>& spill_context) {
+ RETURN_IF_ERROR(_root->revoke_memory(_state, spill_context));
+
+ const auto revocable_size = _sink->revocable_mem_size(_state);
+ if (revocable_size >= vectorized::SpillStream::MIN_SPILL_WRITE_BATCH_MEM) {
+ RETURN_IF_ERROR(_sink->revoke_memory(_state, spill_context));
+ } else if (spill_context) {
+ spill_context->on_task_finished();
+ LOG(INFO) << "query: " << print_id(_state->query_id()) << ", task: " << ((void*)this)
+ << " has not enough data to revoke: " << revocable_size;
+ }
+ return Status::OK();
+}
+
void PipelineTask::wake_up() {
// call by dependency
static_cast<void>(get_task_queue()->push_back(this));
diff --git a/be/src/pipeline/pipeline_task.h b/be/src/pipeline/pipeline_task.h
index e216ef10376..448b2ec5f6f 100644
--- a/be/src/pipeline/pipeline_task.h
+++ b/be/src/pipeline/pipeline_task.h
@@ -17,8 +17,7 @@
#pragma once
-#include <stdint.h>
-
+#include <cstdint>
#include <memory>
#include <string>
#include <vector>
@@ -142,6 +141,11 @@ public:
_wake_up_by_downstream = _wake_up_by_downstream || wake_up_by_downstream;
if (!_finalized) {
_execution_dep->set_always_ready();
+ _memory_sufficient_dependency->set_always_ready();
+ for (auto* dep : _spill_dependencies) {
+ dep->set_always_ready();
+ }
+
for (auto* dep : _filter_dependencies) {
dep->set_always_ready();
}
@@ -178,14 +182,6 @@ public:
void set_core_id(int core_id) { this->_core_id = core_id; }
int get_core_id() const { return this->_core_id; }
- /**
- * Return true if:
- * 1. `enable_force_spill` is true which forces this task to spill data.
- * 2. Or memory consumption reaches the high water mark of current workload group (80% of memory limitation by default) and revocable_mem_bytes is bigger than min_revocable_mem_bytes.
- * 3. Or memory consumption is higher than the low water mark of current workload group (50% of memory limitation by default) and `query_weighted_consumption >= query_weighted_limit` and revocable memory is big enough.
- */
- static bool should_revoke_memory(RuntimeState* state, int64_t revocable_mem_bytes);
-
void put_in_runnable_queue() {
_schedule_time++;
_wait_worker_watcher.start();
@@ -194,7 +190,15 @@ public:
void pop_out_runnable_queue() { _wait_worker_watcher.stop(); }
bool is_running() { return _running.load(); }
- void set_running(bool running) { _running = running; }
+ bool is_revoking() {
+ for (auto* dep : _spill_dependencies) {
+ if (dep->is_blocked_by(nullptr) != nullptr) {
+ return true;
+ }
+ }
+ return false;
+ }
+ bool set_running(bool running) { return _running.exchange(running); }
bool is_exceed_debug_timeout() {
if (_has_exceed_timeout) {
@@ -233,6 +237,12 @@ public:
}
PipelineId pipeline_id() const { return _pipeline->id(); }
+ [[nodiscard]] size_t get_revocable_size() const;
+ [[nodiscard]] Status revoke_memory(const std::shared_ptr<SpillContext>& spill_context);
+
+ void add_spill_dependency(Dependency* dependency) {
+ _spill_dependencies.emplace_back(dependency);
+ }
private:
friend class RuntimeFilterDependency;
@@ -282,6 +292,8 @@ private:
// TODO we should calculate the time between when really runnable and runnable
RuntimeProfile::Counter* _yield_counts = nullptr;
RuntimeProfile::Counter* _core_change_times = nullptr;
+ RuntimeProfile::Counter* _memory_reserve_times = nullptr;
+ RuntimeProfile::Counter* _memory_reserve_failed_times = nullptr;
MonotonicStopWatch _pipeline_task_watcher;
@@ -292,6 +304,7 @@ private:
// `_read_dependencies` is stored as same order as `_operators`
std::vector<std::vector<Dependency*>> _read_dependencies;
+ std::vector<Dependency*> _spill_dependencies;
std::vector<Dependency*> _write_dependencies;
std::vector<Dependency*> _finish_dependencies;
std::vector<Dependency*> _filter_dependencies;
@@ -309,12 +322,15 @@ private:
Dependency* _execution_dep = nullptr;
- std::atomic<bool> _finalized = false;
+ std::atomic<bool> _wake_up_by_downstream = false;
+ Dependency* _memory_sufficient_dependency = nullptr;
+
+ std::atomic<bool> _finalized {false};
std::mutex _dependency_lock;
- std::atomic<bool> _running = false;
- std::atomic<bool> _eos = false;
- std::atomic<bool> _wake_up_by_downstream = false;
+ std::atomic<bool> _running {false};
+ std::atomic<bool> _eos {false};
+ std::atomic<bool> _revoking {false};
};
} // namespace doris::pipeline
diff --git a/be/src/pipeline/task_scheduler.cpp b/be/src/pipeline/task_scheduler.cpp
index 475d3a8065f..491f8cd97d1 100644
--- a/be/src/pipeline/task_scheduler.cpp
+++ b/be/src/pipeline/task_scheduler.cpp
@@ -24,19 +24,25 @@
#include <sched.h>
// IWYU pragma: no_include <bits/chrono.h>
+#include <algorithm>
#include <chrono> // IWYU pragma: keep
+#include <cstddef>
#include <functional>
+#include <memory>
+#include <mutex>
#include <ostream>
#include <string>
#include <thread>
#include <utility>
#include "common/logging.h"
+#include "common/status.h"
#include "pipeline/pipeline_task.h"
#include "pipeline/task_queue.h"
#include "pipeline_fragment_context.h"
#include "runtime/exec_env.h"
#include "runtime/query_context.h"
+#include "runtime/thread_context.h"
#include "util/thread.h"
#include "util/threadpool.h"
#include "util/time.h"
@@ -52,6 +58,8 @@ TaskScheduler::~TaskScheduler() {
Status TaskScheduler::start() {
int cores = _task_queue->cores();
+ // Init the thread pool with cores thread
+ // some for pipeline task running
RETURN_IF_ERROR(ThreadPoolBuilder(_name)
.set_min_threads(cores)
.set_max_threads(cores)
@@ -59,7 +67,6 @@ Status TaskScheduler::start() {
.set_cgroup_cpu_ctl(_cgroup_cpu_ctl)
.build(&_fix_thread_pool));
LOG_INFO("TaskScheduler set cores").tag("size", cores);
- _markers.resize(cores, true);
for (size_t i = 0; i < cores; ++i) {
RETURN_IF_ERROR(_fix_thread_pool->submit_func([this, i] { _do_work(i); }));
}
@@ -98,17 +105,19 @@ void _close_task(PipelineTask* task, Status exec_status) {
}
void TaskScheduler::_do_work(size_t index) {
- while (_markers[index]) {
+ while (!_need_to_stop) {
auto* task = _task_queue->take(index);
if (!task) {
continue;
}
+
if (task->is_running()) {
static_cast<void>(_task_queue->push_back(task, index));
continue;
}
- task->log_detail_if_need();
task->set_running(true);
+
+ task->log_detail_if_need();
task->set_task_queue(_task_queue.get());
auto* fragment_ctx = task->fragment_context();
bool canceled = fragment_ctx->is_canceled();
@@ -193,9 +202,7 @@ void TaskScheduler::stop() {
_task_queue->close();
}
if (_fix_thread_pool) {
- for (size_t i = 0; i < _markers.size(); ++i) {
- _markers[i] = false;
- }
+ _need_to_stop = true;
_fix_thread_pool->shutdown();
_fix_thread_pool->wait();
}
diff --git a/be/src/pipeline/task_scheduler.h b/be/src/pipeline/task_scheduler.h
index 9a20807ea26..8ad510fd8a8 100644
--- a/be/src/pipeline/task_scheduler.h
+++ b/be/src/pipeline/task_scheduler.h
@@ -17,10 +17,10 @@
#pragma once
-#include <stddef.h>
-
#include <atomic>
+#include <chrono>
#include <condition_variable>
+#include <cstddef>
#include <list>
#include <memory>
#include <mutex>
@@ -30,8 +30,10 @@
#include "common/status.h"
#include "gutil/ref_counted.h"
#include "pipeline_task.h"
+#include "runtime/query_context.h"
#include "runtime/workload_group/workload_group.h"
#include "util/thread.h"
+#include "util/uid_util.h"
namespace doris {
class ExecEnv;
@@ -49,7 +51,6 @@ public:
TaskScheduler(ExecEnv* exec_env, std::shared_ptr<TaskQueue> task_queue, std::string name,
CgroupCpuCtl* cgroup_cpu_ctl)
: _task_queue(std::move(task_queue)),
- _shutdown(false),
_name(std::move(name)),
_cgroup_cpu_ctl(cgroup_cpu_ctl) {}
@@ -66,8 +67,8 @@ public:
private:
std::unique_ptr<ThreadPool> _fix_thread_pool;
std::shared_ptr<TaskQueue> _task_queue;
- std::vector<bool> _markers;
- bool _shutdown;
+ bool _need_to_stop = false;
+ bool _shutdown = false;
std::string _name;
CgroupCpuCtl* _cgroup_cpu_ctl = nullptr;
diff --git a/be/src/runtime/fragment_mgr.cpp b/be/src/runtime/fragment_mgr.cpp
index 3f8e762408c..bcd1f8be337 100644
--- a/be/src/runtime/fragment_mgr.cpp
+++ b/be/src/runtime/fragment_mgr.cpp
@@ -710,8 +710,7 @@ Status FragmentMgr::_get_query_ctx(const Params& params, TUniqueId query_id, boo
// temp query ctx now. For example, the query id maybe removed from workload group's queryset.
_query_ctx_map.insert(std::make_pair(query_ctx->query_id(), query_ctx));
LOG(INFO) << "Register query/load memory tracker, query/load id: "
- << print_id(query_ctx->query_id())
- << " limit: " << PrettyPrinter::print(query_ctx->mem_limit(), TUnit::BYTES);
+ << print_id(query_ctx->query_id());
}
return Status::OK();
}
diff --git a/be/src/runtime/memory/global_memory_arbitrator.cpp b/be/src/runtime/memory/global_memory_arbitrator.cpp
index 45d7781786f..2b649efba50 100644
--- a/be/src/runtime/memory/global_memory_arbitrator.cpp
+++ b/be/src/runtime/memory/global_memory_arbitrator.cpp
@@ -38,7 +38,13 @@ std::atomic<int64_t> GlobalMemoryArbitrator::refresh_interval_memory_growth = 0;
std::mutex GlobalMemoryArbitrator::cache_adjust_capacity_lock;
std::condition_variable GlobalMemoryArbitrator::cache_adjust_capacity_cv;
std::atomic<bool> GlobalMemoryArbitrator::cache_adjust_capacity_notify {false};
+// This capacity is set by gc thread, it is running periodicity.
std::atomic<double> GlobalMemoryArbitrator::last_cache_capacity_adjust_weighted {1};
+// This capacity is set by workload group spill disk thread
+std::atomic<double> GlobalMemoryArbitrator::last_wg_trigger_cache_capacity_adjust_weighted {1};
+// The value that take affect
+std::atomic<double> GlobalMemoryArbitrator::last_affected_cache_capacity_adjust_weighted {1};
+std::atomic<bool> GlobalMemoryArbitrator::any_workload_group_exceed_limit {false};
std::mutex GlobalMemoryArbitrator::memtable_memory_refresh_lock;
std::condition_variable GlobalMemoryArbitrator::memtable_memory_refresh_cv;
std::atomic<bool> GlobalMemoryArbitrator::memtable_memory_refresh_notify {false};
diff --git a/be/src/runtime/memory/global_memory_arbitrator.h b/be/src/runtime/memory/global_memory_arbitrator.h
index 1859f45391f..e2b55c8aa98 100644
--- a/be/src/runtime/memory/global_memory_arbitrator.h
+++ b/be/src/runtime/memory/global_memory_arbitrator.h
@@ -178,6 +178,12 @@ public:
static std::condition_variable cache_adjust_capacity_cv;
static std::atomic<bool> cache_adjust_capacity_notify;
static std::atomic<double> last_cache_capacity_adjust_weighted;
+ // This capacity is set by workload group spill disk thread
+ static std::atomic<double> last_wg_trigger_cache_capacity_adjust_weighted;
+ // The value that take affect
+ static std::atomic<double> last_affected_cache_capacity_adjust_weighted;
+ static std::atomic<bool> any_workload_group_exceed_limit;
+
static void notify_cache_adjust_capacity() {
cache_adjust_capacity_notify.store(true, std::memory_order_relaxed);
cache_adjust_capacity_cv.notify_all();
diff --git a/be/src/runtime/memory/mem_tracker_limiter.cpp b/be/src/runtime/memory/mem_tracker_limiter.cpp
index 78e66b6a579..3a30ae1c5bf 100644
--- a/be/src/runtime/memory/mem_tracker_limiter.cpp
+++ b/be/src/runtime/memory/mem_tracker_limiter.cpp
@@ -709,7 +709,7 @@ int64_t MemTrackerLimiter::free_top_overcommit_query(
seek_num++;
// 32M small query does not cancel
if (tracker->consumption() <= 33554432 ||
- tracker->consumption() < tracker->limit()) {
+ tracker->consumption() < tracker->_limit) {
small_num++;
continue;
}
@@ -719,7 +719,7 @@ int64_t MemTrackerLimiter::free_top_overcommit_query(
continue;
}
auto overcommit_ratio = int64_t(
- (static_cast<double>(tracker->consumption()) / tracker->limit()) *
+ (static_cast<double>(tracker->consumption()) / tracker->_limit) *
10000);
max_pq.emplace(overcommit_ratio, tracker->label());
query_consumption[tracker->label()] = tracker->consumption();
diff --git a/be/src/runtime/memory/mem_tracker_limiter.h b/be/src/runtime/memory/mem_tracker_limiter.h
index 251a7c25a74..0b86cea7ba9 100644
--- a/be/src/runtime/memory/mem_tracker_limiter.h
+++ b/be/src/runtime/memory/mem_tracker_limiter.h
@@ -120,6 +120,7 @@ public:
bool limit_exceeded() const { return _limit >= 0 && _limit < consumption(); }
Status check_limit(int64_t bytes = 0);
bool is_overcommit_tracker() const { return type() == Type::QUERY || type() == Type::LOAD; }
+ void set_limit(int64_t new_mem_limit) { _limit = new_mem_limit; }
bool is_query_cancelled() { return _is_query_cancelled; }
void set_is_query_cancelled(bool is_cancelled) { _is_query_cancelled.store(is_cancelled); }
@@ -146,23 +147,6 @@ public:
void release(int64_t bytes) { _mem_counter.sub(bytes); }
- bool try_consume(int64_t bytes) {
- if (UNLIKELY(bytes == 0)) {
- return true;
- }
- bool rt = true;
- if (is_overcommit_tracker() && !config::enable_query_memory_overcommit) {
- rt = _mem_counter.try_add(bytes, _limit);
- } else {
- _mem_counter.add(bytes);
- }
- if (rt && _query_statistics) {
- _query_statistics->set_max_peak_memory_bytes(peak_consumption());
- _query_statistics->set_current_used_memory_bytes(consumption());
- }
- return rt;
- }
-
void set_consumption(int64_t bytes) { _mem_counter.set(bytes); }
// Transfer 'bytes' of consumption from this tracker to 'dst'.
@@ -184,8 +168,27 @@ public:
int64_t reserved_consumption() const { return _reserved_counter.current_value(); }
int64_t reserved_peak_consumption() const { return _reserved_counter.peak_value(); }
+ void reserve(int64_t bytes) {
+ if (UNLIKELY(bytes == 0)) {
+ return;
+ }
+ _mem_counter.add(bytes);
+ if (_query_statistics) {
+ _query_statistics->set_max_peak_memory_bytes(peak_consumption());
+ _query_statistics->set_current_used_memory_bytes(consumption());
+ }
+ _reserved_counter.add(bytes);
+ }
+
bool try_reserve(int64_t bytes) {
- bool rt = try_consume(bytes);
+ if (UNLIKELY(bytes == 0)) {
+ return true;
+ }
+ bool rt = _mem_counter.try_add(bytes, _limit);
+ if (rt && _query_statistics) {
+ _query_statistics->set_max_peak_memory_bytes(peak_consumption());
+ _query_statistics->set_current_used_memory_bytes(consumption());
+ }
if (rt) {
_reserved_counter.add(bytes);
}
@@ -337,7 +340,7 @@ private:
MemCounter _reserved_counter;
// Limit on memory consumption, in bytes.
- int64_t _limit;
+ std::atomic<int64_t> _limit;
// Group number in mem_tracker_limiter_pool and mem_tracker_pool, generated by the timestamp.
int64_t _group_num;
diff --git a/be/src/runtime/memory/memory_reclamation.cpp b/be/src/runtime/memory/memory_reclamation.cpp
index 17f5a41f462..4a4f73ee098 100644
--- a/be/src/runtime/memory/memory_reclamation.cpp
+++ b/be/src/runtime/memory/memory_reclamation.cpp
@@ -213,7 +213,7 @@ int64_t MemoryReclamation::tg_enable_overcommit_group_gc(int64_t request_free_me
int64_t total_free_memory = 0;
bool gc_all_exceeded = request_free_memory >= total_exceeded_memory;
std::string log_prefix = fmt::format(
- "work load group that enable overcommit, number of group: {}, request_free_memory:{}, "
+ "workload group that enable overcommit, number of group: {}, request_free_memory:{}, "
"total_exceeded_memory:{}",
task_groups.size(), request_free_memory, total_exceeded_memory);
if (gc_all_exceeded) {
diff --git a/be/src/runtime/memory/thread_mem_tracker_mgr.h b/be/src/runtime/memory/thread_mem_tracker_mgr.h
index fd14750d8b8..1d7fa8bba07 100644
--- a/be/src/runtime/memory/thread_mem_tracker_mgr.h
+++ b/be/src/runtime/memory/thread_mem_tracker_mgr.h
@@ -285,32 +285,52 @@ inline doris::Status ThreadMemTrackerMgr::try_reserve(int64_t size) {
// if _reserved_mem not equal to 0, repeat reserve,
// _untracked_mem store bytes that not synchronized to process reserved memory.
flush_untracked_mem();
+ auto wg_ptr = _wg_wptr.lock();
+ // For wg with overcommit, the limit will only task affect when memory > soft limit
+ // wg mgr will change wg's hard limit property.
+ if (wg_ptr != nullptr && wg_ptr->enable_memory_overcommit() &&
+ !wg_ptr->has_changed_to_hard_limit()) {
+ // Only do a check here, do not real reserve. If we could reserve it, it is better, but the logic is too complicated.
+ if (!doris::GlobalMemoryArbitrator::try_reserve_process_memory(size)) {
+ return doris::Status::Error<ErrorCode::PROCESS_MEMORY_EXCEEDED>(
+ "reserve memory failed, size: {}, because {}",
+ PrettyPrinter::print(size, TUnit::BYTES),
+ GlobalMemoryArbitrator::process_mem_log_str());
+ } else {
+ doris::GlobalMemoryArbitrator::release_process_reserved_memory(size);
+ return Status::OK();
+ }
+ }
if (!_limiter_tracker->try_reserve(size)) {
auto err_msg = fmt::format(
- "reserve memory failed, size: {}, because memory tracker consumption: {}, limit: "
- "{}",
- size, _limiter_tracker->consumption(), _limiter_tracker->limit());
- return doris::Status::MemoryLimitExceeded(err_msg);
+ "reserve memory failed, size: {}, because query memory exceeded, memory tracker "
+ "consumption: {}, limit: {}",
+ PrettyPrinter::print(size, TUnit::BYTES),
+ PrettyPrinter::print(_limiter_tracker->consumption(), TUnit::BYTES),
+ PrettyPrinter::print(_limiter_tracker->limit(), TUnit::BYTES));
+ return doris::Status::Error<ErrorCode::QUERY_MEMORY_EXCEEDED>(err_msg);
}
- auto wg_ptr = _wg_wptr.lock();
if (wg_ptr) {
if (!wg_ptr->add_wg_refresh_interval_memory_growth(size)) {
- auto err_msg = fmt::format("reserve memory failed, size: {}, because {}", size,
- wg_ptr->memory_debug_string());
+ auto err_msg = fmt::format(
+ "reserve memory failed, size: {}, because wg memory exceeded, wg info: {}",
+ PrettyPrinter::print(size, TUnit::BYTES), wg_ptr->memory_debug_string());
_limiter_tracker->release(size); // rollback
_limiter_tracker->release_reserved(size); // rollback
- return doris::Status::MemoryLimitExceeded(err_msg);
+ return doris::Status::Error<ErrorCode::WORKLOAD_GROUP_MEMORY_EXCEEDED>(err_msg);
}
}
if (!doris::GlobalMemoryArbitrator::try_reserve_process_memory(size)) {
- auto err_msg = fmt::format("reserve memory failed, size: {}, because {}", size,
- GlobalMemoryArbitrator::process_mem_log_str());
+ auto err_msg =
+ fmt::format("reserve memory failed, size: {}, because proccess memory exceeded, {}",
+ PrettyPrinter::print(size, TUnit::BYTES),
+ GlobalMemoryArbitrator::process_mem_log_str());
_limiter_tracker->release(size); // rollback
_limiter_tracker->release_reserved(size); // rollback
if (wg_ptr) {
wg_ptr->sub_wg_refresh_interval_memory_growth(size); // rollback
}
- return doris::Status::MemoryLimitExceeded(err_msg);
+ return doris::Status::Error<ErrorCode::PROCESS_MEMORY_EXCEEDED>(err_msg);
}
_reserved_mem += size;
return doris::Status::OK();
diff --git a/be/src/runtime/query_context.cpp b/be/src/runtime/query_context.cpp
index c602dc683fe..9717a969d47 100644
--- a/be/src/runtime/query_context.cpp
+++ b/be/src/runtime/query_context.cpp
@@ -23,13 +23,17 @@
#include <gen_cpp/Types_types.h>
#include <glog/logging.h>
+#include <algorithm>
#include <exception>
#include <memory>
#include <mutex>
+#include <shared_mutex>
#include <sstream>
#include <utility>
+#include <vector>
#include "common/logging.h"
+#include "common/status.h"
#include "olap/olap_common.h"
#include "pipeline/dependency.h"
#include "pipeline/pipeline_fragment_context.h"
@@ -77,7 +81,7 @@ QueryContext::QueryContext(TUniqueId query_id, ExecEnv* exec_env,
bool is_pipeline, bool is_nereids, TNetworkAddress current_connect_fe,
QuerySource query_source)
: _timeout_second(-1),
- _query_id(query_id),
+ _query_id(std::move(query_id)),
_exec_env(exec_env),
_is_pipeline(is_pipeline),
_is_nereids(is_nereids),
@@ -88,6 +92,9 @@ QueryContext::QueryContext(TUniqueId query_id, ExecEnv* exec_env,
_query_watcher.start();
_shared_hash_table_controller.reset(new vectorized::SharedHashTableController());
_execution_dependency = pipeline::Dependency::create_unique(-1, -1, "ExecutionDependency");
+ _memory_sufficient_dependency =
+ pipeline::Dependency::create_unique(-1, -1, "MemorySufficientDependency", true);
+
_runtime_filter_mgr = std::make_unique<RuntimeFilterMgr>(
TUniqueId(), RuntimeFilterParamsContext::create(this), query_mem_tracker);
@@ -115,30 +122,36 @@ QueryContext::QueryContext(TUniqueId query_id, ExecEnv* exec_env,
void QueryContext::_init_query_mem_tracker() {
bool has_query_mem_limit = _query_options.__isset.mem_limit && (_query_options.mem_limit > 0);
- int64_t _bytes_limit = has_query_mem_limit ? _query_options.mem_limit : -1;
- if (_bytes_limit > MemInfo::mem_limit()) {
- VLOG_NOTICE << "Query memory limit " << PrettyPrinter::print(_bytes_limit, TUnit::BYTES)
+ int64_t bytes_limit = has_query_mem_limit ? _query_options.mem_limit : -1;
+ if (bytes_limit > MemInfo::mem_limit() || bytes_limit == -1) {
+ VLOG_NOTICE << "Query memory limit " << PrettyPrinter::print(bytes_limit, TUnit::BYTES)
<< " exceeds process memory limit of "
<< PrettyPrinter::print(MemInfo::mem_limit(), TUnit::BYTES)
- << ". Using process memory limit instead";
- _bytes_limit = MemInfo::mem_limit();
+ << " OR is -1. Using process memory limit instead.";
+ bytes_limit = MemInfo::mem_limit();
+ }
+ // If the query is a pure load task(streamload, routine load, group commit), then it should not use
+ // memlimit per query to limit their memory usage.
+ if (is_pure_load_task()) {
+ bytes_limit = MemInfo::mem_limit();
}
if (_query_options.query_type == TQueryType::SELECT) {
query_mem_tracker = MemTrackerLimiter::create_shared(
MemTrackerLimiter::Type::QUERY, fmt::format("Query#Id={}", print_id(_query_id)),
- _bytes_limit);
+ bytes_limit);
} else if (_query_options.query_type == TQueryType::LOAD) {
query_mem_tracker = MemTrackerLimiter::create_shared(
MemTrackerLimiter::Type::LOAD, fmt::format("Load#Id={}", print_id(_query_id)),
- _bytes_limit);
+ bytes_limit);
} else { // EXTERNAL
query_mem_tracker = MemTrackerLimiter::create_shared(
MemTrackerLimiter::Type::LOAD, fmt::format("External#Id={}", print_id(_query_id)),
- _bytes_limit);
+ bytes_limit);
}
if (_query_options.__isset.is_report_success && _query_options.is_report_success) {
query_mem_tracker->enable_print_log_usage();
}
+ _user_set_mem_limit = bytes_limit;
}
QueryContext::~QueryContext() {
@@ -150,7 +163,7 @@ QueryContext::~QueryContext() {
std::string mem_tracker_msg;
if (query_mem_tracker->peak_consumption() != 0) {
mem_tracker_msg = fmt::format(
- ", deregister query/load memory tracker, queryId={}, Limit={}, CurrUsed={}, "
+ ", deregister query/load memory tracker, queryId={}, CurrUsed={}, "
"PeakUsed={}",
print_id(_query_id), MemCounter::print_bytes(query_mem_tracker->limit()),
MemCounter::print_bytes(query_mem_tracker->consumption()),
@@ -190,6 +203,7 @@ QueryContext::~QueryContext() {
}
_runtime_filter_mgr.reset();
_execution_dependency.reset();
+ _memory_sufficient_dependency.reset();
_shared_hash_table_controller.reset();
_runtime_predicates.clear();
file_scan_range_params_map.clear();
@@ -217,6 +231,18 @@ void QueryContext::set_execution_dependency_ready() {
_execution_dependency->set_ready();
}
+void QueryContext::set_memory_sufficient(bool sufficient) {
+ if (sufficient) {
+ {
+ std::lock_guard l(_paused_mutex);
+ _paused_reason = Status::OK();
+ }
+ _memory_sufficient_dependency->set_ready();
+ } else {
+ _memory_sufficient_dependency->block();
+ }
+}
+
void QueryContext::cancel(Status new_status, int fragment_id) {
if (!_exec_status.update(new_status)) {
return;
@@ -382,11 +408,139 @@ void QueryContext::_report_query_profile() {
ExecEnv::GetInstance()->runtime_query_statistics_mgr()->trigger_report_profile();
}
+void QueryContext::get_revocable_info(size_t* revocable_size, size_t* memory_usage,
+ bool* has_running_task) const {
+ *revocable_size = 0;
+ for (auto&& [fragment_id, fragment_wptr] : _fragment_id_to_pipeline_ctx) {
+ auto fragment_ctx = fragment_wptr.lock();
+ if (!fragment_ctx) {
+ continue;
+ }
+
+ *revocable_size += fragment_ctx->get_revocable_size(has_running_task);
+
+ // Should wait for all tasks are not running before revoking memory.
+ if (*has_running_task) {
+ break;
+ }
+ }
+
+ *memory_usage = query_mem_tracker->consumption();
+}
+
+size_t QueryContext::get_revocable_size() const {
+ size_t revocable_size = 0;
+ for (auto&& [fragment_id, fragment_wptr] : _fragment_id_to_pipeline_ctx) {
+ auto fragment_ctx = fragment_wptr.lock();
+ if (!fragment_ctx) {
+ continue;
+ }
+
+ bool has_running_task = false;
+ revocable_size += fragment_ctx->get_revocable_size(&has_running_task);
+
+ // Should wait for all tasks are not running before revoking memory.
+ if (has_running_task) {
+ return 0;
+ }
+ }
+ return revocable_size;
+}
+
+Status QueryContext::revoke_memory() {
+ std::vector<std::pair<size_t, pipeline::PipelineTask*>> tasks;
+ std::vector<std::shared_ptr<pipeline::PipelineFragmentContext>> fragments;
+ for (auto&& [fragment_id, fragment_wptr] : _fragment_id_to_pipeline_ctx) {
+ auto fragment_ctx = fragment_wptr.lock();
+ if (!fragment_ctx) {
+ continue;
+ }
+
+ auto tasks_of_fragment = fragment_ctx->get_revocable_tasks();
+ for (auto* task : tasks_of_fragment) {
+ tasks.emplace_back(task->get_revocable_size(), task);
+ }
+ fragments.emplace_back(std::move(fragment_ctx));
+ }
+
+ std::sort(tasks.begin(), tasks.end(), [](auto&& l, auto&& r) { return l.first > r.first; });
+
+ // Do not use memlimit, use current memory usage.
+ // For example, if current limit is 1.6G, but current used is 1G, if reserve failed
+ // should free 200MB memory, not 300MB
+ //const int64_t target_revoking_size = (int64_t)(query_mem_tracker->consumption() * 0.2);
+ size_t revoked_size = 0;
+
+ std::vector<pipeline::PipelineTask*> chosen_tasks;
+ for (auto&& [revocable_size, task] : tasks) {
+ chosen_tasks.emplace_back(task);
+
+ revoked_size += revocable_size;
+ // Only revoke the largest task to ensure memory is used as much as possible
+ break;
+ //if (revoked_size >= target_revoking_size) {
+ // break;
+ //}
+ }
+
+ std::weak_ptr<QueryContext> this_ctx = shared_from_this();
+ auto spill_context = std::make_shared<pipeline::SpillContext>(
+ chosen_tasks.size(), _query_id, [this_ctx](pipeline::SpillContext* context) {
+ auto query_context = this_ctx.lock();
+ if (!query_context) {
+ return;
+ }
+
+ LOG(INFO) << query_context->debug_string() << ", context: " << ((void*)context)
+ << " all spill tasks done, resume it.";
+ query_context->set_memory_sufficient(true);
+ });
+
+ for (auto* task : chosen_tasks) {
+ RETURN_IF_ERROR(task->revoke_memory(spill_context));
+ }
+
+ LOG(INFO) << this->debug_string() << ", context: " << ((void*)spill_context.get())
+ << " total revoked size: " << PrettyPrinter::print(revoked_size, TUnit::BYTES)
+ << ", tasks count: " << chosen_tasks.size() << "/" << tasks.size();
+ return Status::OK();
+}
+
+void QueryContext::decrease_revoking_tasks_count() {
+ _revoking_tasks_count.fetch_sub(1);
+}
+
+std::vector<pipeline::PipelineTask*> QueryContext::get_revocable_tasks() const {
+ std::vector<pipeline::PipelineTask*> tasks;
+ for (auto&& [fragment_id, fragment_wptr] : _fragment_id_to_pipeline_ctx) {
+ auto fragment_ctx = fragment_wptr.lock();
+ if (!fragment_ctx) {
+ continue;
+ }
+ auto tasks_of_fragment = fragment_ctx->get_revocable_tasks();
+ tasks.insert(tasks.end(), tasks_of_fragment.cbegin(), tasks_of_fragment.cend());
+ }
+ return tasks;
+}
+
+std::string QueryContext::debug_string() {
+ std::lock_guard l(_paused_mutex);
+ return fmt::format(
+ "Label={}, Used={}, Limit={}, Peak={}, running revoke task count {}, "
+ "MemorySufficient={}, PausedReason={}",
+ query_mem_tracker->label(),
+ PrettyPrinter::print(query_mem_tracker->consumption(), TUnit::BYTES),
+ PrettyPrinter::print(query_mem_tracker->limit(), TUnit::BYTES),
+ PrettyPrinter::print(query_mem_tracker->peak_consumption(), TUnit::BYTES),
+ _revoking_tasks_count, _memory_sufficient_dependency->ready(),
+ _paused_reason.to_string());
+}
+
std::unordered_map<int, std::vector<std::shared_ptr<TRuntimeProfileTree>>>
QueryContext::_collect_realtime_query_profile() const {
std::unordered_map<int, std::vector<std::shared_ptr<TRuntimeProfileTree>>> res;
- for (auto& [fragment_id, fragment_ctx_wptr] : _fragment_id_to_pipeline_ctx) {
+ for (const auto& [fragment_id, fragment_ctx_wptr] : _fragment_id_to_pipeline_ctx) {
if (auto fragment_ctx = fragment_ctx_wptr.lock()) {
if (fragment_ctx == nullptr) {
std::string msg =
diff --git a/be/src/runtime/query_context.h b/be/src/runtime/query_context.h
index d1d78573923..864ea1f9ce1 100644
--- a/be/src/runtime/query_context.h
+++ b/be/src/runtime/query_context.h
@@ -22,6 +22,7 @@
#include <gen_cpp/Types_types.h>
#include <atomic>
+#include <cstdint>
#include <memory>
#include <mutex>
#include <string>
@@ -30,6 +31,7 @@
#include "common/config.h"
#include "common/factory_creator.h"
#include "common/object_pool.h"
+#include "pipeline/dependency.h"
#include "runtime/exec_env.h"
#include "runtime/memory/mem_tracker_limiter.h"
#include "runtime/query_statistics.h"
@@ -45,6 +47,7 @@ namespace doris {
namespace pipeline {
class PipelineFragmentContext;
+class PipelineTask;
} // namespace pipeline
struct ReportStatusRequest {
@@ -74,7 +77,7 @@ const std::string toString(QuerySource query_source);
// Some components like DescriptorTbl may be very large
// that will slow down each execution of fragments when DeSer them every time.
class DescriptorTbl;
-class QueryContext {
+class QueryContext : public std::enable_shared_from_this<QueryContext> {
ENABLE_FACTORY_CREATOR(QueryContext);
public:
@@ -116,6 +119,8 @@ public:
void set_execution_dependency_ready();
+ void set_memory_sufficient(bool sufficient);
+
void set_ready_to_execute_only();
std::shared_ptr<vectorized::SharedHashTableController> get_shared_hash_table_controller() {
@@ -179,6 +184,14 @@ public:
pipeline::Dependency* get_execution_dependency() { return _execution_dependency.get(); }
+ pipeline::Dependency* get_memory_sufficient_dependency() {
+ return _memory_sufficient_dependency.get();
+ }
+
+ std::vector<pipeline::PipelineTask*> get_revocable_tasks() const;
+
+ Status revoke_memory();
+
void register_query_statistics(std::shared_ptr<QueryStatistics> qs);
std::shared_ptr<QueryStatistics> get_query_statistics();
@@ -195,8 +208,6 @@ public:
std::vector<TUniqueId> get_fragment_instance_ids() const { return fragment_instance_ids; }
- int64_t mem_limit() const { return _bytes_limit; }
-
void set_merge_controller_handler(
std::shared_ptr<RuntimeFilterMergeControllerEntity>& handler) {
_merge_controller_handler = handler;
@@ -216,8 +227,41 @@ public:
return _running_big_mem_op_num.load(std::memory_order_relaxed);
}
- void set_spill_threshold(int64_t spill_threshold) { _spill_threshold = spill_threshold; }
- int64_t spill_threshold() { return _spill_threshold; }
+ void increase_revoking_tasks_count() { _revoking_tasks_count.fetch_add(1); }
+
+ void decrease_revoking_tasks_count();
+
+ int get_revoking_tasks_count() const { return _revoking_tasks_count.load(); }
+
+ void get_revocable_info(size_t* revocable_size, size_t* memory_usage,
+ bool* has_running_task) const;
+ size_t get_revocable_size() const;
+
+ // This method is called by workload group manager to set query's memlimit using slot
+ // If user set query limit explicitly, then should use less one
+ void set_mem_limit(int64_t new_mem_limit) {
+ query_mem_tracker->set_limit(std::min<int64_t>(new_mem_limit, _user_set_mem_limit));
+ }
+
+ int64_t get_mem_limit() const { return query_mem_tracker->limit(); }
+
+ void set_expected_mem_limit(int64_t new_mem_limit) {
+ _expected_mem_limit = std::min<int64_t>(new_mem_limit, _user_set_mem_limit);
+ }
+
+ int64_t expected_mem_limit() { return _expected_mem_limit; }
+
+ std::shared_ptr<MemTrackerLimiter>& get_mem_tracker() { return query_mem_tracker; }
+
+ int32_t get_slot_count() const {
+ return _query_options.__isset.query_slot_count ? _query_options.query_slot_count : 1;
+ }
+
+ bool enable_query_slot_hard_limit() const {
+ return _query_options.__isset.enable_query_slot_hard_limit
+ ? _query_options.enable_query_slot_hard_limit
+ : false;
+ }
DescriptorTbl* desc_tbl = nullptr;
bool set_rsc_info = false;
std::string user;
@@ -242,16 +286,96 @@ public:
}
}
+ // Query will run in low memory mode when
+ // 1. the query is enable spill and wg's low water mark reached, if not release buffer, it will trigger spill disk, it is very expensive.
+ // 2. the query is not enable spill, but wg's high water mark reached, if not release buffer, the query will be cancelled.
+ // 3. the process reached soft mem_limit, if not release these, if not release buffer, the query will be cancelled.
+ // 4. If the query reserve memory failed.
+ // Under low memory mode, the query should release some buffers such as scan operator block queue, union operator queue, exchange buffer size, streaming agg
+ void update_low_memory_mode() {
+ if (_low_memory_mode) {
+ return;
+ }
+
+ // If less than 100MB left, then it is low memory mode
+ if (doris::GlobalMemoryArbitrator::is_exceed_soft_mem_limit(100 * 1024 * 1024)) {
+ _low_memory_mode = true;
+ LOG(INFO) << "Query " << print_id(_query_id)
+ << " goes to low memory mode due to exceed process soft memory limit";
+ return;
+ }
+
+ if (_workload_group) {
+ bool is_low_wartermark = false;
+ bool is_high_wartermark = false;
+ _workload_group->check_mem_used(&is_low_wartermark, &is_high_wartermark);
+ // If the wg is not enable hard limit, this will also take effect to lower down the memory usage.
+ if (is_high_wartermark) {
+ LOG(INFO)
+ << "Query " << print_id(_query_id)
+ << " goes to low memory mode due to workload group high water mark reached";
+ _low_memory_mode = true;
+ return;
+ }
+
+ if (is_low_wartermark &&
+ ((_query_options.__isset.enable_join_spill && _query_options.enable_join_spill) ||
+ (_query_options.__isset.enable_sort_spill && _query_options.enable_sort_spill) ||
+ (_query_options.__isset.enable_agg_spill && _query_options.enable_agg_spill))) {
+ LOG(INFO) << "Query " << print_id(_query_id)
+ << " goes to low memory mode due to workload group low water mark "
+ "reached and the query enable spill";
+ _low_memory_mode = true;
+ return;
+ }
+ }
+ }
+
+ void set_low_memory_mode() { _low_memory_mode = true; }
+
+ bool low_memory_mode() { return _low_memory_mode; }
+
+ void update_paused_reason(const Status& st) {
+ std::lock_guard l(_paused_mutex);
+ if (_paused_reason.is<ErrorCode::QUERY_MEMORY_EXCEEDED>()) {
+ return;
+ } else if (_paused_reason.is<ErrorCode::WORKLOAD_GROUP_MEMORY_EXCEEDED>()) {
+ if (st.is<ErrorCode::QUERY_MEMORY_EXCEEDED>()) {
+ _paused_reason = st;
+ return;
+ } else {
+ return;
+ }
+ } else {
+ _paused_reason = st;
+ }
+ }
+
+ Status paused_reason() {
+ std::lock_guard l(_paused_mutex);
+ return _paused_reason;
+ }
+
+ bool is_pure_load_task() {
+ return _query_source == QuerySource::STREAM_LOAD ||
+ _query_source == QuerySource::ROUTINE_LOAD ||
+ _query_source == QuerySource::GROUP_COMMIT_LOAD;
+ }
+
+ std::string debug_string();
+
private:
int _timeout_second;
TUniqueId _query_id;
ExecEnv* _exec_env = nullptr;
MonotonicStopWatch _query_watcher;
- int64_t _bytes_limit = 0;
bool _is_pipeline = false;
bool _is_nereids = false;
std::atomic<int> _running_big_mem_op_num = 0;
+ std::mutex _revoking_tasks_mutex;
+ std::atomic<int> _revoking_tasks_count = 0;
+
// A token used to submit olap scanner to the "_limited_scan_thread_pool",
// This thread pool token is created from "_limited_scan_thread_pool" from exec env.
// And will be shared by all instances of this query.
@@ -278,6 +402,9 @@ private:
vectorized::SimplifiedScanScheduler* _remote_scan_task_scheduler = nullptr;
std::unique_ptr<pipeline::Dependency> _execution_dependency;
+ std::unique_ptr<pipeline::Dependency> _memory_sufficient_dependency;
+ std::vector<std::weak_ptr<pipeline::PipelineTask>> _pipeline_tasks;
+
std::shared_ptr<QueryStatistics> _cpu_statistics = nullptr;
// This shared ptr is never used. It is just a reference to hold the object.
// There is a weak ptr in runtime filter manager to reference this object.
@@ -286,7 +413,9 @@ private:
std::map<int, std::weak_ptr<pipeline::PipelineFragmentContext>> _fragment_id_to_pipeline_ctx;
std::mutex _pipeline_map_write_lock;
- std::atomic<int64_t> _spill_threshold {0};
+ std::atomic<bool> _low_memory_mode = false;
+ int64_t _user_set_mem_limit = 0;
+ std::atomic<int64_t> _expected_mem_limit = 0;
std::mutex _profile_mutex;
timespec _query_arrival_timestamp;
@@ -294,6 +423,9 @@ private:
// help us manage the query.
QuerySource _query_source;
+ Status _paused_reason;
+ std::mutex _paused_mutex;
+
// when fragment of pipeline is closed, it will register its profile to this map by using add_fragment_profile
// flatten profile of one fragment:
// Pipeline 0
diff --git a/be/src/runtime/runtime_state.h b/be/src/runtime/runtime_state.h
index f5fd7547532..66ae64d00e3 100644
--- a/be/src/runtime/runtime_state.h
+++ b/be/src/runtime/runtime_state.h
@@ -56,6 +56,7 @@ class PipelineXLocalStateBase;
class PipelineXSinkLocalStateBase;
class PipelineFragmentContext;
class PipelineTask;
+class Dependency;
} // namespace pipeline
class DescriptorTbl;
@@ -589,6 +590,15 @@ public:
return 1;
}
+ size_t minimum_operator_memory_required_bytes() const {
+ if (_query_options.__isset.minimum_operator_memory_required_kb) {
+ return _query_options.minimum_operator_memory_required_kb * 1024;
+ } else {
+ // refer other database
+ return 100 * 1024;
+ }
+ }
+
void set_max_operator_id(int max_operator_id) { _max_operator_id = max_operator_id; }
int max_operator_id() const { return _max_operator_id; }
@@ -607,7 +617,7 @@ public:
vectorized::ColumnInt64* partial_update_auto_inc_column() {
return _partial_update_auto_inc_column;
- };
+ }
private:
Status create_error_log_file();
diff --git a/be/src/runtime/thread_context.cpp b/be/src/runtime/thread_context.cpp
index c89f532e592..55c5273ef71 100644
--- a/be/src/runtime/thread_context.cpp
+++ b/be/src/runtime/thread_context.cpp
@@ -46,10 +46,18 @@ AttachTask::AttachTask(const std::shared_ptr<MemTrackerLimiter>& mem_tracker) {
AttachTask::AttachTask(RuntimeState* runtime_state) {
signal::set_signal_is_nereids(runtime_state->is_nereids());
- QueryThreadContext query_thread_context = {runtime_state->query_id(),
- runtime_state->query_mem_tracker(),
- runtime_state->get_query_ctx()->workload_group()};
- init(query_thread_context);
+ // RuntimeState not always has query ctx.
+ // For example during push handler or schema change
+ if (runtime_state->get_query_ctx() == nullptr) {
+ QueryThreadContext query_thread_context = {runtime_state->query_id(),
+ runtime_state->query_mem_tracker()};
+ init(query_thread_context);
+ } else {
+ QueryThreadContext query_thread_context = {
+ runtime_state->query_id(), runtime_state->query_mem_tracker(),
+ runtime_state->get_query_ctx()->workload_group()};
+ init(query_thread_context);
+ }
}
AttachTask::AttachTask(const QueryThreadContext& query_thread_context) {
diff --git a/be/src/runtime/workload_group/workload_group.cpp b/be/src/runtime/workload_group/workload_group.cpp
index 0488e9ec83c..6fbd7b752df 100644
--- a/be/src/runtime/workload_group/workload_group.cpp
+++ b/be/src/runtime/workload_group/workload_group.cpp
@@ -49,6 +49,8 @@ const static bool ENABLE_MEMORY_OVERCOMMIT_DEFAULT_VALUE = true;
const static int CPU_HARD_LIMIT_DEFAULT_VALUE = -1;
const static int SPILL_LOW_WATERMARK_DEFAULT_VALUE = 50;
const static int SPILL_HIGH_WATERMARK_DEFAULT_VALUE = 80;
+// This is a invalid value, and should ignore this value during usage
+const static int TOTAL_QUERY_SLOT_COUNT_DEFAULT_VALUE = 0;
WorkloadGroup::WorkloadGroup(const WorkloadGroupInfo& tg_info)
: _id(tg_info.id),
@@ -65,7 +67,8 @@ WorkloadGroup::WorkloadGroup(const WorkloadGroupInfo& tg_info)
_spill_low_watermark(tg_info.spill_low_watermark),
_spill_high_watermark(tg_info.spill_high_watermark),
_scan_bytes_per_second(tg_info.read_bytes_per_second),
- _remote_scan_bytes_per_second(tg_info.remote_read_bytes_per_second) {
+ _remote_scan_bytes_per_second(tg_info.remote_read_bytes_per_second),
+ _total_query_slot_count(tg_info.total_query_slot_count) {
std::vector<DataDirInfo>& data_dir_list = io::BeConfDataDirReader::be_config_data_dir_list;
for (const auto& data_dir : data_dir_list) {
_scan_io_throttle_map[data_dir.path] =
@@ -80,35 +83,74 @@ WorkloadGroup::WorkloadGroup(const WorkloadGroupInfo& tg_info)
std::make_unique<bvar::Adder<size_t>>(_name, "total_local_read_bytes");
_total_local_scan_io_per_second = std::make_unique<bvar::PerSecond<bvar::Adder<size_t>>>(
_name, "total_local_read_bytes_per_second", _total_local_scan_io_adder.get(), 1);
+ _load_buffer_limit = (int64_t)(_memory_limit * 0.2);
+ // Its initial value should equal to memory limit, or it will be 0 and all reserve memory request will failed.
+ _weighted_memory_limit = _memory_limit;
}
std::string WorkloadGroup::debug_string() const {
std::shared_lock<std::shared_mutex> rl {_mutex};
+ auto realtime_total_mem_used = _total_mem_used + _wg_refresh_interval_memory_growth.load();
+ auto mem_used_ratio = realtime_total_mem_used / ((double)_weighted_memory_limit + 1);
return fmt::format(
- "TG[id = {}, name = {}, cpu_share = {}, memory_limit = {}, enable_memory_overcommit = "
- "{}, version = {}, cpu_hard_limit = {}, scan_thread_num = "
+ "WorkloadGroup[id = {}, name = {}, version = {}, cpu_share = {}, "
+ "total_query_slot_count={}, "
+ "memory_limit = {}, "
+ "enable_memory_overcommit = {}, weighted_memory_limit = {}, total_mem_used = {},"
+ "wg_refresh_interval_memory_growth = {}, mem_used_ratio = {}, spill_low_watermark = "
+ "{}, spill_high_watermark = {},cpu_hard_limit = {}, scan_thread_num = "
"{}, max_remote_scan_thread_num = {}, min_remote_scan_thread_num = {}, "
- "spill_low_watermark={}, spill_high_watermark={}, is_shutdown={}, query_num={}, "
+ "is_shutdown={}, query_num={}, "
"read_bytes_per_second={}, remote_read_bytes_per_second={}]",
- _id, _name, cpu_share(), PrettyPrinter::print(_memory_limit, TUnit::BYTES),
- _enable_memory_overcommit ? "true" : "false", _version, cpu_hard_limit(),
+ _id, _name, _version, cpu_share(), _total_query_slot_count,
+ PrettyPrinter::print(_memory_limit, TUnit::BYTES),
+ _enable_memory_overcommit ? "true" : "false",
+ PrettyPrinter::print(_weighted_memory_limit.load(), TUnit::BYTES),
+ PrettyPrinter::print(_total_mem_used.load(), TUnit::BYTES),
+ PrettyPrinter::print(_wg_refresh_interval_memory_growth.load(), TUnit::BYTES),
+ mem_used_ratio, _spill_low_watermark, _spill_high_watermark, cpu_hard_limit(),
_scan_thread_num, _max_remote_scan_thread_num, _min_remote_scan_thread_num,
- _spill_low_watermark, _spill_high_watermark, _is_shutdown, _query_ctxs.size(),
- _scan_bytes_per_second, _remote_scan_bytes_per_second);
+ _is_shutdown, _query_ctxs.size(), _scan_bytes_per_second,
+ _remote_scan_bytes_per_second);
+}
+
+bool WorkloadGroup::add_wg_refresh_interval_memory_growth(int64_t size) {
+ // If a group is enable memory overcommit, then not need check the limit
+ // It is always true, and it will only fail when process memory is not
+ // enough.
+ if (_enable_memory_overcommit) {
+ if (doris::GlobalMemoryArbitrator::is_exceed_soft_mem_limit(size)) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+ auto realtime_total_mem_used =
+ _total_mem_used + _wg_refresh_interval_memory_growth.load() + size;
+ if ((realtime_total_mem_used >
+ ((double)_memory_limit * _spill_high_watermark.load(std::memory_order_relaxed) / 100))) {
+ return false;
+ } else {
+ _wg_refresh_interval_memory_growth.fetch_add(size);
+ return true;
+ }
}
std::string WorkloadGroup::memory_debug_string() const {
+ auto realtime_total_mem_used = _total_mem_used + _wg_refresh_interval_memory_growth.load();
+ auto mem_used_ratio = realtime_total_mem_used / ((double)_weighted_memory_limit + 1);
return fmt::format(
- "TG[id = {}, name = {}, memory_limit = {}, enable_memory_overcommit = "
- "{}, weighted_memory_limit = {}, total_mem_used = {}, "
- "wg_refresh_interval_memory_growth = {}, spill_low_watermark = {}, "
+ "WorkloadGroup[id = {}, name = {}, memory_limit = {}, enable_memory_overcommit = "
+ "{}, weighted_memory_limit = {}, total_mem_used = {},"
+ "wg_refresh_interval_memory_growth = {}, mem_used_ratio = {}, spill_low_watermark = "
+ "{}, "
"spill_high_watermark = {}, version = {}, is_shutdown = {}, query_num = {}]",
_id, _name, PrettyPrinter::print(_memory_limit, TUnit::BYTES),
_enable_memory_overcommit ? "true" : "false",
- PrettyPrinter::print(_weighted_memory_limit, TUnit::BYTES),
- PrettyPrinter::print(_total_mem_used, TUnit::BYTES),
- PrettyPrinter::print(_wg_refresh_interval_memory_growth, TUnit::BYTES),
- _spill_low_watermark, _spill_high_watermark, _version, _is_shutdown,
+ PrettyPrinter::print(_weighted_memory_limit.load(), TUnit::BYTES),
+ PrettyPrinter::print(_total_mem_used.load(), TUnit::BYTES),
+ PrettyPrinter::print(_wg_refresh_interval_memory_growth.load(), TUnit::BYTES),
+ mem_used_ratio, _spill_low_watermark, _spill_high_watermark, _version, _is_shutdown,
_query_ctxs.size());
}
@@ -138,6 +180,7 @@ void WorkloadGroup::check_and_update(const WorkloadGroupInfo& tg_info) {
_spill_high_watermark = tg_info.spill_high_watermark;
_scan_bytes_per_second = tg_info.read_bytes_per_second;
_remote_scan_bytes_per_second = tg_info.remote_read_bytes_per_second;
+ _total_query_slot_count = tg_info.total_query_slot_count;
} else {
return;
}
@@ -417,6 +460,12 @@ WorkloadGroupInfo WorkloadGroupInfo::parse_topic_info(
remote_read_bytes_per_second = tworkload_group_info.remote_read_bytes_per_second;
}
+ // 16 total slots
+ int total_query_slot_count = TOTAL_QUERY_SLOT_COUNT_DEFAULT_VALUE;
+ if (tworkload_group_info.__isset.total_query_slot_count) {
+ total_query_slot_count = tworkload_group_info.total_query_slot_count;
+ }
+
return {.id = tg_id,
.name = name,
.cpu_share = cpu_share,
@@ -431,7 +480,8 @@ WorkloadGroupInfo WorkloadGroupInfo::parse_topic_info(
.spill_low_watermark = spill_low_watermark,
.spill_high_watermark = spill_high_watermark,
.read_bytes_per_second = read_bytes_per_second,
- .remote_read_bytes_per_second = remote_read_bytes_per_second};
+ .remote_read_bytes_per_second = remote_read_bytes_per_second,
+ .total_query_slot_count = total_query_slot_count};
}
void WorkloadGroup::upsert_task_scheduler(WorkloadGroupInfo* tg_info, ExecEnv* exec_env) {
diff --git a/be/src/runtime/workload_group/workload_group.h b/be/src/runtime/workload_group/workload_group.h
index 933c5afdb4e..f2e2b395ec3 100644
--- a/be/src/runtime/workload_group/workload_group.h
+++ b/be/src/runtime/workload_group/workload_group.h
@@ -76,9 +76,9 @@ public:
int64_t memory_limit() const {
std::shared_lock<std::shared_mutex> r_lock(_mutex);
return _memory_limit;
- };
+ }
- int64_t weighted_memory_limit() const { return _weighted_memory_limit; };
+ int64_t total_mem_used() const { return _total_mem_used; }
void set_weighted_memory_limit(int64_t weighted_memory_limit) {
_weighted_memory_limit = weighted_memory_limit;
@@ -95,23 +95,17 @@ public:
int spill_threshold_low_water_mark() const {
return _spill_low_watermark.load(std::memory_order_relaxed);
}
- int spill_threashold_high_water_mark() const {
+
+ int spill_threshold_high_water_mark() const {
return _spill_high_watermark.load(std::memory_order_relaxed);
}
- void set_weighted_memory_ratio(double ratio);
- bool add_wg_refresh_interval_memory_growth(int64_t size) {
- auto realtime_total_mem_used =
- _total_mem_used + _wg_refresh_interval_memory_growth.load() + size;
- if ((realtime_total_mem_used >
- ((double)_weighted_memory_limit *
- _spill_high_watermark.load(std::memory_order_relaxed) / 100))) {
- return false;
- } else {
- _wg_refresh_interval_memory_growth.fetch_add(size);
- return true;
- }
+ int total_query_slot_count() const {
+ return _total_query_slot_count.load(std::memory_order_relaxed);
}
+
+ bool add_wg_refresh_interval_memory_growth(int64_t size);
+
void sub_wg_refresh_interval_memory_growth(int64_t size) {
_wg_refresh_interval_memory_growth.fetch_sub(size);
}
@@ -119,13 +113,27 @@ public:
void check_mem_used(bool* is_low_wartermark, bool* is_high_wartermark) const {
auto realtime_total_mem_used = _total_mem_used + _wg_refresh_interval_memory_growth.load();
*is_low_wartermark = (realtime_total_mem_used >
- ((double)_weighted_memory_limit *
+ ((double)_memory_limit *
_spill_low_watermark.load(std::memory_order_relaxed) / 100));
*is_high_wartermark = (realtime_total_mem_used >
- ((double)_weighted_memory_limit *
+ ((double)_memory_limit *
_spill_high_watermark.load(std::memory_order_relaxed) / 100));
}
+ void update_load_mem_usage(int64_t active_bytes, int64_t queue_bytes, int64_t flush_bytes) {
+ std::unique_lock<std::shared_mutex> wlock(_mutex);
+ _active_mem_usage = active_bytes;
+ _queue_mem_usage = queue_bytes;
+ _flush_mem_usage = flush_bytes;
+ }
+
+ void get_load_mem_usage(int64_t* active_bytes, int64_t* queue_bytes, int64_t* flush_bytes) {
+ std::shared_lock<std::shared_mutex> r_lock(_mutex);
+ *active_bytes += _active_mem_usage;
+ *queue_bytes += _queue_mem_usage;
+ *flush_bytes += _flush_mem_usage;
+ }
+
std::string debug_string() const;
std::string memory_debug_string() const;
@@ -140,6 +148,11 @@ public:
return _memory_limit > 0;
}
+ bool exceed_limit() {
+ std::shared_lock<std::shared_mutex> r_lock(_mutex);
+ return _memory_limit > 0 ? _total_mem_used > _memory_limit : false;
+ }
+
Status add_query(TUniqueId query_id, std::shared_ptr<QueryContext> query_ctx) {
std::unique_lock<std::shared_mutex> wlock(_mutex);
if (_is_shutdown) {
@@ -198,12 +211,29 @@ public:
}
int64_t get_remote_scan_bytes_per_second();
+ int64_t load_buffer_limit() { return _load_buffer_limit; }
+
+ bool has_changed_to_hard_limit() const { return _has_changed_hard_limit; }
+
+ void change_to_hard_limit(bool to_hard_limit) { _has_changed_hard_limit = to_hard_limit; }
+
private:
mutable std::shared_mutex _mutex; // lock _name, _version, _cpu_share, _memory_limit
const uint64_t _id;
std::string _name;
int64_t _version;
int64_t _memory_limit; // bytes
+ // For example, load memtable, write to parquet.
+ // If the wg's memory reached high water mark, then the load buffer
+ // will be restricted to this limit.
+ int64_t _load_buffer_limit;
+ std::atomic<bool> _has_changed_hard_limit = false;
+
+ // memory used by load memtable
+ int64_t _active_mem_usage = 0;
+ int64_t _queue_mem_usage = 0;
+ int64_t _flush_mem_usage = 0;
+
// `weighted_memory_limit` less than or equal to _memory_limit, calculate after exclude public memory.
// more detailed description in `refresh_wg_weighted_memory_limit`.
std::atomic<int64_t> _weighted_memory_limit {0}; //
@@ -221,6 +251,7 @@ private:
std::atomic<int> _spill_high_watermark;
std::atomic<int64_t> _scan_bytes_per_second {-1};
std::atomic<int64_t> _remote_scan_bytes_per_second {-1};
+ std::atomic<int> _total_query_slot_count = 0;
// means workload group is mark dropped
// new query can not submit
@@ -264,6 +295,7 @@ struct WorkloadGroupInfo {
const int spill_high_watermark = 0;
const int read_bytes_per_second = -1;
const int remote_read_bytes_per_second = -1;
+ const int total_query_slot_count = 0;
// log cgroup cpu info
uint64_t cgroup_cpu_shares = 0;
int cgroup_cpu_hard_limit = 0;
diff --git a/be/src/runtime/workload_group/workload_group_manager.cpp b/be/src/runtime/workload_group/workload_group_manager.cpp
index 003f07f1db0..87a8fc5be0b 100644
--- a/be/src/runtime/workload_group/workload_group_manager.cpp
+++ b/be/src/runtime/workload_group/workload_group_manager.cpp
@@ -17,13 +17,17 @@
#include "workload_group_manager.h"
+#include <glog/logging.h>
+
#include <algorithm>
#include <memory>
#include <mutex>
#include <unordered_map>
+#include "common/status.h"
#include "exec/schema_scanner/schema_scanner_helper.h"
#include "pipeline/task_scheduler.h"
+#include "runtime/memory/global_memory_arbitrator.h"
#include "runtime/memory/mem_tracker_limiter.h"
#include "runtime/workload_group/workload_group.h"
#include "util/mem_info.h"
@@ -34,6 +38,16 @@
namespace doris {
+PausedQuery::PausedQuery(std::shared_ptr<QueryContext> query_ctx, double cache_ratio,
+ bool any_wg_exceed_limit, int64_t reserve_size)
+ : query_ctx_(query_ctx),
+ cache_ratio_(cache_ratio),
+ any_wg_exceed_limit_(any_wg_exceed_limit),
+ reserve_size_(reserve_size),
+ query_id_(print_id(query_ctx->query_id())) {
+ enqueue_at = std::chrono::system_clock::now();
+}
+
WorkloadGroupPtr WorkloadGroupMgr::get_or_create_workload_group(
const WorkloadGroupInfo& workload_group_info) {
{
@@ -148,7 +162,6 @@ struct WorkloadGroupMemInfo {
std::list<std::shared_ptr<MemTrackerLimiter>> tracker_snapshots =
std::list<std::shared_ptr<MemTrackerLimiter>>();
};
-
void WorkloadGroupMgr::refresh_wg_weighted_memory_limit() {
std::shared_lock<std::shared_mutex> r_lock(_group_mutex);
@@ -156,11 +169,16 @@ void WorkloadGroupMgr::refresh_wg_weighted_memory_limit() {
// and calculate total memory used of all queries.
int64_t all_workload_groups_mem_usage = 0;
std::unordered_map<uint64_t, WorkloadGroupMemInfo> wgs_mem_info;
+ bool has_wg_exceed_limit = false;
for (auto& [wg_id, wg] : _workload_groups) {
wgs_mem_info[wg_id].total_mem_used =
wg->make_memory_tracker_snapshots(&wgs_mem_info[wg_id].tracker_snapshots);
all_workload_groups_mem_usage += wgs_mem_info[wg_id].total_mem_used;
+ if (wg->exceed_limit()) {
+ has_wg_exceed_limit = true;
+ }
}
+ doris::GlobalMemoryArbitrator::any_workload_group_exceed_limit = has_wg_exceed_limit;
if (all_workload_groups_mem_usage <= 0) {
return;
}
@@ -188,6 +206,8 @@ void WorkloadGroupMgr::refresh_wg_weighted_memory_limit() {
if (all_workload_groups_mem_usage < process_memory_usage) {
int64_t public_memory = process_memory_usage - all_workload_groups_mem_usage;
weighted_memory_limit_ratio = 1 - (double)public_memory / (double)process_memory_limit;
+ // Round the value from 1% to 100%.
+ weighted_memory_limit_ratio = std::floor(weighted_memory_limit_ratio * 100) / 100;
}
std::string debug_msg = fmt::format(
@@ -197,59 +217,9 @@ void WorkloadGroupMgr::refresh_wg_weighted_memory_limit() {
doris::GlobalMemoryArbitrator::sys_mem_available_details_str(),
PrettyPrinter::print(all_workload_groups_mem_usage, TUnit::BYTES),
weighted_memory_limit_ratio);
- LOG_EVERY_T(INFO, 10) << debug_msg;
-
+ LOG_EVERY_T(INFO, 60) << debug_msg;
for (auto& wg : _workload_groups) {
- // 3.1 calculate query spill threshold of task group
- auto wg_weighted_mem_limit =
- int64_t(wg.second->memory_limit() * weighted_memory_limit_ratio);
- wg.second->set_weighted_memory_limit(wg_weighted_mem_limit);
-
- // 3.2 set workload groups weighted memory limit and all query spill threshold.
- auto wg_query_count = wgs_mem_info[wg.first].tracker_snapshots.size();
- int64_t query_spill_threshold =
- wg_query_count ? (wg_weighted_mem_limit + wg_query_count) / wg_query_count
- : wg_weighted_mem_limit;
- for (const auto& query : wg.second->queries()) {
- auto query_ctx = query.second.lock();
- if (!query_ctx) {
- continue;
- }
- query_ctx->set_spill_threshold(query_spill_threshold);
- }
-
- // 3.3 only print debug logs, if workload groups is_high_wartermark or is_low_wartermark.
- bool is_low_wartermark = false;
- bool is_high_wartermark = false;
- wg.second->check_mem_used(&is_low_wartermark, &is_high_wartermark);
- std::string debug_msg;
- if (is_high_wartermark || is_low_wartermark) {
- debug_msg = fmt::format(
- "\nWorkload Group {}: mem limit: {}, mem used: {}, weighted mem limit: {}, "
- "used "
- "ratio: {}, query count: {}, query spill threshold: {}",
- wg.second->name(),
- PrettyPrinter::print(wg.second->memory_limit(), TUnit::BYTES),
- PrettyPrinter::print(wgs_mem_info[wg.first].total_mem_used, TUnit::BYTES),
- PrettyPrinter::print(wg_weighted_mem_limit, TUnit::BYTES),
- (double)wgs_mem_info[wg.first].total_mem_used / wg_weighted_mem_limit,
- wg_query_count, PrettyPrinter::print(query_spill_threshold, TUnit::BYTES));
-
- debug_msg += "\n Query Memory Summary:";
- // check whether queries need to revoke memory for task group
- for (const auto& query_mem_tracker : wgs_mem_info[wg.first].tracker_snapshots) {
- debug_msg += fmt::format(
- "\n MemTracker Label={}, Used={}, SpillThreshold={}, "
- "Peak={}",
- query_mem_tracker->label(),
- PrettyPrinter::print(query_mem_tracker->consumption(), TUnit::BYTES),
- PrettyPrinter::print(query_spill_threshold, TUnit::BYTES),
- PrettyPrinter::print(query_mem_tracker->peak_consumption(), TUnit::BYTES));
- }
- LOG_EVERY_T(INFO, 1) << debug_msg;
- } else {
- continue;
- }
+ update_queries_limit(wg.second, false);
}
}
@@ -277,10 +247,475 @@ void WorkloadGroupMgr::get_wg_resource_usage(vectorized::Block* block) {
}
}
+void WorkloadGroupMgr::add_paused_query(const std::shared_ptr<QueryContext>& query_ctx,
+ int64_t reserve_size) {
+ std::lock_guard<std::mutex> lock(_paused_queries_lock);
+ DCHECK(query_ctx != nullptr);
+ auto wg = query_ctx->workload_group();
+ auto&& [it, inserted] = _paused_queries_list[wg].emplace(
+ query_ctx, doris::GlobalMemoryArbitrator::last_affected_cache_capacity_adjust_weighted,
+ doris::GlobalMemoryArbitrator::any_workload_group_exceed_limit, reserve_size);
+ // Check if this is an invalid reserve, for example, if the reserve size is too large, larger than the query limit
+ // if hard limit is enabled, then not need enable other queries hard limit.
+ if (inserted) {
+ LOG(INFO) << "workload group " << wg->debug_string()
+ << " insert one new paused query: " << it->query_id();
+ }
+}
+
+/**
+ * 1. When Process's memory is lower than soft limit, then all workload group will be converted to hard limit (Exception: there is only one workload group).
+ * 2. Reserve logic for workload group that is soft limit take no effect, it will always return success.
+ * 3. QueryLimit for streamload,routineload,group commit, take no affect, it will always return success, but workload group's hard limit will take affect.
+ * 4. See handle_non_overcommit_wg_paused_queries for hard limit logic.
+ */
+void WorkloadGroupMgr::handle_paused_queries() {
+ handle_non_overcommit_wg_paused_queries();
+ handle_overcommit_wg_paused_queries();
+}
+
+/**
+ * Strategy 1: A revocable query should not have any running task(PipelineTask).
+ * strategy 2: If the workload group has any task exceed workload group memlimit, then set all queryctx's memlimit
+ * strategy 3: If any query exceed process memlimit, then should clear all caches.
+ * strategy 4: If any query exceed query's memlimit, then do spill disk or cancel it.
+ * strategy 5: If any query exceed process's memlimit and cache is zero, then do following:
+ * 1. cancel other wg's(soft limit) query that exceed limit
+ * 2. spill disk
+ * 3. cancel it self.
+ */
+void WorkloadGroupMgr::handle_non_overcommit_wg_paused_queries() {
+ const int64_t TIMEOUT_IN_QUEUE = 1000L * 10;
+ std::unique_lock<std::mutex> lock(_paused_queries_lock);
+ std::vector<std::weak_ptr<QueryContext>> resume_after_gc;
+ for (auto it = _paused_queries_list.begin(); it != _paused_queries_list.end();) {
+ auto& queries_list = it->second;
+ const auto& wg = it->first;
+ if (queries_list.empty()) {
+ it = _paused_queries_list.erase(it);
+ continue;
+ }
+ bool is_low_wartermark = false;
+ bool is_high_wartermark = false;
+
+ wg->check_mem_used(&is_low_wartermark, &is_high_wartermark);
+ bool has_changed_hard_limit = false;
+ // If the query is paused because its limit exceed the query itself's memlimit, then just spill disk.
+ // The query's memlimit is set using slot mechanism and its value is set using the user settings, not
+ // by weighted value. So if reserve failed, then it is actually exceed limit.
+ for (auto query_it = queries_list.begin(); query_it != queries_list.end();) {
+ auto query_ctx = query_it->query_ctx_.lock();
+ // The query is finished during in paused list.
+ if (query_ctx == nullptr) {
+ query_it = queries_list.erase(query_it);
+ continue;
+ }
+ if (query_ctx->is_cancelled()) {
+ LOG(INFO) << "query: " << print_id(query_ctx->query_id())
+ << " was canceled, remove from paused list";
+ query_it = queries_list.erase(query_it);
+ continue;
+ }
+ bool wg_changed_to_hard_limit = wg->has_changed_to_hard_limit();
+ // Only deal with non overcommit workload group.
+ if (wg->enable_memory_overcommit() && !wg_changed_to_hard_limit &&
+ !query_ctx->paused_reason().is<ErrorCode::PROCESS_MEMORY_EXCEEDED>()) {
+ // Soft limit wg will only reserve failed when process limit exceed. But in some corner case,
+ // when reserve, the wg is hard limit, the query reserve failed, but when this loop run
+ // the wg is converted to soft limit.
+ // So that should resume the query.
+ LOG(WARNING) << "query: " << print_id(query_ctx->query_id())
+ << " reserve memory failed, but workload group not converted to hard "
+ "limit, it should not happen, resume it again. paused reason: "
+ << query_ctx->paused_reason();
+ query_ctx->set_memory_sufficient(true);
+ query_it = queries_list.erase(query_it);
+ continue;
+ }
+
+ if (query_ctx->paused_reason().is<ErrorCode::QUERY_MEMORY_EXCEEDED>()) {
+ CHECK(!wg->enable_memory_overcommit() || wg_changed_to_hard_limit);
+ // Streamload, kafka load, group commit will never have query memory exceeded error because
+ // their query limit is very large.
+ bool spill_res = handle_single_query(query_ctx, query_it->reserve_size_,
+ query_ctx->paused_reason());
+ if (!spill_res) {
+ ++query_it;
+ continue;
+ } else {
+ query_it = queries_list.erase(query_it);
+ continue;
+ }
+ } else if (query_ctx->paused_reason().is<ErrorCode::WORKLOAD_GROUP_MEMORY_EXCEEDED>()) {
+ CHECK(!wg->enable_memory_overcommit() || wg_changed_to_hard_limit);
+ // check if the reserve is too large, if it is too large,
+ // should set the query's limit only.
+ // Check the query's reserve with expected limit.
+ if (query_ctx->expected_mem_limit() <
+ query_ctx->get_mem_tracker()->consumption() + query_it->reserve_size_) {
+ query_ctx->set_mem_limit(query_ctx->expected_mem_limit());
+ query_ctx->set_memory_sufficient(true);
+ LOG(INFO) << "workload group memory reserve failed because "
+ << query_ctx->debug_string() << " reserve size "
+ << query_it->reserve_size_ << " is too large, set hard limit to "
+ << query_ctx->expected_mem_limit() << " and resume running.";
+ query_it = queries_list.erase(query_it);
+ continue;
+ }
+ if (!has_changed_hard_limit) {
+ update_queries_limit(wg, true);
+ has_changed_hard_limit = true;
+ LOG(INFO) << "query: " << print_id(query_ctx->query_id())
+ << " reserve memory failed due to workload group memory exceed, "
+ "should set the workload group work in memory insufficent mode, "
+ "so that other query will reduce their memory. wg: "
+ << wg->debug_string();
+ }
+ // If there are a lot of memtable memory, then wait them flush finished.
+ MemTableMemoryLimiter* memtable_limiter =
+ doris::ExecEnv::GetInstance()->memtable_memory_limiter();
+ // Not use memlimit, should use high water mark.
+ int64_t memtable_active_bytes = 0;
+ int64_t memtable_queue_bytes = 0;
+ int64_t memtable_flush_bytes = 0;
+ wg->get_load_mem_usage(&memtable_active_bytes, &memtable_queue_bytes,
+ &memtable_flush_bytes);
+ // TODO: should add a signal in memtable limiter to prevent new batch
+ // For example, streamload, it will not reserve many memory, but it will occupy many memtable memory.
+ // TODO: 0.2 should be a workload group properties. For example, the group is optimized for load,then the value
+ // should be larged, if the group is optimized for query, then the value should be smaller.
+ int64_t max_wg_memtable_bytes = wg->load_buffer_limit();
+ if (memtable_active_bytes + memtable_queue_bytes + memtable_flush_bytes >
+ max_wg_memtable_bytes) {
+ // There are many table in flush queue, just waiting them flush finished.
+ if (memtable_active_bytes < (int64_t)(max_wg_memtable_bytes * 0.6)) {
+ LOG_EVERY_T(INFO, 60)
+ << wg->name() << " load memtable size is: " << memtable_active_bytes
+ << ", " << memtable_queue_bytes << ", " << memtable_flush_bytes
+ << ", load buffer limit is: " << max_wg_memtable_bytes
+ << " wait for flush finished to release more memory";
+ continue;
+ } else {
+ // Flush some memtables(currently written) to flush queue.
+ memtable_limiter->flush_workload_group_memtables(
+ wg->id(),
+ memtable_active_bytes - (int64_t)(max_wg_memtable_bytes * 0.6));
+ LOG_EVERY_T(INFO, 60)
+ << wg->name() << " load memtable size is: " << memtable_active_bytes
+ << ", " << memtable_queue_bytes << ", " << memtable_flush_bytes
+ << ", flush some active memtable to revoke memory";
+ continue;
+ }
+ }
+ // Should not put the query back to task scheduler immediately, because when wg's memory not sufficient,
+ // and then set wg's flag, other query may not free memory very quickly.
+ if (query_it->elapsed_time() > TIMEOUT_IN_QUEUE) {
+ // set wg's memory to insufficent, then add it back to task scheduler to run.
+ LOG(INFO) << "query: " << print_id(query_ctx->query_id()) << " will be resume.";
+ query_ctx->set_memory_sufficient(true);
+ query_it = queries_list.erase(query_it);
+ } else {
+ ++query_it;
+ }
+ continue;
+ } else {
+ // PROCESS Reserve logic using hard limit, if reached here, should try to spill or cancel.
+ // GC Logic also work at hard limit, so GC may cancel some query and could not spill here.
+ // If wg's memlimit not exceed, but process memory exceed, it means cache or other metadata
+ // used too much memory. Should clean all cache here.
+ // 1. Check cache used, if cache is larger than > 0, then just return and wait for it to 0 to release some memory.
+ if (doris::GlobalMemoryArbitrator::last_affected_cache_capacity_adjust_weighted >
+ 0.001 &&
+ doris::GlobalMemoryArbitrator::last_wg_trigger_cache_capacity_adjust_weighted >
+ 0.001) {
+ doris::GlobalMemoryArbitrator::last_wg_trigger_cache_capacity_adjust_weighted =
+ 0;
+ doris::GlobalMemoryArbitrator::notify_cache_adjust_capacity();
+ LOG(INFO) << "There are some queries need process memory, so that set cache "
+ "capacity "
+ "to 0 now";
+ }
+ if (query_it->cache_ratio_ < 0.001) {
+ if (query_it->any_wg_exceed_limit_) {
+ if (wg->enable_memory_overcommit()) {
+ if (query_it->elapsed_time() > TIMEOUT_IN_QUEUE) {
+ resume_after_gc.push_back(query_ctx);
+ query_it = queries_list.erase(query_it);
+ continue;
+ } else {
+ ++query_it;
+ continue;
+ }
+ } else {
+ // current workload group is hard limit, should not wait other wg with
+ // soft limit, just cancel
+ resume_after_gc.push_back(query_ctx);
+ query_it = queries_list.erase(query_it);
+ continue;
+ }
+ } else {
+ // TODO: Find other exceed limit workload group and cancel query.
+ bool spill_res = handle_single_query(query_ctx, query_it->reserve_size_,
+ query_ctx->paused_reason());
+ if (!spill_res) {
+ ++query_it;
+ continue;
+ } else {
+ query_it = queries_list.erase(query_it);
+ continue;
+ }
+ }
+ }
+ if (doris::GlobalMemoryArbitrator::last_affected_cache_capacity_adjust_weighted <
+ 0.001 &&
+ query_it->cache_ratio_ > 0.001) {
+ LOG(INFO) << "query: " << print_id(query_ctx->query_id())
+ << " will be resume after cache adjust.";
+ query_ctx->set_memory_sufficient(true);
+ query_it = queries_list.erase(query_it);
+ continue;
+ }
+ ++query_it;
+ }
+ }
+
+ // Finished deal with one workload group, and should deal with next one.
+ ++it;
+ }
+ // TODO minor GC to release some query
+ if (!resume_after_gc.empty()) {
+ }
+ for (auto resume_it = resume_after_gc.begin(); resume_it != resume_after_gc.end();
+ ++resume_it) {
+ auto query_ctx = resume_it->lock();
+ if (query_ctx != nullptr) {
+ query_ctx->set_memory_sufficient(true);
+ }
+ }
+}
+
+// streamload, kafka routine load, group commit
+// insert into select
+// select
+
+void WorkloadGroupMgr::handle_overcommit_wg_paused_queries() {
+ std::shared_lock<std::shared_mutex> r_lock(_group_mutex);
+ // If there is only one workload group and it is overcommit, then do nothing.
+ // And should also start MinorGC logic.
+ if (_workload_groups.size() == 1) {
+ return;
+ }
+ // soft_limit - 10%, will change workload group to hard limit.
+ // soft limit, process memory reserve failed.
+ // hard limit, FullGC will kill query randomly.
+ if (doris::GlobalMemoryArbitrator::is_exceed_soft_mem_limit(
+ (int64_t)(MemInfo::mem_limit() * 0.1))) {
+ for (auto& [wg_id, wg] : _workload_groups) {
+ if (wg->enable_memory_overcommit() && !wg->has_changed_to_hard_limit()) {
+ wg->change_to_hard_limit(true);
+ LOG(INFO) << "Process memory usage + 10% will exceed soft limit, change all "
+ "workload "
+ "group with overcommit to hard limit now. "
+ << wg->debug_string();
+ }
+ }
+ }
+ // If current memory usage is below soft memlimit - 15%, then enable wg's overcommit
+ if (!doris::GlobalMemoryArbitrator::is_exceed_soft_mem_limit(
+ (int64_t)(MemInfo::mem_limit() * 0.15))) {
+ for (auto& [wg_id, wg] : _workload_groups) {
+ if (wg->enable_memory_overcommit() && wg->has_changed_to_hard_limit()) {
+ wg->change_to_hard_limit(false);
+ LOG(INFO) << "Process memory usage is lower than soft limit, enable all workload "
+ "group overcommit now. "
+ << wg->debug_string();
+ }
+ }
+ }
+}
+
+// If the query could release some memory, for example, spill disk, flush memtable then the return value is true.
+// If the query could not release memory, then cancel the query, the return value is true.
+// If the query is not ready to do these tasks, it means just wait.
+bool WorkloadGroupMgr::handle_single_query(std::shared_ptr<QueryContext> query_ctx,
+ size_t size_to_reserve, Status paused_reason) {
+ // TODO: If the query is an insert into select query, should consider memtable as revoke memory.
+ size_t revocable_size = 0;
+ size_t memory_usage = 0;
+ bool has_running_task = false;
+ const auto query_id = print_id(query_ctx->query_id());
+ query_ctx->get_revocable_info(&revocable_size, &memory_usage, &has_running_task);
+ if (has_running_task) {
+ LOG(INFO) << "query: " << print_id(query_ctx->query_id())
+ << " is paused, but still has running task, skip it.";
+ return false;
+ }
+
+ auto revocable_tasks = query_ctx->get_revocable_tasks();
+ if (revocable_tasks.empty()) {
+ if (paused_reason.is<ErrorCode::QUERY_MEMORY_EXCEEDED>()) {
+ const auto limit = query_ctx->get_mem_limit();
+ if ((memory_usage + size_to_reserve) < limit) {
+ LOG(INFO) << "query: " << query_id << ", usage(" << memory_usage << " + "
+ << size_to_reserve << ") less than limit(" << limit << "), resume it.";
+ query_ctx->set_memory_sufficient(true);
+ return true;
+ } else {
+ // Use MEM_LIMIT_EXCEEDED so that FE could parse the error code and do try logic
+ query_ctx->cancel(doris::Status::Error<ErrorCode::MEM_LIMIT_EXCEEDED>(
+ "query({}) reserve memory failed, but could not find memory that could "
+ "release or spill to disk(usage:{}, limit: {})",
+ query_id, memory_usage, query_ctx->get_mem_limit()));
+ }
+ } else {
+ if (!GlobalMemoryArbitrator::is_exceed_hard_mem_limit()) {
+ LOG(INFO) << "query: " << query_id
+ << ", process limit not exceeded now, resume this query"
+ << ", process memory info: "
+ << GlobalMemoryArbitrator::process_memory_used_details_str()
+ << ", wg info: " << query_ctx->workload_group()->memory_debug_string();
+ query_ctx->set_memory_sufficient(true);
+ return true;
+ }
+
+ LOG(INFO) << "query: " << query_id << ", process limit exceeded, info: "
+ << GlobalMemoryArbitrator::process_memory_used_details_str()
+ << ", wg info: " << query_ctx->workload_group()->memory_debug_string();
+ query_ctx->cancel(doris::Status::Error<ErrorCode::MEM_LIMIT_EXCEEDED>(
+ "The query({}) reserved memory failed because process limit exceeded, and "
+ "there is no cache now. And could not find task to spill. Maybe you should set "
+ "the workload group's limit to a lower value.",
+ query_id));
+ }
+ } else {
+ SCOPED_ATTACH_TASK(query_ctx.get());
+ RETURN_IF_ERROR(query_ctx->revoke_memory());
+ }
+ return true;
+}
+
+void WorkloadGroupMgr::update_queries_limit(WorkloadGroupPtr wg, bool enable_hard_limit) {
+ auto wg_mem_limit = wg->memory_limit();
+ auto wg_weighted_mem_limit = int64_t(wg_mem_limit * 1);
+ wg->set_weighted_memory_limit(wg_weighted_mem_limit);
+ auto all_query_ctxs = wg->queries();
+ bool is_low_wartermark = false;
+ bool is_high_wartermark = false;
+ wg->check_mem_used(&is_low_wartermark, &is_high_wartermark);
+ int64_t wg_high_water_mark_limit =
+ (int64_t)(wg_mem_limit * wg->spill_threshold_high_water_mark() * 1.0 / 100);
+ int64_t memtable_active_bytes = 0;
+ int64_t memtable_queue_bytes = 0;
+ int64_t memtable_flush_bytes = 0;
+ wg->get_load_mem_usage(&memtable_active_bytes, &memtable_queue_bytes, &memtable_flush_bytes);
+ int64_t memtable_usage = memtable_active_bytes + memtable_queue_bytes + memtable_flush_bytes;
+ int64_t wg_high_water_mark_except_load = wg_high_water_mark_limit;
+ if (memtable_usage > wg->load_buffer_limit()) {
+ wg_high_water_mark_except_load = wg_high_water_mark_limit - wg->load_buffer_limit();
+ } else {
+ wg_high_water_mark_except_load =
+ wg_high_water_mark_limit - memtable_usage - 10 * 1024 * 1024;
+ }
+ std::string debug_msg;
+ if (is_high_wartermark || is_low_wartermark) {
+ debug_msg = fmt::format(
+ "\nWorkload Group {}: mem limit: {}, mem used: {}, weighted mem limit: {}, "
+ "high water mark mem limit: {}, load memtable usage: {}, used ratio: {}",
+ wg->name(), PrettyPrinter::print(wg->memory_limit(), TUnit::BYTES),
+ PrettyPrinter::print(wg->total_mem_used(), TUnit::BYTES),
+ PrettyPrinter::print(wg_weighted_mem_limit, TUnit::BYTES),
+ PrettyPrinter::print(wg_high_water_mark_limit, TUnit::BYTES),
+ PrettyPrinter::print(memtable_usage, TUnit::BYTES),
+ (double)(wg->total_mem_used()) / wg_weighted_mem_limit);
+ }
+
+ // If the wg enable over commit memory, then it is no need to update query memlimit
+ if (wg->enable_memory_overcommit() && !wg->has_changed_to_hard_limit()) {
+ return;
+ }
+ int32_t total_used_slot_count = 0;
+ int32_t total_slot_count = wg->total_query_slot_count();
+ // calculate total used slot count
+ for (const auto& query : all_query_ctxs) {
+ auto query_ctx = query.second.lock();
+ if (!query_ctx) {
+ continue;
+ }
+ // Streamload kafka load group commit, not modify slot
+ if (!query_ctx->is_pure_load_task()) {
+ total_used_slot_count += query_ctx->get_slot_count();
+ }
+ }
+ // calculate per query weighted memory limit
+ debug_msg = "Query Memory Summary: \n";
+ for (const auto& query : all_query_ctxs) {
+ auto query_ctx = query.second.lock();
+ if (!query_ctx) {
+ continue;
+ }
+ int64_t query_weighted_mem_limit = 0;
+ int64_t expected_query_weighted_mem_limit = 0;
+ // If the query enable hard limit, then it should not use the soft limit
+ if (query_ctx->enable_query_slot_hard_limit()) {
+ if (total_slot_count < 1) {
+ LOG(WARNING)
+ << "query " << print_id(query_ctx->query_id())
+ << " enabled hard limit, but the slot count < 1, could not take affect";
+ } else {
+ // If the query enable hard limit, then not use weighted info any more, just use the settings limit.
+ query_weighted_mem_limit = (int64_t)((wg_high_water_mark_except_load *
+ query_ctx->get_slot_count() * 1.0) /
+ total_slot_count);
+ expected_query_weighted_mem_limit = query_weighted_mem_limit;
+ }
+ } else {
+ // If low water mark is not reached, then use process memory limit as query memory limit.
+ // It means it will not take effect.
+ // If there are some query in paused list, then limit should take effect.
+ expected_query_weighted_mem_limit =
+ total_used_slot_count > 0
+ ? (int64_t)((wg_high_water_mark_except_load + total_used_slot_count) *
+ query_ctx->get_slot_count() * 1.0 / total_used_slot_count)
+ : wg_high_water_mark_except_load;
+ if (!is_low_wartermark && !enable_hard_limit) {
+ query_weighted_mem_limit = wg_high_water_mark_except_load;
+ } else {
+ query_weighted_mem_limit = expected_query_weighted_mem_limit;
+ }
+ }
+ debug_msg += query_ctx->debug_string() + "\n";
+ // If the query is a pure load task, then should not modify its limit. Or it will reserve
+ // memory failed and we did not hanle it.
+ if (!query_ctx->is_pure_load_task()) {
+ query_ctx->set_mem_limit(query_weighted_mem_limit);
+ query_ctx->set_expected_mem_limit(expected_query_weighted_mem_limit);
+ }
+ }
+ LOG(INFO) << debug_msg;
+ //LOG_EVERY_T(INFO, 60) << debug_msg;
+}
+
void WorkloadGroupMgr::stop() {
for (auto iter = _workload_groups.begin(); iter != _workload_groups.end(); iter++) {
iter->second->try_stop_schedulers();
}
}
+void WorkloadGroupMgr::update_load_memtable_usage(
+ const std::map<uint64_t, MemtableUsage>& wg_memtable_usages) {
+ // Use readlock here, because it will not modify workload_groups
+ std::shared_lock<std::shared_mutex> r_lock(_group_mutex);
+ for (auto it = _workload_groups.begin(); it != _workload_groups.end(); ++it) {
+ auto wg_usage = wg_memtable_usages.find(it->first);
+ if (wg_usage != wg_memtable_usages.end()) {
+ it->second->update_load_mem_usage(wg_usage->second.active_mem_usage,
+ wg_usage->second.queue_mem_usage,
+ wg_usage->second.flush_mem_usage);
+ } else {
+ // Not anything in memtable limiter, then set to 0
+ it->second->update_load_mem_usage(0, 0, 0);
+ }
+ }
+}
+
} // namespace doris
diff --git a/be/src/runtime/workload_group/workload_group_manager.h b/be/src/runtime/workload_group/workload_group_manager.h
index f76e98d2606..c8e1ee7adf7 100644
--- a/be/src/runtime/workload_group/workload_group_manager.h
+++ b/be/src/runtime/workload_group/workload_group_manager.h
@@ -29,6 +29,7 @@ class CgroupCpuCtl;
namespace vectorized {
class Block;
+class QueryContext;
} // namespace vectorized
namespace pipeline {
@@ -36,6 +37,42 @@ class TaskScheduler;
class MultiCoreTaskQueue;
} // namespace pipeline
+class PausedQuery {
+public:
+ // Use weak ptr to save query ctx, to make sure if the query is cancelled
+ // the resource will be released
+ std::weak_ptr<QueryContext> query_ctx_;
+ std::chrono::system_clock::time_point enqueue_at;
+ size_t last_mem_usage {0};
+ double cache_ratio_ {0.0};
+ bool any_wg_exceed_limit_ {false};
+ int64_t reserve_size_ {0};
+
+ PausedQuery(std::shared_ptr<QueryContext> query_ctx, double cache_ratio,
+ bool any_wg_exceed_limit, int64_t reserve_size);
+
+ int64_t elapsed_time() const {
+ auto now = std::chrono::system_clock::now();
+ return std::chrono::duration_cast<std::chrono::milliseconds>(now - enqueue_at).count();
+ }
+
+ std::string query_id() const { return query_id_; }
+
+ bool operator<(const PausedQuery& other) const { return query_id_ < other.query_id_; }
+
+ bool operator==(const PausedQuery& other) const { return query_id_ == other.query_id_; }
+
+private:
+ std::string query_id_;
+};
+
+class MemtableUsage {
+public:
+ int64_t active_mem_usage = 0;
+ int64_t queue_mem_usage = 0;
+ int64_t flush_mem_usage = 0;
+};
+
class WorkloadGroupMgr {
public:
WorkloadGroupMgr() = default;
@@ -64,11 +101,29 @@ public:
void get_wg_resource_usage(vectorized::Block* block);
+ void add_paused_query(const std::shared_ptr<QueryContext>& query_ctx, int64_t reserve_size);
+
+ void handle_paused_queries();
+
+ void update_load_memtable_usage(const std::map<uint64_t, MemtableUsage>& wg_memtable_usages);
+
+private:
+ bool handle_single_query(std::shared_ptr<QueryContext> query_ctx, size_t size_to_reserve,
+ Status paused_reason);
+ void handle_non_overcommit_wg_paused_queries();
+ void handle_overcommit_wg_paused_queries();
+ void update_queries_limit(WorkloadGroupPtr wg, bool enable_hard_limit);
+
private:
std::shared_mutex _group_mutex;
std::unordered_map<uint64_t, WorkloadGroupPtr> _workload_groups;
std::shared_mutex _clear_cgroup_lock;
+
+ // Save per group paused query list, it should be a global structure, not per
+ // workload group, because we need do some coordinate work globally.
+ std::mutex _paused_queries_lock;
+ std::map<WorkloadGroupPtr, std::set<PausedQuery>> _paused_queries_list;
};
} // namespace doris
diff --git a/be/src/util/mem_info.cpp b/be/src/util/mem_info.cpp
index b1bcfdcc56b..f3e29ecbcdb 100644
--- a/be/src/util/mem_info.cpp
+++ b/be/src/util/mem_info.cpp
@@ -196,9 +196,9 @@ void MemInfo::refresh_proc_meminfo() {
_s_cgroup_mem_limit = std::numeric_limits<int64_t>::max();
// find cgroup limit failed, wait 300s, 1000 * 100ms.
_s_cgroup_mem_refresh_wait_times = -3000;
- LOG(INFO) << "Refresh cgroup memory limit failed, refresh again after 300s, cgroup "
- "mem limit: "
- << _s_cgroup_mem_limit;
+ LOG(INFO) << "Refresh cgroup memory success, refresh again after 10s, cgroup mem "
+ "limit: "
+ << _s_cgroup_mem_limit << ", cgroup mem usage: " << _s_cgroup_mem_usage;
} else {
// wait 10s, 100 * 100ms, avoid too frequently.
_s_cgroup_mem_refresh_wait_times = -100;
diff --git a/be/src/vec/columns/column.h b/be/src/vec/columns/column.h
index ec4b8585db3..86c66985c14 100644
--- a/be/src/vec/columns/column.h
+++ b/be/src/vec/columns/column.h
@@ -37,6 +37,7 @@
#include "olap/olap_common.h"
#include "runtime/define_primitive_type.h"
#include "vec/common/cow.h"
+#include "vec/common/custom_allocator.h"
#include "vec/common/pod_array_fwd.h"
#include "vec/common/string_ref.h"
#include "vec/common/typeid_cast.h"
@@ -336,14 +337,23 @@ public:
return 0;
}
- virtual void serialize_vec(std::vector<StringRef>& keys, size_t num_rows,
- size_t max_row_byte_size) const {
+ void serialize_vec(std::vector<StringRef>& keys, size_t num_rows,
+ size_t max_row_byte_size) const {
+ serialize_vec(keys.data(), num_rows, max_row_byte_size);
+ }
+
+ void serialize_vec_with_null_map(std::vector<StringRef>& keys, size_t num_rows,
+ const uint8_t* null_map) const {
+ serialize_vec_with_null_map(keys.data(), num_rows, null_map);
+ }
+
+ virtual void serialize_vec(StringRef* keys, size_t num_rows, size_t max_row_byte_size) const {
throw doris::Exception(ErrorCode::NOT_IMPLEMENTED_ERROR,
"Method serialize_vec is not supported for " + get_name());
__builtin_unreachable();
}
- virtual void serialize_vec_with_null_map(std::vector<StringRef>& keys, size_t num_rows,
+ virtual void serialize_vec_with_null_map(StringRef* keys, size_t num_rows,
const uint8_t* null_map) const {
throw doris::Exception(
ErrorCode::NOT_IMPLEMENTED_ERROR,
@@ -351,15 +361,24 @@ public:
__builtin_unreachable();
}
+ void deserialize_vec(std::vector<StringRef>& keys, const size_t num_rows) {
+ deserialize_vec(keys.data(), num_rows);
+ }
+
+ void deserialize_vec_with_null_map(std::vector<StringRef>& keys, const size_t num_rows,
+ const uint8_t* null_map) {
+ deserialize_vec_with_null_map(keys.data(), num_rows, null_map);
+ }
+
// This function deserializes group-by keys into column in the vectorized way.
- virtual void deserialize_vec(std::vector<StringRef>& keys, const size_t num_rows) {
+ virtual void deserialize_vec(StringRef* keys, const size_t num_rows) {
throw doris::Exception(ErrorCode::NOT_IMPLEMENTED_ERROR,
"Method deserialize_vec is not supported for " + get_name());
__builtin_unreachable();
}
// Used in ColumnNullable::deserialize_vec
- virtual void deserialize_vec_with_null_map(std::vector<StringRef>& keys, const size_t num_rows,
+ virtual void deserialize_vec_with_null_map(StringRef* keys, const size_t num_rows,
const uint8_t* null_map) {
throw doris::Exception(
ErrorCode::NOT_IMPLEMENTED_ERROR,
diff --git a/be/src/vec/columns/column_const.h b/be/src/vec/columns/column_const.h
index b88a2a980f8..6e4a2f3e431 100644
--- a/be/src/vec/columns/column_const.h
+++ b/be/src/vec/columns/column_const.h
@@ -177,8 +177,7 @@ public:
size_t get_max_row_byte_size() const override { return data->get_max_row_byte_size(); }
- void serialize_vec(std::vector<StringRef>& keys, size_t num_rows,
- size_t max_row_byte_size) const override {
+ void serialize_vec(StringRef* keys, size_t num_rows, size_t max_row_byte_size) const override {
data->serialize_vec(keys, num_rows, max_row_byte_size);
}
@@ -197,7 +196,7 @@ public:
get_data_column_ptr()->update_crc_with_value(start, end, hash, nullptr);
}
- void serialize_vec_with_null_map(std::vector<StringRef>& keys, size_t num_rows,
+ void serialize_vec_with_null_map(StringRef* keys, size_t num_rows,
const uint8_t* null_map) const override {
data->serialize_vec_with_null_map(keys, num_rows, null_map);
}
diff --git a/be/src/vec/columns/column_decimal.cpp b/be/src/vec/columns/column_decimal.cpp
index 2e5fc5e136a..9af67dac5ec 100644
--- a/be/src/vec/columns/column_decimal.cpp
+++ b/be/src/vec/columns/column_decimal.cpp
@@ -74,7 +74,7 @@ size_t ColumnDecimal<T>::get_max_row_byte_size() const {
}
template <typename T>
-void ColumnDecimal<T>::serialize_vec(std::vector<StringRef>& keys, size_t num_rows,
+void ColumnDecimal<T>::serialize_vec(StringRef* keys, size_t num_rows,
size_t max_row_byte_size) const {
for (size_t i = 0; i < num_rows; ++i) {
memcpy_fixed<T>(const_cast<char*>(keys[i].data + keys[i].size), (char*)&data[i]);
@@ -83,7 +83,7 @@ void ColumnDecimal<T>::serialize_vec(std::vector<StringRef>& keys, size_t num_ro
}
template <typename T>
-void ColumnDecimal<T>::serialize_vec_with_null_map(std::vector<StringRef>& keys, size_t num_rows,
+void ColumnDecimal<T>::serialize_vec_with_null_map(StringRef* keys, size_t num_rows,
const UInt8* null_map) const {
DCHECK(null_map != nullptr);
const bool has_null = simd::contain_byte(null_map, num_rows, 1);
@@ -111,7 +111,7 @@ void ColumnDecimal<T>::serialize_vec_with_null_map(std::vector<StringRef>& keys,
}
template <typename T>
-void ColumnDecimal<T>::deserialize_vec(std::vector<StringRef>& keys, const size_t num_rows) {
+void ColumnDecimal<T>::deserialize_vec(StringRef* keys, const size_t num_rows) {
for (size_t i = 0; i < num_rows; ++i) {
keys[i].data = deserialize_and_insert_from_arena(keys[i].data);
keys[i].size -= sizeof(T);
@@ -119,8 +119,7 @@ void ColumnDecimal<T>::deserialize_vec(std::vector<StringRef>& keys, const size_
}
template <typename T>
-void ColumnDecimal<T>::deserialize_vec_with_null_map(std::vector<StringRef>& keys,
- const size_t num_rows,
+void ColumnDecimal<T>::deserialize_vec_with_null_map(StringRef* keys, const size_t num_rows,
const uint8_t* null_map) {
for (size_t i = 0; i < num_rows; ++i) {
if (null_map[i] == 0) {
diff --git a/be/src/vec/columns/column_decimal.h b/be/src/vec/columns/column_decimal.h
index 0927cb88e15..30561005e04 100644
--- a/be/src/vec/columns/column_decimal.h
+++ b/be/src/vec/columns/column_decimal.h
@@ -165,15 +165,14 @@ public:
size_t get_max_row_byte_size() const override;
- void serialize_vec(std::vector<StringRef>& keys, size_t num_rows,
- size_t max_row_byte_size) const override;
+ void serialize_vec(StringRef* keys, size_t num_rows, size_t max_row_byte_size) const override;
- void serialize_vec_with_null_map(std::vector<StringRef>& keys, size_t num_rows,
+ void serialize_vec_with_null_map(StringRef* keys, size_t num_rows,
const uint8_t* null_map) const override;
- void deserialize_vec(std::vector<StringRef>& keys, const size_t num_rows) override;
+ void deserialize_vec(StringRef* keys, const size_t num_rows) override;
- void deserialize_vec_with_null_map(std::vector<StringRef>& keys, const size_t num_rows,
+ void deserialize_vec_with_null_map(StringRef* keys, const size_t num_rows,
const uint8_t* null_map) override;
void update_hash_with_value(size_t n, SipHash& hash) const override;
diff --git a/be/src/vec/columns/column_nullable.cpp b/be/src/vec/columns/column_nullable.cpp
index dbee5a2025a..38ca32ce291 100644
--- a/be/src/vec/columns/column_nullable.cpp
+++ b/be/src/vec/columns/column_nullable.cpp
@@ -246,13 +246,13 @@ size_t ColumnNullable::get_max_row_byte_size() const {
return flag_size + get_nested_column().get_max_row_byte_size();
}
-void ColumnNullable::serialize_vec(std::vector<StringRef>& keys, size_t num_rows,
+void ColumnNullable::serialize_vec(StringRef* keys, size_t num_rows,
size_t max_row_byte_size) const {
const auto& arr = get_null_map_data();
get_nested_column().serialize_vec_with_null_map(keys, num_rows, arr.data());
}
-void ColumnNullable::deserialize_vec(std::vector<StringRef>& keys, const size_t num_rows) {
+void ColumnNullable::deserialize_vec(StringRef* keys, const size_t num_rows) {
auto& arr = get_null_map_data();
const size_t old_size = arr.size();
arr.resize(old_size + num_rows);
diff --git a/be/src/vec/columns/column_nullable.h b/be/src/vec/columns/column_nullable.h
index 7772b6e80ad..c39036c2e3a 100644
--- a/be/src/vec/columns/column_nullable.h
+++ b/be/src/vec/columns/column_nullable.h
@@ -180,10 +180,10 @@ public:
StringRef serialize_value_into_arena(size_t n, Arena& arena, char const*& begin) const override;
const char* deserialize_and_insert_from_arena(const char* pos) override;
size_t get_max_row_byte_size() const override;
- void serialize_vec(std::vector<StringRef>& keys, size_t num_rows,
- size_t max_row_byte_size) const override;
- void deserialize_vec(std::vector<StringRef>& keys, size_t num_rows) override;
+ void serialize_vec(StringRef* keys, size_t num_rows, size_t max_row_byte_size) const override;
+
+ void deserialize_vec(StringRef* keys, size_t num_rows) override;
void insert_range_from(const IColumn& src, size_t start, size_t length) override;
diff --git a/be/src/vec/columns/column_object.h b/be/src/vec/columns/column_object.h
index 96a27e44e92..6e3c754fc32 100644
--- a/be/src/vec/columns/column_object.h
+++ b/be/src/vec/columns/column_object.h
@@ -518,24 +518,23 @@ public:
"get_max_row_byte_size" + std::string(get_family_name()));
}
- void serialize_vec(std::vector<StringRef>& keys, size_t num_rows,
- size_t max_row_byte_size) const override {
+ void serialize_vec(StringRef* keys, size_t num_rows, size_t max_row_byte_size) const override {
throw doris::Exception(ErrorCode::NOT_IMPLEMENTED_ERROR,
"serialize_vec" + std::string(get_family_name()));
}
- void serialize_vec_with_null_map(std::vector<StringRef>& keys, size_t num_rows,
+ void serialize_vec_with_null_map(StringRef* keys, size_t num_rows,
const uint8_t* null_map) const override {
throw doris::Exception(ErrorCode::NOT_IMPLEMENTED_ERROR,
"serialize_vec_with_null_map" + std::string(get_family_name()));
}
- void deserialize_vec(std::vector<StringRef>& keys, const size_t num_rows) override {
+ void deserialize_vec(StringRef* keys, const size_t num_rows) override {
throw doris::Exception(ErrorCode::NOT_IMPLEMENTED_ERROR,
"deserialize_vec" + std::string(get_family_name()));
}
- void deserialize_vec_with_null_map(std::vector<StringRef>& keys, const size_t num_rows,
+ void deserialize_vec_with_null_map(StringRef* keys, const size_t num_rows,
const uint8_t* null_map) override {
throw doris::Exception(ErrorCode::NOT_IMPLEMENTED_ERROR,
"deserialize_vec_with_null_map" + std::string(get_family_name()));
diff --git a/be/src/vec/columns/column_string.cpp b/be/src/vec/columns/column_string.cpp
index d8fd42e36c7..a6cf116b6db 100644
--- a/be/src/vec/columns/column_string.cpp
+++ b/be/src/vec/columns/column_string.cpp
@@ -383,8 +383,7 @@ size_t ColumnStr<T>::get_max_row_byte_size() const {
}
template <typename T>
-void ColumnStr<T>::serialize_vec(std::vector<StringRef>& keys, size_t num_rows,
- size_t max_row_byte_size) const {
+void ColumnStr<T>::serialize_vec(StringRef* keys, size_t num_rows, size_t max_row_byte_size) const {
for (size_t i = 0; i < num_rows; ++i) {
uint32_t offset(offset_at(i));
uint32_t string_size(size_at(i));
@@ -397,7 +396,7 @@ void ColumnStr<T>::serialize_vec(std::vector<StringRef>& keys, size_t num_rows,
}
template <typename T>
-void ColumnStr<T>::serialize_vec_with_null_map(std::vector<StringRef>& keys, size_t num_rows,
+void ColumnStr<T>::serialize_vec_with_null_map(StringRef* keys, size_t num_rows,
const UInt8* null_map) const {
DCHECK(null_map != nullptr);
@@ -438,7 +437,7 @@ void ColumnStr<T>::serialize_vec_with_null_map(std::vector<StringRef>& keys, siz
}
template <typename T>
-void ColumnStr<T>::deserialize_vec(std::vector<StringRef>& keys, const size_t num_rows) {
+void ColumnStr<T>::deserialize_vec(StringRef* keys, const size_t num_rows) {
for (size_t i = 0; i != num_rows; ++i) {
auto original_ptr = keys[i].data;
keys[i].data = deserialize_and_insert_from_arena(original_ptr);
@@ -447,8 +446,8 @@ void ColumnStr<T>::deserialize_vec(std::vector<StringRef>& keys, const size_t nu
}
template <typename T>
-void ColumnStr<T>::deserialize_vec_with_null_map(std::vector<StringRef>& keys,
- const size_t num_rows, const uint8_t* null_map) {
+void ColumnStr<T>::deserialize_vec_with_null_map(StringRef* keys, const size_t num_rows,
+ const uint8_t* null_map) {
for (size_t i = 0; i != num_rows; ++i) {
if (null_map[i] == 0) {
auto original_ptr = keys[i].data;
diff --git a/be/src/vec/columns/column_string.h b/be/src/vec/columns/column_string.h
index b441b81613b..9f3771bbe96 100644
--- a/be/src/vec/columns/column_string.h
+++ b/be/src/vec/columns/column_string.h
@@ -387,17 +387,16 @@ public:
const char* deserialize_and_insert_from_arena(const char* pos) override;
- void deserialize_vec(std::vector<StringRef>& keys, const size_t num_rows) override;
+ void deserialize_vec(StringRef* keys, const size_t num_rows) override;
size_t get_max_row_byte_size() const override;
- void serialize_vec(std::vector<StringRef>& keys, size_t num_rows,
- size_t max_row_byte_size) const override;
+ void serialize_vec(StringRef* keys, size_t num_rows, size_t max_row_byte_size) const override;
- void serialize_vec_with_null_map(std::vector<StringRef>& keys, size_t num_rows,
+ void serialize_vec_with_null_map(StringRef* keys, size_t num_rows,
const uint8_t* null_map) const override;
- void deserialize_vec_with_null_map(std::vector<StringRef>& keys, const size_t num_rows,
+ void deserialize_vec_with_null_map(StringRef* keys, const size_t num_rows,
const uint8_t* null_map) override;
void update_xxHash_with_value(size_t start, size_t end, uint64_t& hash,
diff --git a/be/src/vec/columns/column_vector.cpp b/be/src/vec/columns/column_vector.cpp
index 300152b5784..4382fa62851 100644
--- a/be/src/vec/columns/column_vector.cpp
+++ b/be/src/vec/columns/column_vector.cpp
@@ -63,7 +63,7 @@ size_t ColumnVector<T>::get_max_row_byte_size() const {
}
template <typename T>
-void ColumnVector<T>::serialize_vec(std::vector<StringRef>& keys, size_t num_rows,
+void ColumnVector<T>::serialize_vec(StringRef* keys, size_t num_rows,
size_t max_row_byte_size) const {
for (size_t i = 0; i < num_rows; ++i) {
memcpy_fixed<T>(const_cast<char*>(keys[i].data + keys[i].size), (char*)&data[i]);
@@ -72,7 +72,7 @@ void ColumnVector<T>::serialize_vec(std::vector<StringRef>& keys, size_t num_row
}
template <typename T>
-void ColumnVector<T>::serialize_vec_with_null_map(std::vector<StringRef>& keys, size_t num_rows,
+void ColumnVector<T>::serialize_vec_with_null_map(StringRef* keys, size_t num_rows,
const UInt8* null_map) const {
DCHECK(null_map != nullptr);
@@ -102,7 +102,7 @@ void ColumnVector<T>::serialize_vec_with_null_map(std::vector<StringRef>& keys,
}
template <typename T>
-void ColumnVector<T>::deserialize_vec(std::vector<StringRef>& keys, const size_t num_rows) {
+void ColumnVector<T>::deserialize_vec(StringRef* keys, const size_t num_rows) {
for (size_t i = 0; i != num_rows; ++i) {
keys[i].data = deserialize_and_insert_from_arena(keys[i].data);
keys[i].size -= sizeof(T);
@@ -110,8 +110,7 @@ void ColumnVector<T>::deserialize_vec(std::vector<StringRef>& keys, const size_t
}
template <typename T>
-void ColumnVector<T>::deserialize_vec_with_null_map(std::vector<StringRef>& keys,
- const size_t num_rows,
+void ColumnVector<T>::deserialize_vec_with_null_map(StringRef* keys, const size_t num_rows,
const uint8_t* null_map) {
for (size_t i = 0; i < num_rows; ++i) {
if (null_map[i] == 0) {
diff --git a/be/src/vec/columns/column_vector.h b/be/src/vec/columns/column_vector.h
index 015595797b9..a6e329b9522 100644
--- a/be/src/vec/columns/column_vector.h
+++ b/be/src/vec/columns/column_vector.h
@@ -247,17 +247,16 @@ public:
const char* deserialize_and_insert_from_arena(const char* pos) override;
- void deserialize_vec(std::vector<StringRef>& keys, const size_t num_rows) override;
+ void deserialize_vec(StringRef* keys, const size_t num_rows) override;
- void deserialize_vec_with_null_map(std::vector<StringRef>& keys, const size_t num_rows,
+ void deserialize_vec_with_null_map(StringRef* keys, const size_t num_rows,
const uint8_t* null_map) override;
size_t get_max_row_byte_size() const override;
- void serialize_vec(std::vector<StringRef>& keys, size_t num_rows,
- size_t max_row_byte_size) const override;
+ void serialize_vec(StringRef* keys, size_t num_rows, size_t max_row_byte_size) const override;
- void serialize_vec_with_null_map(std::vector<StringRef>& keys, size_t num_rows,
+ void serialize_vec_with_null_map(StringRef* keys, size_t num_rows,
const uint8_t* null_map) const override;
void update_xxHash_with_value(size_t start, size_t end, uint64_t& hash,
diff --git a/be/src/vec/common/custom_allocator.h b/be/src/vec/common/custom_allocator.h
index eee800a059d..6361a60689c 100644
--- a/be/src/vec/common/custom_allocator.h
+++ b/be/src/vec/common/custom_allocator.h
@@ -20,7 +20,7 @@
#include "vec/common/allocator.h"
#include "vec/common/allocator_fwd.h"
-template <class T, typename MemoryAllocator = Allocator<true>>
+template <class T, typename MemoryAllocator = Allocator<false>>
class CustomStdAllocator;
template <typename T>
diff --git a/be/src/vec/common/hash_table/hash_map_context.h b/be/src/vec/common/hash_table/hash_map_context.h
index 0df0c8997f0..2d0b46150b1 100644
--- a/be/src/vec/common/hash_table/hash_map_context.h
+++ b/be/src/vec/common/hash_table/hash_map_context.h
@@ -56,10 +56,10 @@ struct MethodBaseInner {
bool inited_iterator = false;
Key* keys = nullptr;
Arena arena;
- std::vector<size_t> hash_values;
+ DorisVector<size_t> hash_values;
// use in join case
- std::vector<uint32_t> bucket_nums;
+ DorisVector<uint32_t> bucket_nums;
MethodBaseInner() { hash_table.reset(new HashMap()); }
virtual ~MethodBaseInner() = default;
@@ -198,10 +198,10 @@ struct MethodSerialized : public MethodBase<TData> {
using State = ColumnsHashing::HashMethodSerialized<typename Base::Value, typename Base::Mapped>;
using Base::try_presis_key;
// need keep until the hash probe end.
- std::vector<StringRef> build_stored_keys;
+ DorisVector<StringRef> build_stored_keys;
Arena build_arena;
// refresh each time probe
- std::vector<StringRef> stored_keys;
+ DorisVector<StringRef> stored_keys;
StringRef serialize_keys_to_pool_contiguous(size_t i, size_t keys_size,
const ColumnRawPtrs& key_columns, Arena& pool) {
@@ -216,7 +216,7 @@ struct MethodSerialized : public MethodBase<TData> {
}
void init_serialized_keys_impl(const ColumnRawPtrs& key_columns, size_t num_rows,
- std::vector<StringRef>& input_keys, Arena& input_arena) {
+ DorisVector<StringRef>& input_keys, Arena& input_arena) {
input_arena.clear();
input_keys.resize(num_rows);
@@ -243,14 +243,18 @@ struct MethodSerialized : public MethodBase<TData> {
}
for (const auto& column : key_columns) {
- column->serialize_vec(input_keys, num_rows, max_one_row_byte_size);
+ column->serialize_vec(input_keys.data(), num_rows, max_one_row_byte_size);
}
}
Base::keys = input_keys.data();
}
size_t serialized_keys_size(bool is_build) const override {
- return is_build ? build_arena.size() : Base::arena.size();
+ if (is_build) {
+ return build_stored_keys.size() * sizeof(StringRef) + build_arena.size();
+ } else {
+ return stored_keys.size() * sizeof(StringRef) + Base::arena.size();
+ }
}
void init_serialized_keys(const ColumnRawPtrs& key_columns, size_t num_rows,
@@ -286,9 +290,9 @@ struct MethodStringNoCache : public MethodBase<TData> {
ColumnsHashing::HashMethodString<typename Base::Value, typename Base::Mapped, true>;
// need keep until the hash probe end.
- std::vector<StringRef> _build_stored_keys;
+ DorisVector<StringRef> _build_stored_keys;
// refresh each time probe
- std::vector<StringRef> _stored_keys;
+ DorisVector<StringRef> _stored_keys;
size_t serialized_keys_size(bool is_build) const override {
return is_build ? (_build_stored_keys.size() * sizeof(StringRef))
@@ -296,13 +300,13 @@ struct MethodStringNoCache : public MethodBase<TData> {
}
void init_serialized_keys_impl(const ColumnRawPtrs& key_columns, size_t num_rows,
- std::vector<StringRef>& stored_keys) {
+ DorisVector<StringRef>& stored_keys) {
const IColumn& column = *key_columns[0];
const auto& nested_column =
column.is_nullable()
? assert_cast<const ColumnNullable&>(column).get_nested_column()
: column;
- auto serialized_str = [](const auto& column_string, std::vector<StringRef>& stored_keys) {
+ auto serialized_str = [](const auto& column_string, DorisVector<StringRef>& stored_keys) {
const auto& offsets = column_string.get_offsets();
const auto* chars = column_string.get_chars().data();
stored_keys.resize(column_string.size());
@@ -388,16 +392,16 @@ struct MethodKeysFixed : public MethodBase<TData> {
has_nullable_keys>;
// need keep until the hash probe end. use only in join
- std::vector<Key> build_stored_keys;
+ DorisVector<Key> build_stored_keys;
// refresh each time probe hash table
- std::vector<Key> stored_keys;
+ DorisVector<Key> stored_keys;
Sizes key_sizes;
MethodKeysFixed(Sizes key_sizes_) : key_sizes(std::move(key_sizes_)) {}
template <typename T>
void pack_fixeds(size_t row_numbers, const ColumnRawPtrs& key_columns,
- const ColumnRawPtrs& nullmap_columns, std::vector<T>& result) {
+ const ColumnRawPtrs& nullmap_columns, DorisVector<T>& result) {
size_t bitmap_size = get_bitmap_size(nullmap_columns.size());
// set size to 0 at first, then use resize to call default constructor on index included from [0, row_numbers) to reset all memory
result.clear();
diff --git a/be/src/vec/common/hash_table/hash_table.h b/be/src/vec/common/hash_table/hash_table.h
index 490cd501692..ea912da32f1 100644
--- a/be/src/vec/common/hash_table/hash_table.h
+++ b/be/src/vec/common/hash_table/hash_table.h
@@ -807,6 +807,18 @@ public:
}
}
+ size_t estimate_memory(size_t num_elem) const {
+ if (!add_elem_size_overflow(num_elem)) {
+ return 0;
+ }
+
+ auto new_size = num_elem + grower.buf_size();
+ Grower new_grower = grower;
+ new_grower.set(new_size);
+
+ return new_grower.buf_size() * sizeof(Cell);
+ }
+
/// Insert a value. In the case of any more complex values, it is better to use the `emplace` function.
std::pair<LookupResult, bool> ALWAYS_INLINE insert(const value_type& x) {
std::pair<LookupResult, bool> res;
diff --git a/be/src/vec/common/hash_table/join_hash_table.h b/be/src/vec/common/hash_table/join_hash_table.h
index 317987541cd..d95512e275c 100644
--- a/be/src/vec/common/hash_table/join_hash_table.h
+++ b/be/src/vec/common/hash_table/join_hash_table.h
@@ -22,6 +22,7 @@
#include <limits>
#include "vec/columns/column_filter_helper.h"
+#include "vec/common/custom_allocator.h"
#include "vec/common/hash_table/hash.h"
#include "vec/common/hash_table/hash_table.h"
#include "vec/common/hash_table/hash_table_allocator.h"
@@ -69,7 +70,7 @@ public:
size_t size() const { return next.size(); }
- std::vector<uint8_t>& get_visited() { return visited; }
+ DorisVector<uint8_t>& get_visited() { return visited; }
template <int JoinOpType, bool with_other_conjuncts>
void build(const Key* __restrict keys, const uint32_t* __restrict bucket_nums,
@@ -255,7 +256,7 @@ public:
bool has_null_key() { return _has_null_key; }
- void pre_build_idxs(std::vector<uint32>& buckets, const uint8_t* null_map) const {
+ void pre_build_idxs(DorisVector<uint32>& buckets, const uint8_t* null_map) const {
if (null_map) {
for (unsigned int& bucket : buckets) {
bucket = bucket == bucket_size ? bucket_size : first[bucket];
@@ -454,13 +455,13 @@ private:
}
const Key* __restrict build_keys;
- std::vector<uint8_t> visited;
+ DorisVector<uint8_t> visited;
uint32_t bucket_size = 1;
int max_batch_size = 4064;
- std::vector<uint32_t> first = {0};
- std::vector<uint32_t> next = {0};
+ DorisVector<uint32_t> first = {0};
+ DorisVector<uint32_t> next = {0};
// use in iter hash map
mutable uint32_t iter_idx = 1;
diff --git a/be/src/vec/common/hash_table/ph_hash_map.h b/be/src/vec/common/hash_table/ph_hash_map.h
index 8f61ee966c5..10896c73a77 100644
--- a/be/src/vec/common/hash_table/ph_hash_map.h
+++ b/be/src/vec/common/hash_table/ph_hash_map.h
@@ -219,6 +219,15 @@ public:
return (_hash_map.size() + row) > (capacity * 7 / 8);
}
+ size_t estimate_memory(size_t num_elem) const {
+ if (!add_elem_size_overflow(num_elem)) {
+ return 0;
+ }
+
+ auto new_size = _hash_map.capacity() * 2 + 1;
+ return new_size * sizeof(typename HashMapImpl::slot_type);
+ }
+
size_t size() const { return _hash_map.size(); }
template <typename MappedType>
char* get_null_key_data() {
diff --git a/be/src/vec/common/hash_table/string_hash_table.h b/be/src/vec/common/hash_table/string_hash_table.h
index 74be1e85e1e..8ae1f235613 100644
--- a/be/src/vec/common/hash_table/string_hash_table.h
+++ b/be/src/vec/common/hash_table/string_hash_table.h
@@ -679,4 +679,30 @@ public:
m3.add_elem_size_overflow(add_size) || m4.add_elem_size_overflow(add_size) ||
ms.add_elem_size_overflow(add_size);
}
+
+ size_t estimate_memory(size_t num_elem) const {
+ size_t estimate_size = 0;
+
+ if (m1.add_elem_size_overflow(num_elem)) {
+ estimate_size = m1.estimate_memory(num_elem);
+ }
+
+ if (m2.add_elem_size_overflow(num_elem)) {
+ estimate_size = std::max(estimate_size, m2.estimate_memory(num_elem));
+ }
+
+ if (m3.add_elem_size_overflow(num_elem)) {
+ estimate_size = std::max(estimate_size, m3.estimate_memory(num_elem));
+ }
+
+ if (m4.add_elem_size_overflow(num_elem)) {
+ estimate_size = std::max(estimate_size, m4.estimate_memory(num_elem));
+ }
+
+ if (ms.add_elem_size_overflow(num_elem)) {
+ estimate_size = std::max(estimate_size, ms.estimate_memory(num_elem));
+ }
+
+ return estimate_size;
+ }
};
diff --git a/be/src/vec/exec/scan/scanner_context.cpp b/be/src/vec/exec/scan/scanner_context.cpp
index ee1d60d2902..db979d4c9c5 100644
--- a/be/src/vec/exec/scan/scanner_context.cpp
+++ b/be/src/vec/exec/scan/scanner_context.cpp
@@ -332,7 +332,10 @@ Status ScannerContext::get_block_from_queue(RuntimeState* state, vectorized::Blo
update_peak_memory_usage(-current_block->allocated_bytes());
// consume current block
block->swap(*current_block);
- return_free_block(std::move(current_block));
+ // If under low memory mode, should not return the freeblock, it will occupy too memory.
+ if (!_local_state->low_memory_mode()) {
+ return_free_block(std::move(current_block));
+ }
} else {
// This scan task do not have any cached blocks.
_blocks_queue.pop_front();
@@ -341,37 +344,54 @@ Status ScannerContext::get_block_from_queue(RuntimeState* state, vectorized::Blo
_num_finished_scanners++;
std::weak_ptr<ScannerDelegate> next_scanner;
// submit one of the remaining scanners
- if (_scanners.try_dequeue(next_scanner)) {
- auto submit_status = submit_scan_task(std::make_shared<ScanTask>(next_scanner));
- if (!submit_status.ok()) {
- _process_status = submit_status;
- _set_scanner_done();
- return _process_status;
- }
- } else {
- // no more scanner to be scheduled
- // `_free_blocks` serve all running scanners, maybe it's too large for the remaining scanners
- int free_blocks_for_each = _free_blocks.size_approx() / _num_running_scanners;
+ // If under low memory mode, then there should be at most 4 scanner running
+ if (_num_running_scanners > low_memory_mode_scanners() &&
+ _local_state->low_memory_mode()) {
_num_running_scanners--;
- for (int i = 0; i < free_blocks_for_each; ++i) {
- vectorized::BlockUPtr removed_block;
- if (_free_blocks.try_dequeue(removed_block)) {
- _block_memory_usage -= block->allocated_bytes();
+ } else {
+ if (_scanners.try_dequeue(next_scanner)) {
+ auto submit_status =
+ submit_scan_task(std::make_shared<ScanTask>(next_scanner));
+ if (!submit_status.ok()) {
+ _process_status = submit_status;
+ _set_scanner_done();
+ return _process_status;
+ }
+ } else {
+ // no more scanner to be scheduled
+ // `_free_blocks` serve all running scanners, maybe it's too large for the remaining scanners
+ int free_blocks_for_each =
+ _free_blocks.size_approx() / _num_running_scanners;
+ _num_running_scanners--;
+ for (int i = 0; i < free_blocks_for_each; ++i) {
+ vectorized::BlockUPtr removed_block;
+ if (_free_blocks.try_dequeue(removed_block)) {
+ _block_memory_usage -= block->allocated_bytes();
+ }
}
}
}
} else {
- // resubmit current running scanner to read the next block
- Status submit_status = submit_scan_task(scan_task);
- if (!submit_status.ok()) {
- _process_status = submit_status;
- _set_scanner_done();
- return _process_status;
+ if (_local_state->low_memory_mode() &&
+ _num_running_scanners > low_memory_mode_scanners()) {
+ _num_running_scanners--;
+ // push the scanner to the stack since it is not eos
+ _scanners.enqueue(scan_task->scanner);
+ } else {
+ // resubmit current running scanner to read the next block
+ Status submit_status = submit_scan_task(scan_task);
+ if (!submit_status.ok()) {
+ _process_status = submit_status;
+ _set_scanner_done();
+ return _process_status;
+ }
}
}
}
- // scale up
- RETURN_IF_ERROR(_try_to_scale_up());
+ if (!_local_state->low_memory_mode()) {
+ // scale up
+ RETURN_IF_ERROR(_try_to_scale_up());
+ }
}
if (_num_finished_scanners == _all_scanners.size() && _blocks_queue.empty()) {
@@ -554,4 +574,8 @@ void ScannerContext::update_peak_memory_usage(int64_t usage) {
_local_state->_scanner_peak_memory_usage->add(usage);
}
+bool ScannerContext::low_memory_mode() const {
+ return _local_state->low_memory_mode();
+}
+
} // namespace doris::vectorized
diff --git a/be/src/vec/exec/scan/scanner_context.h b/be/src/vec/exec/scan/scanner_context.h
index 85669765df8..36f590af38f 100644
--- a/be/src/vec/exec/scan/scanner_context.h
+++ b/be/src/vec/exec/scan/scanner_context.h
@@ -126,6 +126,8 @@ public:
void return_free_block(vectorized::BlockUPtr block);
inline void inc_block_usage(size_t usage) { _block_memory_usage += usage; }
+ int64_t block_memory_usage() { return _block_memory_usage; }
+
// Caller should make sure the pipeline task is still running when calling this function
void update_peak_running_scanner(int num);
// Caller should make sure the pipeline task is still running when calling this function
@@ -170,6 +172,17 @@ public:
int batch_size() const { return _batch_size; }
+ // During low memory mode, there will be at most 4 scanners running and every scanner will
+ // cache at most 1MB data. So that every instance will keep 8MB buffer.
+ bool low_memory_mode() const;
+
+ // TODO(yiguolei) add this as session variable
+ int32_t low_memory_mode_scan_bytes_per_scanner() const {
+ return 1 * 1024 * 1024; // 1MB
+ }
+
+ int32_t low_memory_mode_scanners() const { return 4; }
+
// the unique id of this context
std::string ctx_id;
TUniqueId _query_id;
@@ -215,7 +228,7 @@ protected:
moodycamel::ConcurrentQueue<std::weak_ptr<ScannerDelegate>> _scanners;
int32_t _num_scheduled_scanners = 0;
int32_t _num_finished_scanners = 0;
- int32_t _num_running_scanners = 0;
+ std::atomic_int _num_running_scanners = 0;
// weak pointer for _scanners, used in stop function
std::vector<std::weak_ptr<ScannerDelegate>> _all_scanners;
std::shared_ptr<RuntimeProfile> _scanner_profile;
diff --git a/be/src/vec/exec/scan/scanner_scheduler.cpp b/be/src/vec/exec/scan/scanner_scheduler.cpp
index 3ad4e758e79..063d4fd51c2 100644
--- a/be/src/vec/exec/scan/scanner_scheduler.cpp
+++ b/be/src/vec/exec/scan/scanner_scheduler.cpp
@@ -271,8 +271,21 @@ void ScannerScheduler::_scanner_scan(std::shared_ptr<ScannerContext> ctx,
}
size_t raw_bytes_threshold = config::doris_scanner_row_bytes;
- size_t raw_bytes_read = 0; bool first_read = true;
- while (!eos && raw_bytes_read < raw_bytes_threshold) {
+ if (ctx->low_memory_mode() &&
+ raw_bytes_threshold > ctx->low_memory_mode_scan_bytes_per_scanner()) {
+ raw_bytes_threshold = ctx->low_memory_mode_scan_bytes_per_scanner();
+ }
+
+ size_t raw_bytes_read = 0;
+ bool first_read = true;
+ // If the first block is full, then it is true. Or the first block + second block > batch_size
+ bool has_first_full_block = false;
+
+ // During low memory mode, every scan task will return at most 2 block to reduce memory usage.
+ while (!eos && raw_bytes_read < raw_bytes_threshold &&
+ !(ctx->low_memory_mode() && has_first_full_block) &&
+ !(has_first_full_block &&
+ doris::thread_context()->thread_mem_tracker()->limit_exceeded())) {
if (UNLIKELY(ctx->done())) {
eos = true;
break;
@@ -322,6 +335,9 @@ void ScannerScheduler::_scanner_scan(std::shared_ptr<ScannerContext> ctx,
ctx->inc_block_usage(scan_task->cached_blocks.back().first->allocated_bytes() -
block_size);
} else {
+ if (!scan_task->cached_blocks.empty()) {
+ has_first_full_block = true;
+ }
ctx->inc_block_usage(free_block->allocated_bytes());
scan_task->cached_blocks.emplace_back(std::move(free_block), free_block_bytes);
}
diff --git a/be/src/vec/exprs/vectorized_fn_call.cpp b/be/src/vec/exprs/vectorized_fn_call.cpp
index 65d4230488a..9ff45c5dad9 100644
--- a/be/src/vec/exprs/vectorized_fn_call.cpp
+++ b/be/src/vec/exprs/vectorized_fn_call.cpp
@@ -189,6 +189,24 @@ Status VectorizedFnCall::_do_execute(doris::vectorized::VExprContext* context,
return Status::OK();
}
+size_t VectorizedFnCall::estimate_memory(const size_t rows) {
+ if (is_const_and_have_executed()) { // const have execute in open function
+ return 0;
+ }
+
+ size_t estimate_size = 0;
+ for (auto& child : _children) {
+ estimate_size += child->estimate_memory(rows);
+ }
+
+ if (_data_type->have_maximum_size_of_value()) {
+ estimate_size += rows * _data_type->get_maximum_size_of_value_in_memory();
+ } else {
+ estimate_size += rows * 512; /// FIXME: estimated value...
+ }
+ return estimate_size;
+}
+
Status VectorizedFnCall::execute_runtime_fitler(doris::vectorized::VExprContext* context,
doris::vectorized::Block* block,
int* result_column_id, std::vector<size_t>& args) {
diff --git a/be/src/vec/exprs/vectorized_fn_call.h b/be/src/vec/exprs/vectorized_fn_call.h
index bae996136dd..b78b27e9764 100644
--- a/be/src/vec/exprs/vectorized_fn_call.h
+++ b/be/src/vec/exprs/vectorized_fn_call.h
@@ -72,6 +72,8 @@ public:
bool can_push_down_to_index() const override;
bool equals(const VExpr& other) override;
+ size_t estimate_memory(const size_t rows) override;
+
protected:
FunctionBasePtr _function;
std::string _expr_name;
diff --git a/be/src/vec/exprs/vexpr.cpp b/be/src/vec/exprs/vexpr.cpp
index da496cdd9f0..be869e4cc8f 100644
--- a/be/src/vec/exprs/vexpr.cpp
+++ b/be/src/vec/exprs/vexpr.cpp
@@ -731,6 +731,24 @@ Status VExpr::_evaluate_inverted_index(VExprContext* context, const FunctionBase
return Status::OK();
}
+size_t VExpr::estimate_memory(const size_t rows) {
+ if (is_const_and_have_executed()) {
+ return 0;
+ }
+
+ size_t estimate_size = 0;
+ for (auto& child : _children) {
+ estimate_size += child->estimate_memory(rows);
+ }
+
+ if (_data_type->have_maximum_size_of_value()) {
+ estimate_size += rows * _data_type->get_maximum_size_of_value_in_memory();
+ } else {
+ estimate_size += rows * 64; /// TODO: need a more reasonable value
+ }
+ return estimate_size;
+}
+
bool VExpr::fast_execute(doris::vectorized::VExprContext* context, doris::vectorized::Block* block,
int* result_column_id) {
if (context->get_inverted_index_context() &&
diff --git a/be/src/vec/exprs/vexpr.h b/be/src/vec/exprs/vexpr.h
index 32ed4c84300..ed1c23f2fc4 100644
--- a/be/src/vec/exprs/vexpr.h
+++ b/be/src/vec/exprs/vexpr.h
@@ -126,6 +126,8 @@ public:
Status _evaluate_inverted_index(VExprContext* context, const FunctionBasePtr& function,
uint32_t segment_num_rows);
+ virtual size_t estimate_memory(const size_t rows);
+
// Only the 4th parameter is used in the runtime filter. In and MinMax need overwrite the
// interface
virtual Status execute_runtime_fitler(VExprContext* context, Block* block,
diff --git a/be/src/vec/exprs/vexpr_context.cpp b/be/src/vec/exprs/vexpr_context.cpp
index 0afb1de51de..beb23336f0f 100644
--- a/be/src/vec/exprs/vexpr_context.cpp
+++ b/be/src/vec/exprs/vexpr_context.cpp
@@ -17,6 +17,7 @@
#include "vec/exprs/vexpr_context.h"
+#include <algorithm>
#include <ostream>
#include <string>
@@ -136,7 +137,9 @@ Status VExprContext::filter_block(VExprContext* vexpr_ctx, Block* block, size_t
return Status::OK();
}
int result_column_id = -1;
+ size_t origin_size = block->allocated_bytes();
RETURN_IF_ERROR(vexpr_ctx->execute(block, &result_column_id));
+ vexpr_ctx->_memory_usage = (block->allocated_bytes() - origin_size);
return Block::filter_block(block, result_column_id, column_to_keep);
}
@@ -283,14 +286,14 @@ Status VExprContext::execute_conjuncts(const VExprContextSPtrs& conjuncts, Block
for (const auto& conjunct : conjuncts) {
int result_column_id = -1;
RETURN_IF_ERROR(conjunct->execute(block, &result_column_id));
- auto& filter_column =
+ const auto& filter_column =
unpack_if_const(block->get_by_position(result_column_id).column).first;
- if (auto* nullable_column = check_and_get_column<ColumnNullable>(*filter_column)) {
+ if (const auto* nullable_column = check_and_get_column<ColumnNullable>(*filter_column)) {
const ColumnPtr& nested_column = nullable_column->get_nested_column_ptr();
const IColumn::Filter& result =
assert_cast<const ColumnUInt8&>(*nested_column).get_data();
- auto* __restrict filter_data = result.data();
- auto* __restrict null_map_data = nullable_column->get_null_map_data().data();
+ const auto* __restrict filter_data = result.data();
+ const auto* __restrict null_map_data = nullable_column->get_null_map_data().data();
DCHECK_EQ(rows, nullable_column->size());
for (size_t i = 0; i != rows; ++i) {
@@ -302,7 +305,8 @@ Status VExprContext::execute_conjuncts(const VExprContextSPtrs& conjuncts, Block
final_filter_ptr[i] = final_filter_ptr[i] & filter_data[i];
}
} else {
- auto* filter_data = assert_cast<const ColumnUInt8&>(*filter_column).get_data().data();
+ const auto* filter_data =
+ assert_cast<const ColumnUInt8&>(*filter_column).get_data().data();
for (size_t i = 0; i != rows; ++i) {
final_filter_ptr[i] = final_filter_ptr[i] & filter_data[i];
}
@@ -318,11 +322,19 @@ Status VExprContext::execute_conjuncts_and_filter_block(const VExprContextSPtrs&
int column_to_keep) {
IColumn::Filter result_filter(block->rows(), 1);
bool can_filter_all;
+
+ _reset_memory_usage(ctxs);
+
RETURN_IF_ERROR(
execute_conjuncts(ctxs, nullptr, false, block, &result_filter, &can_filter_all));
+
+ // Accumulate the usage of `result_filter` into the first context.
+ if (!ctxs.empty()) {
+ ctxs[0]->_memory_usage += result_filter.allocated_bytes();
+ }
if (can_filter_all) {
for (auto& col : columns_to_filter) {
- std::move(*block->get_by_position(col).column).assume_mutable()->clear();
+ block->get_by_position(col).column->assume_mutable()->clear();
}
} else {
try {
@@ -349,12 +361,18 @@ Status VExprContext::execute_conjuncts_and_filter_block(const VExprContextSPtrs&
std::vector<uint32_t>& columns_to_filter,
int column_to_keep,
IColumn::Filter& filter) {
+ _reset_memory_usage(ctxs);
filter.resize_fill(block->rows(), 1);
bool can_filter_all;
RETURN_IF_ERROR(execute_conjuncts(ctxs, nullptr, false, block, &filter, &can_filter_all));
+
+ // Accumulate the usage of `result_filter` into the first context.
+ if (!ctxs.empty()) {
+ ctxs[0]->_memory_usage += filter.allocated_bytes();
+ }
if (can_filter_all) {
for (auto& col : columns_to_filter) {
- std::move(*block->get_by_position(col).column).assume_mutable()->clear();
+ block->get_by_position(col).column->assume_mutable()->clear();
}
} else {
RETURN_IF_CATCH_EXCEPTION(Block::filter_block_internal(block, columns_to_filter, filter));
@@ -375,13 +393,20 @@ Status VExprContext::get_output_block_after_execute_exprs(
auto rows = input_block.rows();
vectorized::Block tmp_block(input_block.get_columns_with_type_and_name());
vectorized::ColumnsWithTypeAndName result_columns;
+ _reset_memory_usage(output_vexpr_ctxs);
+
for (const auto& vexpr_ctx : output_vexpr_ctxs) {
int result_column_id = -1;
+ int origin_columns = tmp_block.columns();
+ size_t origin_usage = tmp_block.allocated_bytes();
RETURN_IF_ERROR(vexpr_ctx->execute(&tmp_block, &result_column_id));
DCHECK(result_column_id != -1);
+
+ vexpr_ctx->_memory_usage = tmp_block.allocated_bytes() - origin_usage;
const auto& col = tmp_block.get_by_position(result_column_id);
- if (do_projection) {
+ if (do_projection && origin_columns <= result_column_id) {
result_columns.emplace_back(col.column->clone_resized(rows), col.type, col.name);
+ vexpr_ctx->_memory_usage += result_columns.back().column->allocated_bytes();
} else {
result_columns.emplace_back(tmp_block.get_by_position(result_column_id));
}
@@ -390,4 +415,9 @@ Status VExprContext::get_output_block_after_execute_exprs(
return Status::OK();
}
+void VExprContext::_reset_memory_usage(const VExprContextSPtrs& contexts) {
+ std::for_each(contexts.begin(), contexts.end(),
+ [](auto&& context) { context->_memory_usage = 0; });
+}
+
} // namespace doris::vectorized
diff --git a/be/src/vec/exprs/vexpr_context.h b/be/src/vec/exprs/vexpr_context.h
index d3b159fc400..3fb6a1a3b74 100644
--- a/be/src/vec/exprs/vexpr_context.h
+++ b/be/src/vec/exprs/vexpr_context.h
@@ -19,6 +19,8 @@
#include <glog/logging.h>
+#include <algorithm>
+#include <cstddef>
#include <memory>
#include <utility>
#include <vector>
@@ -271,10 +273,21 @@ public:
return *this;
}
+ [[nodiscard]] static size_t get_memory_usage(const VExprContextSPtrs& contexts) {
+ size_t usage = 0;
+ std::for_each(contexts.cbegin(), contexts.cend(),
+ [&usage](auto&& context) { usage += context->_memory_usage; });
+ return usage;
+ }
+
+ [[nodiscard]] size_t get_memory_usage() const { return _memory_usage; }
+
private:
// Close method is called in vexpr context dector, not need call expicility
void close();
+ static void _reset_memory_usage(const VExprContextSPtrs& contexts);
+
friend class VExpr;
/// The expr tree this context is for.
@@ -301,5 +314,6 @@ private:
bool _force_materialize_slot = false;
std::shared_ptr<InvertedIndexContext> _inverted_index_context;
+ size_t _memory_usage = 0;
};
} // namespace doris::vectorized
diff --git a/be/src/vec/exprs/vin_predicate.cpp b/be/src/vec/exprs/vin_predicate.cpp
index 9e00a3afbae..3e15138fe59 100644
--- a/be/src/vec/exprs/vin_predicate.cpp
+++ b/be/src/vec/exprs/vin_predicate.cpp
@@ -155,6 +155,26 @@ Status VInPredicate::execute(VExprContext* context, Block* block, int* result_co
return Status::OK();
}
+size_t VInPredicate::estimate_memory(const size_t rows) {
+ if (is_const_and_have_executed()) {
+ return 0;
+ }
+
+ size_t estimate_size = 0;
+
+ for (int i = 0; i < _children.size(); ++i) {
+ estimate_size += _children[i]->estimate_memory(rows);
+ }
+
+ if (_data_type->is_nullable()) {
+ estimate_size += rows * sizeof(uint8_t);
+ }
+
+ estimate_size += rows * sizeof(uint8_t);
+
+ return estimate_size;
+}
+
const std::string& VInPredicate::expr_name() const {
return _expr_name;
}
diff --git a/be/src/vec/exprs/vin_predicate.h b/be/src/vec/exprs/vin_predicate.h
index 024ad68f2ba..10caa3e7543 100644
--- a/be/src/vec/exprs/vin_predicate.h
+++ b/be/src/vec/exprs/vin_predicate.h
@@ -43,6 +43,7 @@ public:
VInPredicate(const TExprNode& node);
~VInPredicate() override = default;
Status execute(VExprContext* context, Block* block, int* result_column_id) override;
+ size_t estimate_memory(const size_t rows) override;
Status prepare(RuntimeState* state, const RowDescriptor& desc, VExprContext* context) override;
Status open(RuntimeState* state, VExprContext* context,
FunctionContext::FunctionStateScope scope) override;
diff --git a/be/src/vec/exprs/vslot_ref.h b/be/src/vec/exprs/vslot_ref.h
index 2bea6ea5c06..f9165ee1e35 100644
--- a/be/src/vec/exprs/vslot_ref.h
+++ b/be/src/vec/exprs/vslot_ref.h
@@ -54,6 +54,8 @@ public:
bool equals(const VExpr& other) override;
+ size_t estimate_memory(const size_t rows) override { return 0; }
+
private:
int _slot_id;
int _column_id;
diff --git a/be/src/vec/runtime/vdata_stream_recvr.cpp b/be/src/vec/runtime/vdata_stream_recvr.cpp
index f1dfbbf3047..6ff1fcc0aca 100644
--- a/be/src/vec/runtime/vdata_stream_recvr.cpp
+++ b/be/src/vec/runtime/vdata_stream_recvr.cpp
@@ -411,6 +411,9 @@ Status VDataStreamRecvr::add_block(const PBlock& pblock, int sender_id, int be_n
}
void VDataStreamRecvr::add_block(Block* block, int sender_id, bool use_move) {
+ if (_parent->state()->get_query_ctx()->low_memory_mode()) {
+ set_low_memory_mode();
+ }
int use_sender_id = _is_merging ? sender_id : 0;
_sender_queues[use_sender_id]->add_block(block, use_move);
}
diff --git a/be/src/vec/runtime/vdata_stream_recvr.h b/be/src/vec/runtime/vdata_stream_recvr.h
index b2d76590ba2..c4237a9d5cd 100644
--- a/be/src/vec/runtime/vdata_stream_recvr.h
+++ b/be/src/vec/runtime/vdata_stream_recvr.h
@@ -115,6 +115,8 @@ public:
std::shared_ptr<pipeline::Dependency> get_local_channel_dependency(int sender_id);
+ void set_low_memory_mode() { _sender_queue_mem_limit = 1012 * 1024; }
+
private:
friend struct BlockSupplierSortCursorImpl;
@@ -140,7 +142,7 @@ private:
std::unique_ptr<MemTracker> _mem_tracker;
// Managed by object pool
std::vector<SenderQueue*> _sender_queues;
- size_t _sender_queue_mem_limit;
+ std::atomic<size_t> _sender_queue_mem_limit;
std::unique_ptr<VSortedRunMerger> _merger;
diff --git a/be/src/vec/sink/vdata_stream_sender.cpp b/be/src/vec/sink/vdata_stream_sender.cpp
index 496e68c97f0..9c9a11d60aa 100644
--- a/be/src/vec/sink/vdata_stream_sender.cpp
+++ b/be/src/vec/sink/vdata_stream_sender.cpp
@@ -108,7 +108,12 @@ Status Channel<Parent>::open(RuntimeState* state) {
_brpc_request->set_sender_id(_parent->sender_id());
_brpc_request->set_be_number(_be_number);
- _brpc_timeout_ms = std::min(3600, state->execution_timeout()) * 1000;
+ const auto& query_options = state->query_options();
+ if (query_options.__isset.query_timeout) {
+ _brpc_timeout_ms = query_options.query_timeout * 1000;
+ } else {
+ _brpc_timeout_ms = std::min(3600, state->execution_timeout()) * 1000;
+ }
_serializer.set_is_local(_is_local);
diff --git a/be/src/vec/spill/spill_stream.h b/be/src/vec/spill/spill_stream.h
index ad30a0bbd1d..26b7dcbaf06 100644
--- a/be/src/vec/spill/spill_stream.h
+++ b/be/src/vec/spill/spill_stream.h
@@ -35,7 +35,8 @@ class SpillDataDir;
class SpillStream {
public:
// to avoid too many small file writes
- static constexpr int MIN_SPILL_WRITE_BATCH_MEM = 32 * 1024;
+ static constexpr size_t MIN_SPILL_WRITE_BATCH_MEM = 32 * 1024;
+ static constexpr size_t MAX_SPILL_WRITE_BATCH_MEM = 32 * 1024 * 1024;
SpillStream(RuntimeState* state, int64_t stream_id, SpillDataDir* data_dir,
std::string spill_dir, size_t batch_rows, size_t batch_bytes,
RuntimeProfile* profile);
@@ -67,9 +68,10 @@ public:
RuntimeProfile::Counter* write_block_counter,
RuntimeProfile::Counter* write_bytes_counter,
RuntimeProfile::Counter* write_timer,
- RuntimeProfile::Counter* wait_io_timer) {
+ RuntimeProfile::Counter* wait_io_timer,
+ RuntimeProfile::Counter* memory_used_counter) {
writer_->set_counters(serialize_timer, write_block_counter, write_bytes_counter,
- write_timer);
+ write_timer, memory_used_counter);
write_wait_io_timer_ = wait_io_timer;
}
diff --git a/be/src/vec/spill/spill_writer.cpp b/be/src/vec/spill/spill_writer.cpp
index 46a97285802..9fbd81601b6 100644
--- a/be/src/vec/spill/spill_writer.cpp
+++ b/be/src/vec/spill/spill_writer.cpp
@@ -84,6 +84,9 @@ Status SpillWriter::write(RuntimeState* state, const Block& block, size_t& writt
}
});
+ auto tmp_blcok_mem = tmp_block.allocated_bytes();
+ memory_used_counter_->update(tmp_blcok_mem);
+ Defer defer {[&]() { memory_used_counter_->update(-tmp_blcok_mem); }};
RETURN_IF_ERROR(_write_internal(tmp_block, written_bytes));
row_idx += block_rows;
@@ -107,10 +110,14 @@ Status SpillWriter::_write_internal(const Block& block, size_t& written_bytes) {
&compressed_bytes,
segment_v2::CompressionTypePB::ZSTD); // ZSTD for better compression ratio
RETURN_IF_ERROR(status);
+ auto pblock_mem = pblock.ByteSizeLong();
+ memory_used_counter_->update(pblock_mem);
+ Defer defer {[&]() { memory_used_counter_->update(-pblock_mem); }};
if (!pblock.SerializeToString(&buff)) {
return Status::Error<ErrorCode::SERIALIZE_PROTOBUF_ERROR>(
"serialize spill data error. [path={}]", file_path_);
}
+ memory_used_counter_->update(buff.size());
}
if (data_dir_->reach_capacity_limit(buff.size())) {
return Status::Error<ErrorCode::DISK_REACH_CAPACITY_LIMIT>(
@@ -124,6 +131,7 @@ Status SpillWriter::_write_internal(const Block& block, size_t& written_bytes) {
{
auto buff_size = buff.size();
Defer defer {[&]() {
+ memory_used_counter_->update(-buff_size);
if (status.ok()) {
data_dir_->update_spill_data_usage(buff_size);
diff --git a/be/src/vec/spill/spill_writer.h b/be/src/vec/spill/spill_writer.h
index d77bbd6908c..c3502b5d6a4 100644
--- a/be/src/vec/spill/spill_writer.h
+++ b/be/src/vec/spill/spill_writer.h
@@ -51,11 +51,13 @@ public:
void set_counters(RuntimeProfile::Counter* serialize_timer,
RuntimeProfile::Counter* write_block_counter,
RuntimeProfile::Counter* write_bytes_counter,
- RuntimeProfile::Counter* write_timer) {
+ RuntimeProfile::Counter* write_timer,
+ RuntimeProfile::Counter* memory_used_counter) {
serialize_timer_ = serialize_timer;
write_block_counter_ = write_block_counter;
write_bytes_counter_ = write_bytes_counter;
write_timer_ = write_timer;
+ memory_used_counter_ = memory_used_counter;
}
private:
@@ -78,10 +80,11 @@ private:
int64_t total_written_bytes_ = 0;
std::string meta_;
- RuntimeProfile::Counter* write_bytes_counter_;
- RuntimeProfile::Counter* serialize_timer_;
- RuntimeProfile::Counter* write_timer_;
- RuntimeProfile::Counter* write_block_counter_;
+ RuntimeProfile::Counter* write_bytes_counter_ = nullptr;
+ RuntimeProfile::Counter* serialize_timer_ = nullptr;
+ RuntimeProfile::Counter* write_timer_ = nullptr;
+ RuntimeProfile::Counter* write_block_counter_ = nullptr;
+ RuntimeProfile::Counter* memory_used_counter_ = nullptr;
};
using SpillWriterUPtr = std::unique_ptr<SpillWriter>;
} // namespace vectorized
diff --git a/be/test/runtime/memory/mem_tracker_test.cpp b/be/test/runtime/memory/mem_tracker_test.cpp
index 49f6aa3bf0c..5104cf8a40d 100644
--- a/be/test/runtime/memory/mem_tracker_test.cpp
+++ b/be/test/runtime/memory/mem_tracker_test.cpp
@@ -27,30 +27,24 @@ namespace doris {
TEST(MemTrackerTest, SingleTrackerNoLimit) {
auto t = MemTrackerLimiter::create_shared(MemTrackerLimiter::Type::GLOBAL);
- EXPECT_FALSE(t->has_limit());
t->consume(10);
EXPECT_EQ(t->consumption(), 10);
t->consume(10);
EXPECT_EQ(t->consumption(), 20);
t->release(15);
EXPECT_EQ(t->consumption(), 5);
- EXPECT_FALSE(t->limit_exceeded());
t->release(5);
}
TEST(MemTrackerTest, SingleTrackerWithLimit) {
auto t = std::make_unique<MemTrackerLimiter>(MemTrackerLimiter::Type::GLOBAL, "limit tracker",
11);
- EXPECT_TRUE(t->has_limit());
t->consume(10);
EXPECT_EQ(t->consumption(), 10);
- EXPECT_FALSE(t->limit_exceeded());
t->consume(10);
EXPECT_EQ(t->consumption(), 20);
- EXPECT_TRUE(t->limit_exceeded());
t->release(15);
EXPECT_EQ(t->consumption(), 5);
- EXPECT_FALSE(t->limit_exceeded());
t->release(5);
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/Coordinator.java b/fe/fe-core/src/main/java/org/apache/doris/qe/Coordinator.java
index cd083c0450e..925324a1f7f 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/Coordinator.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/Coordinator.java
@@ -672,7 +672,7 @@ public class Coordinator implements CoordInterface {
// throw exception during workload group manager.
throw new UserException("could not find query queue");
}
- queueToken = queryQueue.getToken();
+ queueToken = queryQueue.getToken(context.getSessionVariable().wgQuerySlotCount);
queueToken.get(DebugUtil.printId(queryId),
this.queryOptions.getExecutionTimeout() * 1000);
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
index ee383780a05..907ec80f272 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
@@ -628,6 +628,10 @@ public class SessionVariable implements Serializable, Writable {
public static final String BYPASS_WORKLOAD_GROUP = "bypass_workload_group";
+ public static final String QUERY_SLOT_COUNT = "query_slot_count";
+
+ public static final String ENABLE_QUERY_SLOT_HARD_LIMIT = "enable_query_slot_hard_limit";
+
public static final String MAX_COLUMN_READER_NUM = "max_column_reader_num";
public static final String USE_MAX_LENGTH_OF_VARCHAR_IN_CTAS = "use_max_length_of_varchar_in_ctas";
@@ -664,6 +668,7 @@ public class SessionVariable implements Serializable, Writable {
"enable_adaptive_pipeline_task_serial_read_on_limit";
public static final String ADAPTIVE_PIPELINE_TASK_SERIAL_READ_ON_LIMIT =
"adaptive_pipeline_task_serial_read_on_limit";
+ public static final String MINIMUM_OPERATOR_MEMORY_REQUIRED_KB = "minimum_operator_memory_required_kb";
public static final String ENABLE_PHRASE_QUERY_SEQUENYIAL_OPT = "enable_phrase_query_sequential_opt";
@@ -790,6 +795,25 @@ public class SessionVariable implements Serializable, Writable {
"whether bypass workload group's limitation, currently only support bypass query queue"})
public boolean bypassWorkloadGroup = false;
+ @VariableMgr.VarAttr(name = QUERY_SLOT_COUNT, needForward = true, checker = "checkQuerySlotCount",
+ description = {
+ "每个查询占用的slot的数量,workload group的query slot的总数等于设置的最大并发数",
+ "Number of slots occupied by each query, the total number of query slots "
+ + "of the workload group equals the maximum number of concurrent requests"})
+ public int wgQuerySlotCount = 1;
+
+ public void checkQuerySlotCount(String slotCnt) {
+ Long slotCount = Long.valueOf(slotCnt);
+ if (slotCount < 1 || slotCount > 1025) {
+ throw new InvalidParameterException("query_slot_count should be between 1 and 1024)");
+ }
+ }
+
+ @VariableMgr.VarAttr(name = ENABLE_QUERY_SLOT_HARD_LIMIT, needForward = true, description = {
+ "是否通过硬限的方式来计算每个Slot的内存资源",
+ "Whether to calculate the memory resources of each Slot by hard limit"})
+ public boolean enableQuerySlotHardLimit = false;
+
@VariableMgr.VarAttr(name = MAX_COLUMN_READER_NUM)
public int maxColumnReaderNum = 20000;
@@ -1971,6 +1995,11 @@ public class SessionVariable implements Serializable, Writable {
description = {"对外表启用 count(*) 下推优化", "enable count(*) pushdown optimization for external table"})
private boolean enableCountPushDownForExternalTable = true;
+ @VariableMgr.VarAttr(name = MINIMUM_OPERATOR_MEMORY_REQUIRED_KB, needForward = true,
+ description = {"一个算子运行需要的最小的内存大小",
+ "The minimum memory required to be used by an operator, if not meet, the operator will not run"})
+ public int minimumOperatorMemoryRequiredKB = 1000;
+
public static final String IGNORE_RUNTIME_FILTER_IDS = "ignore_runtime_filter_ids";
public Set<Integer> getIgnoredRuntimeFilterIds() {
@@ -3767,6 +3796,7 @@ public class SessionVariable implements Serializable, Writable {
tResult.setEnableSortSpill(enableSortSpill);
tResult.setEnableAggSpill(enableAggSpill);
tResult.setEnableForceSpill(enableForceSpill);
+ tResult.setExternalAggPartitionBits(externalAggPartitionBits);
tResult.setMinRevocableMem(minRevocableMem);
tResult.setDataQueueMaxBlocks(dataQueueMaxBlocks);
@@ -3781,6 +3811,9 @@ public class SessionVariable implements Serializable, Writable {
tResult.setEnableFallbackOnMissingInvertedIndex(enableFallbackOnMissingInvertedIndex);
tResult.setHiveOrcUseColumnNames(hiveOrcUseColumnNames);
tResult.setHiveParquetUseColumnNames(hiveParquetUseColumnNames);
+ tResult.setQuerySlotCount(wgQuerySlotCount);
+ tResult.setEnableQuerySlotHardLimit(enableQuerySlotHardLimit);
+
tResult.setKeepCarriageReturn(keepCarriageReturn);
tResult.setEnableSegmentCache(enableSegmentCache);
@@ -3789,6 +3822,7 @@ public class SessionVariable implements Serializable, Writable {
tResult.setAdaptivePipelineTaskSerialReadOnLimit(adaptivePipelineTaskSerialReadOnLimit);
tResult.setInListValueCountThreshold(inListValueCountThreshold);
tResult.setEnablePhraseQuerySequentialOpt(enablePhraseQuerySequentialOpt);
+ tResult.setMinimumOperatorMemoryRequiredKb(minimumOperatorMemoryRequiredKB);
return tResult;
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/resource/workloadgroup/QueryQueue.java b/fe/fe-core/src/main/java/org/apache/doris/resource/workloadgroup/QueryQueue.java
index ba2d2526f2e..82822c05a0d 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/resource/workloadgroup/QueryQueue.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/resource/workloadgroup/QueryQueue.java
@@ -26,6 +26,7 @@ import org.apache.doris.resource.workloadgroup.QueueToken.TokenState;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
@@ -98,24 +99,43 @@ public class QueryQueue {
+ runningQueryQueue.size() + ", currentWaitingQueryNum=" + waitingQueryQueue.size();
}
- public QueueToken getToken() throws UserException {
+ public int usedSlotCount() {
+ int cnt = 0;
+ for (Iterator iterator = runningQueryQueue.iterator(); iterator.hasNext();) {
+ QueueToken queueToken = (QueueToken) iterator.next();
+ cnt += queueToken.getQuerySlotCount();
+ }
+ return cnt;
+ }
+
+ public QueueToken getToken(int querySlotCount) throws UserException {
+ if (maxConcurrency > 0 && (querySlotCount > maxConcurrency || querySlotCount < 1)) {
+ throw new UserException("query slot count " + querySlotCount
+ + " should be smaller than workload group's max concurrency "
+ + maxConcurrency + " and > 0");
+ }
AdmissionControl admissionControl = Env.getCurrentEnv().getAdmissionControl();
queueLock.lock();
try {
if (LOG.isDebugEnabled()) {
LOG.info(this.debugString());
}
- QueueToken queueToken = new QueueToken(queueTimeout, this);
+ QueueToken queueToken = new QueueToken(queueTimeout, querySlotCount, this);
boolean isReachMaxCon = runningQueryQueue.size() >= maxConcurrency;
+ boolean hasFreeSlot = queueToken.getQuerySlotCount() <= maxConcurrency - usedSlotCount();
boolean isResourceAvailable = admissionControl.checkResourceAvailable(queueToken);
- if (!isReachMaxCon && isResourceAvailable) {
+ if (!isReachMaxCon && isResourceAvailable && hasFreeSlot) {
runningQueryQueue.offer(queueToken);
queueToken.complete();
return queueToken;
} else if (waitingQueryQueue.size() >= maxQueueSize) {
- throw new UserException("query waiting queue is full, queue length=" + maxQueueSize);
+ throw new UserException("query waiting queue is full, queue capacity=" + maxQueueSize
+ + ", waiting num=" + waitingQueryQueue.size());
} else {
+ if (!hasFreeSlot) {
+ queueToken.setQueueMsg("NO_FREE_SLOT");
+ }
if (isReachMaxCon) {
queueToken.setQueueMsg("WAIT_IN_QUEUE");
}
@@ -145,14 +165,19 @@ public class QueryQueue {
AdmissionControl admissionControl = Env.getCurrentEnv().getAdmissionControl();
queueLock.lock();
try {
- runningQueryQueue.remove(releaseToken);
- waitingQueryQueue.remove(releaseToken);
- admissionControl.removeQueueToken(releaseToken);
+ if (releaseToken != null) {
+ runningQueryQueue.remove(releaseToken);
+ waitingQueryQueue.remove(releaseToken);
+ admissionControl.removeQueueToken(releaseToken);
+ }
while (runningQueryQueue.size() < maxConcurrency) {
QueueToken queueToken = waitingQueryQueue.peek();
if (queueToken == null) {
break;
}
+ if (queueToken.getQuerySlotCount() > maxConcurrency - usedSlotCount()) {
+ break;
+ }
if (admissionControl.checkResourceAvailable(queueToken)) {
queueToken.complete();
runningQueryQueue.offer(queueToken);
diff --git a/fe/fe-core/src/main/java/org/apache/doris/resource/workloadgroup/QueueToken.java b/fe/fe-core/src/main/java/org/apache/doris/resource/workloadgroup/QueueToken.java
index 748c0c21bda..67afcca1340 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/resource/workloadgroup/QueueToken.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/resource/workloadgroup/QueueToken.java
@@ -54,6 +54,7 @@ public class QueueToken implements Comparable<QueueToken> {
private long queueStartTime = -1;
private long queueEndTime = -1;
+ private final int querySlotCount;
private volatile String queueMsg = "";
@@ -62,10 +63,11 @@ public class QueueToken implements Comparable<QueueToken> {
// Object is just a placeholder, it's meaningless now
private CompletableFuture<Object> future;
- public QueueToken(long queueWaitTimeout, QueryQueue queryQueue) {
+ public QueueToken(long queueWaitTimeout, int querySlotCount, QueryQueue queryQueue) {
this.tokenId = tokenIdGenerator.addAndGet(1);
this.queueWaitTimeout = queueWaitTimeout;
this.queueStartTime = System.currentTimeMillis();
+ this.querySlotCount = querySlotCount;
this.queryQueue = queryQueue;
this.future = new CompletableFuture<>();
}
@@ -146,4 +148,8 @@ public class QueueToken implements Comparable<QueueToken> {
public long getTokenId() {
return tokenId;
}
+
+ public int getQuerySlotCount() {
+ return querySlotCount;
+ }
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/resource/workloadgroup/WorkloadGroup.java b/fe/fe-core/src/main/java/org/apache/doris/resource/workloadgroup/WorkloadGroup.java
index 44fb98e10ef..42929be609b 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/resource/workloadgroup/WorkloadGroup.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/resource/workloadgroup/WorkloadGroup.java
@@ -621,6 +621,11 @@ public class WorkloadGroup implements Writable, GsonPostProcessable {
tWorkloadGroupInfo.setTag(tagStr);
}
+ String totalQuerySlotCountStr = properties.get(MAX_CONCURRENCY);
+ if (totalQuerySlotCountStr != null) {
+ tWorkloadGroupInfo.setTotalQuerySlotCount(Integer.parseInt(totalQuerySlotCountStr));
+ }
+
TopicInfo topicInfo = new TopicInfo();
topicInfo.setWorkloadGroupInfo(tWorkloadGroupInfo);
return topicInfo;
diff --git a/gensrc/thrift/BackendService.thrift b/gensrc/thrift/BackendService.thrift
index ed0ae243a1d..4acd4602432 100644
--- a/gensrc/thrift/BackendService.thrift
+++ b/gensrc/thrift/BackendService.thrift
@@ -270,6 +270,7 @@ struct TWorkloadGroupInfo {
14: optional i64 read_bytes_per_second
15: optional i64 remote_read_bytes_per_second
16: optional string tag
+ 17: optional i32 total_query_slot_count
}
enum TWorkloadMetricType {
diff --git a/gensrc/thrift/PaloInternalService.thrift b/gensrc/thrift/PaloInternalService.thrift
index b560059819f..75c3122b5bd 100644
--- a/gensrc/thrift/PaloInternalService.thrift
+++ b/gensrc/thrift/PaloInternalService.thrift
@@ -333,9 +333,7 @@ struct TQueryOptions {
125: optional bool enable_segment_cache = true;
126: optional i32 runtime_bloom_filter_max_size = 16777216;
-
127: optional i32 in_list_value_count_threshold = 10;
-
// We need this two fields to make sure thrift id on master is compatible with other branch.
128: optional bool enable_verbose_profile = false;
129: optional i32 rpc_verbose_profile_max_instance_count = 0;
@@ -350,6 +348,12 @@ struct TQueryOptions {
135: optional bool enable_parallel_outfile = false;
136: optional bool enable_phrase_query_sequential_opt = true;
+
+ // The minimum memory that an operator required to run.
+ 137: optional i32 minimum_operator_memory_required_kb = 1024;
+
+ 138: optional bool enable_query_slot_hard_limit = false;
+ 139: optional i32 query_slot_count = 0;
// For cloud, to control if the content would be written into file cache
// In write path, to control if the content would be written into file cache.
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org