github-actions[bot] commented on code in PR #67053:
URL: https://github.com/apache/doris/pull/67053#discussion_r3849497419
##########
be/src/runtime/runtime_query_statistics_mgr.cpp:
##########
@@ -466,25 +498,79 @@ void
RuntimeQueryStatisticsMgr::report_runtime_query_statistics() {
}
}
- // 3 when query is finished and (last rpc is send success), remove
finished query statistics
+ // 3 release accepted/expired compact snapshots and shed finished
ResourceContexts.
if (fe_qs_map.empty()) {
return;
}
{
std::lock_guard<std::shared_mutex>
write_lock(_resource_contexts_map_lock);
- for (auto& [addr, qs_map] : fe_qs_map) {
- bool is_rpc_success = rpc_result[addr];
- for (auto& [query_id, qs] : qs_map) {
- auto& qs_status_pair = qs_status[query_id];
- bool is_query_finished = qs_status_pair.first;
- bool is_timeout_after_finish = qs_status_pair.second;
- if ((is_rpc_success && is_query_finished) ||
is_timeout_after_finish) {
- _resource_contexts_map.erase(query_id);
- }
+ for (const auto& [query_id, reported_statistics] :
reported_pending_statistics) {
+ auto iter = _pending_final_statistics.find(query_id);
+ if (iter == _pending_final_statistics.end() ||
+ iter->second.statistics != reported_statistics) {
+ continue;
+ }
+ const auto& report_result = rpc_results[iter->second.fe_addr];
+ int64_t elapsed_after_finish_ms = MonotonicMillis() -
iter->second.finish_time_ms;
+ if (should_remove_finished_query(report_result.response_received,
+ report_result.accepted,
elapsed_after_finish_ms,
+
config::query_statistics_reserve_timeout_ms,
+ report_result.retention_ms)) {
+ _pending_final_statistics.erase(iter);
}
}
+
+ for (const auto& [query_id, final_statistics] :
active_final_statistics) {
+ auto iter = _resource_contexts_map.find(query_id);
+ // A later group-commit generation may reuse the query ID while
this RPC is in flight;
+ // only compact or erase the exact ResourceContext that produced
this snapshot.
+ if (iter == _resource_contexts_map.end() ||
+ iter->second != qs_resource_contexts[query_id]) {
+ continue;
+ }
+ auto resource_ctx = iter->second;
+ const auto& fe_addr = resource_ctx->task_controller()->fe_addr();
+ const auto& report_result = rpc_results[fe_addr];
+ int64_t finish_time_ms =
resource_ctx->task_controller()->finish_time();
+ int64_t elapsed_after_finish_ms = MonotonicMillis() -
finish_time_ms;
+ FinalStatisticsAction action = final_statistics_action(
+ report_result.response_received, report_result.accepted,
+ elapsed_after_finish_ms,
config::query_statistics_reserve_timeout_ms,
+ report_result.retention_ms);
+ if (action == FinalStatisticsAction::COMPACT_RESOURCE_CONTEXT) {
+ _pending_final_statistics[query_id] = {fe_addr,
final_statistics, finish_time_ms};
+ }
+ if (action != FinalStatisticsAction::KEEP_RESOURCE_CONTEXT) {
+ _resource_contexts_map.erase(iter);
+ }
+ }
+ }
+}
+
+bool RuntimeQueryStatisticsMgr::should_remove_finished_query(bool
response_received, bool accepted,
+ int64_t
elapsed_after_finish_ms,
+ int64_t
local_retention_ms,
+ int64_t
fe_retention_ms) {
+ if (accepted) {
+ return true;
+ }
+ // Without an FE response the BE does not know the audit fallback window,
so timing out here
+ // could discard the only final snapshot before a lagging FE can accept a
retry.
+ return response_received &&
Review Comment:
[P1] Bound final snapshots when an FE never responds. A transport/client
failure leaves `response_received` false, so this condition keeps every compact
entry forever; meanwhile each completed DML for that destination is copied into
every later retry payload. A permanently removed or partitioned FE therefore
grows `_pending_final_statistics` and the Thrift request without limit
(eventually making recovery harder once the batch becomes too large). This is
distinct from an explicit rejection, where FE supplies a retention window.
Please add a finite no-response/dead-destination policy and a reporter
failure/recovery test.
##########
fe/fe-core/src/main/java/org/apache/doris/qe/ConnectProcessor.java:
##########
@@ -831,6 +831,7 @@ public TMasterOpResult proxyExecute(TMasterOpRequest
request) throws TException
}
}
if (executor != null) {
+ result.setAuditBackendIds(executor.getAuditBackendIds());
Review Comment:
[P1] Return participants from the executor that ran the prepared DML. In the
`prepareExecuteBuffer` branch, this local variable still refers to the
COM_STMT_PREPARE executor. `MysqlConnectProcessor.handleExecute(...)` creates
the actual COM_STMT_EXECUTE executor in the inherited instance field/context,
so `getAuditBackendIds()` here serializes an empty set. The follower can then
release the DML audit after the short timeout before periodic final counters
arrive. Read the execution executor after `handleExecute()` and cover a
forwarded prepared INSERT.
##########
fe/fe-core/src/main/java/org/apache/doris/resource/workloadschedpolicy/WorkloadRuntimeStatusMgr.java:
##########
@@ -147,92 +240,398 @@ public void submitFinishQueryToAudit(AuditEvent event) {
// put the event to queryAuditEventList and let the worker
thread to handle it.
// the worker thread will try best to wait for the statistic
info before logging this event.
event.pushToAuditLogQueueTime = System.currentTimeMillis();
- queryAuditEventList.add(event);
+ PendingAuditEvent pending = new PendingAuditEvent(event,
expectedBackendIds);
+ // Register before publishing to the queue so cleanup cannot
remove a first report
+ // in the gap between queue insertion and participant
retention.
+ registerPendingAuditEvent(pending);
+ queryAuditEventList.add(pending);
}
} finally {
queryAuditEventLogWriteUnlock();
}
}
- private List<AuditEvent> getQueryNeedAudit() {
- List<AuditEvent> ret = new ArrayList<>();
+ @VisibleForTesting
+ List<AuditEvent> getQueryNeedAudit() {
+ RuntimeStatisticsSnapshot runtimeSnapshot =
buildRuntimeStatisticsSnapshot();
+ queryStatisticsSnapshot =
ImmutableMap.copyOf(runtimeSnapshot.queryStatistics);
+
long currentTime = System.currentTimeMillis();
+ int queryAuditLogTimeout = Config.query_audit_log_timeout_ms;
+ long maximumWaitMs = Math.max(queryAuditLogTimeout,
+ Config.be_report_query_statistics_timeout_ms);
+ List<PendingAuditEvent> dueEvents = new ArrayList<>();
+ // Keep only identity/timestamp traversal under the producer-facing
queue lock; participant
+ // lookup and statistics merging may touch every expected backend and
must run lock-free.
queryAuditEventLogWriteLock();
try {
- int queryAuditLogTimeout = Config.query_audit_log_timeout_ms;
- Iterator<AuditEvent> iter = queryAuditEventList.iterator();
- while (iter.hasNext()) {
- AuditEvent ae = iter.next();
- if (currentTime - ae.pushToAuditLogQueueTime >
queryAuditLogTimeout) {
- ret.add(ae);
- iter.remove();
- } else {
+ for (PendingAuditEvent pending : queryAuditEventList) {
+ long elapsed = currentTime -
pending.event.pushToAuditLogQueueTime;
+ if (elapsed <= queryAuditLogTimeout) {
+ // Events are appended in timestamp order, so later
entries cannot be due yet.
break;
}
+ dueEvents.add(pending);
+ }
+ } finally {
+ queryAuditEventLogWriteUnlock();
+ }
+
+ Map<PendingAuditEvent, TQueryStatistics> readyEvents = new
IdentityHashMap<>();
+ for (PendingAuditEvent pending : dueEvents) {
+ beforeHydrateAuditEvent(pending.event.queryId);
+ boolean allFinalSnapshotsReceived = true;
+ TQueryStatistics auditStatistics =
pending.expectedBackendIds.length == 0
+ ?
runtimeSnapshot.queryStatistics.get(pending.event.queryId)
+ : new TQueryStatistics();
+ for (int i = 0; i < pending.expectedBackendIds.length; i++) {
+ bindParticipantFromExistingReport(pending, i);
+ long backendStartTime =
pending.expectedBackendStartTimes.get(i);
+ TQueryStatisticsResult statistics = findStatisticsForBackend(
+ pending.event.queryId, pending.expectedBackendIds[i],
backendStartTime);
+ if (statistics == null) {
+ allFinalSnapshotsReceived = false;
+ continue;
+ }
+ mergeQueryStatistics(auditStatistics, statistics);
+ if (!statistics.isSetQueryFinished() ||
!statistics.isQueryFinished()) {
+ allFinalSnapshotsReceived = false;
+ }
+ }
+ long elapsed = currentTime - pending.event.pushToAuditLogQueueTime;
+ if (allFinalSnapshotsReceived || elapsed > maximumWaitMs) {
+ readyEvents.put(pending, auditStatistics);
+ }
+ }
+
+ List<PendingAuditEvent> removedEvents = new ArrayList<>();
+ queryAuditEventLogWriteLock();
+ try {
+ for (PendingAuditEvent pending : dueEvents) {
+ if (readyEvents.containsKey(pending) &&
queryAuditEventList.remove(pending)) {
+ removedEvents.add(pending);
+ }
}
} finally {
queryAuditEventLogWriteUnlock();
}
+
+ List<AuditEvent> ret = new ArrayList<>(removedEvents.size());
+ for (PendingAuditEvent pending : removedEvents) {
+ // A DML audit must not pass the queue until every scheduled BE
has published its
+ // query-level final snapshot. The upper bound only protects the
queue if a BE dies.
+ applyQueryStatisticsToAuditEvent(pending.event,
readyEvents.get(pending));
+ ret.add(pending.event);
+ unregisterPendingAuditEvent(pending);
+ }
return ret;
}
- public void updateBeQueryStats(TReportWorkloadRuntimeStatusParams params) {
+ public boolean updateBeQueryStats(TReportWorkloadRuntimeStatusParams
params) {
if (!params.isSetBackendId()) {
LOG.warn("be report workload runtime status but without beid");
- return;
+ return false;
}
if (!params.isSetQueryStatisticsResultMap()) {
LOG.warn("be report workload runtime status but without query
stats map");
- return;
+ return false;
+ }
+ if (!params.isSetBackendStartTime()) {
+ LOG.warn("be report workload runtime status without backend start
time");
+ return false;
}
long beId = params.backend_id;
- // NOTE(wb) one be sends update request one by one,
- // so there is no need a global lock for beToQueryStatsMap here,
- // just keep one be's put/remove/get is atomic operation is enough
- long currentTime = System.currentTimeMillis();
- BeReportInfo beReportInfo = beToQueryStatsMap.get(beId);
- if (beReportInfo == null) {
- beReportInfo = new BeReportInfo(currentTime);
- beToQueryStatsMap.put(beId, beReportInfo);
+ long backendStartTime = params.backend_start_time;
+ long currentBackendStartTime = getBackendStartTime(beId);
+ if (currentBackendStartTime > 0 && currentBackendStartTime !=
backendStartTime) {
+ LOG.info("ignore stale workload runtime status from backend {},
report start time {}, current {}",
+ beId, backendStartTime, currentBackendStartTime);
+ return false;
+ }
+ // Newly replayed backends use -1 as well as 0 for an unknown
heartbeat epoch. In either
+ // state the first accepted report must latch the only process allowed
to publish data.
+ if (currentBackendStartTime <= 0) {
+ Long acceptedStartTime =
lastAcceptedBackendStartTimes.putIfAbsent(beId, backendStartTime);
+ if (acceptedStartTime != null && acceptedStartTime !=
backendStartTime) {
+ return false;
+ }
} else {
- beReportInfo.beLastReportTime = currentTime;
+ lastAcceptedBackendStartTimes.put(beId, backendStartTime);
+ }
+ BackendIncarnation incarnation = new BackendIncarnation(beId,
backendStartTime);
+ beToQueryStatsMap.computeIfAbsent(incarnation, ignored -> new
BeReportInfo(backendStartTime));
+ long currentTime = System.currentTimeMillis();
+ for (Map.Entry<String, TQueryStatisticsResult> entry
+ : params.query_statistics_result_map.entrySet()) {
+ beforeUpdateQueryStatistics(entry.getKey());
+ while (true) {
+ BeReportInfo beReportInfo =
beToQueryStatsMap.computeIfAbsent(incarnation,
+ ignored -> new BeReportInfo(backendStartTime));
+ beReportInfo.lifecycleLock.readLock().lock();
+ try {
+ // Cleanup removes only an empty shell under the write
lock. If this insertion
+ // raced with removal, retry the single entry against the
replacement shell.
+ if (beToQueryStatsMap.get(incarnation) != beReportInfo) {
+ continue;
+ }
+ Pair<Long, TQueryStatisticsResult> incoming =
+ Pair.of(currentTime, entry.getValue());
+ beReportInfo.queryStatsMap.compute(entry.getKey(),
(queryId, previous) ->
+ previous == null
+ ||
shouldReplaceQueryStatistics(previous.second, incoming.second)
+ ? incoming : previous);
+ break;
+ } finally {
+ beReportInfo.lifecycleLock.readLock().unlock();
+ }
+ }
}
- for (Map.Entry<String, TQueryStatisticsResult> entry :
params.query_statistics_result_map.entrySet()) {
- beReportInfo.queryStatsMap.put(entry.getKey(),
Pair.of(currentTime, entry.getValue()));
+ // Heartbeat can change while a large report is being merged. Refuse
the acknowledgement
+ // so the BE retries against the FE state that is authoritative after
the merge.
+ long latestBackendStartTime = getBackendStartTime(beId);
+ boolean accepted = latestBackendStartTime <= 0 ||
latestBackendStartTime == backendStartTime;
+ if (accepted) {
+ beforeBindPendingAuditEvents();
+ bindPendingAuditEvents(beId, backendStartTime,
+ params.query_statistics_result_map.keySet());
}
+ return accepted;
}
- private void clearReportTimeoutBeStatistics() {
- // 1 clear report timeout be
- Set<Long> currentBeIdSet = beToQueryStatsMap.keySet();
+ private TQueryStatisticsResult findStatisticsForBackend(String queryId,
+ long backendId, long backendStartTime) {
+ if (backendStartTime <= 0) {
+ return null;
+ }
+ BeReportInfo reportInfo = beToQueryStatsMap.get(
+ new BackendIncarnation(backendId, backendStartTime));
+ if (reportInfo != null) {
+ Pair<Long, TQueryStatisticsResult> pair =
reportInfo.queryStatsMap.get(queryId);
+ return pair == null ? null : pair.second;
+ }
+ return null;
+ }
+
+ private void registerPendingAuditEvent(PendingAuditEvent pending) {
+ pendingAuditBindingLock.lock();
+ try {
+ if (!pending.active.get()) {
+ return;
+ }
+ for (int i = 0; i < pending.expectedBackendIds.length; i++) {
+ if (bindParticipantFromExistingReportLocked(pending, i)) {
+ continue;
+ }
+ BackendQuery key = new BackendQuery(
+ pending.expectedBackendIds[i], pending.event.queryId);
+ unboundAuditParticipants.computeIfAbsent(key, ignored -> new
ArrayList<>())
+ .add(new PendingAuditBinding(pending, i));
+ }
+ } finally {
+ pendingAuditBindingLock.unlock();
+ }
+ }
+
+ private void bindPendingAuditEvents(long backendId, long backendStartTime,
+ Set<String> reportedQueryIds) {
+ for (String queryId : reportedQueryIds) {
+ pendingAuditBindingLock.lock();
+ try {
+ BackendQuery key = new BackendQuery(backendId, queryId);
+ List<PendingAuditBinding> bindings =
unboundAuditParticipants.remove(key);
+ if (bindings == null) {
+ continue;
+ }
+ for (PendingAuditBinding binding : bindings) {
+ bindParticipantLocked(binding.pending,
binding.participantIndex,
+ backendStartTime);
+ }
+ } finally {
+ pendingAuditBindingLock.unlock();
+ }
+ }
+ }
+
+ private void bindParticipantFromExistingReport(PendingAuditEvent pending,
int participantIndex) {
+ if (pending.expectedBackendStartTimes.get(participantIndex) > 0) {
+ return;
+ }
+ pendingAuditBindingLock.lock();
+ try {
+ if (bindParticipantFromExistingReportLocked(pending,
participantIndex)) {
+ removeUnboundParticipantLocked(pending, participantIndex);
+ }
+ } finally {
+ pendingAuditBindingLock.unlock();
+ }
+ }
+
+ private boolean bindParticipantFromExistingReportLocked(
+ PendingAuditEvent pending, int participantIndex) {
+ if (!pending.active.get()) {
+ return false;
+ }
+ if (pending.expectedBackendStartTimes.get(participantIndex) > 0) {
+ return true;
+ }
+ long backendId = pending.expectedBackendIds[participantIndex];
+ long currentBackendStartTime = getBackendStartTime(backendId);
+ long reportStartTime = currentBackendStartTime > 0
+ ? currentBackendStartTime
+ : lastAcceptedBackendStartTimes.getOrDefault(backendId, 0L);
+ if (findStatisticsForBackend(pending.event.queryId, backendId,
reportStartTime) == null) {
+ return false;
+ }
+ bindParticipantLocked(pending, participantIndex, reportStartTime);
+ return true;
+ }
+
+ private void bindParticipantLocked(PendingAuditEvent pending, int
participantIndex,
+ long backendStartTime) {
+ if (!pending.active.get()
+ || !pending.expectedBackendStartTimes.compareAndSet(
+ participantIndex, 0, backendStartTime)) {
+ return;
+ }
+ // Retention is query-scoped so one queued audit cannot pin unrelated
completed queries
+ // from the same long-lived backend process.
+ RetainedQuery retainedQuery = new RetainedQuery(
+ new
BackendIncarnation(pending.expectedBackendIds[participantIndex],
+ backendStartTime), pending.event.queryId);
+ pendingQueryReferences.compute(retainedQuery,
+ (ignored, count) -> count == null ? 1 : count + 1);
+ }
+
+ private void unregisterPendingAuditEvent(PendingAuditEvent pending) {
+ pendingAuditBindingLock.lock();
+ try {
+ if (!pending.active.compareAndSet(true, false)) {
+ return;
+ }
+ for (int i = 0; i < pending.expectedBackendIds.length; i++) {
+ long backendStartTime =
pending.expectedBackendStartTimes.get(i);
+ if (backendStartTime > 0) {
+ RetainedQuery retainedQuery = new RetainedQuery(
+ new
BackendIncarnation(pending.expectedBackendIds[i], backendStartTime),
+ pending.event.queryId);
+ pendingQueryReferences.computeIfPresent(retainedQuery,
(ignored, count) ->
+ count == 1 ? null : count - 1);
+ } else {
+ removeUnboundParticipantLocked(pending, i);
+ }
+ }
+ } finally {
+ pendingAuditBindingLock.unlock();
+ }
+ }
+
+ private void removeUnboundParticipantLocked(PendingAuditEvent pending, int
participantIndex) {
+ BackendQuery key = new BackendQuery(
+ pending.expectedBackendIds[participantIndex],
pending.event.queryId);
+ List<PendingAuditBinding> bindings = unboundAuditParticipants.get(key);
+ if (bindings == null) {
+ return;
+ }
+ bindings.removeIf(binding -> binding.pending == pending
+ && binding.participantIndex == participantIndex);
+ if (bindings.isEmpty()) {
+ unboundAuditParticipants.remove(key);
+ }
+ }
+
+ @VisibleForTesting
+ void beforeHydrateAuditEvent(String queryId) {
+ }
+
+ @VisibleForTesting
+ void beforeBindPendingAuditEvents() {
+ }
+
+ private boolean isQueryRetainedForAudit(BackendIncarnation incarnation,
String queryId) {
+ RetainedQuery retainedQuery = new RetainedQuery(incarnation, queryId);
+ if (pendingQueryReferences.containsKey(retainedQuery)) {
+ return true;
+ }
+ pendingAuditBindingLock.lock();
+ try {
+ // Serialize cleanup with first-report binding. Until the
participant has an epoch,
+ // retaining only this backend/query candidate closes the
insert-to-bind race.
+ return pendingQueryReferences.containsKey(retainedQuery)
+ || unboundAuditParticipants.containsKey(
+ new BackendQuery(incarnation.backendId, queryId));
+ } finally {
+ pendingAuditBindingLock.unlock();
+ }
+ }
+
+ @VisibleForTesting
+ long getBackendStartTime(long backendId) {
+ Backend backend = Env.getCurrentSystemInfo().getBackend(backendId);
+ return backend == null ? 0 : backend.getLastStartTime();
+ }
+
+ @VisibleForTesting
+ void beforeUpdateQueryStatistics(String queryId) {
+ }
+
+ private boolean shouldReplaceQueryStatistics(TQueryStatisticsResult
previous,
+ TQueryStatisticsResult incoming) {
+ long previousGeneration = previous.isSetQueryStatisticsGeneration()
+ ? previous.getQueryStatisticsGeneration() : 0;
+ long incomingGeneration = incoming.isSetQueryStatisticsGeneration()
+ ? incoming.getQueryStatisticsGeneration() : 0;
+ if (previousGeneration != incomingGeneration) {
+ return incomingGeneration > previousGeneration;
+ }
+
+ long previousSequence = previous.isSetQueryStatisticsSequence()
+ ? previous.getQueryStatisticsSequence() : 0;
+ long incomingSequence = incoming.isSetQueryStatisticsSequence()
+ ? incoming.getQueryStatisticsSequence() : 0;
+ if (previousSequence != incomingSequence) {
+ return incomingSequence > previousSequence;
+ }
+
+ return false;
+ }
+
+ void clearReportTimeoutBeStatistics() {
+ Set<BackendIncarnation> currentBeIdSet = beToQueryStatsMap.keySet();
Long currentTime = System.currentTimeMillis();
- for (Long beId : currentBeIdSet) {
- BeReportInfo beReportInfo = beToQueryStatsMap.get(beId);
- if (currentTime - beReportInfo.beLastReportTime >
Config.be_report_query_statistics_timeout_ms) {
- beToQueryStatsMap.remove(beId);
+ for (BackendIncarnation incarnation : currentBeIdSet) {
+ BeReportInfo beReportInfo = beToQueryStatsMap.get(incarnation);
+ if (beReportInfo == null) {
continue;
}
Set<String> queryIdSet = beReportInfo.queryStatsMap.keySet();
for (String queryId : queryIdSet) {
- Pair<Long, TQueryStatisticsResult> pair =
beReportInfo.queryStatsMap.get(queryId);
- long queryLastReportTime = pair.first;
- boolean timeout = currentTime - queryLastReportTime
- > Config.be_report_query_statistics_timeout_ms;
- // Remove query statistics only when both conditions are
satisfied:
- // 1) this query statistics is timeout, and
- // 2) FE no longer has this query in QeProcessorImpl.
- // Example timeline:
- // - t0: query q1 is still running, but one periodic BE report
is delayed for > timeout.
- // - t1: clear thread runs. timeout condition is true, but q1
still exists in FE.
- // - t2: we keep q1 statistics instead of removing it; later
reports can update it again.
- if (timeout && isQueryNotExistInFe(queryId)) {
- beReportInfo.queryStatsMap.remove(queryId);
+ beforeExpireQueryStatistics(queryId);
+ beReportInfo.queryStatsMap.computeIfPresent(queryId,
(ignoredQueryId, pair) -> {
+ long queryLastReportTime = pair.first;
+ boolean timeout = currentTime - queryLastReportTime
+ > Config.be_report_query_statistics_timeout_ms;
+ // Conditional removal and updates serialize only for the
same query. Work on
+ // a high-cardinality report cannot delay another query's
terminal update.
+ return timeout && !isQueryRetainedForAudit(incarnation,
queryId)
Review Comment:
[P1] Retain accepted finals until audit ownership is registered. This
cleaner can expire S1 whenever the query is absent from the local QeProcessor
and no pending reference exists. On a follower forwarding a long external
INSERT, the follower never owns the master's coordinator: BEs report to the
follower, so an early participant's accepted S1 can age out while the master is
still finishing, before `TMasterOpResult` returns the participant set. Local
inserts also unregister before `auditAfterExec()` submits the event, and this
check is not atomic with concurrent registration. BE has already dropped S1
after acceptance, so the audit cannot recover it. Make coordinator-to-audit
ownership transfer atomic and test an aged forwarded final plus cleanup racing
audit registration.
##########
fe/fe-core/src/main/java/org/apache/doris/resource/workloadschedpolicy/WorkloadRuntimeStatusMgr.java:
##########
@@ -147,92 +240,398 @@ public void submitFinishQueryToAudit(AuditEvent event) {
// put the event to queryAuditEventList and let the worker
thread to handle it.
// the worker thread will try best to wait for the statistic
info before logging this event.
event.pushToAuditLogQueueTime = System.currentTimeMillis();
- queryAuditEventList.add(event);
+ PendingAuditEvent pending = new PendingAuditEvent(event,
expectedBackendIds);
+ // Register before publishing to the queue so cleanup cannot
remove a first report
+ // in the gap between queue insertion and participant
retention.
+ registerPendingAuditEvent(pending);
+ queryAuditEventList.add(pending);
}
} finally {
queryAuditEventLogWriteUnlock();
}
}
- private List<AuditEvent> getQueryNeedAudit() {
- List<AuditEvent> ret = new ArrayList<>();
+ @VisibleForTesting
+ List<AuditEvent> getQueryNeedAudit() {
+ RuntimeStatisticsSnapshot runtimeSnapshot =
buildRuntimeStatisticsSnapshot();
+ queryStatisticsSnapshot =
ImmutableMap.copyOf(runtimeSnapshot.queryStatistics);
+
long currentTime = System.currentTimeMillis();
+ int queryAuditLogTimeout = Config.query_audit_log_timeout_ms;
+ long maximumWaitMs = Math.max(queryAuditLogTimeout,
+ Config.be_report_query_statistics_timeout_ms);
+ List<PendingAuditEvent> dueEvents = new ArrayList<>();
+ // Keep only identity/timestamp traversal under the producer-facing
queue lock; participant
+ // lookup and statistics merging may touch every expected backend and
must run lock-free.
queryAuditEventLogWriteLock();
try {
- int queryAuditLogTimeout = Config.query_audit_log_timeout_ms;
- Iterator<AuditEvent> iter = queryAuditEventList.iterator();
- while (iter.hasNext()) {
- AuditEvent ae = iter.next();
- if (currentTime - ae.pushToAuditLogQueueTime >
queryAuditLogTimeout) {
- ret.add(ae);
- iter.remove();
- } else {
+ for (PendingAuditEvent pending : queryAuditEventList) {
+ long elapsed = currentTime -
pending.event.pushToAuditLogQueueTime;
+ if (elapsed <= queryAuditLogTimeout) {
Review Comment:
[P2] Avoid wall-clock head-of-line blocking here. Events are insertion
ordered, but their enqueue time comes from `System.currentTimeMillis()`. After
a backward clock correction, head event A can have a later timestamp than
subsequently inserted B; A is not due, this `break` skips B even after B
exceeds its audit deadline, and the same head can retain the whole later queue
until wall time catches up. Use a monotonic deadline/timestamp in the pending
entry, or continue scanning past non-due events, with a reversed-timestamp
regression test.
##########
fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java:
##########
@@ -2284,6 +2285,14 @@ public Coordinator getCoord() {
return coord;
}
+ public Set<Long> getAuditBackendIds() {
+ if (coord != null) {
+ return ImmutableSet.copyOf(coord.getInvolvedBackendIds());
+ }
+ return masterOpExecutor == null
Review Comment:
[P1] Include full-prepare group-commit executions in audit binding. This
fast path dispatches a real fragment through `GroupCommitPlanner`, but it
installs neither a coordinator nor a master executor, so this fallback returns
no participant. On reuse, the cached planner also keeps the first execution's
`loadId` as the BE query ID while the audit event uses the new COM_STMT_EXECUTE
query ID, making lookup impossible even if the backend were known. Propagate
the selected backend and actual statistics identity to the audit event, and
test two prepared group-commit executions with auditing enabled.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]