Reputation: 1074
In my project I am running a massive query returning lots of data. Due to the size of this query, the runtime sometimes exceeds 1 minute. Ignoring other issues this may create, is there any way to cancel a currently running query?
For example, the user starts the query and is redirected to a standard 'Loading Results...' page. If they then decide that they no longer want the results, they can click a 'Cancel' button which will somehow terminate the running query.
Is this possible and how can it be done?
Thanks!
Upvotes: 2
Views: 2825
Reputation: 9756
I did something like this recently (adding a "cancel" button to our application). The plpgsql function I hacked together is below, which is basically a wrapper around the pg_cancel_backend()
function.
Caveats:
1). Your application must create an unique id (or UUID) and pass that in to the function as the "p_uid" parameter.
2). I hardcoded the application user (that doesn't change).
3). Postgresql 9.0+ (this was created for 9.2, untested in any earlier versions)
4). Security could be tightened up a bit in the function.
CREATE OR REPLACE FUNCTION public.cancel_user_query(p_uid TEXT, OUT retval BOOLEAN) RETURNS boolean
AS $function$
DECLARE
BEGIN
/* The pg_sleep() call at the start is because if a user issues a query
then promptly tries to kill it, it will likely not have shown up in the
pg_stat_activity view yet (there is a delay before it appears).
XXX: The GUC setting "track_activities" *MUST* be enabled for this to work.
*/
retval := FALSE;
IF ( current_setting('track_activities')::BOOLEAN IS NOT TRUE ) THEN
RAISE WARNING '[PUBLIC.CANCEL_USER_QUERY] - "track_activities" *MUST* be enabled for this to work';
RETURN;
END IF;
/* In a system under regular high load, this might need to be bumped higher than 2 seconds */
PERFORM pg_sleep(2);
WITH q AS ( SELECT pid FROM pg_stat_activity WHERE LOWER(usename) = 'YOUR_APP_USER' AND application_name = p_uid AND state <> 'idle' )
SELECT pg_cancel_backend(pid) AS retval
INTO retval
FROM q;
if ( retval IS NOT TRUE ) then
retval := FALSE;
end if;
RETURN;
EXCEPTION
WHEN others THEN
RAISE WARNING '[PUBLIC.CANCEL_USER_QUERY] - ERROR: %',sqlerrm;
retval := FALSE;
RETURN;
END;
$function$ LANGUAGE plpgsql SECURITY DEFINER;
On click of the "Cancel query" button, the application executes the function and checks the returned status.
Eg. SELECT retval FROM public.cancel_user_query('asifdaqiwaviafasdf') retval
Upvotes: 1
Reputation: 10349
Try using pg_cancel_backend.
From the docs:
Cancel a backend's current query. You can execute this against another backend that has exactly the same role as the user calling the function. In all other cases, you must be a superuser.
Upvotes: 3
Reputation: 14949
The only way I know of is to restart the database, although sometimes you can send a cancel signal, depending on the DB setup. You could look at the rack-timeout gem perhaps?
The real issue is that your query should not take that long, and I would strongly advise not approaching the problem this way. A far better way would be to examine the query and attempt to optimise it. There are few, if any queries that should legitimately be taking >1 minute to complete. Get a dump of the data onto your local machine and run EXPLAIN on the query to find the bottlenecks. I have gotten large 142s queries down to <3s by adding the right indexes, so I would be amazed if you cannot get the same effect.
Upvotes: 1