mrhhsg commented on code in PR #68032:
URL: https://github.com/apache/doris/pull/68032#discussion_r4279090307


##########
fe/fe-core/src/main/java/org/apache/doris/resource/workloadschedpolicy/WorkloadRuntimeStatusMgr.java:
##########
@@ -178,11 +180,11 @@ private List<AuditEvent> getQueryNeedAudit() {
                 if (waitTimeMs <= queryAuditLogTimeout) {
                     continue;
                 }
-                Set<Long> expectedBackendIds = 
externalDmlAuditBackendIds.get(ae);
+                Set<Long> expectedBackendIds = auditBackendIds.get(ae);

Review Comment:
   Resolving, unchanged: Flight audit timing is not changed by this PR. On 
master a Flight statement's audit event is released after 
`query_audit_log_timeout_ms` with whatever BE statistics have arrived, for 
every field; this PR only makes events with known participating BEs wait for 
their final reports, bounded by `be_report_query_statistics_timeout_ms`. 
Deferring Flight audits until DoGet finishes is a separate change to Flight 
auditing.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowDataCommand.java:
##########
@@ -585,8 +594,22 @@ public String toString() {
         return toSql();
     }
 
+    /**
+     * Bytes of query spill currently held in object storage, summed over the 
alive BEs from their
+     * latest disk reports. Only the master FE keeps these values, so the 
command is forwarded to it.
+     */
+    private long getRemoteSpillSize() throws AnalysisException {
+        long total = 0;
+        for (Backend backend : 
Env.getCurrentSystemInfo().getAllBackendsByAllCluster().values()) {
+            if (backend.isAlive()) {

Review Comment:
   Resolving, kept on purpose: a BE the master considers dead may be down, and 
its last report would otherwise be counted indefinitely (a gracefully stopped 
BE has usually deleted its spill, so a stale non-zero value would overcount). 
Heartbeat failing while disk reports keep succeeding is transient; the BE is 
counted again as soon as its heartbeat recovers.



##########
be/src/exec/spill/spill_file_manager.cpp:
##########
@@ -345,68 +489,153 @@ void SpillFileManager::delete_spill_file(SpillFileSPtr 
spill_file) {
 
 void SpillFileManager::delete_query_spill_directory(const std::string& 
query_id,
                                                     SpillDataDir* data_dir) {
-    PendingQuerySpillDirectory pending_directory {
-            .query_dir = data_dir->get_spill_data_path(query_id),
+    if (data_dir->is_remote()) {
+        // Remote spill is deleted per spill file, when the SpillFile is 
destroyed (retried by the
+        // GC thread when that failed); nothing is deleted by directory on 
object storage.
+        return;
+    }
+    PendingSpillDirectory pending_directory {
+            .failed_count = 0,
+            .dir = data_dir->get_spill_data_path(query_id),
+            .data_dir = data_dir,
+            .fs = nullptr,
+            .charged_bytes = 0,
+            .persisted_bytes = 0,
     };
 
-    auto status = _try_delete_query_spill_directory(pending_directory);
+    auto status = _try_delete_spill_directory(pending_directory);
     if (!status.ok()) {
-        std::lock_guard lock(_pending_query_spill_directories_mutex);
         ++pending_directory.failed_count;
-        
_pending_query_spill_directories.emplace_back(std::move(pending_directory));
+        _add_pending_directory(std::move(pending_directory));
+    }
+}
+
+void SpillFileManager::retry_spill_directory_deletion(SpillDataDir* data_dir, 
io::FileSystemSPtr fs,
+                                                      std::string dir, int64_t 
charged_bytes,
+                                                      int64_t persisted_bytes) 
{
+    _add_pending_directory({.failed_count = 1,
+                            .dir = std::move(dir),
+                            .data_dir = data_dir,
+                            .fs = std::move(fs),
+                            .charged_bytes = charged_bytes,
+                            .persisted_bytes = persisted_bytes});
+}
+
+void SpillFileManager::_add_pending_directory(PendingSpillDirectory 
pending_directory) {
+    auto is_under = [](const std::string& dir, const std::string& ancestor) {
+        return dir.size() > ancestor.size() && dir.starts_with(ancestor) &&
+               dir[ancestor.size()] == '/';
+    };
+    std::lock_guard lock(_pending_spill_directories_mutex);
+    for (auto& pending : _pending_spill_directories) {
+        if (pending.data_dir == pending_directory.data_dir && pending.fs == 
pending_directory.fs &&
+            (pending.dir == pending_directory.dir ||
+             is_under(pending_directory.dir, pending.dir))) {
+            // Deleting the pending ancestor deletes these objects too.
+            pending.charged_bytes += pending_directory.charged_bytes;
+            pending.persisted_bytes += pending_directory.persisted_bytes;
+            return;
+        }
     }
+    std::erase_if(_pending_spill_directories, [&](const PendingSpillDirectory& 
pending) {
+        if (pending.data_dir == pending_directory.data_dir && pending.fs == 
pending_directory.fs &&
+            is_under(pending.dir, pending_directory.dir)) {
+            pending_directory.charged_bytes += pending.charged_bytes;
+            pending_directory.persisted_bytes += pending.persisted_bytes;
+            return true;
+        }
+        return false;
+    });
+    _pending_spill_directories.emplace_back(std::move(pending_directory));
 }
 
-Status SpillFileManager::_try_delete_query_spill_directory(
-        const PendingQuerySpillDirectory& pending_directory) {
+Status SpillFileManager::_try_delete_spill_directory(
+        const PendingSpillDirectory& pending_directory) {
     {
-        std::lock_guard lock(_pending_query_spill_directories_mutex);
-        if 
(_external_spill_directory_leases.contains(pending_directory.query_dir)) {
+        std::lock_guard lock(_pending_spill_directories_mutex);
+        if (_external_spill_directory_leases.contains(pending_directory.dir)) {
             return Status::InternalError("external spill directory is still in 
use: {}",
-                                         pending_directory.query_dir);
+                                         pending_directory.dir);
         }
     }
     
DBUG_EXECUTE_IF("fault_inject::spill_file_manager::delete_query_spill_directory",
 {
         return Status::Error<INTERNAL_ERROR>("injected query spill directory 
deletion failure");
     });
-    const auto& fs = io::global_local_filesystem();
-    return fs->delete_directory(pending_directory.query_dir);
+    // Every pending directory was written through the store, which was ready 
by then and
+    // never goes back.
+    auto fs = pending_directory.fs != nullptr ? pending_directory.fs
+                                              : 
pending_directory.data_dir->fs();
+    DORIS_CHECK(fs != nullptr) << "spill store " << 
pending_directory.data_dir->path()
+                               << " is not ready";
+    return fs->delete_directory(pending_directory.dir);
+}
+
+void SpillFileManager::_retry_pending_local_spill_directories() {
+    MonotonicStopWatch watch;
+    watch.start();
+    _retry_pending_spill_directories(watch, 
std::numeric_limits<int64_t>::max(),
+                                     /*retry_remote=*/false);
 }
 
-void SpillFileManager::_retry_pending_query_spill_directories() {
-    std::vector<PendingQuerySpillDirectory> pending_directories;
+void SpillFileManager::_retry_pending_spill_directories(const 
MonotonicStopWatch& watch,
+                                                        int64_t 
max_work_time_ns,
+                                                        bool retry_remote) {
+    std::vector<PendingSpillDirectory> pending_directories;
     {
-        std::lock_guard lock(_pending_query_spill_directories_mutex);
-        pending_directories.swap(_pending_query_spill_directories);
+        std::lock_guard lock(_pending_spill_directories_mutex);
+        pending_directories.swap(_pending_spill_directories);
     }
-    DBUG_EXECUTE_IF(
-            
"fault_inject::spill_file_manager::retry_pending_query_spill_directories_after_drain",
-            { DBUG_RUN_CALLBACK(); });
+    
DBUG_EXECUTE_IF("fault_inject::spill_file_manager::retry_pending_spill_directories_after_drain",
+                    { DBUG_RUN_CALLBACK(); });
 
     // Limit repeated warnings for a persistently unavailable directory while 
retaining it for
     // every subsequent retry.
     constexpr int log_interval = 5;
-    std::vector<PendingQuerySpillDirectory> failed_directories;
+    std::vector<PendingSpillDirectory> untried_directories;
+    std::vector<PendingSpillDirectory> failed_directories;
+    size_t tried = 0;
     for (auto& pending_directory : pending_directories) {
-        auto status = _try_delete_query_spill_directory(pending_directory);
+        // A remote deletion can wait for a request timeout: after the time 
slice is used up the
+        // remaining directories are queued again without being tried, ahead 
of the ones that
+        // just failed, so that a slow failing directory cannot starve the 
others.
+        if ((!retry_remote && pending_directory.data_dir->is_remote()) ||
+            (tried > 0 && watch.elapsed_time() >= max_work_time_ns)) {
+            untried_directories.emplace_back(std::move(pending_directory));
+            continue;
+        }
+        ++tried;
+        auto status = _try_delete_spill_directory(pending_directory);
         if (status.ok()) {
+            
pending_directory.data_dir->release(pending_directory.charged_bytes);
+            
pending_directory.data_dir->release_persisted_bytes(pending_directory.persisted_bytes);
             continue;
         }
 
+        if (pending_directory.data_dir->is_remote()) {
+            auto reconcile_status = reconcile_remote_spill_bytes(
+                    pending_directory.data_dir, pending_directory.fs, 
pending_directory.dir,
+                    &pending_directory.persisted_bytes);
+            if (!reconcile_status.ok()) {
+                LOG_EVERY_T(WARNING, 60)
+                        << "failed to reconcile remote spill bytes after 
deletion: "
+                        << reconcile_status;
+            }
+        }
+
         ++pending_directory.failed_count;
         if (pending_directory.failed_count % log_interval == 0) {
             LOG(WARNING) << fmt::format(
-                    "failed to retry deleting spill query directory, dir {}, 
error: {}",
-                    pending_directory.query_dir, status.to_string());
+                    "failed to retry deleting spill directory, dir {}, error: 
{}",
+                    pending_directory.dir, status.to_string());
         }
         failed_directories.emplace_back(std::move(pending_directory));
     }
 
-    if (!failed_directories.empty()) {
-        std::lock_guard lock(_pending_query_spill_directories_mutex);
-        for (auto& pending_directory : failed_directories) {
-            
_pending_query_spill_directories.emplace_back(std::move(pending_directory));
-        }
+    for (auto& pending_directory : untried_directories) {
+        _add_pending_directory(std::move(pending_directory));

Review Comment:
   Fixed in 2c17ec7ee0b: `_retry_pending_spill_directories` now puts the 
untried and the failed entries back as one batch under the mutex (untried 
first, as before; they were already merged with each other when queued), and 
only the directories queued while the pass ran go through 
`_add_pending_directory`. A pass therefore costs one rescan per newly queued 
directory instead of one per kept entry; `stop()` and the destructor use the 
same function. Covered by 
`SpillFileS3Test.PendingRemoteDeleteBacklogIsRequeuedInBulk`: 16 queued 
spill-file prefixes, a slow failing object store, and a duplicate plus a new 
directory queued during the pass (via the 
`retry_pending_spill_directories_after_drain` debug point); it checks that the 
backlog is kept in order, the duplicate is merged and the new one is added, 
then that everything is deleted.



##########
fe/fe-core/src/main/java/org/apache/doris/qe/AuditLogHelper.java:
##########
@@ -274,6 +274,10 @@ private static void logAuditLogImpl(ConnectContext ctx, 
String origStmt, Stateme
                         statistics.getSpillWriteBytesToLocalStorage())
                 .setSpillReadBytesFromLocalStorage(statistics == null ? 0 :
                         statistics.getSpillReadBytesFromLocalStorage())
+                // Remote spill totals arrive in final BE reports; 
WorkloadRuntimeStatusMgr
+                // waits for the participants, with a bounded fallback if a BE 
never reports.
+                .setSpillWriteBytesToRemoteStorage(0)

Review Comment:
   Not changed: this is not specific to the remote pair. The final 
`PQueryStatistics` of a result set is filled by `GetResultBatchCtx::on_close()` 
(`be/src/exec/sink/writer/vmysql_result_writer.cpp`) with `returned_rows` only, 
so on master the scan bytes, CPU time, peak memory and local spill bytes of the 
audit event all come from the same `WorkloadRuntimeStatusMgr` BE reports 
(`runAfterCatalogReady` overwrites them from `queryStatisticsMap`) and are lost 
in exactly the same way when `clearReportTimeoutBeStatistics()` evicts an idle 
BE's entry before the audit event is queued. This PR adds two fields to that 
path and makes the audit wait for the participants' final reports while they 
are there. Retaining final statistics through result delivery is a change to 
the statistics manager for every field and belongs in its own PR.



-- 
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]

Reply via email to