简体   繁体   中英

Real-Time PostgreSQL client updates based on notification

I try to create client -> database notification / update system. The client is written in Java / Hibernate. To notify the client i use the trigger:

CREATE OR REPLACE FUNCTION notifyUsers() RETURNS TRIGGER AS $$
DECLARE 
    data integer;
    notification json;

BEGIN

    IF (TG_OP = 'DELETE') THEN
        data = OLD.id;
    ELSE
        data = NEW.id;
    END IF;

    -- Contruct the notification
    notification = json_build_object(
                      'table',TG_TABLE_NAME,
                      'action', TG_OP,
                      'id', data);


    -- Execute pg_notify(channel, notification)
    PERFORM pg_notify('events',notification::text);

    -- Result is ignored since this is an AFTER trigger
    RETURN NULL; 
END;
$$ LANGUAGE 'plpgsql';


CREATE TRIGGER notifyUsersAccountData AFTER INSERT OR UPDATE OR DELETE ON document FOR EACH ROW EXECUTE PROCEDURE notifyUsers();

but when the notification is received by client I cant determine who trigger it. Can I send any additional parameter that would determine who triggers it (session_id? In my application there are custom users but I would like to avoid sending User.id as logging on the same user by 2 PC would break my notification/update system)

After notification is receiver the client updates the value, the "session id" is needed to determine whether "THIS client" made the change (of course in this case the client should not be updated) or someone else (in this case the update should be applied)

EDIT - - -

I would like to refresh this topic as it is not resolved. The solution with calling pg_backend_pid sometimes breaks resulting that "The PID of caller that performs SQL query" is not the same as PGNotification.getPid. The code:

public int getSessionPid() {
int lResult = -1;
try
{
    connect();
    Session session = _sessionFactory.getCurrentSession();
    Transaction lTransaction = session.beginTransaction();
    SQLQuery lQuery = session.createSQLQuery("SELECT pg_backend_pid()");

    List lResultQuery = lQuery.list();
    lResult = Integer.parseInt(lResultQuery.get(0).toString());
    lTransaction.commit();
    }catch(org.hibernate.SessionException e)
    {
        e.printStackTrace();
    }

    return lResult;
}

and the code where comparison appears:

int lPid = Facade.databaseConnector.warehouse.getSessionPid();
for(int i = 0; i < notifications.length; ++i)
{
    if(lPid == notifications[i].getPID())
...

Additionally I see that in the table where current backend connections are visible select * from pg_stat_activity there are 3 connections per one instance of my program.

  1. SELECT 1 - just to be updated with notifications as without "touching" database, notifications wont be triggered
  2. one connection is related with hibernate
  3. one connection is related with SQL queries in database (like insert/update/remove rows etc...)

All the backend connections have different ports and PIDs in the pg_stat_activity.

Any ideas?

PostgreSQL documentation for pg_notify seems to give solution to this problem.

It is common for a client that executes NOTIFY to be listening on the same notification channel itself. In that case it will get back a notification event, just like all the other listening sessions. Depending on the application logic, this could result in useless work, for example, reading a database table to find the same updates that that session just wrote out. It is possible to avoid such extra work by noticing whether the notifying session's server process PID (supplied in the notification event message) is the same as one's own session's PID (available from libpq). When they are the same, the notification event is one's own work bouncing back, and can be ignored.

So your client gets payload like this:

SELECT pg_notify('foo', 'payload');
Asynchronous notification of 'foo' received from backend pid 13976
    Data: payload

You can just parse that pid out of it. It is kinda like session_id I guess.

You can get your current session pid like this:

SELECT pg_backend_pid();
13976

It took me a while to polish the solution but I have found really cool one. I wanted to be independent from amount of opened session across one instance of my software and be sure that the instance of the software properly determine whether trigger comes from this instance or not (even if one instance may open more than one session, and on the same PC more than one instances can be opened). And here it is:

  1. When logging to the database I use Application_name to name the app record in session (within database): jdbc:postgresql://localhost:5432/?ApplicationName=based_on_run_time_hash, you can see this using SELECT * from pg_stat_activity
  2. in trigger function I have

EXECUTE 'SELECT application_name from pg_stat_activity where pid IN (SELECT pg_backend_pid())' INTO session_app_name; notification = json_build_object( 'session', session_app_name, ...);

and when the PC software instance receives the trigger it compares whether local Application_name is the same as that sent in json ('session') and this way I am sure that the trigger was created by action that came from this instance

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM