Managing Cortex AI Function costs with Account Usage¶
Snowflake Cortex AI Functions (AI_COMPLETE, AI_SUMMARIZE, AI_TRANSLATE, AI_SENTIMENT, and others) consume credits based
on token or page usage. Without monitoring and controls, costs for using these functions can escalate quickly due to:
Unoptimized prompts generating excessive tokens
Long-running or runaway queries
Lack of per-user spending limits
Insufficient visibility into usage patterns
This topic suggests strategies for monitoring, managing, and controlling the costs associated with Snowflake Cortex AI
Functions. Using the CORTEX_AI_FUNCTIONS_USAGE_HISTORY view, you can track usage patterns and implement automated cost
controls. These techniques can help you monitor usage, alert when spending limits are exceeded, control access to
functions based on monthly limits, and stop runaway queries.
The SNOWFLAKE.ACCOUNT_USAGE.CORTEX_AI_FUNCTIONS_USAGE_HISTORY view provides detailed telemetry for all Cortex AI Functions invoked via SQL.
The view has a maximum latency of sixty minutes, although data may be available in as few as ten minutes after function execution begins.
For detailed information on this view, see the CORTEX_AI_FUNCTIONS_USAGE_HISTORY view.
The following queries help you understand your AI Functions usage patterns. Run these periodically yourself, or integrate them into dashboards for ongoing visibility.
Identify top consumers and track per-user spending over time. This query joins with the USERS view to provide user details including email and default role for easier identification and follow-up.
SELECTDATE_TRUNC('month', h.START_TIME)AS usage_month,
u.NAMEASuser_name,
u.EMAIL,
u.DEFAULT_ROLE,SUM(h.CREDITS)AS total_credits,COUNT(DISTINCT h.QUERY_ID)AS query_count
FROMSNOWFLAKE.ACCOUNT_USAGE.CORTEX_AI_FUNCTIONS_USAGE_HISTORY h
JOINSNOWFLAKE.ACCOUNT_USAGE.USERS u
ON h.USER_ID= u.USER_IDWHERE h.START_TIME>=DATEADD('month',-3,CURRENT_TIMESTAMP())GROUP BY1,2,3,4ORDER BY usage_month DESC, total_credits DESC;
Define automated mechanisms to detect excessive spending and take corrective action. These queries can be used independently of each other, or combined for comprehensive cost governance.
Set up an automated alert that monitors total monthly AI Function credit consumption across your entire account. When spending exceeds a defined threshold, the alert sends an email notification to designated administrators.
Setting up the alert requires the following privileges on the role running these examples:
-- Run as ACCOUNTADMINGRANTIMPORTEDPRIVILEGESONDATABASESNOWFLAKETOROLE<your_role>;GRANTCREATEINTEGRATIONONACCOUNTTOROLE<your_role>;GRANTEXECUTEALERTONACCOUNTTOROLE<your_role>;GRANTUSAGEONWAREHOUSE<your_warehouse>TOROLE<your_role>;-- Grant CREATE TABLE and CREATE PROCEDURE on your target schema:GRANTCREATETABLE,CREATEPROCEDUREONSCHEMA<your_db>.<your_schema>TOROLE<your_role>;-- To grant your role to yourself (IDENTIFIER requires a session variable, not a function call):SET my_user =CURRENT_USER();GRANTROLE<your_role>TOUSERIDENTIFIER($my_user);-- Switch to your role for all subsequent steps in this section-- (Objects must be owned by this role for EXECUTE AS OWNER procedures to work):USEROLE<your_role>;USEWAREHOUSE<your_warehouse>;
First, create a notification integration if one does not already exist. This example replaces any existing integration named ai_cost_alerts.
CREATE OR REPLACENOTIFICATIONINTEGRATION ai_cost_alerts
TYPE=EMAILENABLED=TRUEALLOWED_RECIPIENTS=('admin@company.com','finops@company.com')
Important
For SYSTEM$SEND_EMAIL to deliver, every recipient address must satisfy all three conditions:
Listed in the integration’s ALLOWED_RECIPIENTS.
Used as the to_email argument inside the procedure body (the procedures in this guide hardcode 'admin@company.com'; replace this with your own address everywhere it appears).
Set as the EMAIL field on a Snowflake user in this account, with the verification email confirmed.
If any of the three is missing or out of sync, the call fails with Email recipients in the given list at indexes [...] are not allowed.
Next, create a table to track when alerts were sent for each month. This is used to prevent duplicate alerts within a month.
CREATETABLEIF NOT EXISTSAI_FUNCTIONS_ALERT_STATE(ALERT_NAMEVARCHARNOT NULL,ALERT_MONTHDATENOT NULL,SENT_ATTIMESTAMP_LTZDEFAULTCURRENT_TIMESTAMP(),CREDITS_AT_ALERTNUMBER(38,6),PRIMARY KEY(ALERT_NAME,ALERT_MONTH));
Now create a stored procedure to check if an alert was already sent this month, record the alert state, and send the email notification.
CREATE OR REPLACEPROCEDURESEND_MONTHLY_SPEND_ALERT(P_THRESHOLDFLOAT)RETURNSVARCHARLANGUAGESQLEXECUTEASCALLERAS
$$
DECLARE
v_already_sent INTEGER;
v_credits NUMBER(38,6);
v_email_body VARCHAR;BEGIN-- Check if an alert has already been sent this monthSELECTCOUNT(*)INTO :v_already_sent
FROMAI_FUNCTIONS_ALERT_STATEWHEREALERT_NAME='monthly_spend'ANDALERT_MONTH=DATE_TRUNC('month',CURRENT_DATE());IF(v_already_sent >0)THENRETURN'Alert already sent for this month';ENDIF;-- Get the current month spendSELECTCOALESCE(SUM(CREDITS),0)INTO :v_credits
FROMSNOWFLAKE.ACCOUNT_USAGE.CORTEX_AI_FUNCTIONS_USAGE_HISTORYWHERESTART_TIME>=DATE_TRUNC('month',CURRENT_TIMESTAMP());-- Compare against the thresholdIF(v_credits <= :P_THRESHOLD)THENRETURN'Threshold not exceeded. Current: '|| v_credits::VARCHAR||' / '|| :P_THRESHOLD::VARCHAR;ENDIF;-- Record the alert so it does not fire again this monthINSERTINTOAI_FUNCTIONS_ALERT_STATE(ALERT_NAME,ALERT_MONTH,CREDITS_AT_ALERT)VALUES('monthly_spend',DATE_TRUNC('month',CURRENT_DATE()), :v_credits);-- Build the email body and send the alert
v_email_body :='Monthly AI Function credit consumption has exceeded the threshold.'||CHR(10)||CHR(10)||'Current spend: '|| v_credits::VARCHAR||' credits'||CHR(10)||'Threshold: '|| :P_THRESHOLD::VARCHAR||' credits'||CHR(10)||CHR(10)||'Please review usage accordingly.';EXECUTEIMMEDIATE'CALL SYSTEM$SEND_EMAIL(''ai_cost_alerts'', ''admin@company.com'', '||'''AI Functions Monthly Spend Alert'', '''||REPLACE(v_email_body,'''','''''')||''')';RETURN'Alert sent. Credits: '|| v_credits::VARCHAR;END;
$$;
Finally, create an alert that checks usage against the spending threshold each hour and calls the procedure to send the notification if needed.
You should adjust the limit of 1000 credits, which appears in two places in the example below, to the desired threshold.
CREATE OR REPLACEALERT ai_functions_monthly_spend_alert
WAREHOUSE=<your_warehouse>SCHEDULE='USING CRON 0 * * * * UTC'-- Runs every hourIF(EXISTS(SELECT1FROMSNOWFLAKE.ACCOUNT_USAGE.CORTEX_AI_FUNCTIONS_USAGE_HISTORYWHERESTART_TIME>=DATE_TRUNC('month',CURRENT_TIMESTAMP())HAVINGSUM(CREDITS)>1000-- adjust the limit accordingly))THENCALLSEND_MONTHLY_SPEND_ALERT(1000);-- please adjust the limit accordingly-- enable the alertALTERALERT ai_functions_monthly_spend_alert RESUME;
Tip
For testing purposes, set the limit to 0 at first to trigger the alert immediately. Recreate the alert with the desired threshold after confirming that it works as expected.
After testing with a 0 threshold, run the following SQL to allow the alert to trigger again in the current month.
You can make sure that the alert is operating by querying the alert history and the alert state table as follows:
-- Make sure alert existsSHOWALERTSLIKE'ai_functions_monthly_spend_alert';-- Check alert historySELECT*FROMTABLE(INFORMATION_SCHEMA.ALERT_HISTORY(SCHEDULED_TIME_RANGE_START=>DATEADD('day',-1,CURRENT_TIMESTAMP()),ALERT_NAME=>'ai_functions_monthly_spend_alert'))ORDER BYSCHEDULED_TIMEDESC;-- Check which months have had alerts sentSELECT*FROMAI_FUNCTIONS_ALERT_STATEORDER BYALERT_MONTHDESC;
This example implements per-user monthly spending limits. Users are granted a dedicated custom AI_FUNCTIONS_USER_ROLE that
provides access to Cortex AI Functions. A table stores individual users’ monthly token budget. When a user exceeds their
budget for the month, an hourly task revokes their access to AI Functions by removing AI_FUNCTIONS_USER_ROLE. A monthly
task restores the role at the beginning of the next month.
Setting up per-user limits requires the following privileges on the role running these examples:
-- Run as ACCOUNTADMINGRANTIMPORTEDPRIVILEGESONDATABASESNOWFLAKETOROLE<your_role>;GRANTEXECUTETASKONACCOUNTTOROLE<your_role>;GRANTEXECUTEMANAGEDTASKONACCOUNTTOROLE<your_role>;GRANTUSAGEONWAREHOUSE<your_warehouse>TOROLE<your_role>;-- Grant CREATE TABLE, CREATE PROCEDURE, and CREATE TASK on your target schema:GRANTCREATETABLE,CREATEPROCEDURE,CREATETASKONSCHEMA<your_db>.<your_schema>TOROLE<your_role>;-- Create AI_FUNCTIONS_USER_ROLE and transfer ownership so the procedures can GRANT/REVOKE it:CREATEROLEIF NOT EXISTSAI_FUNCTIONS_USER_ROLE;GRANTOWNERSHIPONROLEAI_FUNCTIONS_USER_ROLETOROLE<your_role>COPYCURRENTGRANTS;-- To grant your role to yourself (IDENTIFIER requires a session variable, not a function call):SET my_user =CURRENT_USER();GRANTROLE<your_role>TOUSERIDENTIFIER($my_user);-- Switch to your role for all subsequent steps in this section-- (Objects must be owned by this role for EXECUTE AS OWNER procedures to work):USEROLE<your_role>;USEWAREHOUSE<your_warehouse>;
Important
By default, all users have access to AI Functions (and other Snowflake Cortex features) because the SNOWFLAKE.CORTEX_USER database role is granted to the PUBLIC role.
To enforce per-user limits, you must revoke SNOWFLAKE.CORTEX_USER from PUBLIC and grant it only through the AI_FUNCTIONS_USER_ROLE. Use the
following SQL to revoke the role from PUBLIC (run as ACCOUNTADMIN, then switch back to your role):
Be sure that all users who need access to Cortex features are granted only the AI_FUNCTIONS_USER_ROLE. Use of
any other role that includes SNOWFLAKE.CORTEX_USER allows users to bypass the spending limit controls implemented in
this example. In some cases, you could use a more specific role; for example, users who need access only to Cortex Analyst
can be granted the SNOWFLAKE.CORTEX_ANALYST_USER role instead of SNOWFLAKE.CORTEX_USER.
To restore the default state at any point, grant the role back to PUBLIC:
To audit which roles still grant SNOWFLAKE.CORTEX_USER:
SHOWGRANTSOFDATABASE ROLESNOWFLAKE.CORTEX_USER;
To set up per-user spending limits, first create a role that controls access to AI Functions, allowing this access to be managed separately from other privileges.
-- Create a role specifically for AI Function accessCREATEROLEIF NOT EXISTSAI_FUNCTIONS_USER_ROLE;-- Grant necessary privileges to the roleGRANTDATABASE ROLESNOWFLAKE.CORTEX_USERTOROLEAI_FUNCTIONS_USER_ROLE;-- Grant usage on warehouseGRANTUSAGEONWAREHOUSEAI_FUNCTIONS_WAREHOUSETOROLEAI_FUNCTIONS_USER_ROLE;
Now, set up the access control table, which tracks which users have AI Function access, their individual spending
limits, and their revocation history. It serves as the source of truth for the automated monitoring and access
restoration processes.
CREATETABLEIF NOT EXISTSAI_FUNCTIONS_ACCESS_CONTROL(USER_NAMEVARCHARNOT NULL,USER_IDNUMBER,GRANTED_ATTIMESTAMP_LTZDEFAULTCURRENT_TIMESTAMP(),MONTHLY_CREDIT_LIMITNUMBER(38,6)DEFAULT100,-- adjust the limit accordinglyIS_ACTIVEBOOLEANDEFAULTTRUE,REVOKED_ATTIMESTAMP_LTZ,REVOCATION_REASONVARCHAR,PRIMARY KEY(USER_NAME));
Next, create a stored procedure to grant AI Function access to a user and register them in the access control table with their spending limit. The code looks up the user’s ID from the Account Usage view to enable efficient joins in monitoring queries.
This procedure (and the two below) use EXECUTE AS OWNER so that the role that owns the procedure (and AI_FUNCTIONS_USER_ROLE) handles the GRANT/REVOKE. Callers do not need ownership of the user role.
CREATE OR REPLACEPROCEDUREGRANT_AI_FUNCTIONS_ACCESS(P_USER_NAMEVARCHAR,P_MONTHLY_LIMITNUMBER(38,6)DEFAULT100-- adjust the limit accordingly)RETURNSVARCHARLANGUAGESQLEXECUTEASOWNERAS
$$
DECLARE
v_user_id NUMBER;BEGIN-- Look up USER_ID from account usageSELECTUSER_IDINTO :v_user_id
FROMSNOWFLAKE.ACCOUNT_USAGE.USERSWHERENAME= :P_USER_NAME
LIMIT1;-- Grant the AI Functions role to the userEXECUTEIMMEDIATE'GRANT ROLE AI_FUNCTIONS_USER_ROLE TO USER '||P_USER_NAME;-- Register or update the user in the access control tableMERGEINTOAI_FUNCTIONS_ACCESS_CONTROL tgt
USING(SELECT :P_USER_NAME ASUSER_NAME) src
ON tgt.USER_NAME= src.USER_NAMEWHENMATCHEDTHENUPDATESETUSER_ID= :v_user_id,IS_ACTIVE=TRUE,MONTHLY_CREDIT_LIMIT= :P_MONTHLY_LIMIT,GRANTED_AT=CURRENT_TIMESTAMP(),REVOKED_AT=NULL,REVOCATION_REASON=NULLWHENNOTMATCHEDTHENINSERT(USER_NAME,USER_ID,MONTHLY_CREDIT_LIMIT,IS_ACTIVE)VALUES(:P_USER_NAME, :v_user_id, :P_MONTHLY_LIMIT,TRUE);RETURN'Access granted to '||P_USER_NAME||' with monthly limit of '||P_MONTHLY_LIMIT||' credits';END;
$$;
Use this stored procedure to add users and their credit quotas to the access control table.
CALLGRANT_AI_FUNCTIONS_ACCESS('ALICE',1000);-- grants access to user ALICE with a monthly limit of 100 creditsCALLGRANT_AI_FUNCTIONS_ACCESS('BOB',2000);-- grants access to user BOB with a monthly limit of 200 credits
Create the monthly access refresh task next. This task runs on the first day of each month to restore AI Function access
for all entitled users. When a user’s access was revoked due to exceeding their limit in the previous month, this task
grants them a fresh budget for the new month.
-- Create the revocation procedureCREATE OR REPLACEPROCEDUREREVOKE_EXCESSIVE_SPENDERS()RETURNSTABLE(USER_NAMEVARCHAR,CREDITS_USEDNUMBER,CREDIT_LIMITNUMBER,ACTIONVARCHAR)LANGUAGESQLEXECUTEASOWNERAS
$$
DECLAREresultRESULTSET;BEGIN-- Find users who exceeded their monthly limit and revoke accessresult:=(WITH user_spending AS(SELECT
u.NAMEASUSER_NAME,
ac.MONTHLY_CREDIT_LIMIT,COALESCE(SUM(h.CREDITS),0)ASCREDITS_USEDFROMAI_FUNCTIONS_ACCESS_CONTROL ac
JOINSNOWFLAKE.ACCOUNT_USAGE.USERS u
ON ac.USER_NAME= u.NAMELEFT JOINSNOWFLAKE.ACCOUNT_USAGE.CORTEX_AI_FUNCTIONS_USAGE_HISTORY h
ON u.USER_ID= h.USER_IDAND h.START_TIME>=DATE_TRUNC('month',CURRENT_TIMESTAMP())WHERE ac.IS_ACTIVE=TRUEGROUP BY u.NAME, ac.MONTHLY_CREDIT_LIMIT)SELECTUSER_NAME,CREDITS_USED,MONTHLY_CREDIT_LIMITASCREDIT_LIMIT,'REVOKED'ASACTIONFROM user_spending
WHERECREDITS_USED>MONTHLY_CREDIT_LIMIT);-- Revoke access for each excessive spenderFOR rec INresultDOLET v_user_name VARCHAR:= rec.USER_NAME;LET v_credits_used NUMBER:= rec.CREDITS_USED;LET v_credit_limit NUMBER:= rec.CREDIT_LIMIT;EXECUTEIMMEDIATE'REVOKE ROLE AI_FUNCTIONS_USER_ROLE FROM USER '|| v_user_name;UPDATEAI_FUNCTIONS_ACCESS_CONTROLSETIS_ACTIVE=FALSE,REVOKED_AT=CURRENT_TIMESTAMP(),REVOCATION_REASON='Exceeded monthly limit: '|| :v_credits_used ||' / '|| :v_credit_limit
WHEREUSER_NAME= :v_user_name;ENDFOR;RETURNTABLE(result);END;
$$;-- Create a task to run the revocation check every hourCREATE OR REPLACETASKMONITOR_AI_FUNCTIONS_SPENDINGWAREHOUSE=<your_warehouse>SCHEDULE='USING CRON 0 * * * * UTC'-- Every hourASCALLREVOKE_EXCESSIVE_SPENDERS();-- Enable the taskALTERTASKMONITOR_AI_FUNCTIONS_SPENDINGRESUME;-- Verify task statusSHOWTASKSLIKE'MONITOR_AI_FUNCTIONS_SPENDING';
Finally, create an hourly task to monitor user spending and revoke access for any user who exceeds their monthly limit.
-- Create a procedure to re-grant access to all entitled usersCREATE OR REPLACEPROCEDUREGRANT_ALL_ENTITLED_USERS()RETURNSTABLE(USER_NAMEVARCHAR,CREDIT_LIMITNUMBER,ACTIONVARCHAR)LANGUAGESQLEXECUTEASOWNERAS
$$
DECLAREresultRESULTSET;BEGINresult:=(SELECTUSER_NAME,MONTHLY_CREDIT_LIMITASCREDIT_LIMIT,'GRANTED'ASACTIONFROMAI_FUNCTIONS_ACCESS_CONTROL);-- Re-grant access for each entitled userFOR rec INresultDOLET v_user_name VARCHAR:= rec.USER_NAME;LET v_credit_limit NUMBER:= rec.CREDIT_LIMIT;CALLGRANT_AI_FUNCTIONS_ACCESS(:v_user_name, :v_credit_limit);ENDFOR;RETURNTABLE(result);END;
$$;-- Create a task to run on the 1st of each month at midnight UTCCREATE OR REPLACETASKMONTHLY_AI_FUNCTIONS_ACCESS_REFRESHWAREHOUSE=<your_warehouse>SCHEDULE='USING CRON 0 0 1 * * UTC'-- 1st day of each month at 00:00 UTCASCALLGRANT_ALL_ENTITLED_USERS();-- Enable the taskALTERTASKMONTHLY_AI_FUNCTIONS_ACCESS_REFRESHRESUME;-- Run once initially to populate granteesCALLGRANT_ALL_ENTITLED_USERS();-- Verify task statusSHOWTASKSLIKE'MONTHLY_AI_FUNCTIONS_ACCESS_REFRESH';
Long-running AI Function queries can accumulate significant costs. This example implements an automated system to detect queries that exceed a credit threshold and cancel them before they consume even more resources. An email alert is sent with full query details.
Setting up runaway query detection requires the following privileges on the role running these examples:
-- Run as ACCOUNTADMINGRANTIMPORTEDPRIVILEGESONDATABASESNOWFLAKETOROLE<your_role>;GRANTEXECUTETASKONACCOUNTTOROLE<your_role>;GRANTEXECUTEMANAGEDTASKONACCOUNTTOROLE<your_role>;GRANTOPERATEONWAREHOUSE<your_warehouse>TOROLE<your_role>;GRANTUSAGEONWAREHOUSE<your_warehouse>TOROLE<your_role>;GRANTCREATEINTEGRATIONONACCOUNTTOROLE<your_role>;-- Grant CREATE PROCEDURE and CREATE TASK on your target schema:GRANTCREATEPROCEDURE,CREATETASKONSCHEMA<your_db>.<your_schema>TOROLE<your_role>;-- To grant your role to yourself (IDENTIFIER requires a session variable, not a function call):SET my_user =CURRENT_USER();GRANTROLE<your_role>TOUSERIDENTIFIER($my_user);-- Switch to your role for all subsequent steps in this section-- (Objects must be owned by this role for EXECUTE AS OWNER procedures to work):USEROLE<your_role>;USEWAREHOUSE<your_warehouse>;
Note
When a query is cancelled, the client is still charged for all resources consumed up to the moment of cancellation. Cancelling a runaway query prevents further cost accumulation but does not refund credits already spent.
Tip
The CORTEX_AI_FUNCTIONS_USAGE_HISTORY view splits usage into one-hour windows, so a long-running query
produces multiple rows. IS_COMPLETED is TRUE only on the final row of a completed query. To find
still-running queries, aggregate by QUERY_ID and check that all rows have IS_COMPLETED = FALSE.
The procedure also reads ROLE_NAMES, QUERY_TAG, and WAREHOUSE_ID from the view to populate the alert email so the responder knows which user, project, and warehouse owned the query. See the view reference for full column descriptions.
Note
If the role running this procedure lacks OPERATE on a target warehouse, the cancellation fails. The email alert subject and body indicate CANCEL FAILED and include the underlying error so administrators can act. Grant OPERATE on every warehouse that runs AI Function queries to ensure cancellations succeed.
For each query in the last 48 hours, this procedure sums the credits used across all hourly windows. If the total exceeds the threshold and the query is still running, the procedure cancels it and reports it to an administrator.
-- Create a procedure to detect and cancel expensive runaway queriesCREATE OR REPLACEPROCEDUREMONITOR_AND_CANCEL_RUNAWAY_QUERIES(P_CREDIT_THRESHOLDNUMBERDEFAULT50-- adjust the limit accordingly)RETURNSVARCHARLANGUAGESQLEXECUTEASCALLERAS
$$
DECLAREresultRESULTSET;
cancelled_count INTEGERDEFAULT0;
failed_cancel_count INTEGERDEFAULT0;
cancelled_ids VARCHARDEFAULT'';
failed_ids VARCHARDEFAULT'';
v_query_id VARCHAR;
v_user_name VARCHAR;
v_functions VARCHAR;
v_models VARCHAR;
v_credits VARCHAR;
v_start_time VARCHAR;
v_roles VARCHAR;
v_query_tag VARCHAR;
v_warehouse_id VARCHAR;
v_email_body VARCHAR;
v_email_subject VARCHAR;
v_cancel_status VARCHAR;
v_cancel_error VARCHAR;BEGIN-- Aggregate credits across hourly windows per query. A single query can invoke-- multiple AI functions or models, each producing separate rows, so we sum all of them.result:=(WITH query_credits AS(SELECT
h.QUERY_ID,ANY_VALUE(h.USER_ID)ASUSER_ID,ARRAY_AGG(DISTINCT h.FUNCTION_NAME)ASFUNCTION_NAMES,ARRAY_AGG(DISTINCT h.MODEL_NAME)ASMODEL_NAMES,SUM(h.CREDITS)ASTOTAL_CREDITS,MIN(h.START_TIME)ASFIRST_SEEN,ANY_VALUE(h.ROLE_NAMES)ASROLE_NAMES,ANY_VALUE(h.QUERY_TAG)ASQUERY_TAG,ANY_VALUE(h.WAREHOUSE_ID)ASWAREHOUSE_IDFROMSNOWFLAKE.ACCOUNT_USAGE.CORTEX_AI_FUNCTIONS_USAGE_HISTORY h
WHERE h.START_TIME>=DATEADD('hour',-48,CURRENT_TIMESTAMP())GROUP BY h.QUERY_IDHAVINGSUM(h.CREDITS)> :P_CREDIT_THRESHOLD
ANDBOOLOR_AGG(h.IS_COMPLETED)=FALSE)SELECT
qc.QUERY_ID,COALESCE(u.NAME,'Unknown')ASUSER_NAME,ARRAY_TO_STRING(qc.FUNCTION_NAMES,', ')ASFUNCTION_NAMES,ARRAY_TO_STRING(qc.MODEL_NAMES,', ')ASMODEL_NAMES,
qc.TOTAL_CREDITS::VARCHARASCREDITS,
qc.FIRST_SEEN::VARCHARASSTART_TIME,COALESCE(qc.ROLE_NAMES::VARCHAR,'N/A')ASROLE_NAMES,COALESCE(qc.QUERY_TAG,'N/A')ASQUERY_TAG,COALESCE(qc.WAREHOUSE_ID::VARCHAR,'N/A')ASWAREHOUSE_IDFROM query_credits qc
LEFT JOINSNOWFLAKE.ACCOUNT_USAGE.USERS u
ON qc.USER_ID= u.USER_ID);-- Cancel each runaway query and send an alertFOR rec INresultDO
v_query_id := rec.QUERY_ID;
v_user_name := rec.USER_NAME;
v_functions := rec.FUNCTION_NAMES;
v_models := rec.MODEL_NAMES;
v_credits := rec.CREDITS;
v_start_time := rec.START_TIME;
v_roles := rec.ROLE_NAMES;
v_query_tag := rec.QUERY_TAG;
v_warehouse_id := rec.WAREHOUSE_ID;-- Attempt to cancel the query; record the outcome so it can be reported in the email
v_cancel_status :='CANCELLED';
v_cancel_error :='';BEGINEXECUTEIMMEDIATE'SELECT SYSTEM$CANCEL_QUERY('''|| v_query_id ||''')';EXCEPTIONWHENOTHERTHEN
v_cancel_status :='CANCEL FAILED';
v_cancel_error :=SQLERRM;END;-- Build the email body and send the alert
v_email_body :=CASEWHEN v_cancel_status ='CANCELLED'THEN'A runaway AI Function query has been cancelled due to excessive cost.'ELSE'A runaway AI Function query was detected, but cancellation FAILED. The query may still be running and consuming credits. Investigate immediately.'END||CHR(10)||CHR(10)||'Query Details:'||CHR(10)||'- Cancellation status: '|| v_cancel_status ||CHR(10)||CASEWHEN v_cancel_error <>''THEN'- Cancellation error: '|| v_cancel_error ||CHR(10)ELSE''END||'- Query ID: '|| v_query_id ||CHR(10)||'- User: '|| v_user_name ||CHR(10)||'- Function(s): '|| v_functions ||CHR(10)||'- Model(s): '|| v_models ||CHR(10)||'- Credits Used: '|| v_credits ||CHR(10)||'- Threshold: '|| :P_CREDIT_THRESHOLD::VARCHAR||CHR(10)||'- Start Time: '|| v_start_time ||CHR(10)||'- Roles: '|| v_roles ||CHR(10)||'- Query Tag: '|| v_query_tag ||CHR(10)||'- Warehouse ID: '|| v_warehouse_id ||CHR(10)||CHR(10)||'Please investigate this query and take appropriate action.';
v_email_subject :=CASEWHEN v_cancel_status ='CANCELLED'THEN'Runaway AI Query Cancelled - 'ELSE'Runaway AI Query Detected, CANCEL FAILED - 'END|| v_query_id;EXECUTEIMMEDIATE'CALL SYSTEM$SEND_EMAIL(''ai_cost_alerts'', ''admin@company.com'', '''||REPLACE(v_email_subject,'''','''''')||''', '''||REPLACE(v_email_body,'''','''''')||''')';IF(v_cancel_status ='CANCELLED')THEN
cancelled_count := cancelled_count +1;IF(cancelled_ids ='')THEN
cancelled_ids := v_query_id;ELSE
cancelled_ids := cancelled_ids ||', '|| v_query_id;ENDIF;ELSE
failed_cancel_count := failed_cancel_count +1;IF(failed_ids ='')THEN
failed_ids := v_query_id;ELSE
failed_ids := failed_ids ||', '|| v_query_id;ENDIF;ENDIF;ENDFOR;IF(cancelled_count =0AND failed_cancel_count =0)THENRETURN'No runaway queries found exceeding '|| :P_CREDIT_THRESHOLD::VARCHAR||' credits.';ENDIF;LET msg VARCHAR:='Processed '||(cancelled_count + failed_cancel_count)::VARCHAR||' runaway queries.';IF(cancelled_count >0)THEN
msg := msg ||' Cancelled: '|| cancelled_ids ||'.';ENDIF;IF(failed_cancel_count >0)THEN
msg := msg ||' Cancel failed (still running): '|| failed_ids ||'.';ENDIF;RETURN msg;END;
$$;-- Create a task to monitor and cancel runaway queries every hourCREATE OR REPLACETASKMONITOR_RUNAWAY_AI_QUERIESWAREHOUSE=<your_warehouse>SCHEDULE='USING CRON 0 * * * * UTC'-- Every hourASCALLMONITOR_AND_CANCEL_RUNAWAY_QUERIES(50);-- adjust the limit accordingly-- Enable the taskALTERTASKMONITOR_RUNAWAY_AI_QUERIESRESUME;-- Verify task statusSHOWTASKSLIKE'MONITOR_RUNAWAY_AI_QUERIES';-- Check task execution historySELECT*FROMTABLE(INFORMATION_SCHEMA.TASK_HISTORY(SCHEDULED_TIME_RANGE_START=>DATEADD('day',-1,CURRENT_TIMESTAMP()),TASK_NAME=>'MONITOR_RUNAWAY_AI_QUERIES'))ORDER BYSCHEDULED_TIMEDESC;
Tip
If you already know that some of your queries will run a long time, define a special role for these queries, and then exclude that role
from the cancellation logic. For example, to create the role: