From c2f8d5d68624b6e889e055becaad74957cf511b2 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 10 Feb 2026 17:41:47 +0100 Subject: [PATCH] add benchmark variants and fix db pool connection starvation (#7884) Add new BENCHMARK_KIND variants (sequentialflow, scriptlogs, concurrencylimit, concurrencykey, mixed, mixed_no_cc) for targeted performance testing. Fix shared iteration counting across workers using a global atomic counter. Add job_perms inserts and queue diagnostics for benchmark mode. Move db connection setup to dedicated module and drop the initial connection pool before creating the main one, preventing connection starvation when PostgreSQL max_connections is low. Co-authored-by: Claude Opus 4.6 --- ...001f29e58e81b3f782887db6e0a627dd8ad75.json | 3 +- ...82b33947c5a5c702ce586e6e6ea51430380bf.json | 3 +- ...1f7f387f5055c47f493271d26731336257384.json | 10 +- ...bc7e9ee85289ff5c52af43166928337c257e7.json | 3 +- ...7b23d6dcf2e5364852a5e85b3c41c417bace2.json | 3 +- ...9e8c0bb132b70668301b535872d1632753e30.json | 3 +- ...66f38ffe2f3a0958adea53e67757c6ac463ec.json | 3 +- ...243ad2e0aef485a780daea5d76b0be2bb2ea2.json | 3 +- ...6f315c689ca772c736b333dcdf07c8086087.json} | 4 +- ...adbb2860ce52d37a56b2861de1215260ecff8.json | 6 +- ...fbe7db4887c4ac5295052c796bd78a7edc50b.json | 3 +- ...f518b42f4cdb33e034441015494bd26c172d2.json | 3 +- ...f446d88c7bc671d5e90307febfd60bb235031.json | 3 +- ...e075c6ae51404037b0a7d4d33faf6a7d6a6d8.json | 3 +- ...a94ff708ce8e0b0b4fb59e5de7c9698c75c25.json | 3 +- ...823d8e54c96889e06895c57cafae9ca27bf8b.json | 3 +- ...748e0639c2f73cbc1d8be66420c70b14c96e1.json | 3 +- ...655ddf728cd3f21ee308db7393694ccc5c7b3.json | 3 +- ...72f20bb5a0aa921a614bd02898d3f162660c2.json | 6 +- ...cad6d8995242dc20a4e14c1a8fae4fc6a9fd2.json | 3 +- ...2f7175cc6f2e02d9c565bb5159cae3e0b6835.json | 3 +- ...08532a1020cabaf5e22325a1e05f0491d785c.json | 3 +- ...e35adf640d44d607e62a1fb296e38ac3fdd36.json | 6 +- ...2ab0f2348646d79f5306c6868e2c3a8f389ee.json | 3 +- ...c6c70f7f3958564a79bb0c6b3863b1ebb0cbf.json | 3 +- ...1a803e1bced2f8e1a12f8933b7598ee85a147.json | 3 +- ...4f412c57af931f80621aee5012e9cb3535626.json | 3 +- ...8d92a983470adcd3cc850734960c905e61e83.json | 3 +- ...ca846dd5e7fe7ceb1aa8b3fcd26a2bd30eb2c.json | 3 +- ...a22e4276c1da653475628889ce17dc904fbaa.json | 3 +- ...6eb9016207975af1e37042d81df8cb6ae2c53.json | 3 +- ...506bf08c28efe82b68b3d44bafbd3d0e91c29.json | 3 +- ...d98d365acec09f2f102b7de71dcc9d356900e.json | 3 +- ...d2393e297043e4e58555d872b5c4dd89c196a.json | 3 +- ...e3dc360b88429f03dd1e2b1b55cfabe208cb7.json | 3 +- ...3a960b89e02ec40b292f3b5198baf2a1d3dbf.json | 3 +- ...fbafe5dc3cdecc17a3d5a7476b160c1b6e7e1.json | 3 +- ...2ede46b4471e21b11f607573a844013911abe.json | 3 +- ...e33244fe8ab9a803248fc23f334034d24aad4.json | 3 +- ...bb6a8f961c119516e4c33dc928cd3b4f4edbc.json | 3 +- ...9e637e9e156cd85b69126c87dfff89a47711d.json | 3 +- ...f892bf40e629ee11ee4845199481755f2789d.json | 6 +- ...9746147aa9b8d0a81642a7b7cb4738a0cad66.json | 3 +- ...d434f860922fbc021185beffb9827647feb8e.json | 3 +- ...384514987b6a4eb807ee375fb8b5cd4e92307.json | 3 +- ...13f1f882b695f896f94e5cf91d205633355a1.json | 3 +- ...d2ca5449dad0fe361bee1a9834989bfeabbae.json | 3 +- ...e1ea2fe96a1b94f5a92953fb1c1642d15c016.json | 3 +- ...1a8a9cc35f362197c0766a5787436af141106.json | 3 +- ...9352d4d1e3cb3a8a08f1c9c03055a1cab1235.json | 3 +- ...f03f72ef81db302d727e18e63a307ac902022.json | 3 +- ...c35c7342ac9a814c73f6290e1d6ebd3a55423.json | 3 +- ...26275203fc8ecddf83ca6d05c9fb77e46e7ee.json | 6 +- ...50d6a6935420fbedc83aa3ad1e9be7465c8dd.json | 3 +- ...7d42e7add6011c34d91761ba23e2c74c4032c.json | 6 +- backend/src/db_connect.rs | 159 +++++ backend/src/main.rs | 11 +- backend/update_sqlx.sh | 15 +- backend/v8.snap | Bin 500024 -> 0 bytes backend/windmill-common/src/bench.rs | 624 +++++++++++++++++- backend/windmill-common/src/lib.rs | 173 +---- backend/windmill-common/src/utils.rs | 7 + .../windmill-worker/src/result_processor.rs | 26 +- backend/windmill-worker/src/worker.rs | 195 ++++-- .../src/lib/components/DeployWorkspace.svelte | 1 - 65 files changed, 1032 insertions(+), 370 deletions(-) rename backend/.sqlx/{query-a7468e9054beed88636786c5495ac3b9d9a6086ae6212ad9237b39a0346d7d26.json => query-209dc4c1b91eeab1c12ffcd9f9e16f315c689ca772c736b333dcdf07c8086087.json} (53%) create mode 100644 backend/src/db_connect.rs delete mode 100644 backend/v8.snap diff --git a/backend/.sqlx/query-023cdbc77ea9e2c17a1aa92a5b9001f29e58e81b3f782887db6e0a627dd8ad75.json b/backend/.sqlx/query-023cdbc77ea9e2c17a1aa92a5b9001f29e58e81b3f782887db6e0a627dd8ad75.json index d3f1c39c7a..5b58cedc72 100644 --- a/backend/.sqlx/query-023cdbc77ea9e2c17a1aa92a5b9001f29e58e81b3f782887db6e0a627dd8ad75.json +++ b/backend/.sqlx/query-023cdbc77ea9e2c17a1aa92a5b9001f29e58e81b3f782887db6e0a627dd8ad75.json @@ -12,8 +12,7 @@ "name": "native_trigger_service", "kind": { "Enum": [ - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-06072cfe26abe58629623a8b38382b33947c5a5c702ce586e6e6ea51430380bf.json b/backend/.sqlx/query-06072cfe26abe58629623a8b38382b33947c5a5c702ce586e6e6ea51430380bf.json index 97e39aa0e5..8ecc409fc8 100644 --- a/backend/.sqlx/query-06072cfe26abe58629623a8b38382b33947c5a5c702ce586e6e6ea51430380bf.json +++ b/backend/.sqlx/query-06072cfe26abe58629623a8b38382b33947c5a5c702ce586e6e6ea51430380bf.json @@ -17,8 +17,7 @@ "name": "native_trigger_service", "kind": { "Enum": [ - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-07168aaf14cb6beff0ad4274b441f7f387f5055c47f493271d26731336257384.json b/backend/.sqlx/query-07168aaf14cb6beff0ad4274b441f7f387f5055c47f493271d26731336257384.json index d29a18c691..e7ed0aee65 100644 --- a/backend/.sqlx/query-07168aaf14cb6beff0ad4274b441f7f387f5055c47f493271d26731336257384.json +++ b/backend/.sqlx/query-07168aaf14cb6beff0ad4274b441f7f387f5055c47f493271d26731336257384.json @@ -46,11 +46,11 @@ ] }, "nullable": [ - true, - true, - true, - true, - true, + false, + false, + false, + false, + false, true, true ] diff --git a/backend/.sqlx/query-089d7bc7acdbb97cf477159e111bc7e9ee85289ff5c52af43166928337c257e7.json b/backend/.sqlx/query-089d7bc7acdbb97cf477159e111bc7e9ee85289ff5c52af43166928337c257e7.json index b925141065..a032a87239 100644 --- a/backend/.sqlx/query-089d7bc7acdbb97cf477159e111bc7e9ee85289ff5c52af43166928337c257e7.json +++ b/backend/.sqlx/query-089d7bc7acdbb97cf477159e111bc7e9ee85289ff5c52af43166928337c257e7.json @@ -30,8 +30,7 @@ "sqs", "gcp", "mqtt", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-0f7e01b613a94b29784aae6d7b17b23d6dcf2e5364852a5e85b3c41c417bace2.json b/backend/.sqlx/query-0f7e01b613a94b29784aae6d7b17b23d6dcf2e5364852a5e85b3c41c417bace2.json index de4dd4a7e3..1648882c1a 100644 --- a/backend/.sqlx/query-0f7e01b613a94b29784aae6d7b17b23d6dcf2e5364852a5e85b3c41c417bace2.json +++ b/backend/.sqlx/query-0f7e01b613a94b29784aae6d7b17b23d6dcf2e5364852a5e85b3c41c417bace2.json @@ -24,8 +24,7 @@ "mqtt", "gcp", "default_email", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-14276a040cb4db88d71fccdc3579e8c0bb132b70668301b535872d1632753e30.json b/backend/.sqlx/query-14276a040cb4db88d71fccdc3579e8c0bb132b70668301b535872d1632753e30.json index bed99ef1b7..d4f7afa966 100644 --- a/backend/.sqlx/query-14276a040cb4db88d71fccdc3579e8c0bb132b70668301b535872d1632753e30.json +++ b/backend/.sqlx/query-14276a040cb4db88d71fccdc3579e8c0bb132b70668301b535872d1632753e30.json @@ -122,8 +122,7 @@ "sqs", "gcp", "mqtt", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-16d438374b03a9c515f4c2d638366f38ffe2f3a0958adea53e67757c6ac463ec.json b/backend/.sqlx/query-16d438374b03a9c515f4c2d638366f38ffe2f3a0958adea53e67757c6ac463ec.json index 1fdc78c472..c091114374 100644 --- a/backend/.sqlx/query-16d438374b03a9c515f4c2d638366f38ffe2f3a0958adea53e67757c6ac463ec.json +++ b/backend/.sqlx/query-16d438374b03a9c515f4c2d638366f38ffe2f3a0958adea53e67757c6ac463ec.json @@ -24,8 +24,7 @@ "mqtt", "gcp", "default_email", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-19b59c478744d029c6006b01f04243ad2e0aef485a780daea5d76b0be2bb2ea2.json b/backend/.sqlx/query-19b59c478744d029c6006b01f04243ad2e0aef485a780daea5d76b0be2bb2ea2.json index 30e071f8ce..43c68f8c5a 100644 --- a/backend/.sqlx/query-19b59c478744d029c6006b01f04243ad2e0aef485a780daea5d76b0be2bb2ea2.json +++ b/backend/.sqlx/query-19b59c478744d029c6006b01f04243ad2e0aef485a780daea5d76b0be2bb2ea2.json @@ -40,8 +40,7 @@ "sqs", "gcp", "mqtt", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-a7468e9054beed88636786c5495ac3b9d9a6086ae6212ad9237b39a0346d7d26.json b/backend/.sqlx/query-209dc4c1b91eeab1c12ffcd9f9e16f315c689ca772c736b333dcdf07c8086087.json similarity index 53% rename from backend/.sqlx/query-a7468e9054beed88636786c5495ac3b9d9a6086ae6212ad9237b39a0346d7d26.json rename to backend/.sqlx/query-209dc4c1b91eeab1c12ffcd9f9e16f315c689ca772c736b333dcdf07c8086087.json index cead5d7019..e4fbe7efe0 100644 --- a/backend/.sqlx/query-a7468e9054beed88636786c5495ac3b9d9a6086ae6212ad9237b39a0346d7d26.json +++ b/backend/.sqlx/query-209dc4c1b91eeab1c12ffcd9f9e16f315c689ca772c736b333dcdf07c8086087.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n flow_version.id AS version,\n flow_version.value->>'early_return' as early_return, \n flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor, \n (flow_version.value->>'chat_input_enabled')::boolean as chat_input_enabled, \n flow.tag, \n flow.dedicated_worker, \n flow.on_behalf_of_email, \n flow.edited_by\n FROM \n flow_version\n INNER JOIN flow\n ON flow.path = flow_version.path AND\n flow.workspace_id = flow_version.workspace_id\n WHERE \n flow_version.workspace_id = $1 AND\n flow_version.path = $2 AND\n flow_version.id = $3\n ", + "query": "\n SELECT\n flow_version.id AS version,\n flow_version.value->>'early_return' as early_return,\n flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor,\n (flow_version.value->>'chat_input_enabled')::boolean as chat_input_enabled,\n flow.tag,\n flow.dedicated_worker,\n flow.on_behalf_of_email,\n flow.edited_by\n FROM\n flow_version\n INNER JOIN flow\n ON flow.path = flow_version.path AND\n flow.workspace_id = flow_version.workspace_id\n WHERE\n flow_version.workspace_id = $1 AND\n flow_version.path = $2 AND\n flow_version.id = $3\n ", "describe": { "columns": [ { @@ -62,5 +62,5 @@ false ] }, - "hash": "a7468e9054beed88636786c5495ac3b9d9a6086ae6212ad9237b39a0346d7d26" + "hash": "209dc4c1b91eeab1c12ffcd9f9e16f315c689ca772c736b333dcdf07c8086087" } diff --git a/backend/.sqlx/query-212553c83e4dcdc6d045eb2fe2dadbb2860ce52d37a56b2861de1215260ecff8.json b/backend/.sqlx/query-212553c83e4dcdc6d045eb2fe2dadbb2860ce52d37a56b2861de1215260ecff8.json index b0354db034..7d8b2eba1e 100644 --- a/backend/.sqlx/query-212553c83e4dcdc6d045eb2fe2dadbb2860ce52d37a56b2861de1215260ecff8.json +++ b/backend/.sqlx/query-212553c83e4dcdc6d045eb2fe2dadbb2860ce52d37a56b2861de1215260ecff8.json @@ -34,8 +34,7 @@ "sqs", "gcp", "mqtt", - "nextcloud", - "google" + "nextcloud" ] } } @@ -68,8 +67,7 @@ "sqs", "gcp", "mqtt", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-22e0e8a1aa48f8b21763452bd36fbe7db4887c4ac5295052c796bd78a7edc50b.json b/backend/.sqlx/query-22e0e8a1aa48f8b21763452bd36fbe7db4887c4ac5295052c796bd78a7edc50b.json index 235f255dd1..66c9e6e96a 100644 --- a/backend/.sqlx/query-22e0e8a1aa48f8b21763452bd36fbe7db4887c4ac5295052c796bd78a7edc50b.json +++ b/backend/.sqlx/query-22e0e8a1aa48f8b21763452bd36fbe7db4887c4ac5295052c796bd78a7edc50b.json @@ -24,8 +24,7 @@ "mqtt", "gcp", "default_email", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-23419adcd74c326d716527293eff518b42f4cdb33e034441015494bd26c172d2.json b/backend/.sqlx/query-23419adcd74c326d716527293eff518b42f4cdb33e034441015494bd26c172d2.json index 7bd7367d8d..242b358ff5 100644 --- a/backend/.sqlx/query-23419adcd74c326d716527293eff518b42f4cdb33e034441015494bd26c172d2.json +++ b/backend/.sqlx/query-23419adcd74c326d716527293eff518b42f4cdb33e034441015494bd26c172d2.json @@ -40,8 +40,7 @@ "mqtt", "gcp", "default_email", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-256118bb9c87675d6a8db2d13c4f446d88c7bc671d5e90307febfd60bb235031.json b/backend/.sqlx/query-256118bb9c87675d6a8db2d13c4f446d88c7bc671d5e90307febfd60bb235031.json index c51175e921..4e48ae281c 100644 --- a/backend/.sqlx/query-256118bb9c87675d6a8db2d13c4f446d88c7bc671d5e90307febfd60bb235031.json +++ b/backend/.sqlx/query-256118bb9c87675d6a8db2d13c4f446d88c7bc671d5e90307febfd60bb235031.json @@ -16,8 +16,7 @@ "name": "native_trigger_service", "kind": { "Enum": [ - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-2602402bedcdbc45cdc0d64a76ce075c6ae51404037b0a7d4d33faf6a7d6a6d8.json b/backend/.sqlx/query-2602402bedcdbc45cdc0d64a76ce075c6ae51404037b0a7d4d33faf6a7d6a6d8.json index 5a4cd89224..128a435c8b 100644 --- a/backend/.sqlx/query-2602402bedcdbc45cdc0d64a76ce075c6ae51404037b0a7d4d33faf6a7d6a6d8.json +++ b/backend/.sqlx/query-2602402bedcdbc45cdc0d64a76ce075c6ae51404037b0a7d4d33faf6a7d6a6d8.json @@ -11,8 +11,7 @@ "name": "native_trigger_service", "kind": { "Enum": [ - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-26141a1c3c48184285d94a2e2aca94ff708ce8e0b0b4fb59e5de7c9698c75c25.json b/backend/.sqlx/query-26141a1c3c48184285d94a2e2aca94ff708ce8e0b0b4fb59e5de7c9698c75c25.json index 08c6c1cbc2..9abe8af9af 100644 --- a/backend/.sqlx/query-26141a1c3c48184285d94a2e2aca94ff708ce8e0b0b4fb59e5de7c9698c75c25.json +++ b/backend/.sqlx/query-26141a1c3c48184285d94a2e2aca94ff708ce8e0b0b4fb59e5de7c9698c75c25.json @@ -11,8 +11,7 @@ "name": "native_trigger_service", "kind": { "Enum": [ - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-27ada97cb533c8595f1d73987c7823d8e54c96889e06895c57cafae9ca27bf8b.json b/backend/.sqlx/query-27ada97cb533c8595f1d73987c7823d8e54c96889e06895c57cafae9ca27bf8b.json index 1199e9441f..5d7c0e2abc 100644 --- a/backend/.sqlx/query-27ada97cb533c8595f1d73987c7823d8e54c96889e06895c57cafae9ca27bf8b.json +++ b/backend/.sqlx/query-27ada97cb533c8595f1d73987c7823d8e54c96889e06895c57cafae9ca27bf8b.json @@ -15,8 +15,7 @@ "name": "native_trigger_service", "kind": { "Enum": [ - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-2fd22c4ffa2d222bb116260994a748e0639c2f73cbc1d8be66420c70b14c96e1.json b/backend/.sqlx/query-2fd22c4ffa2d222bb116260994a748e0639c2f73cbc1d8be66420c70b14c96e1.json index afd0f503bf..8f6c8edf25 100644 --- a/backend/.sqlx/query-2fd22c4ffa2d222bb116260994a748e0639c2f73cbc1d8be66420c70b14c96e1.json +++ b/backend/.sqlx/query-2fd22c4ffa2d222bb116260994a748e0639c2f73cbc1d8be66420c70b14c96e1.json @@ -12,8 +12,7 @@ "name": "native_trigger_service", "kind": { "Enum": [ - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-3b3f60623126626b52ca0a4a188655ddf728cd3f21ee308db7393694ccc5c7b3.json b/backend/.sqlx/query-3b3f60623126626b52ca0a4a188655ddf728cd3f21ee308db7393694ccc5c7b3.json index 0df550b57c..27b2503991 100644 --- a/backend/.sqlx/query-3b3f60623126626b52ca0a4a188655ddf728cd3f21ee308db7393694ccc5c7b3.json +++ b/backend/.sqlx/query-3b3f60623126626b52ca0a4a188655ddf728cd3f21ee308db7393694ccc5c7b3.json @@ -12,8 +12,7 @@ "name": "native_trigger_service", "kind": { "Enum": [ - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-3e5bdc2e071fc2f1e3c7971736272f20bb5a0aa921a614bd02898d3f162660c2.json b/backend/.sqlx/query-3e5bdc2e071fc2f1e3c7971736272f20bb5a0aa921a614bd02898d3f162660c2.json index fdcb10be1c..e6dc6ff08b 100644 --- a/backend/.sqlx/query-3e5bdc2e071fc2f1e3c7971736272f20bb5a0aa921a614bd02898d3f162660c2.json +++ b/backend/.sqlx/query-3e5bdc2e071fc2f1e3c7971736272f20bb5a0aa921a614bd02898d3f162660c2.json @@ -16,8 +16,7 @@ "name": "native_trigger_service", "kind": { "Enum": [ - "nextcloud", - "google" + "nextcloud" ] } } @@ -52,8 +51,7 @@ "name": "native_trigger_service", "kind": { "Enum": [ - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-42b4b73e9d60348e2d90fcade9dcad6d8995242dc20a4e14c1a8fae4fc6a9fd2.json b/backend/.sqlx/query-42b4b73e9d60348e2d90fcade9dcad6d8995242dc20a4e14c1a8fae4fc6a9fd2.json index 537ecbeacf..e6d71f386f 100644 --- a/backend/.sqlx/query-42b4b73e9d60348e2d90fcade9dcad6d8995242dc20a4e14c1a8fae4fc6a9fd2.json +++ b/backend/.sqlx/query-42b4b73e9d60348e2d90fcade9dcad6d8995242dc20a4e14c1a8fae4fc6a9fd2.json @@ -24,8 +24,7 @@ "mqtt", "gcp", "default_email", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-45024b932383199974616bba1fc2f7175cc6f2e02d9c565bb5159cae3e0b6835.json b/backend/.sqlx/query-45024b932383199974616bba1fc2f7175cc6f2e02d9c565bb5159cae3e0b6835.json index 30c5ff7a49..f215cf6147 100644 --- a/backend/.sqlx/query-45024b932383199974616bba1fc2f7175cc6f2e02d9c565bb5159cae3e0b6835.json +++ b/backend/.sqlx/query-45024b932383199974616bba1fc2f7175cc6f2e02d9c565bb5159cae3e0b6835.json @@ -30,8 +30,7 @@ "mqtt", "gcp", "default_email", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-4d272cf4a77aab7007a5b35589e08532a1020cabaf5e22325a1e05f0491d785c.json b/backend/.sqlx/query-4d272cf4a77aab7007a5b35589e08532a1020cabaf5e22325a1e05f0491d785c.json index cfb975b838..896b21dc7f 100644 --- a/backend/.sqlx/query-4d272cf4a77aab7007a5b35589e08532a1020cabaf5e22325a1e05f0491d785c.json +++ b/backend/.sqlx/query-4d272cf4a77aab7007a5b35589e08532a1020cabaf5e22325a1e05f0491d785c.json @@ -37,8 +37,7 @@ "mqtt", "gcp", "default_email", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-4f547c0fd54f3bc57212ce87810e35adf640d44d607e62a1fb296e38ac3fdd36.json b/backend/.sqlx/query-4f547c0fd54f3bc57212ce87810e35adf640d44d607e62a1fb296e38ac3fdd36.json index 4e8ec56d12..789c0334d7 100644 --- a/backend/.sqlx/query-4f547c0fd54f3bc57212ce87810e35adf640d44d607e62a1fb296e38ac3fdd36.json +++ b/backend/.sqlx/query-4f547c0fd54f3bc57212ce87810e35adf640d44d607e62a1fb296e38ac3fdd36.json @@ -32,8 +32,7 @@ "mqtt", "gcp", "default_email", - "nextcloud", - "google" + "nextcloud" ] } } @@ -71,8 +70,7 @@ "mqtt", "gcp", "default_email", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-5368683c19f8d6744d5dbc53e5b2ab0f2348646d79f5306c6868e2c3a8f389ee.json b/backend/.sqlx/query-5368683c19f8d6744d5dbc53e5b2ab0f2348646d79f5306c6868e2c3a8f389ee.json index ebf1df39a3..1058d78d8b 100644 --- a/backend/.sqlx/query-5368683c19f8d6744d5dbc53e5b2ab0f2348646d79f5306c6868e2c3a8f389ee.json +++ b/backend/.sqlx/query-5368683c19f8d6744d5dbc53e5b2ab0f2348646d79f5306c6868e2c3a8f389ee.json @@ -16,8 +16,7 @@ "name": "native_trigger_service", "kind": { "Enum": [ - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-5bf200f2c8db25ddf231b564503c6c70f7f3958564a79bb0c6b3863b1ebb0cbf.json b/backend/.sqlx/query-5bf200f2c8db25ddf231b564503c6c70f7f3958564a79bb0c6b3863b1ebb0cbf.json index 9422e3e8d0..6d3941ad69 100644 --- a/backend/.sqlx/query-5bf200f2c8db25ddf231b564503c6c70f7f3958564a79bb0c6b3863b1ebb0cbf.json +++ b/backend/.sqlx/query-5bf200f2c8db25ddf231b564503c6c70f7f3958564a79bb0c6b3863b1ebb0cbf.json @@ -245,8 +245,7 @@ "sqs", "gcp", "mqtt", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-66a0e51cf149ba532463e29dd361a803e1bced2f8e1a12f8933b7598ee85a147.json b/backend/.sqlx/query-66a0e51cf149ba532463e29dd361a803e1bced2f8e1a12f8933b7598ee85a147.json index 5f9be1cba9..0d12be2448 100644 --- a/backend/.sqlx/query-66a0e51cf149ba532463e29dd361a803e1bced2f8e1a12f8933b7598ee85a147.json +++ b/backend/.sqlx/query-66a0e51cf149ba532463e29dd361a803e1bced2f8e1a12f8933b7598ee85a147.json @@ -35,8 +35,7 @@ "mqtt", "gcp", "default_email", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-7065f23d04e26831664048f2cfc4f412c57af931f80621aee5012e9cb3535626.json b/backend/.sqlx/query-7065f23d04e26831664048f2cfc4f412c57af931f80621aee5012e9cb3535626.json index 48ea42f8a2..02823e8291 100644 --- a/backend/.sqlx/query-7065f23d04e26831664048f2cfc4f412c57af931f80621aee5012e9cb3535626.json +++ b/backend/.sqlx/query-7065f23d04e26831664048f2cfc4f412c57af931f80621aee5012e9cb3535626.json @@ -29,8 +29,7 @@ "mqtt", "gcp", "default_email", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-73fdd01bad58b8be1a52f89faef8d92a983470adcd3cc850734960c905e61e83.json b/backend/.sqlx/query-73fdd01bad58b8be1a52f89faef8d92a983470adcd3cc850734960c905e61e83.json index 8aacd8a805..cba7ffdfef 100644 --- a/backend/.sqlx/query-73fdd01bad58b8be1a52f89faef8d92a983470adcd3cc850734960c905e61e83.json +++ b/backend/.sqlx/query-73fdd01bad58b8be1a52f89faef8d92a983470adcd3cc850734960c905e61e83.json @@ -24,8 +24,7 @@ "mqtt", "gcp", "default_email", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-757ef6215d3d385cb3a69e26ee4ca846dd5e7fe7ceb1aa8b3fcd26a2bd30eb2c.json b/backend/.sqlx/query-757ef6215d3d385cb3a69e26ee4ca846dd5e7fe7ceb1aa8b3fcd26a2bd30eb2c.json index 59b56ceda7..cd795e6fec 100644 --- a/backend/.sqlx/query-757ef6215d3d385cb3a69e26ee4ca846dd5e7fe7ceb1aa8b3fcd26a2bd30eb2c.json +++ b/backend/.sqlx/query-757ef6215d3d385cb3a69e26ee4ca846dd5e7fe7ceb1aa8b3fcd26a2bd30eb2c.json @@ -40,8 +40,7 @@ "sqs", "gcp", "mqtt", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-7fbf72d9059fcd77e4c1112fa4fa22e4276c1da653475628889ce17dc904fbaa.json b/backend/.sqlx/query-7fbf72d9059fcd77e4c1112fa4fa22e4276c1da653475628889ce17dc904fbaa.json index 8c380df861..393a920b7c 100644 --- a/backend/.sqlx/query-7fbf72d9059fcd77e4c1112fa4fa22e4276c1da653475628889ce17dc904fbaa.json +++ b/backend/.sqlx/query-7fbf72d9059fcd77e4c1112fa4fa22e4276c1da653475628889ce17dc904fbaa.json @@ -27,8 +27,7 @@ "mqtt", "gcp", "default_email", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-836bac47d89113d90bd03a471446eb9016207975af1e37042d81df8cb6ae2c53.json b/backend/.sqlx/query-836bac47d89113d90bd03a471446eb9016207975af1e37042d81df8cb6ae2c53.json index 3012e9ef77..437d644eb2 100644 --- a/backend/.sqlx/query-836bac47d89113d90bd03a471446eb9016207975af1e37042d81df8cb6ae2c53.json +++ b/backend/.sqlx/query-836bac47d89113d90bd03a471446eb9016207975af1e37042d81df8cb6ae2c53.json @@ -24,8 +24,7 @@ "mqtt", "gcp", "default_email", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-87564a196a1662f524407d853db506bf08c28efe82b68b3d44bafbd3d0e91c29.json b/backend/.sqlx/query-87564a196a1662f524407d853db506bf08c28efe82b68b3d44bafbd3d0e91c29.json index 5a61f82be3..9350442134 100644 --- a/backend/.sqlx/query-87564a196a1662f524407d853db506bf08c28efe82b68b3d44bafbd3d0e91c29.json +++ b/backend/.sqlx/query-87564a196a1662f524407d853db506bf08c28efe82b68b3d44bafbd3d0e91c29.json @@ -35,8 +35,7 @@ "mqtt", "gcp", "default_email", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-9052b7cd438ff029a37bd489190d98d365acec09f2f102b7de71dcc9d356900e.json b/backend/.sqlx/query-9052b7cd438ff029a37bd489190d98d365acec09f2f102b7de71dcc9d356900e.json index 62f8d3100f..02cad7d11d 100644 --- a/backend/.sqlx/query-9052b7cd438ff029a37bd489190d98d365acec09f2f102b7de71dcc9d356900e.json +++ b/backend/.sqlx/query-9052b7cd438ff029a37bd489190d98d365acec09f2f102b7de71dcc9d356900e.json @@ -17,8 +17,7 @@ "name": "native_trigger_service", "kind": { "Enum": [ - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-940b6d78bab940a37a42492f030d2393e297043e4e58555d872b5c4dd89c196a.json b/backend/.sqlx/query-940b6d78bab940a37a42492f030d2393e297043e4e58555d872b5c4dd89c196a.json index 09aadbda1e..47554ad43e 100644 --- a/backend/.sqlx/query-940b6d78bab940a37a42492f030d2393e297043e4e58555d872b5c4dd89c196a.json +++ b/backend/.sqlx/query-940b6d78bab940a37a42492f030d2393e297043e4e58555d872b5c4dd89c196a.json @@ -24,8 +24,7 @@ "mqtt", "gcp", "default_email", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-95c57fb921a2e3725b92cbafac6e3dc360b88429f03dd1e2b1b55cfabe208cb7.json b/backend/.sqlx/query-95c57fb921a2e3725b92cbafac6e3dc360b88429f03dd1e2b1b55cfabe208cb7.json index a189bc4da2..4b989c893b 100644 --- a/backend/.sqlx/query-95c57fb921a2e3725b92cbafac6e3dc360b88429f03dd1e2b1b55cfabe208cb7.json +++ b/backend/.sqlx/query-95c57fb921a2e3725b92cbafac6e3dc360b88429f03dd1e2b1b55cfabe208cb7.json @@ -17,8 +17,7 @@ "name": "native_trigger_service", "kind": { "Enum": [ - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-9c50e3a136a8ee3ec56e083f26d3a960b89e02ec40b292f3b5198baf2a1d3dbf.json b/backend/.sqlx/query-9c50e3a136a8ee3ec56e083f26d3a960b89e02ec40b292f3b5198baf2a1d3dbf.json index 66a487695f..9759bad4d4 100644 --- a/backend/.sqlx/query-9c50e3a136a8ee3ec56e083f26d3a960b89e02ec40b292f3b5198baf2a1d3dbf.json +++ b/backend/.sqlx/query-9c50e3a136a8ee3ec56e083f26d3a960b89e02ec40b292f3b5198baf2a1d3dbf.json @@ -32,8 +32,7 @@ "mqtt", "gcp", "default_email", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-9ecb404e46a4eac55f977f05a3afbafe5dc3cdecc17a3d5a7476b160c1b6e7e1.json b/backend/.sqlx/query-9ecb404e46a4eac55f977f05a3afbafe5dc3cdecc17a3d5a7476b160c1b6e7e1.json index b6fe1b7fa0..79c8f0b45d 100644 --- a/backend/.sqlx/query-9ecb404e46a4eac55f977f05a3afbafe5dc3cdecc17a3d5a7476b160c1b6e7e1.json +++ b/backend/.sqlx/query-9ecb404e46a4eac55f977f05a3afbafe5dc3cdecc17a3d5a7476b160c1b6e7e1.json @@ -30,8 +30,7 @@ "sqs", "gcp", "mqtt", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-a1745a4f525b251d2f5a602ab2b2ede46b4471e21b11f607573a844013911abe.json b/backend/.sqlx/query-a1745a4f525b251d2f5a602ab2b2ede46b4471e21b11f607573a844013911abe.json index 33636da608..a25f845f91 100644 --- a/backend/.sqlx/query-a1745a4f525b251d2f5a602ab2b2ede46b4471e21b11f607573a844013911abe.json +++ b/backend/.sqlx/query-a1745a4f525b251d2f5a602ab2b2ede46b4471e21b11f607573a844013911abe.json @@ -155,8 +155,7 @@ "sqs", "gcp", "mqtt", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-a4b6371d33206010b2f3ffd2b09e33244fe8ab9a803248fc23f334034d24aad4.json b/backend/.sqlx/query-a4b6371d33206010b2f3ffd2b09e33244fe8ab9a803248fc23f334034d24aad4.json index 03ee58ca3f..da6d213748 100644 --- a/backend/.sqlx/query-a4b6371d33206010b2f3ffd2b09e33244fe8ab9a803248fc23f334034d24aad4.json +++ b/backend/.sqlx/query-a4b6371d33206010b2f3ffd2b09e33244fe8ab9a803248fc23f334034d24aad4.json @@ -185,8 +185,7 @@ "sqs", "gcp", "mqtt", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-b3771b690c5966272b1f42c9965bb6a8f961c119516e4c33dc928cd3b4f4edbc.json b/backend/.sqlx/query-b3771b690c5966272b1f42c9965bb6a8f961c119516e4c33dc928cd3b4f4edbc.json index 4fa7da00e0..d08af6ffdd 100644 --- a/backend/.sqlx/query-b3771b690c5966272b1f42c9965bb6a8f961c119516e4c33dc928cd3b4f4edbc.json +++ b/backend/.sqlx/query-b3771b690c5966272b1f42c9965bb6a8f961c119516e4c33dc928cd3b4f4edbc.json @@ -160,8 +160,7 @@ "sqs", "gcp", "mqtt", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-b3f0595cacba194e08b9a3e244d9e637e9e156cd85b69126c87dfff89a47711d.json b/backend/.sqlx/query-b3f0595cacba194e08b9a3e244d9e637e9e156cd85b69126c87dfff89a47711d.json index 186822a000..54a4e3cd93 100644 --- a/backend/.sqlx/query-b3f0595cacba194e08b9a3e244d9e637e9e156cd85b69126c87dfff89a47711d.json +++ b/backend/.sqlx/query-b3f0595cacba194e08b9a3e244d9e637e9e156cd85b69126c87dfff89a47711d.json @@ -24,8 +24,7 @@ "mqtt", "gcp", "default_email", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-b615d73ddb43e9d655b86a0cf98f892bf40e629ee11ee4845199481755f2789d.json b/backend/.sqlx/query-b615d73ddb43e9d655b86a0cf98f892bf40e629ee11ee4845199481755f2789d.json index e6ef13f492..96f59aab3f 100644 --- a/backend/.sqlx/query-b615d73ddb43e9d655b86a0cf98f892bf40e629ee11ee4845199481755f2789d.json +++ b/backend/.sqlx/query-b615d73ddb43e9d655b86a0cf98f892bf40e629ee11ee4845199481755f2789d.json @@ -21,8 +21,7 @@ "name": "native_trigger_service", "kind": { "Enum": [ - "nextcloud", - "google" + "nextcloud" ] } } @@ -72,8 +71,7 @@ "name": "native_trigger_service", "kind": { "Enum": [ - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-be6d2c92a62b7b284651c45af809746147aa9b8d0a81642a7b7cb4738a0cad66.json b/backend/.sqlx/query-be6d2c92a62b7b284651c45af809746147aa9b8d0a81642a7b7cb4738a0cad66.json index 4118760af2..0688afebd3 100644 --- a/backend/.sqlx/query-be6d2c92a62b7b284651c45af809746147aa9b8d0a81642a7b7cb4738a0cad66.json +++ b/backend/.sqlx/query-be6d2c92a62b7b284651c45af809746147aa9b8d0a81642a7b7cb4738a0cad66.json @@ -105,8 +105,7 @@ "sqs", "gcp", "mqtt", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-c3b1152b554812d65eb27f95b1fd434f860922fbc021185beffb9827647feb8e.json b/backend/.sqlx/query-c3b1152b554812d65eb27f95b1fd434f860922fbc021185beffb9827647feb8e.json index 07243717a0..0098e51ab2 100644 --- a/backend/.sqlx/query-c3b1152b554812d65eb27f95b1fd434f860922fbc021185beffb9827647feb8e.json +++ b/backend/.sqlx/query-c3b1152b554812d65eb27f95b1fd434f860922fbc021185beffb9827647feb8e.json @@ -31,8 +31,7 @@ "mqtt", "gcp", "default_email", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-cc5919b087e3319045fc2242f22384514987b6a4eb807ee375fb8b5cd4e92307.json b/backend/.sqlx/query-cc5919b087e3319045fc2242f22384514987b6a4eb807ee375fb8b5cd4e92307.json index 8a0dacd864..0e338633fe 100644 --- a/backend/.sqlx/query-cc5919b087e3319045fc2242f22384514987b6a4eb807ee375fb8b5cd4e92307.json +++ b/backend/.sqlx/query-cc5919b087e3319045fc2242f22384514987b6a4eb807ee375fb8b5cd4e92307.json @@ -11,8 +11,7 @@ "name": "native_trigger_service", "kind": { "Enum": [ - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-ccef7a1bde5cac6c362c5fedb6c13f1f882b695f896f94e5cf91d205633355a1.json b/backend/.sqlx/query-ccef7a1bde5cac6c362c5fedb6c13f1f882b695f896f94e5cf91d205633355a1.json index 2b39145512..5091b2fc69 100644 --- a/backend/.sqlx/query-ccef7a1bde5cac6c362c5fedb6c13f1f882b695f896f94e5cf91d205633355a1.json +++ b/backend/.sqlx/query-ccef7a1bde5cac6c362c5fedb6c13f1f882b695f896f94e5cf91d205633355a1.json @@ -24,8 +24,7 @@ "mqtt", "gcp", "default_email", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-d0b493da8ff5b3b78a9a0c6972cd2ca5449dad0fe361bee1a9834989bfeabbae.json b/backend/.sqlx/query-d0b493da8ff5b3b78a9a0c6972cd2ca5449dad0fe361bee1a9834989bfeabbae.json index b71bf15f85..b0cef08da3 100644 --- a/backend/.sqlx/query-d0b493da8ff5b3b78a9a0c6972cd2ca5449dad0fe361bee1a9834989bfeabbae.json +++ b/backend/.sqlx/query-d0b493da8ff5b3b78a9a0c6972cd2ca5449dad0fe361bee1a9834989bfeabbae.json @@ -12,8 +12,7 @@ "name": "native_trigger_service", "kind": { "Enum": [ - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-d41ea93fd58381b89e151c965eae1ea2fe96a1b94f5a92953fb1c1642d15c016.json b/backend/.sqlx/query-d41ea93fd58381b89e151c965eae1ea2fe96a1b94f5a92953fb1c1642d15c016.json index 9d2f8d9a9f..11271e94d8 100644 --- a/backend/.sqlx/query-d41ea93fd58381b89e151c965eae1ea2fe96a1b94f5a92953fb1c1642d15c016.json +++ b/backend/.sqlx/query-d41ea93fd58381b89e151c965eae1ea2fe96a1b94f5a92953fb1c1642d15c016.json @@ -105,8 +105,7 @@ "sqs", "gcp", "mqtt", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-d495c94b580fd34d5ae90615ef21a8a9cc35f362197c0766a5787436af141106.json b/backend/.sqlx/query-d495c94b580fd34d5ae90615ef21a8a9cc35f362197c0766a5787436af141106.json index 67a38d1904..9cef6500d3 100644 --- a/backend/.sqlx/query-d495c94b580fd34d5ae90615ef21a8a9cc35f362197c0766a5787436af141106.json +++ b/backend/.sqlx/query-d495c94b580fd34d5ae90615ef21a8a9cc35f362197c0766a5787436af141106.json @@ -25,8 +25,7 @@ "mqtt", "gcp", "default_email", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-e4d71278fb80126a7a9da73f1889352d4d1e3cb3a8a08f1c9c03055a1cab1235.json b/backend/.sqlx/query-e4d71278fb80126a7a9da73f1889352d4d1e3cb3a8a08f1c9c03055a1cab1235.json index 7309b03a02..5b07bcd9c9 100644 --- a/backend/.sqlx/query-e4d71278fb80126a7a9da73f1889352d4d1e3cb3a8a08f1c9c03055a1cab1235.json +++ b/backend/.sqlx/query-e4d71278fb80126a7a9da73f1889352d4d1e3cb3a8a08f1c9c03055a1cab1235.json @@ -185,8 +185,7 @@ "sqs", "gcp", "mqtt", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-e80177f3ffd4c1f52cdb4757483f03f72ef81db302d727e18e63a307ac902022.json b/backend/.sqlx/query-e80177f3ffd4c1f52cdb4757483f03f72ef81db302d727e18e63a307ac902022.json index dd65a586d1..1e38e57fb5 100644 --- a/backend/.sqlx/query-e80177f3ffd4c1f52cdb4757483f03f72ef81db302d727e18e63a307ac902022.json +++ b/backend/.sqlx/query-e80177f3ffd4c1f52cdb4757483f03f72ef81db302d727e18e63a307ac902022.json @@ -31,8 +31,7 @@ "mqtt", "gcp", "default_email", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-eac595e19e5c8e70f1514ef29dec35c7342ac9a814c73f6290e1d6ebd3a55423.json b/backend/.sqlx/query-eac595e19e5c8e70f1514ef29dec35c7342ac9a814c73f6290e1d6ebd3a55423.json index 7c45c44b13..57607ae052 100644 --- a/backend/.sqlx/query-eac595e19e5c8e70f1514ef29dec35c7342ac9a814c73f6290e1d6ebd3a55423.json +++ b/backend/.sqlx/query-eac595e19e5c8e70f1514ef29dec35c7342ac9a814c73f6290e1d6ebd3a55423.json @@ -24,8 +24,7 @@ "mqtt", "gcp", "default_email", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-ecab1af12a7afa685c056b9d0e526275203fc8ecddf83ca6d05c9fb77e46e7ee.json b/backend/.sqlx/query-ecab1af12a7afa685c056b9d0e526275203fc8ecddf83ca6d05c9fb77e46e7ee.json index 16ccdd10e9..f80b75b5ec 100644 --- a/backend/.sqlx/query-ecab1af12a7afa685c056b9d0e526275203fc8ecddf83ca6d05c9fb77e46e7ee.json +++ b/backend/.sqlx/query-ecab1af12a7afa685c056b9d0e526275203fc8ecddf83ca6d05c9fb77e46e7ee.json @@ -21,8 +21,7 @@ "name": "native_trigger_service", "kind": { "Enum": [ - "nextcloud", - "google" + "nextcloud" ] } } @@ -72,8 +71,7 @@ "name": "native_trigger_service", "kind": { "Enum": [ - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-ed8facbf29ebb670d05fe8aa34b50d6a6935420fbedc83aa3ad1e9be7465c8dd.json b/backend/.sqlx/query-ed8facbf29ebb670d05fe8aa34b50d6a6935420fbedc83aa3ad1e9be7465c8dd.json index 1876db927e..43fd90abe5 100644 --- a/backend/.sqlx/query-ed8facbf29ebb670d05fe8aa34b50d6a6935420fbedc83aa3ad1e9be7465c8dd.json +++ b/backend/.sqlx/query-ed8facbf29ebb670d05fe8aa34b50d6a6935420fbedc83aa3ad1e9be7465c8dd.json @@ -24,8 +24,7 @@ "mqtt", "gcp", "default_email", - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/.sqlx/query-ee537def1ead8bee48bb9f5c1f57d42e7add6011c34d91761ba23e2c74c4032c.json b/backend/.sqlx/query-ee537def1ead8bee48bb9f5c1f57d42e7add6011c34d91761ba23e2c74c4032c.json index 536175599d..4ce08c9e1c 100644 --- a/backend/.sqlx/query-ee537def1ead8bee48bb9f5c1f57d42e7add6011c34d91761ba23e2c74c4032c.json +++ b/backend/.sqlx/query-ee537def1ead8bee48bb9f5c1f57d42e7add6011c34d91761ba23e2c74c4032c.json @@ -21,8 +21,7 @@ "name": "native_trigger_service", "kind": { "Enum": [ - "nextcloud", - "google" + "nextcloud" ] } } @@ -72,8 +71,7 @@ "name": "native_trigger_service", "kind": { "Enum": [ - "nextcloud", - "google" + "nextcloud" ] } } diff --git a/backend/src/db_connect.rs b/backend/src/db_connect.rs new file mode 100644 index 0000000000..05bf7b6522 --- /dev/null +++ b/backend/src/db_connect.rs @@ -0,0 +1,159 @@ +use windmill_common::{ + error::{self, Error}, + get_database_url, DatabaseUrl, +}; + +pub const DEFAULT_MAX_CONNECTIONS_SERVER: u32 = 50; +pub const DEFAULT_MAX_CONNECTIONS_WORKER: u32 = 5; +pub const DEFAULT_MAX_CONNECTIONS_INDEXER: u32 = 5; + +pub async fn initial_connection() -> Result, error::Error> { + let connect_options = get_database_url().await?.connect_options().await?; + sqlx::postgres::PgPoolOptions::new() + .max_connections(2) + .connect_with(connect_options) + .await + .map_err(|err| Error::ConnectingToDatabase(err.to_string())) +} + +pub async fn connect_db( + server_mode: bool, + indexer_mode: bool, + worker_mode: bool, + #[cfg(feature = "private")] mut killpill_rx: tokio::sync::broadcast::Receiver<()>, +) -> anyhow::Result> { + use anyhow::Context; + + let database_url = get_database_url().await?; + + let max_connections = match std::env::var("DATABASE_CONNECTIONS") { + Ok(n) => n.parse::().context("invalid DATABASE_CONNECTIONS")?, + Err(_) => { + if server_mode { + DEFAULT_MAX_CONNECTIONS_SERVER + } else if indexer_mode { + DEFAULT_MAX_CONNECTIONS_INDEXER + } else { + DEFAULT_MAX_CONNECTIONS_WORKER + + std::env::var("NUM_WORKERS") + .ok() + .map(|x| x.parse().ok()) + .flatten() + .unwrap_or(1) + - 1 + } + } + }; + + let pool = connect(database_url.clone(), max_connections, worker_mode).await?; + #[cfg(all(feature = "enterprise", feature = "private"))] + let pool2 = pool.clone(); + #[cfg(all(feature = "enterprise", feature = "private"))] + if let DatabaseUrl::IamRds(database_url) = database_url { + tokio::spawn(async move { + loop { + tokio::select! { + _ = killpill_rx.recv() => { + break; + } + _ = tokio::time::sleep(std::time::Duration::from_secs(10)) => { + let needs_refresh = { + let read_guard = database_url.read().await; + read_guard.needs_refresh() + }; + if needs_refresh { + let new_url = tokio::time::timeout(std::time::Duration::from_secs(10), get_database_url()).await; + match new_url { + Ok(Ok(new_url)) => { + match new_url.connect_options().await { + Ok(connect_options) => { + pool2.set_connect_options(connect_options); + tracing::info!("Refreshed IAM RDS URL successfully"); + } + Err(e) => { + tracing::error!("Error getting IAM RDS connect options, retrying in 10s: {}", e); + continue; + } + } + } + Ok(Err(e)) => { + tracing::error!("Error refreshing IAM RDS URL, trying again in 10s: {}", e); + continue; + } + Err(e) => { + tracing::error!("Timeout after 10s refreshing IAM RDS URL, trying again in 10 seconds: {}", e); + continue; + } + } + } + } + } + } + }); + } + + Ok(pool) +} + +pub async fn connect( + database_url: DatabaseUrl, + max_connections: u32, + worker_mode: bool, +) -> Result, error::Error> { + use sqlx::Executor; + use std::time::Duration; + let mut pool_options = sqlx::postgres::PgPoolOptions::new() + .min_connections((max_connections / 5).clamp(1, max_connections)) + .max_connections(max_connections) + .max_lifetime(Duration::from_secs(30 * 60)); // 30 mins + if worker_mode { + pool_options = pool_options.idle_timeout(Duration::from_secs(60)); + } + pool_options + .after_connect(move |conn, _| { + if worker_mode { + Box::pin(async move { + if let Err(e) = conn + .execute( + r#" + SET enable_seqscan = OFF; + SET statement_timeout = '5min'; + SET idle_in_transaction_session_timeout = '10min'; + SET tcp_keepalives_idle = 300; + SET tcp_keepalives_interval = 60; + SET tcp_keepalives_count = 10;"#, + ) + .await + { + tracing::error!("Error setting postgres settings: {}", e); + } + Ok(()) + }) + } else { + Box::pin(async move { + if let Err(e) = conn + .execute( + r#" + SET statement_timeout = '5min'; + SET idle_in_transaction_session_timeout = '10min'; + SET tcp_keepalives_idle = 300; + SET tcp_keepalives_interval = 60; + SET tcp_keepalives_count = 10;"#, + ) + .await + { + tracing::error!("Error setting postgres settings: {}", e); + } + Ok(()) + }) + } + }) + .connect_with( + database_url + .connect_options() + .await? + .statement_cache_capacity(400), + ) + .await + .map_err(|err| Error::ConnectingToDatabase(err.to_string())) +} diff --git a/backend/src/main.rs b/backend/src/main.rs index be424a6712..08f6d081a5 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -117,6 +117,7 @@ const BIND_ADDR_ENV: &str = "SERVER_BIND_ADDR"; #[cfg(target_os = "linux")] mod cgroups; +mod db_connect; #[cfg(feature = "private")] pub mod ee; mod ee_oss; @@ -665,7 +666,7 @@ async fn windmill_main() -> anyhow::Result<()> { } else { println!("Connecting to database..."); - let db = windmill_common::initial_connection().await?; + let db = crate::db_connect::initial_connection().await?; let num_version = sqlx::query_scalar!("SELECT version()").fetch_one(&db).await; @@ -770,8 +771,12 @@ async fn windmill_main() -> anyhow::Result<()> { let conn = if mode == Mode::Agent { conn } else { - // This time we use a pool of connections - let db = windmill_common::connect_db( + // Drop the initial connection pool before creating the main one. + // With low PostgreSQL max_connections, both pools existing simultaneously + // can exhaust all available connection slots, causing connect_db to hang. + drop(conn); + + let db = crate::db_connect::connect_db( server_mode, indexer_mode, worker_mode, diff --git a/backend/update_sqlx.sh b/backend/update_sqlx.sh index 669aa90dc9..f87fd72d5c 100755 --- a/backend/update_sqlx.sh +++ b/backend/update_sqlx.sh @@ -10,22 +10,11 @@ if [[ "$(uname)" == "Darwin" ]]; then # Uncomment the git-based samael dependency sed -i '' 's/^# \(samael = { git="https:\/\/github.com\/njaremko\/samael", rev="464d015e3ae393e4b5dd00b4d6baa1b617de0dd6", features = \["xmlsec"\] }\)/\1/' Cargo.toml - # Run cargo sqlx prepare with deno_core_mac + # Run cargo sqlx prepare with deno_core_mac echo "Running cargo sqlx prepare with deno_core_mac..." cargo sqlx prepare --workspace -- --all-targets --features all_sqlx_features,private,deno_core_mac else # Run cargo sqlx prepare echo "Running cargo sqlx prepare..." - cargo sqlx prepare --workspace -- --all-targets --features all_sqlx_features,private -fi - - - -# Undo the samael changes on macOS -if [[ "$(uname)" == "Darwin" ]]; then - echo "Reverting samael changes..." - # Uncomment the version-based samael dependency - sed -i '' 's/^#samael = { version="0.0.14", features = \["xmlsec"\] }/samael = { version="0.0.14", features = ["xmlsec"] }/' Cargo.toml - # Comment out the git-based samael dependency - sed -i '' 's/^\(samael = { git="https:\/\/github.com\/njaremko\/samael", rev="464d015e3ae393e4b5dd00b4d6baa1b617de0dd6", features = \["xmlsec"\] }\)/# \1/' Cargo.toml + cargo sqlx prepare --workspace -- --all-targets --features all_sqlx_features,ee fi diff --git a/backend/v8.snap b/backend/v8.snap deleted file mode 100644 index 4213db10e73296aadd7732d4e4b400bccdb50a8b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 500024 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

D>?wApk{g2`Jdd4${qoQgPLATc?E=Hj1uy5P3 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

Df#pfqZ6=ZO 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 diff --git a/backend/windmill-common/src/bench.rs b/backend/windmill-common/src/bench.rs index cd475edaac..00cb565029 100644 --- a/backend/windmill-common/src/bench.rs +++ b/backend/windmill-common/src/bench.rs @@ -3,7 +3,20 @@ use crate::{ DB, }; use serde::Serialize; +use std::collections::HashSet; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::sync::Arc; use tokio::time::Instant; +use uuid::Uuid; + +static BENCHMARK_INITIALIZED: AtomicBool = AtomicBool::new(false); + +static SHARED_BENCH_ITERS: std::sync::LazyLock> = + std::sync::LazyLock::new(|| Arc::new(AtomicU64::new(0))); + +pub fn shared_bench_iters() -> Arc { + SHARED_BENCH_ITERS.clone() +} #[derive(Serialize)] pub struct PoolStats { @@ -36,6 +49,10 @@ pub struct BenchmarkInfo { pub start: Instant, #[serde(skip)] pub iters: u64, + #[serde(skip)] + seen_top_level: HashSet, + #[serde(skip)] + pub shared_iters: Arc, timings: Vec, pub iter_durations: Vec, pub total_duration: Option, @@ -43,9 +60,11 @@ pub struct BenchmarkInfo { } impl BenchmarkInfo { - pub fn new() -> Self { + pub fn new(shared_iters: Arc) -> Self { BenchmarkInfo { iters: 0, + seen_top_level: HashSet::new(), + shared_iters, timings: vec![], start: Instant::now(), iter_durations: vec![], @@ -64,13 +83,23 @@ impl BenchmarkInfo { } } - pub fn add_iter(&mut self, bench: BenchmarkIter, inc_iters: bool) { - if inc_iters { + pub fn count_top_level(&mut self, job_id: Uuid) -> bool { + if self.seen_top_level.insert(job_id) { + self.iters += 1; + return true; + } + false + } + + pub fn add_iter(&mut self, bench: BenchmarkIter, job_id: Uuid, is_top_level: bool) -> bool { + let newly_counted = is_top_level && self.seen_top_level.insert(job_id); + if newly_counted { self.iters += 1; } let elapsed_total = bench.start.elapsed().as_nanos() as u64; self.timings.push(bench); self.iter_durations.push(elapsed_total); + newly_counted } pub fn write_to_file(&mut self, path: &str) -> anyhow::Result<()> { @@ -118,12 +147,120 @@ impl BenchmarkIter { } } +pub async fn benchmark_verify(benchmark_jobs: i32, db: &DB) { + let benchmark_kind = std::env::var("BENCHMARK_KIND").unwrap_or("noop".to_string()); + + if benchmark_jobs <= 0 || benchmark_kind == "none" { + return; + } + + // For flows, child jobs are created dynamically so only check top-level (parent_job IS NULL). + // "parallelflow" inserts only 1 top-level flow regardless of benchmark_jobs. + let expected_top_level = match benchmark_kind.as_str() { + "parallelflow" => 1i64, + _ => benchmark_jobs as i64, + }; + + let row = sqlx::query!( + "SELECT + COUNT(*) FILTER (WHERE status = 'success') AS succeeded, + COUNT(*) FILTER (WHERE status = 'failure') AS failed, + COUNT(*) FILTER (WHERE status = 'canceled') AS canceled + FROM v2_job_completed + JOIN v2_job USING (id) + WHERE v2_job.workspace_id = 'admins' AND v2_job.parent_job IS NULL", + ) + .fetch_one(db) + .await + .expect("benchmark verify query failed"); + + let succeeded = row.succeeded.unwrap_or(0); + let failed = row.failed.unwrap_or(0); + let canceled = row.canceled.unwrap_or(0); + let total = succeeded + failed + canceled; + + let remaining_in_queue = sqlx::query_scalar!( + "SELECT COUNT(*) FROM v2_job_queue WHERE workspace_id = 'admins'", + ) + .fetch_one(db) + .await + .expect("benchmark verify queue query failed") + .unwrap_or(0); + + println!("=== BENCHMARK VERIFICATION ==="); + println!(" kind: {benchmark_kind}"); + println!(" expected top-level: {expected_top_level}"); + println!(" completed total: {total}"); + println!(" succeeded: {succeeded}"); + println!(" failed: {failed}"); + println!(" canceled: {canceled}"); + println!(" still in queue: {remaining_in_queue}"); + + if failed > 0 || canceled > 0 { + tracing::error!( + "BENCHMARK VERIFICATION FAILED: {failed} failed, {canceled} canceled out of {total} completed" + ); + } + if remaining_in_queue > 0 { + tracing::warn!( + "BENCHMARK VERIFICATION: {remaining_in_queue} jobs still in queue after benchmark" + ); + } + if succeeded != expected_top_level { + tracing::error!( + "BENCHMARK VERIFICATION FAILED: expected {expected_top_level} succeeded top-level jobs, got {succeeded}" + ); + } else if failed == 0 && canceled == 0 && remaining_in_queue == 0 { + println!(" result: ALL PASSED"); + } + println!("=============================="); +} + pub async fn benchmark_init(benchmark_jobs: i32, db: &DB) { use crate::{jobs::JobKind, scripts::ScriptLang}; + // Only the first worker to reach this point runs init + if BENCHMARK_INITIALIZED.swap(true, Ordering::SeqCst) { + return; + } + let benchmark_kind = std::env::var("BENCHMARK_KIND").unwrap_or("noop".to_string()); if benchmark_jobs > 0 { + // Clean up data from previous benchmark runs + sqlx::query!("DELETE FROM v2_job_completed WHERE workspace_id = 'admins'") + .execute(db) + .await + .unwrap_or_else(|e| panic!("failed to clean up v2_job_completed: {e:#}")); + sqlx::query!("DELETE FROM v2_job_queue WHERE workspace_id = 'admins'") + .execute(db) + .await + .unwrap_or_else(|e| panic!("failed to clean up v2_job_queue: {e:#}")); + sqlx::query!("DELETE FROM v2_job_status WHERE id IN (SELECT id FROM v2_job WHERE workspace_id = 'admins')") + .execute(db) + .await + .unwrap_or_else(|e| panic!("failed to clean up v2_job_status: {e:#}")); + sqlx::query!("DELETE FROM v2_job_runtime WHERE id IN (SELECT id FROM v2_job WHERE workspace_id = 'admins')") + .execute(db) + .await + .unwrap_or_else(|e| panic!("failed to clean up v2_job_runtime: {e:#}")); + sqlx::query("DELETE FROM job_perms WHERE workspace_id = 'admins'") + .execute(db) + .await + .unwrap_or_else(|e| panic!("failed to clean up job_perms: {e:#}")); + sqlx::query!("DELETE FROM concurrency_key WHERE key LIKE 'bench_%' OR key LIKE 'u/admin/bench_%'") + .execute(db) + .await + .unwrap_or_else(|e| panic!("failed to clean up concurrency_key: {e:#}")); + sqlx::query!("DELETE FROM concurrency_counter WHERE concurrency_id LIKE 'bench_%' OR concurrency_id LIKE 'u/admin/bench_%'") + .execute(db) + .await + .unwrap_or_else(|e| panic!("failed to clean up concurrency_counter: {e:#}")); + sqlx::query!("DELETE FROM v2_job WHERE workspace_id = 'admins'") + .execute(db) + .await + .unwrap_or_else(|e| panic!("failed to clean up v2_job: {e:#}")); + let mut tx = db.begin().await.unwrap(); match benchmark_kind.as_str() { "dedicated" => { @@ -260,6 +397,477 @@ pub async fn benchmark_init(benchmark_jobs: i32, db: &DB) { .await .unwrap_or_else(|_e| panic!("failed to insert parallelflow jobs (4)")); } + "sequentialflow" => { + let uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_flow) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 FROM generate_series(1, $11)) RETURNING id", + None::, + None::, + JobKind::FlowPreview as JobKind, + ScriptLang::Deno as ScriptLang, + "flow", + "admin", + "u/admin", + "admin@windmill.dev", + "admins", + serde_json::from_str::(r#" +{ + "modules": [ + { + "id": "a", + "value": { + "type": "rawscript", + "content": "export async function main() { return 'a'; }", + "language": "deno", + "input_transforms": {} + } + }, + { + "id": "b", + "value": { + "type": "rawscript", + "content": "export async function main() { return 'b'; }", + "language": "deno", + "input_transforms": {} + } + }, + { + "id": "c", + "value": { + "type": "rawscript", + "content": "export async function main() { return 'c'; }", + "language": "deno", + "input_transforms": {} + } + }, + { + "id": "d", + "value": { + "type": "rawscript", + "content": "export async function main() { return 'd'; }", + "language": "deno", + "input_transforms": {} + } + } + ], + "preprocessor_module": null +} + "#).unwrap(), + benchmark_jobs, + ) + .fetch_all(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert sequentialflow jobs (1)")); + sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &uuids, "admins", "flow") + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert sequentialflow jobs (2)")); + sqlx::query!( + "INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])", + &uuids + ) + .execute(&mut *tx) + .await + .unwrap_or_else(|_e| panic!("failed to insert sequentialflow jobs (3)")); + sqlx::query!( + "INSERT INTO v2_job_status (id, flow_status) SELECT unnest($1::uuid[]), $2", + &uuids, + serde_json::from_str::( + r#" +{ + "step": 0, + "modules": [ + { "id": "a", "type": "WaitingForPriorSteps" }, + { "id": "b", "type": "WaitingForPriorSteps" }, + { "id": "c", "type": "WaitingForPriorSteps" }, + { "id": "d", "type": "WaitingForPriorSteps" } + ], + "cleanup_module": {}, + "failure_module": { + "id": "failure", + "type": "WaitingForPriorSteps" + }, + "preprocessor_module": null +} + "# + ) + .unwrap() + ) + .execute(&mut *tx) + .await + .unwrap_or_else(|_e| panic!("failed to insert sequentialflow jobs (4)")); + } + "scriptlogs" => { + let uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_code) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 FROM generate_series(1, $11)) RETURNING id", + None::, + None::, + JobKind::Preview as JobKind, + ScriptLang::Deno as ScriptLang, + "deno", + "admin", + "u/admin", + "admin@windmill.dev", + "admins", + "export async function main() { for (let i = 0; i < 1000; i++) { console.log('benchmark log line ' + i); } return 'done'; }", + benchmark_jobs, + ) + .fetch_all(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert scriptlogs jobs (1)")); + sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &uuids, "admins", "deno") + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert scriptlogs jobs (2)")); + sqlx::query!( + "INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])", + &uuids + ) + .execute(&mut *tx) + .await + .unwrap_or_else(|_e| panic!("failed to insert scriptlogs jobs (3)")); + } + "concurrencylimit" => { + let uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_code, concurrent_limit, concurrency_time_window_s) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 FROM generate_series(1, $13)) RETURNING id", + None::, + Some("u/admin/bench_conclimit"), + JobKind::Preview as JobKind, + ScriptLang::Deno as ScriptLang, + "deno", + "admin", + "u/admin", + "admin@windmill.dev", + "admins", + "export async function main() { return 'done'; }", + 2i32, + 0i32, + benchmark_jobs, + ) + .fetch_all(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert concurrencylimit jobs (1)")); + sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &uuids, "admins", "deno") + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert concurrencylimit jobs (2)")); + sqlx::query!( + "INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])", + &uuids + ) + .execute(&mut *tx) + .await + .unwrap_or_else(|_e| panic!("failed to insert concurrencylimit jobs (3)")); + let concurrency_id = "u/admin/bench_conclimit"; + sqlx::query!( + "INSERT INTO concurrency_counter (concurrency_id, job_uuids) VALUES ($1, '{}'::jsonb) ON CONFLICT (concurrency_id) DO NOTHING", + concurrency_id, + ) + .execute(&mut *tx) + .await + .unwrap_or_else(|_e| panic!("failed to insert concurrencylimit counter")); + for uuid in &uuids { + sqlx::query!( + "INSERT INTO concurrency_key (key, ended_at, job_id) VALUES ($1, now() - INTERVAL '1 hour', $2)", + concurrency_id, + uuid, + ) + .execute(&mut *tx) + .await + .unwrap_or_else(|_e| panic!("failed to insert concurrencylimit key")); + } + } + "concurrencykey" => { + let uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_code, concurrent_limit, concurrency_time_window_s) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 FROM generate_series(1, $13)) RETURNING id", + None::, + Some("u/admin/bench_conckey"), + JobKind::Preview as JobKind, + ScriptLang::Deno as ScriptLang, + "deno", + "admin", + "u/admin", + "admin@windmill.dev", + "admins", + "export async function main() { return 'done'; }", + 1i32, + 0i32, + benchmark_jobs, + ) + .fetch_all(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert concurrencykey jobs (1)")); + sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &uuids, "admins", "deno") + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert concurrencykey jobs (2)")); + sqlx::query!( + "INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])", + &uuids + ) + .execute(&mut *tx) + .await + .unwrap_or_else(|_e| panic!("failed to insert concurrencykey jobs (3)")); + let concurrency_id = "bench_shared_concurrency_key"; + sqlx::query!( + "INSERT INTO concurrency_counter (concurrency_id, job_uuids) VALUES ($1, '{}'::jsonb) ON CONFLICT (concurrency_id) DO NOTHING", + concurrency_id, + ) + .execute(&mut *tx) + .await + .unwrap_or_else(|_e| panic!("failed to insert concurrencykey counter")); + for uuid in &uuids { + sqlx::query!( + "INSERT INTO concurrency_key (key, ended_at, job_id) VALUES ($1, now() - INTERVAL '1 hour', $2)", + concurrency_id, + uuid, + ) + .execute(&mut *tx) + .await + .unwrap_or_else(|_e| panic!("failed to insert concurrencykey key")); + } + } + "mixed" => { + let portion = benchmark_jobs / 5; + let remainder = benchmark_jobs % 5; + + // 1) noop jobs + let noop_count = portion + remainder; + let noop_uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9 FROM generate_series(1, $10)) RETURNING id", + None::, + None::, + JobKind::Noop as JobKind, + ScriptLang::Deno as ScriptLang, + "deno", + "admin", + "u/admin", + "admin@windmill.dev", + "admins", + noop_count, + ) + .fetch_all(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed noop jobs")); + sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &noop_uuids, "admins", "deno") + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed noop queue")); + sqlx::query!("INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])", &noop_uuids) + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed noop runtime")); + + // 2) sequentialflow jobs + if portion > 0 { + let sf_uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_flow) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 FROM generate_series(1, $11)) RETURNING id", + None::, + None::, + JobKind::FlowPreview as JobKind, + ScriptLang::Deno as ScriptLang, + "flow", + "admin", + "u/admin", + "admin@windmill.dev", + "admins", + serde_json::from_str::(r#"{"modules":[{"id":"a","value":{"type":"rawscript","content":"export async function main() { return 'a'; }","language":"deno","input_transforms":{}}},{"id":"b","value":{"type":"rawscript","content":"export async function main() { return 'b'; }","language":"deno","input_transforms":{}}},{"id":"c","value":{"type":"rawscript","content":"export async function main() { return 'c'; }","language":"deno","input_transforms":{}}},{"id":"d","value":{"type":"rawscript","content":"export async function main() { return 'd'; }","language":"deno","input_transforms":{}}}],"preprocessor_module":null}"#).unwrap(), + portion, + ) + .fetch_all(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed sequentialflow jobs")); + sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &sf_uuids, "admins", "flow") + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed sequentialflow queue")); + sqlx::query!("INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])", &sf_uuids) + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed sequentialflow runtime")); + sqlx::query!( + "INSERT INTO v2_job_status (id, flow_status) SELECT unnest($1::uuid[]), $2", + &sf_uuids, + serde_json::from_str::(r#"{"step":0,"modules":[{"id":"a","type":"WaitingForPriorSteps"},{"id":"b","type":"WaitingForPriorSteps"},{"id":"c","type":"WaitingForPriorSteps"},{"id":"d","type":"WaitingForPriorSteps"}],"cleanup_module":{},"failure_module":{"id":"failure","type":"WaitingForPriorSteps"},"preprocessor_module":null}"#).unwrap() + ) + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed sequentialflow status")); + } + + // 3) scriptlogs jobs + if portion > 0 { + let sl_uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_code) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 FROM generate_series(1, $11)) RETURNING id", + None::, + None::, + JobKind::Preview as JobKind, + ScriptLang::Deno as ScriptLang, + "deno", + "admin", + "u/admin", + "admin@windmill.dev", + "admins", + "export async function main() { for (let i = 0; i < 1000; i++) { console.log('benchmark log line ' + i); } return 'done'; }", + portion, + ) + .fetch_all(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed scriptlogs jobs")); + sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &sl_uuids, "admins", "deno") + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed scriptlogs queue")); + sqlx::query!("INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])", &sl_uuids) + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed scriptlogs runtime")); + } + + // 4) concurrencylimit jobs + if portion > 0 { + let cl_uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_code, concurrent_limit, concurrency_time_window_s) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 FROM generate_series(1, $13)) RETURNING id", + None::, + Some("u/admin/bench_conclimit"), + JobKind::Preview as JobKind, + ScriptLang::Deno as ScriptLang, + "deno", + "admin", + "u/admin", + "admin@windmill.dev", + "admins", + "export async function main() { return 'done'; }", + 2i32, + 0i32, + portion, + ) + .fetch_all(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencylimit jobs")); + sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &cl_uuids, "admins", "deno") + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencylimit queue")); + sqlx::query!("INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])", &cl_uuids) + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencylimit runtime")); + let cl_concurrency_id = "u/admin/bench_conclimit"; + sqlx::query!( + "INSERT INTO concurrency_counter (concurrency_id, job_uuids) VALUES ($1, '{}'::jsonb) ON CONFLICT (concurrency_id) DO NOTHING", + cl_concurrency_id, + ) + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencylimit counter")); + for uuid in &cl_uuids { + sqlx::query!( + "INSERT INTO concurrency_key (key, ended_at, job_id) VALUES ($1, now() - INTERVAL '1 hour', $2)", + cl_concurrency_id, + uuid, + ) + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencylimit key")); + } + } + + // 5) concurrencykey jobs + if portion > 0 { + let ck_uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_code, concurrent_limit, concurrency_time_window_s) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 FROM generate_series(1, $13)) RETURNING id", + None::, + Some("u/admin/bench_conckey"), + JobKind::Preview as JobKind, + ScriptLang::Deno as ScriptLang, + "deno", + "admin", + "u/admin", + "admin@windmill.dev", + "admins", + "export async function main() { return 'done'; }", + 1i32, + 0i32, + portion, + ) + .fetch_all(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencykey jobs")); + sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &ck_uuids, "admins", "deno") + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencykey queue")); + sqlx::query!("INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])", &ck_uuids) + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencykey runtime")); + let ck_concurrency_id = "bench_shared_concurrency_key"; + sqlx::query!( + "INSERT INTO concurrency_counter (concurrency_id, job_uuids) VALUES ($1, '{}'::jsonb) ON CONFLICT (concurrency_id) DO NOTHING", + ck_concurrency_id, + ) + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencykey counter")); + for uuid in &ck_uuids { + sqlx::query!( + "INSERT INTO concurrency_key (key, ended_at, job_id) VALUES ($1, now() - INTERVAL '1 hour', $2)", + ck_concurrency_id, + uuid, + ) + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencykey key")); + } + } + } + "mixed_no_cc" => { + let portion = benchmark_jobs / 3; + let remainder = benchmark_jobs % 3; + + // 1) noop jobs + let noop_count = portion + remainder; + let noop_uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9 FROM generate_series(1, $10)) RETURNING id", + None::, + None::, + JobKind::Noop as JobKind, + ScriptLang::Deno as ScriptLang, + "deno", + "admin", + "u/admin", + "admin@windmill.dev", + "admins", + noop_count, + ) + .fetch_all(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc noop jobs")); + sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &noop_uuids, "admins", "deno") + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc noop queue")); + sqlx::query!("INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])", &noop_uuids) + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc noop runtime")); + + // 2) sequentialflow jobs + if portion > 0 { + let sf_uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_flow) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 FROM generate_series(1, $11)) RETURNING id", + None::, + None::, + JobKind::FlowPreview as JobKind, + ScriptLang::Deno as ScriptLang, + "flow", + "admin", + "u/admin", + "admin@windmill.dev", + "admins", + serde_json::from_str::(r#"{"modules":[{"id":"a","value":{"type":"rawscript","content":"export async function main() { return 'a'; }","language":"deno","input_transforms":{}}},{"id":"b","value":{"type":"rawscript","content":"export async function main() { return 'b'; }","language":"deno","input_transforms":{}}},{"id":"c","value":{"type":"rawscript","content":"export async function main() { return 'c'; }","language":"deno","input_transforms":{}}},{"id":"d","value":{"type":"rawscript","content":"export async function main() { return 'd'; }","language":"deno","input_transforms":{}}}],"preprocessor_module":null}"#).unwrap(), + portion, + ) + .fetch_all(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc sequentialflow jobs")); + sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &sf_uuids, "admins", "flow") + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc sequentialflow queue")); + sqlx::query!("INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])", &sf_uuids) + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc sequentialflow runtime")); + sqlx::query!( + "INSERT INTO v2_job_status (id, flow_status) SELECT unnest($1::uuid[]), $2", + &sf_uuids, + serde_json::from_str::(r#"{"step":0,"modules":[{"id":"a","type":"WaitingForPriorSteps"},{"id":"b","type":"WaitingForPriorSteps"},{"id":"c","type":"WaitingForPriorSteps"},{"id":"d","type":"WaitingForPriorSteps"}],"cleanup_module":{},"failure_module":{"id":"failure","type":"WaitingForPriorSteps"},"preprocessor_module":null}"#).unwrap() + ) + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc sequentialflow status")); + } + + // 3) scriptlogs jobs + if portion > 0 { + let sl_uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_code) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 FROM generate_series(1, $11)) RETURNING id", + None::, + None::, + JobKind::Preview as JobKind, + ScriptLang::Deno as ScriptLang, + "deno", + "admin", + "u/admin", + "admin@windmill.dev", + "admins", + "export async function main() { for (let i = 0; i < 1000; i++) { console.log('benchmark log line ' + i); } return 'done'; }", + portion, + ) + .fetch_all(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc scriptlogs jobs")); + sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &sl_uuids, "admins", "deno") + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc scriptlogs queue")); + sqlx::query!("INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])", &sl_uuids) + .execute(&mut *tx) + .await.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc scriptlogs runtime")); + } + } "none" => {} _ => { let uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9 FROM generate_series(1, $10)) RETURNING id", @@ -288,6 +896,16 @@ pub async fn benchmark_init(benchmark_jobs: i32, db: &DB) { .unwrap_or_else(|_e| panic!("failed to insert noop jobs (3)")); } } + // Insert job_perms for all benchmark jobs so workers don't fall back to slow permission lookups + sqlx::query( + "INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, groups, folders, workspace_id) + SELECT id, 'admin@windmill.dev', 'admin', true, false, ARRAY['all']::text[], ARRAY[]::jsonb[], 'admins' + FROM v2_job WHERE workspace_id = 'admins'" + ) + .execute(&mut *tx) + .await + .unwrap_or_else(|e| panic!("failed to insert job_perms: {e:#}")); + tx.commit().await.unwrap(); } } diff --git a/backend/windmill-common/src/lib.rs b/backend/windmill-common/src/lib.rs index 99fa1c46ca..d707f3f8e3 100644 --- a/backend/windmill-common/src/lib.rs +++ b/backend/windmill-common/src/lib.rs @@ -664,153 +664,6 @@ pub async fn get_database_url() -> Result { Ok(database_url.clone()) } -pub async fn initial_connection() -> Result, error::Error> { - let connect_options = get_database_url().await?.connect_options().await?; - sqlx::postgres::PgPoolOptions::new() - .max_connections(2) - .connect_with(connect_options) - .await - .map_err(|err| Error::ConnectingToDatabase(err.to_string())) -} - -pub async fn connect_db( - server_mode: bool, - indexer_mode: bool, - worker_mode: bool, - #[cfg(feature = "private")] mut killpill_rx: tokio::sync::broadcast::Receiver<()>, -) -> anyhow::Result> { - use anyhow::Context; - - let database_url = get_database_url().await?; - - let max_connections = match std::env::var("DATABASE_CONNECTIONS") { - Ok(n) => n.parse::().context("invalid DATABASE_CONNECTIONS")?, - Err(_) => { - if server_mode { - DEFAULT_MAX_CONNECTIONS_SERVER - } else if indexer_mode { - DEFAULT_MAX_CONNECTIONS_INDEXER - } else { - DEFAULT_MAX_CONNECTIONS_WORKER - + std::env::var("NUM_WORKERS") - .ok() - .map(|x| x.parse().ok()) - .flatten() - .unwrap_or(1) - - 1 - } - } - }; - - let pool = connect(database_url.clone(), max_connections, worker_mode).await?; - #[cfg(all(feature = "enterprise", feature = "private"))] - let pool2 = pool.clone(); - #[cfg(all(feature = "enterprise", feature = "private"))] - if let DatabaseUrl::IamRds(database_url) = database_url { - tokio::spawn(async move { - loop { - tokio::select! { - _ = killpill_rx.recv() => { - break; - } - _ = tokio::time::sleep(std::time::Duration::from_secs(10)) => { - let needs_refresh = { - let read_guard = database_url.read().await; - read_guard.needs_refresh() - }; - if needs_refresh { - let new_url = tokio::time::timeout(std::time::Duration::from_secs(10), get_database_url()).await; - match new_url { - Ok(Ok(new_url)) => { - match new_url.connect_options().await { - Ok(connect_options) => { - pool2.set_connect_options(connect_options); - tracing::info!("Refreshed IAM RDS URL successfully"); - } - Err(e) => { - tracing::error!("Error getting IAM RDS connect options, retrying in 10s: {}", e); - continue; - } - } - } - Ok(Err(e)) => { - tracing::error!("Error refreshing IAM RDS URL, trying again in 10s: {}", e); - continue; - } - Err(e) => { - tracing::error!("Timeout after 10s refreshing IAM RDS URL, trying again in 10 seconds: {}", e); - continue; - } - } - } - } - } - } - }); - } - - Ok(pool) -} - -pub async fn connect( - database_url: DatabaseUrl, - max_connections: u32, - worker_mode: bool, -) -> Result, error::Error> { - use sqlx::Executor; - use std::time::Duration; - sqlx::postgres::PgPoolOptions::new() - .min_connections((max_connections / 5).clamp(3, max_connections)) - .max_connections(max_connections) - .max_lifetime(Duration::from_secs(30 * 60)) // 30 mins - .after_connect(move |conn, _| { - if worker_mode { - Box::pin(async move { - if let Err(e) = conn - .execute( - r#" - SET enable_seqscan = OFF; - SET statement_timeout = '5min'; - SET idle_in_transaction_session_timeout = '10min'; - SET tcp_keepalives_idle = 300; - SET tcp_keepalives_interval = 60; - SET tcp_keepalives_count = 10;"#, - ) - .await - { - tracing::error!("Error setting postgres settings: {}", e); - } - Ok(()) - }) - } else { - Box::pin(async move { - if let Err(e) = conn - .execute( - r#" - SET statement_timeout = '5min'; - SET idle_in_transaction_session_timeout = '10min'; - SET tcp_keepalives_idle = 300; - SET tcp_keepalives_interval = 60; - SET tcp_keepalives_count = 10;"#, - ) - .await - { - tracing::error!("Error setting postgres settings: {}", e); - } - Ok(()) - }) - } - }) - .connect_with( - database_url - .connect_options() - .await? - .statement_cache_capacity(400), - ) - .await - .map_err(|err| Error::ConnectingToDatabase(err.to_string())) -} - type Tag = String; pub use db::DB; @@ -852,11 +705,9 @@ impl ScriptHashInfo { self, db: &DB, ) -> error::Result> { - let rs = runnable_settings::from_handle( - self.runnable_settings.runnable_settings_handle, - db, - ) - .await?; + let rs = + runnable_settings::from_handle(self.runnable_settings.runnable_settings_handle, db) + .await?; let (debouncing_settings, concurrency_settings) = runnable_settings::prefetch_cached(&rs, db).await?; @@ -1176,25 +1027,25 @@ pub fn get_flow_version_info_from_version< _ => { tracing::debug!("Fetching flow version info for {version} ({path})"); let mut conn = db.acquire().await?; - let flow_info = + let flow_info = sqlx::query_as!( FlowVersionInfo, r#" SELECT flow_version.id AS version, - flow_version.value->>'early_return' as early_return, - flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor, - (flow_version.value->>'chat_input_enabled')::boolean as chat_input_enabled, - flow.tag, - flow.dedicated_worker, - flow.on_behalf_of_email, + flow_version.value->>'early_return' as early_return, + flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor, + (flow_version.value->>'chat_input_enabled')::boolean as chat_input_enabled, + flow.tag, + flow.dedicated_worker, + flow.on_behalf_of_email, flow.edited_by - FROM + FROM flow_version INNER JOIN flow ON flow.path = flow_version.path AND flow.workspace_id = flow_version.workspace_id - WHERE + WHERE flow_version.workspace_id = $1 AND flow_version.path = $2 AND flow_version.id = $3 diff --git a/backend/windmill-common/src/utils.rs b/backend/windmill-common/src/utils.rs index 4a6f4604a1..9f6bbf7893 100644 --- a/backend/windmill-common/src/utils.rs +++ b/backend/windmill-common/src/utils.rs @@ -145,6 +145,13 @@ lazy_static::lazy_static! { tracing::info!("Mode not specified, defaulting to standalone"); Mode::Standalone }); + #[cfg(feature = "benchmark")] + let mode = { + if mode != Mode::Worker { + println!("Benchmark mode: forcing MODE=worker"); + } + Mode::Worker + }; ModeAndAddons { indexer: search_addon, mode, diff --git a/backend/windmill-worker/src/result_processor.rs b/backend/windmill-worker/src/result_processor.rs index 6992d6ff74..c237d8cc02 100644 --- a/backend/windmill-worker/src/result_processor.rs +++ b/backend/windmill-worker/src/result_processor.rs @@ -68,6 +68,7 @@ async fn process_jc( stats_map: &JobStatsMap, killpill_rx: &tokio::sync::broadcast::Receiver<()>, #[cfg(feature = "benchmark")] bench: &mut BenchmarkIter, + #[cfg(feature = "benchmark")] bench_infos: &mut BenchmarkInfo, ) { let success: bool = jc.success; @@ -166,6 +167,13 @@ async fn process_jc( if let Some(root_job) = root_job { add_root_flow_job_to_otlp(&root_job, success); + + #[cfg(feature = "benchmark")] + if bench_infos.count_top_level(root_job.id) { + bench_infos + .shared_iters + .fetch_add(1, std::sync::atomic::Ordering::Relaxed); + } } // Accumulate job stats if duration is available @@ -209,7 +217,7 @@ pub fn start_background_processor( let JobCompletedReceiver { bounded_rx, mut killpill_rx, unbounded_rx } = job_completed_rx; #[cfg(feature = "benchmark")] - let mut infos = BenchmarkInfo::new(); + let mut infos = BenchmarkInfo::new(windmill_common::bench::shared_bench_iters()); // Start periodic stats flush task let db_clone = db.clone(); @@ -278,6 +286,10 @@ pub fn start_background_processor( jc.job.kind, JobKind::Dependencies | JobKind::FlowDependencies ); + #[cfg(feature = "benchmark")] + let bench_job_id = jc.job.id; + #[cfg(feature = "benchmark")] + let is_top_level_job = jc.job.parent_job.is_none(); process_jc( jc, @@ -291,6 +303,8 @@ pub fn start_background_processor( &killpill_rx, #[cfg(feature = "benchmark")] &mut bench, + #[cfg(feature = "benchmark")] + &mut infos, ) .warn_after_seconds(10) .await; @@ -315,7 +329,9 @@ pub fn start_background_processor( #[cfg(feature = "benchmark")] { - infos.add_iter(bench, true); + if infos.add_iter(bench, bench_job_id, is_top_level_job) { + infos.shared_iters.fetch_add(1, Ordering::Relaxed); + } } last_processing_duration .store(time.elapsed().as_secs() as u16, Ordering::SeqCst); @@ -365,6 +381,12 @@ pub fn start_background_processor( { tracing::error!("Error updating flow status after job completion for {flow} on {worker_name}: {e:#}"); } + #[cfg(feature = "benchmark")] + { + if infos.add_iter(bench, flow, true) { + infos.shared_iters.fetch_add(1, Ordering::Relaxed); + } + } last_processing_duration .store(time.elapsed().as_secs() as u16, Ordering::SeqCst); } diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index f3cdc241e0..06f59e3814 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -188,7 +188,7 @@ use crate::mssql_executor::do_mssql; use crate::bigquery_executor::do_bigquery; #[cfg(feature = "benchmark")] -use windmill_common::bench::{benchmark_init, BenchmarkInfo, BenchmarkIter}; +use windmill_common::bench::{benchmark_init, benchmark_verify, BenchmarkInfo, BenchmarkIter}; use windmill_common::add_time; @@ -1109,51 +1109,60 @@ fn start_interactive_worker_shell( if let Ok(_) = killpill_rx.try_recv() { tracing::info!("Received killpill, exiting worker shell"); break; - } else { - let pulled_job = match &conn { - Connection::Sql(db) => { - let common_worker_prefix = retrieve_common_worker_prefix(&worker_name); - let query = ("".to_string(), make_pull_query(&[common_worker_prefix])); - #[cfg(feature = "benchmark")] - let mut bench = windmill_common::bench::BenchmarkIter::new(); + } - let job = pull( - &db, - false, - &worker_name, - Some(&query), + let pulled_job = tokio::select! { + _ = killpill_rx.recv() => { + tracing::info!("Received killpill during pull, exiting worker shell"); + break; + } + result = async { + match &conn { + Connection::Sql(db) => { + let common_worker_prefix = retrieve_common_worker_prefix(&worker_name); + let query = ("".to_string(), make_pull_query(&[common_worker_prefix])); #[cfg(feature = "benchmark")] - &mut bench, - ) - .await; + let mut bench = windmill_common::bench::BenchmarkIter::new(); - use PulledJobResultToJobErr::*; - match job { - Ok(j) => match j.to_pulled_job() { - Ok(j) => Ok(j - .clone() - .map(|job| NextJob::Sql { flow_runners: None, job })), - Err(MissingConcurrencyKey(jc)) - | Err(ErrorWhilePreprocessing(jc)) => { - if let Err(err) = job_completed_tx.send_job(jc, true).await { - tracing::error!( - "An error occurred while sending job completed: {:#?}", - err - ) + let job = pull( + &db, + false, + &worker_name, + Some(&query), + #[cfg(feature = "benchmark")] + &mut bench, + ) + .await; + + use PulledJobResultToJobErr::*; + match job { + Ok(j) => match j.to_pulled_job() { + Ok(j) => Ok(j + .clone() + .map(|job| NextJob::Sql { flow_runners: None, job })), + Err(MissingConcurrencyKey(jc)) + | Err(ErrorWhilePreprocessing(jc)) => { + if let Err(err) = job_completed_tx.send_job(jc, true).await { + tracing::error!( + "An error occurred while sending job completed: {:#?}", + err + ) + } + Ok(None) } - Ok(None) - } - }, - Err(err) => Err(err), + }, + Err(err) => Err(err), + } + } + Connection::Http(client) => { + crate::agent_workers::pull_job(&client, None, Some(true)) + .await + .map_err(|e| error::Error::InternalErr(e.to_string())) + .map(|x| x.map(|y| NextJob::Http(y))) } } - Connection::Http(client) => { - crate::agent_workers::pull_job(&client, None, Some(true)) - .await - .map_err(|e| error::Error::InternalErr(e.to_string())) - .map(|x| x.map(|y| NextJob::Http(y))) - } - }; + } => result, + }; match pulled_job { Ok(Some(job)) => { @@ -1233,7 +1242,6 @@ fn start_interactive_worker_shell( tokio::time::sleep(Duration::from_millis(*SLEEP_QUEUE * 20)).await; } }; - } } }) } @@ -1571,6 +1579,7 @@ pub async fn run_worker( // This is used to wake up the background processor when main loop is done and just waiting for new same workers jobs, and that bg processor is also not processing any jobs, bg processing can exit if no more same worker jobs let wake_up_notify = Arc::new(tokio::sync::Notify::new()); let stats_map = JobStatsMap::default(); + let send_result = match (conn, job_completed_rx) { (Connection::Sql(db), Some(job_completed_receiver)) => Some(start_background_processor( job_completed_receiver, @@ -1615,7 +1624,10 @@ pub async fn run_worker( let mut started = false; #[cfg(feature = "benchmark")] - let mut infos = BenchmarkInfo::new(); + let mut infos = BenchmarkInfo::new(windmill_common::bench::shared_bench_iters()); + + #[cfg(feature = "benchmark")] + let mut bench_empty_queue_count: u64 = 0; #[cfg(feature = "benchmark")] if let Some(db) = conn.as_sql() { @@ -1807,15 +1819,60 @@ pub async fn run_worker( // } #[cfg(feature = "benchmark")] - if benchmark_jobs > 0 && infos.iters == benchmark_jobs as u64 { - tracing::info!("benchmark finished, exiting"); - job_completed_tx - .kill() - .await - .expect("send kill to job completed tx"); - break; - } else { - tracing::info!("benchmark not finished, still pulling jobs {}", infos.iters); + { + let total_iters = infos.shared_iters.load(std::sync::atomic::Ordering::Relaxed); + if benchmark_jobs > 0 && total_iters >= benchmark_jobs as u64 { + tracing::info!("benchmark finished, exiting (total iters: {}, worker iters: {})", total_iters, infos.iters); + job_completed_tx + .kill() + .await + .expect("send kill to job completed tx"); + killpill_tx.send(); + break; + } else if benchmark_jobs > 0 && bench_empty_queue_count > 2000 { + tracing::warn!( + "benchmark stalled: no jobs in queue for 2000 polls, exiting (total iters: {}, worker iters: {}/{})", + total_iters, + infos.iters, + benchmark_jobs + ); + job_completed_tx + .kill() + .await + .expect("send kill to job completed tx"); + killpill_tx.send(); + break; + } else if bench_empty_queue_count % 100 == 0 { + if let Some(db) = conn.as_sql() { + let remaining = sqlx::query_as::<_, (uuid::Uuid, String, bool, Option, Option)>( + "SELECT q.id, q.tag, q.running, j.kind::text, j.parent_job + FROM v2_job_queue q JOIN v2_job j ON q.id = j.id + WHERE q.workspace_id = 'admins' LIMIT 10" + ) + .fetch_all(db) + .await; + match remaining { + Ok(rows) => { + let total_remaining = sqlx::query_scalar::<_, i64>( + "SELECT COUNT(*) FROM v2_job_queue WHERE workspace_id = 'admins'" + ).fetch_one(db).await.unwrap_or(0); + for (id, tag, running, kind, parent) in &rows { + tracing::info!( + " pending job: id={id}, tag={tag}, running={running}, kind={}, parent={:?}", + kind.as_deref().unwrap_or("?"), parent + ); + } + tracing::info!( + "benchmark not finished (total: {}, worker: {}, queue: {})", + total_iters, infos.iters, total_remaining + ); + } + Err(e) => { + tracing::info!("benchmark not finished (total: {}, worker: {}), queue query err: {e}", total_iters, infos.iters); + } + } + } + } } let next_job = { @@ -2029,6 +2086,15 @@ pub async fn run_worker( match next_job { Ok(Some(job)) => { + #[cfg(feature = "benchmark")] + { + bench_empty_queue_count = 0; + } + #[cfg(feature = "benchmark")] + let is_top_level_job = job.parent_job.is_none() && !job.kind.is_flow(); + #[cfg(feature = "benchmark")] + let bench_job_id = job.id; + #[cfg(feature = "prometheus")] if let Some(wb) = worker_busy.as_ref() { wb.set(1); @@ -2074,7 +2140,7 @@ pub async fn run_worker( if let Some(db) = conn.as_sql() { infos.sample_pool(db.size(), db.num_idle() as u32); } - infos.add_iter(bench, true); + infos.add_iter(bench, bench_job_id, is_top_level_job); } continue; @@ -2147,7 +2213,7 @@ pub async fn run_worker( if let Some(db) = conn.as_sql() { infos.sample_pool(db.size(), db.num_idle() as u32); } - infos.add_iter(bench, true); + infos.add_iter(bench, bench_job_id, is_top_level_job); } continue; @@ -2450,7 +2516,7 @@ pub async fn run_worker( if let Some(db) = conn.as_sql() { infos.sample_pool(db.size(), db.num_idle() as u32); } - infos.add_iter(bench, true); + infos.add_iter(bench, bench_job_id, is_top_level_job); } } } @@ -2477,11 +2543,12 @@ pub async fn run_worker( #[cfg(feature = "benchmark")] { + bench_empty_queue_count += 1; add_time!(bench, "sleep because empty job queue"); if let Some(db) = conn.as_sql() { infos.sample_pool(db.size(), db.num_idle() as u32); } - infos.add_iter(bench, false); + infos.add_iter(bench, uuid::Uuid::nil(), false); } #[cfg(feature = "prometheus")] _timer.map(|timer| { @@ -2510,13 +2577,6 @@ pub async fn run_worker( } } - #[cfg(feature = "benchmark")] - { - infos - .write_to_file("profiling_main.json") - .expect("write to file profiling"); - } - drop(dedicated_workers); let has_dedicated_workers = !dedicated_handles.is_empty(); @@ -2537,6 +2597,17 @@ pub async fn run_worker( tracing::error!("error in awaiting send_result process: {e:?}") } } + + #[cfg(feature = "benchmark")] + { + infos + .write_to_file("profiling_main.json") + .expect("write to file profiling"); + + if let Some(db) = conn.as_sql() { + benchmark_verify(benchmark_jobs, db).await; + } + } tracing::info!(worker = %worker_name, hostname = %hostname, "waiting for interactive_shell to finish"); if let Some(interactive_shell) = interactive_shell { match tokio::time::timeout(Duration::from_secs(10), interactive_shell).await { diff --git a/frontend/src/lib/components/DeployWorkspace.svelte b/frontend/src/lib/components/DeployWorkspace.svelte index 0624240bd0..c7c5ba2ca0 100644 --- a/frontend/src/lib/components/DeployWorkspace.svelte +++ b/frontend/src/lib/components/DeployWorkspace.svelte @@ -149,7 +149,6 @@ }) } else if (kind == 'app') { const app = await AppService.getAppByPath({ workspace: $workspaceStore!, path }) - console.log('app', app) let result: { kind: Kind; path: string }[] = [] if (app.raw_app) { const rawAppValue = app.value as { runnables?: Record }