Reputation: 377
I'm currently working on a PostgreSQL 9.2.x Database with a lots of Clients, and tables and functions. We deploy code constantly and sometimes it is necessary to even drop a type or a function due this deployment.
Example:
1.Script to create the needed functions in the first place
CREATE TYPE tmp._myEnum AS ENUM ('OLD', 'NEW', 'BOTH');
CREATE OR REPLACE FUNCTION tmp._get_status()
RETURNS tmp._myEnum AS
$BODY$
BEGIN
RETURN 'OLD'::tmp._myEnum;
END;
$BODY$
LANGUAGE plpgsql VOLATILE SECURITY DEFINER COST 10;
CREATE OR REPLACE FUNCTION tmp._my_testfunction()
RETURNS VOID AS
$BODY$
BEGIN
CASE tmp._get_status()
WHEN 'OLD'::tmp._myEnum THEN
RAISE INFO 'myEnum is OLD';
WHEN 'NEW'::tmp._myEnum THEN
RAISE INFO 'myEnum is NEW';
WHEN 'BOTH'::tmp._myEnum THEN
RAISE INFO 'myEnum is BOTH';
ELSE
RAISE INFO 'myEnum has an unexpected value';
END CASE;
FOR i IN 1..10 LOOP
RAISE INFO 'Step [%]',i;
END LOOP;
RETURN;
END;
$BODY$
LANGUAGE plpgsql VOLATILE SECURITY DEFINER COST 10;
2.Scenario that leads to the exception:
a)One Client is constantly using tmp._my_testfunction() like this
SELECT tmp._my_testfunction()
b)In order to deploy a change to the Composite type i execute in another session
DROP FUNCTION IF EXISTS tmp._get_status();
DROP TYPE IF EXISTS tmp._myEnum;
CREATE TYPE tmp._myEnum AS ENUM ('OLD', 'NEW', 'BOTH','NOTHING');
CREATE OR REPLACE FUNCTION tmp._get_status()
RETURNS tmp._myEnum AS
$BODY$
BEGIN
RETURN 'OLD'::tmp._myEnum;
END;
$BODY$
LANGUAGE plpgsql VOLATILE SECURITY DEFINER COST 10;
c)The Client that is constantly using tmp._my_testfunction() imidiatly throws
ERROR: cache lookup failed for type 386318
CONTEXT: PL/pgSQL function tmp._my_testfunction() line 3 at CASE
How can I prevent that?
Upvotes: 6
Views: 15250
Reputation: 6445
A pretty untechnical solution...
I had my app deployed on AWS and remotely logged into PSQL, dropped the database, reimported and was presented with the above.
Redeploying the application resolved it, why I'm not sure.
Upvotes: 1
Reputation: 377
Since we couldn't find another way to work around this Problem we've implemented a solution that notifies all other connections to refresh itself by using LISTEN and NOTIFY commands. So all Clients are listening on specific channels for a command that tells them to reconnect. The command ist then send in the same Transaction that dropes the type.
Upvotes: 1
Reputation: 32161
What seems to be the case here is a race condition. Your clients calls tmp._my_test_function()
and while that is being set up by interpreting the source code of the function, you drop the function and the enum
type and then re-create them. Internally, virtually all objects are referenced by their oid
(with a value of 386318 for the type in your case) so the function and the enum
in your client function resolve to oid
s. If the interpreter resolves the function and the enum
to two oid
s and you then drop the function and the enum
and re-create them, the old oid
s disappear and your newly created function and enum
get different oid
s. New invocations of the function will work because the interpreter finds the new oid
s for the function and the enum
.
A solution in a scenario like this would be to REVOKE EXECUTE
on the client function, make the changes and then GRANT EXECUTE
the privileges again. On the client side you would have to deal with privilege errors by retrying the function call until you succeed.
In general, you would deploy new code on a production server during low-traffic periods and/or through session management if you want to avoid the overhead of retrying function calls.
Upvotes: 2