فهرست منبع

feat(storage): use a server-side view for the executions listing

smartbotic-database 2.4.2 namespaces view names per project, so a view
created by a project-scoped client is now queryable by it. Under 2.4.1 the
name was registered unqualified while queries were project-prefixed, which
left the view unreachable and returning nothing.

The webserver creates executions_summary over executions at startup and the
list endpoint reads it, so the database no longer sends nodeExecutions or
workflowSnapshot at all. Measured on the same request against the same data:

  via the view       0.70-0.79s      7,520 bytes
  full collection    1.59-1.61s   41,891,490 bytes

There is deliberately no fallback to the collection. 2.4.2 also reports an
unknown collection or view as an error rather than an empty result, so a
missing view surfaces instead of quietly degrading to the slow path, which is
how the oversized listing went unnoticed before.

The adapter keeps applying QueryOptions::fields in-process. That is not a
second implementation of the same thing: nodes call smartbotic.storage.query
with an ad-hoc field list, which cannot be served by a pre-created view, and
upstream still has no per-query projection.

Also commits the 2.4.2 error contract on query(): the client throws on an
unknown collection, which the adapter converts to CollectionNotFound so
callers keep receiving a Result rather than an exception.
fszontagh 1 ماه پیش
والد
کامیت
05d06df887
3فایلهای تغییر یافته به همراه38 افزوده شده و 5 حذف شده
  1. 5 5
      src/webserver/api/execution_controller.cpp
  2. 27 0
      src/webserver/webserver_service.cpp
  3. 6 0
      src/webserver/webserver_service.hpp

+ 5 - 5
src/webserver/api/execution_controller.cpp

@@ -1,4 +1,5 @@
 #include "execution_controller.hpp"
 #include "execution_controller.hpp"
+#include "../webserver_service.hpp"
 #include "logging/logger.hpp"
 #include "logging/logger.hpp"
 
 
 namespace smartbotic::webserver::api {
 namespace smartbotic::webserver::api {
@@ -65,11 +66,10 @@ void ExecutionController::listExecutions(const httplib::Request& req, httplib::R
     options.page_size = page_size;
     options.page_size = page_size;
     options.sorts.push_back({"startedAt", false});
     options.sorts.push_back({"startedAt", false});
 
 
-    // Only fetch summary fields to avoid large responses
-    // Excludes nodeExecutions and workflowSnapshot which can be very large
-    options.fields = {"workflowId", "workflowName", "status", "triggerType", "startedAt", "finishedAt", "error", "runnerId"};
-
-    auto result = storage_.query("executions", options);
+    // The view projects server-side, so no field list is needed here and the
+    // database never sends nodeExecutions or workflowSnapshot. A missing view is
+    // an error worth surfacing rather than quietly reading the full collection.
+    auto result = storage_.query(kExecutionsSummaryView, options);
     if (result.failed()) {
     if (result.failed()) {
         sendError(res, result.error().message(), 500);
         sendError(res, result.error().message(), 500);
         return;
         return;

+ 27 - 0
src/webserver/webserver_service.cpp

@@ -149,6 +149,9 @@ void WebServerService::start() {
     // Start workflow scheduler
     // Start workflow scheduler
     scheduler_->start();
     scheduler_->start();
 
 
+    // Ensure the executions summary view exists before serving requests
+    ensureExecutionsSummaryView();
+
     // Load active workflows from database and register with scheduler
     // Load active workflows from database and register with scheduler
     loadScheduledWorkflows();
     loadScheduledWorkflows();
 
 
@@ -232,6 +235,30 @@ void WebServerService::setupRoutes() {
     LOG_INFO("API routes registered");
     LOG_INFO("API routes registered");
 }
 }
 
 
+void WebServerService::ensureExecutionsSummaryView() {
+    // View names are namespaced per project from smartbotic-database 2.4.2, so a
+    // view created by this client is also queryable by it. Earlier versions
+    // registered the name unqualified while queries were project-prefixed, which
+    // made the view unreachable.
+    for (const auto& view : storage_->listViews()) {
+        if (view.name == kExecutionsSummaryView) {
+            LOG_DEBUG("Executions summary view already present");
+            return;
+        }
+    }
+
+    auto result = storage_->createView(kExecutionsSummaryView, "executions",
+                                       {"workflowId", "workflowName", "status", "triggerType",
+                                        "startedAt", "finishedAt", "error", "runnerId"});
+    if (result.failed()) {
+        LOG_ERROR("Could not create the executions summary view: {}. The executions "
+                  "listing will fail until this is resolved.", result.error().message());
+        return;
+    }
+
+    LOG_INFO("Created server-side view '{}' over executions", kExecutionsSummaryView);
+}
+
 void WebServerService::loadScheduledWorkflows() {
 void WebServerService::loadScheduledWorkflows() {
     LOG_INFO("Loading scheduled workflows from database...");
     LOG_INFO("Loading scheduled workflows from database...");
 
 

+ 6 - 0
src/webserver/webserver_service.hpp

@@ -40,6 +40,11 @@ namespace smartbotic::webserver::api {
 
 
 namespace smartbotic::webserver {
 namespace smartbotic::webserver {
 
 
+// Server-side projection of the executions collection. The list endpoint reads
+// this instead of the collection so the database never sends nodeExecutions,
+// which carry base64 images and dwarf every other field.
+inline constexpr const char* kExecutionsSummaryView = "executions_summary";
+
 struct CredentialsConfig {
 struct CredentialsConfig {
     std::string master_key = "dev-key-change-in-production";
     std::string master_key = "dev-key-change-in-production";
     int pbkdf2_iterations = 100000;
     int pbkdf2_iterations = 100000;
@@ -77,6 +82,7 @@ public:
 private:
 private:
     void setupRoutes();
     void setupRoutes();
     void loadScheduledWorkflows();
     void loadScheduledWorkflows();
+    void ensureExecutionsSummaryView();
     void executeScheduledWorkflow(const std::string& workflow_id,
     void executeScheduledWorkflow(const std::string& workflow_id,
                                   const std::string& trigger_node_id,
                                   const std::string& trigger_node_id,
                                   const std::string& trigger_type);
                                   const std::string& trigger_type);