Compare commits

..

3 Commits

Author SHA1 Message Date
Ruben Fiszel
277b97ffc3 schemaToggle 2025-08-27 18:32:46 +00:00
Ruben Fiszel
4ad0d255f3 feat: autovacuum or high intensity tables 2025-08-27 17:29:06 +00:00
Ruben Fiszel
86f41ffcde minor nits fix 2025-08-27 15:08:58 +00:00
17 changed files with 155 additions and 74 deletions

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO deployment_metadata (workspace_id, path, flow_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path) DO UPDATE SET callback_job_ids = $4, deployment_msg = $5",
"query": "INSERT INTO deployment_metadata (workspace_id, path, flow_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path, flow_version) WHERE flow_version IS NOT NULL DO UPDATE SET callback_job_ids = $4, deployment_msg = $5",
"describe": {
"columns": [],
"parameters": {
@@ -14,5 +14,5 @@
},
"nullable": []
},
"hash": "434d8dfbc25cf7e92de51d763d3a2904ccc2e95ecc3d90b43a6394a7bb4d26ab"
"hash": "2367e7c0f7fbafe0971a187c0909617da55251e97180babf6ac9e8068f26d73d"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "VACUUM v2_job_queue, v2_job_runtime, v2_job_status, job_perms",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "4221d98d76f3cb32d6be581b0f63cf7578429009bee4f648e2c1bc3784fdbefc"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "VACUUM v2_job, v2_job_completed, job_result_stream, job_stats, job_logs, concurrency_key, log_file, metrics",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "807c920bff25f56b10e88900d879cf5e8484c147e457044d6b075323b163ebaa"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO deployment_metadata (workspace_id, path, script_hash, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5) \n ON CONFLICT (workspace_id, path) DO UPDATE SET callback_job_ids = $4, deployment_msg = $5",
"query": "INSERT INTO deployment_metadata (workspace_id, path, script_hash, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5) \n ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL DO UPDATE SET callback_job_ids = $4, deployment_msg = $5",
"describe": {
"columns": [],
"parameters": {
@@ -14,5 +14,5 @@
},
"nullable": []
},
"hash": "e77fcf4e0d58855542605d13177df61671334418820ca942b442adfab413cbae"
"hash": "8d119104337bf99e9aa9dcbac0a54154267a7db96cc0fb3ebaac95635e24da29"
}

View File

@@ -1,12 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "VACUUM v2_job_queue, v2_job_runtime, v2_job_status",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "b9b38d63af3670d1f11d5cbb82a8008a9479bf4b3d7231371ebf26382ecde365"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO deployment_metadata (workspace_id, path, app_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path) DO UPDATE SET callback_job_ids = $4, deployment_msg = $5",
"query": "INSERT INTO deployment_metadata (workspace_id, path, app_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path, app_version) WHERE app_version IS NOT NULL DO UPDATE SET callback_job_ids = $4, deployment_msg = $5",
"describe": {
"columns": [],
"parameters": {
@@ -14,5 +14,5 @@
},
"nullable": []
},
"hash": "f04632c3a8e0d7c5b48cdd26a99bb1dc5bd12df221f82405d663b8f15f5c0c3a"
"hash": "f37140fcdc721a8b199471b30c2baf124affa2eaf56c801c8dac3264c584f981"
}

View File

@@ -1 +1 @@
eabd52eaa454c37a5beb1f456c1ea649052eb58a
6396854336ae27fb14ccb792d80c31ff614b2afa

View File

@@ -8,7 +8,7 @@ use std::{
atomic::{AtomicU16, Ordering},
Arc, Mutex,
},
time::Duration,
time::{Duration, Instant},
};
use chrono::{DateTime, NaiveDateTime, Utc};
@@ -1530,6 +1530,20 @@ pub async fn monitor_db(
}
};
// run every hour
let vacuum_queue_f = async {
if server_mode && iteration.is_some() && iteration.as_ref().unwrap().should_run(60) {
if let Some(db) = conn.as_sql() {
let instant = Instant::now();
tracing::info!("vacuuming tables");
if let Err(e) = vacuuming_tables(&db).await {
tracing::error!("Error vacuuming v2_job: {:?}", e);
}
tracing::info!("vacuum tables done in {}s", instant.elapsed().as_secs());
}
}
};
let expired_items_f = async {
if server_mode && !initial_load {
if let Some(db) = conn.as_sql() {
@@ -1607,6 +1621,7 @@ pub async fn monitor_db(
expired_items_f,
zombie_jobs_f,
stale_jobs_f,
vacuum_queue_f,
expose_queue_metrics_f,
verify_license_key_f,
worker_groups_alerts_f,
@@ -1619,6 +1634,13 @@ pub async fn monitor_db(
);
}
async fn vacuuming_tables(db: &Pool<Postgres>) -> error::Result<()> {
sqlx::query!("VACUUM v2_job, v2_job_completed, job_result_stream, job_stats, job_logs, concurrency_key, log_file, metrics")
.execute(db)
.await?;
Ok(())
}
pub async fn expose_queue_metrics(db: &Pool<Postgres>) {
let last_check = sqlx::query_scalar!(
"SELECT created_at FROM metrics WHERE id LIKE 'queue_count_%' ORDER BY created_at DESC LIMIT 1"

View File

@@ -45,6 +45,7 @@ use windmill_worker::process_relative_imports;
use windmill_common::{
assets::{clear_asset_usage, insert_asset_usage, AssetUsageKind, AssetWithAltAccessType},
error::to_anyhow,
utils::WarnAfterExt,
worker::CLOUD_HOSTED,
};
@@ -1447,6 +1448,7 @@ async fn raw_script_by_path_internal(
w_id
)
.fetch_optional(&mut *tx)
.warn_after_seconds(5)
.await?;
tx.commit().await?;
@@ -1457,6 +1459,7 @@ async fn raw_script_by_path_internal(
w_id
)
.fetch_one(&db)
.warn_after_seconds(5)
.await?
.unwrap_or(false);

View File

@@ -24,7 +24,10 @@ use serde_json::Value;
use windmill_audit::audit_oss::{audit_log, AuditAuthorable};
use windmill_audit::ActionKind;
use windmill_common::{
db::UserDB, error::{Error, JsonResult, Result}, utils::{not_found_if_none, paginate, Pagination, StripPath}, variables::{
db::UserDB,
error::{Error, JsonResult, Result},
utils::{not_found_if_none, paginate, Pagination, StripPath, WarnAfterExt},
variables::{
build_crypt, get_reserved_variables, ContextualVariable, CreateVariable, ListableVariable,
},
worker::CLOUD_HOSTED,
@@ -693,6 +696,7 @@ pub async fn get_value_internal<'c>(
LEFT JOIN account ON variable.account = account.id WHERE variable.path = $1 AND variable.workspace_id = $2", path, w_id
)
.fetch_optional(&mut *tx)
.warn_after_seconds(5)
.await?;
let variable = if let Some(variable) = variable_o {

View File

@@ -7,6 +7,7 @@
*/
use crate::error;
use crate::utils::WarnAfterExt;
use crate::worker::Connection;
use crate::{worker::WORKER_GROUP, BASE_URL, DB};
use chrono::{SecondsFormat, Utc};
@@ -106,6 +107,7 @@ pub async fn get_workspace_key(w_id: &str, db: &DB) -> crate::error::Result<Stri
w_id
)
.fetch_one(db)
.warn_after_seconds(5)
.await
.map_err(|e| crate::Error::internal_err(format!("fetching workspace key: {e:#}")))?;
Ok(key)

View File

@@ -308,7 +308,7 @@ pub(crate) async fn queue_vacuum(conn: &Connection, worker_name: &str, hostname:
tokio::task::spawn(
(async move {
tracing::info!(worker = %worker_name, hostname = %hostname, "vacuuming queue");
if let Err(e) = sqlx::query!("VACUUM v2_job_queue, v2_job_runtime, v2_job_status")
if let Err(e) = sqlx::query!("VACUUM v2_job_queue, v2_job_runtime, v2_job_status, job_perms")
.execute(&db2)
.await
{

View File

@@ -523,7 +523,6 @@
<div class="shrink-0">
<Toggle
bind:checked={jsonView}
label="JSON View"
size="xs"
options={{
right: 'JSON editor',
@@ -650,7 +649,7 @@
bind:order={schema.properties[argName].order}
{isFlowInput}
{isAppInput}
on:change={() => {
onChange={() => {
schema = $state.snapshot(schema)
dispatch('change', schema)
}}

View File

@@ -1,8 +1,7 @@
<script lang="ts">
import { run } from 'svelte/legacy'
import type { EnumType } from '$lib/common'
import { computeKind } from '$lib/utils'
import { untrack } from 'svelte'
import Label from './Label.svelte'
import ResourceTypePicker from './ResourceTypePicker.svelte'
import Toggle from './Toggle.svelte'
@@ -29,6 +28,7 @@
enumLabels?: Record<string, string> | undefined
overrideAllowKindChange?: boolean
originalType?: string | undefined
onChange?: () => void
}
let {
@@ -45,7 +45,8 @@
dateFormat = $bindable(),
enumLabels = $bindable(undefined),
overrideAllowKindChange = true,
originalType = undefined
originalType = undefined,
onChange = () => {}
}: Props = $props()
let kind: 'none' | 'pattern' | 'enum' | 'resource' | 'format' | 'base64' | 'date-time' = $state(
@@ -82,18 +83,22 @@
['Pattern', 'pattern']
]
run(() => {
$effect.pre(() => {
format =
kind == 'resource' ? (resource != undefined ? `resource-${resource}` : 'resource') : format
kind == 'resource'
? resource != undefined
? `resource-${resource}`
: 'resource'
: untrack(() => format)
})
run(() => {
$effect.pre(() => {
pattern = patternStr == '' ? undefined : patternStr
})
run(() => {
$effect.pre(() => {
contentEncoding = kind == 'base64' ? 'base64' : undefined
})
run(() => {
$effect.pre(() => {
if (format == 'email') {
pattern = '^[\\w-+.]+@([\\w-]+\\.)+[\\w-]{2,63}$'
}
@@ -369,6 +374,7 @@
options={{ right: 'Is Password' }}
checked={password}
on:change={(e) => {
onChange?.()
if (e.detail) {
password = true
} else {

View File

@@ -1,4 +1,7 @@
<script lang="ts">
import { createBubbler, stopPropagation } from 'svelte/legacy'
const bubble = createBubbler()
import { classNames } from '$lib/utils'
import { createEventDispatcher } from 'svelte'
import { twMerge } from 'tailwind-merge'
@@ -6,35 +9,62 @@
import { AlertTriangle } from 'lucide-svelte'
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
export let options: {
left?: string
leftTooltip?: string
right?: string
rightTooltip?: string
rightDocumentationLink?: string
} = {}
export let checked: boolean = false
export let disabled = false
export let textClass = ''
export let textStyle = ''
export let color: 'blue' | 'red' | 'nord' = 'blue'
export let id = (Math.random() + 1).toString(36).substring(10)
export let lightMode: boolean = false
export let eeOnly: boolean = false
export let aiId: string | undefined = undefined
export let aiDescription: string | undefined = undefined
export let size: 'sm' | 'xs' | '2xs' | '2sm' = 'sm'
const dispatch = createEventDispatcher<{ change: boolean }>()
const bothOptions = Boolean(options.left) && Boolean(options.right)
export let textDisabled = false
interface Props {
options?: {
left?: string
leftTooltip?: string
right?: string
rightTooltip?: string
rightDocumentationLink?: string
}
checked?: boolean | undefined
disabled?: boolean
textClass?: string
textStyle?: string
color?: 'blue' | 'red' | 'nord'
id?: any
lightMode?: boolean
eeOnly?: boolean
aiId?: string | undefined
aiDescription?: string | undefined
size?: 'sm' | 'xs' | '2xs' | '2sm'
class?: string | undefined
textDisabled?: boolean
right?: import('svelte').Snippet
}
let {
options = {},
checked = $bindable(undefined),
disabled = false,
textClass = '',
textStyle = '',
color = 'blue',
id = (Math.random() + 1).toString(36).substring(10),
lightMode = false,
eeOnly = false,
aiId = undefined,
aiDescription = undefined,
size = 'sm',
class: clazz = undefined,
textDisabled = false,
right
}: Props = $props()
$effect.pre(() => {
if (checked == undefined) {
checked = false
}
})
const bothOptions = Boolean(options.left) && Boolean(options.right)
</script>
<label
for={id}
class="{$$props.class || ''} z-auto flex flex-row items-center duration-50 {disabled
class="{clazz || ''} z-auto flex flex-row items-center duration-50 {disabled
? 'grayscale opacity-50'
: 'cursor-pointer'}"
>
@@ -55,11 +85,11 @@
</span>
{/if}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="relative"
on:click|stopPropagation
onclick={stopPropagation(bubble('click'))}
use:triggerableByAI={{
id: aiId,
description: aiDescription,
@@ -69,16 +99,16 @@
}}
>
<input
on:focus
on:click
onfocus={bubble('focus')}
onclick={bubble('click')}
{disabled}
type="checkbox"
{id}
class="sr-only peer"
bind:checked
on:change|stopPropagation={(e) => {
onchange={stopPropagation((e) => {
dispatch('change', checked)
}}
})}
/>
<div
class={classNames(
@@ -116,7 +146,7 @@
{/if}
</span>
{/if}
<slot name="right" />
{@render right?.()}
</label>
{#if eeOnly && disabled}
<span class="inline-flex text-xs items-center gap-1 !text-yellow-500 whitespace-nowrap ml-8">

View File

@@ -14,8 +14,7 @@
import type { SchemaProperty } from '$lib/common'
import ToggleButtonGroup from '../common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from '../common/toggleButton-v2/ToggleButton.svelte'
import { createEventDispatcher, onMount, untrack } from 'svelte'
import { createDispatcherIfMounted } from '$lib/createDispatcherIfMounted'
import { onMount, untrack } from 'svelte'
interface Props {
description?: string
@@ -45,6 +44,7 @@
| undefined
typeeditor?: import('svelte').Snippet
children?: import('svelte').Snippet
onChange?: () => void
}
let {
@@ -66,7 +66,8 @@
order = $bindable(),
itemsType = $bindable(undefined),
typeeditor,
children
children,
onChange = undefined
}: Props = $props()
$effect.pre(() => {
@@ -75,8 +76,6 @@
}
})
const dispatch = createEventDispatcher()
const dispatchIfMounted = createDispatcherIfMounted(dispatch)
let el: HTMLTextAreaElement | undefined = undefined
let oneOfSelected: string | undefined = $state(
@@ -140,7 +139,8 @@
if (!deepEqual(extra, initialExtra)) {
initialExtra = structuredClone($state.snapshot(extra))
console.debug('property content updated')
dispatchIfMounted('change')
onChange?.()
}
}
@@ -151,7 +151,7 @@
order
}
console.debug('property schema updated')
dispatchIfMounted('change')
onChange?.()
}
}
$effect(() => {
@@ -177,7 +177,7 @@
rows="2"
bind:value={description}
onkeydown={onKeyDown}
onchange={() => dispatch('change')}
onchange={onChange}
placeholder="Field description"
></textarea>
</Label>
@@ -188,7 +188,7 @@
{/snippet}
<input
bind:value={title}
onchange={() => dispatch('change')}
onchange={onChange}
onkeydown={onKeyDown}
placeholder="Field title"
/>
@@ -206,7 +206,7 @@
placeholder="Enter a placeholder"
rows="1"
bind:value={placeholder}
onchange={() => dispatch('change')}
onchange={onChange}
disabled={!shouldDisplayPlaceholder(type, format, enum_, contentEncoding, pattern, extra)}
></textarea>
</Label>
@@ -236,6 +236,7 @@
bind:enumLabels={extra['enumLabels']}
originalType={extra['originalType']}
overrideAllowKindChange={isFlowInput || isAppInput}
{onChange}
/>
{:else if type == 'number' || type == 'integer'}
<NumberTypeNarrowing

View File

@@ -26,4 +26,6 @@
<!-- <ScriptWrapper {script} neverShowMeta={true} {customUi} /> -->
<EditableSchemaSdkWrapper {customUi} {schema} />
<EditableSchemaSdkWrapper onSchemaChange={(schema) => console.log(schema)} {customUi} {schema} />
{JSON.stringify(schema)}