Re: LISTEN denial of service with aborted transaction

From: Matt Newell <newellm(at)blur(dot)com>
To: pgsql-hackers(at)postgresql(dot)org
Cc: it(at)blur(dot)com
Subject: Re: LISTEN denial of service with aborted transaction
Date: 2015-09-30 00:26:11
Message-ID: 2265820.GvUOzIOgn3@obsidian
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

On Monday, September 28, 2015 07:22:26 PM Tom Lane wrote:
> Matt Newell <newellm(at)blur(dot)com> writes:
> > 1. When a connection issues it's first LISTEN command, in
> > Exec_ListenPreCommit QUEUE_BACKEND_POS(MyBackendId) = QUEUE_TAIL;
> > this causes the connection to iterate through every notify queued in the
> > slru, even though at that point I believe the connection can safely
> > ignore any notifications from transactions that are already committed,
> > and if I understand correctly notifications aren't put into the slru
> > until precommit, so wouldn't it be safe to do:
> > QUEUE_BACKEND_POS(MyBackendId) = QUEUE_HEAD;
> > inside Async_Listen?
>
> Per the comment in Exec_ListenPreCommit, the goal here is to see entries
> that have been made and not yet committed. If you don't do it like this,
> then a new listener has the possibility of receiving notifications from
> one transaction, while not seeing notifications from another one that
> committed later, even though longer-established listeners saw outputs from
> both transactions. We felt that that sort of application-visible
> inconsistency would be a bad thing.
>
Right, QUEUE_HEAD can't be used, however...

> > If that's not safe, then could a new member be added to
> > AsyncQueueControl that points to the first uncommitted QueuePosition
> > (wouldn't have to be kept completely up to date).
>
> Err ... isn't that QUEUE_TAIL already? I've not studied this code in
> detail recently, though.
>
QUEUE_TAIL is the oldest notification. My idea is to keep a somewhat up-to-
date pointer to the oldest uncommitted notification, which can be used as a
starting point when a connection issues it's first listen. Just as the
current code will advance a backend's pointer past any committed notifications
when it calls asyncQueueReadAllNotifications in Exec_ListenPreCommit with no
registered listeners.

I came up with a proof of concept and it appears to work as expected. With
500,000 notifications queued a listen is consistently under .5ms, while my
9.4.4 install is taking 70ms, and the speedup should be much greater on a
busy server where there is more lock contention.

Attached is the patch and the ugly test script.

> > 2. Would it be possible when a backend is signaled to check if it is idle
> > inside an aborted transaction, and if so process the notifications and put
> > any that match what the backend is listening on into a local list.
>
> Possibly. sinval catchup notification works like that, so you could maybe
> invent a similar mechanism for notify. Beware that we've had to fix
> performance issues with sinval catchup; you don't want to release a whole
> bunch of processes to do work at the same time.

I'll have to think about this more but i don't envision it causing such as
scenario.

The extra processing that will happen at signaling time would have been done
anyway when the aborted transaction is finally rolled back, the only extra
work is copying the relevant notifications to local memory.

Regardless it might not be worthwhile since my patch for #1 seems to address
the symptom that bit me.

Matt Newell

Attachment Content-Type Size
async.c.diff text/x-patch 8.9 KB
notify_dos.sh application/x-shellscript 773 bytes

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message David Rowley 2015-09-30 01:12:11 Re: PATCH: use foreign keys to improve join estimates v1
Previous Message Michael Paquier 2015-09-30 00:11:36 Re: Add pg_basebackup single tar output format