Thread

Commits

  1. Fix the buffer release order for parallel index scans.

  1. BUG #15290: Stuck Parallel Index Scan query

    The Post Office <noreply@postgresql.org> — 2018-07-22T18:30:29Z

    The following bug has been logged on the website:
    
    Bug reference:      15290
    Logged by:          Victor Yegorov
    Email address:      vyegorov@gmail.com
    PostgreSQL version: 10.4
    Operating system:   Debian GNU/Linux 8.7 (jessie)
    Description:        
    
    We've just encountered an issue on the streaming replica of the client.
    
    In short — query is active for 8 hours and we're not able to terminate it:
    all processes (leader and workers) are ignoring all signals.
    Symptoms are similar to the ones described in #15036. As a result, we had to
    perform immediate shutdown f the instance.
    After restart we've got another such stuck query within minutes, so it's not
    a single occurrence..
    
    
    Query in subject
    ----------------
    -[ RECORD 1
    ]-----------------------------------------------------------------------------------------------------
    ts_age      | 08:32:17.286343
    state       | active
    query_age   | 08:32:17.286345
    change_age  | 08:32:17.286344
    datname     | coub
    pid         | 2877
    usename     | app
    waiting     | f
    client_addr |
    client_port | -1
    query       | select count(*) as value from coubs where type='Coub::Simple'
    and is_done=false and in_process=false
    
                                                                 QUERY PLAN
    -------------------------------------------------------------------------------------------------------------------------------------
     Finalize Aggregate  (cost=666001.67..666001.72 rows=1 width=8)
       ->  Gather  (cost=666001.20..666001.65 rows=8 width=8)
             Workers Planned: 8
             ->  Partial Aggregate  (cost=665901.20..665901.25 rows=1 width=8)
                   ->  Parallel Index Scan using
    coubs_type_is_done_partial_simple on coubs  (cost=0.56..663016.01
    rows=1154077 width=0)
                         Index Cond: (is_done = false)
                         Filter: ((NOT is_done) AND (NOT in_process))
    (7 rows)
    
    In reality this query worked with just 4 workers:
     2877 ?        ts     0:08  \_ postgres: 10/main: app coub [local] SELECT
     3416 ?        Ss     0:00  \_ postgres: 10/main: bgworker: parallel worker
    for PID 2877
     3417 ?        Ss     0:00  \_ postgres: 10/main: bgworker: parallel worker
    for PID 2877
     3418 ?        Ss     0:00  \_ postgres: 10/main: bgworker: parallel worker
    for PID 2877
     3419 ?        Ss     0:00  \_ postgres: 10/main: bgworker: parallel worker
    for PID 2877
    
    We have backtraces of the main process and all workers, and also
    `pg_stat_activity` snapshot — I will attach them to the new e-mail.
    
    
  2. Re: BUG #15290: Stuck Parallel Index Scan query

    Victor Yegorov <vyegorov@gmail.com> — 2018-07-22T18:33:25Z

    вс, 22 июл. 2018 г. в 21:31, PG Bug reporting form <noreply@postgresql.org>:
    
    > The following bug has been logged on the website:
    >
    > Bug reference:      15290
    > Logged by:          Victor Yegorov
    > Email address:      vyegorov@gmail.com
    > PostgreSQL version: 10.4
    > Operating system:   Debian GNU/Linux 8.7 (jessie)
    >
    
    And here're backtraces and pg_stat_activity snapshot.
    
    -- 
    Victor Yegorov
    
  3. Re: BUG #15290: Stuck Parallel Index Scan query

    Victor Yegorov <vyegorov@gmail.com> — 2018-07-22T20:52:23Z

    вс, 22 июл. 2018 г. в 21:31, PG Bug reporting form <noreply@postgresql.org>:
    
    > The following bug has been logged on the website:
    >
    > Bug reference:      15290
    > Logged by:          Victor Yegorov
    > Email address:      vyegorov@gmail.com
    > PostgreSQL version: 10.4
    > Operating system:   Debian GNU/Linux 8.7 (jessie)
    >
    
    Some more details.
    Index that was chosen for Parallel Index Scan was a partial index. And it
    is quite bloated:
    
    "coubs_type_is_done_partial_simple" btree (type, is_done) WHERE type::text
    = 'Coub::Simple'::text
    6766MB
    
    What we did — created another, ordinary index:
    
    "index_coubs_on_is_done_and_in_process_and_type" btree (is_done,
    in_process, type)
    2141MB
    
    So now planner prefers to use new index and issue is not re-appearing.
    Query executes within 10-20ms.
    Old bloated index is still around for analysis. `amcheck` showed no issues
    with it, though.
    
    
    -- 
    Victor Yegorov
    
  4. Re: BUG #15290: Stuck Parallel Index Scan query

    Thomas Munro <thomas.munro@enterprisedb.com> — 2018-07-23T04:30:28Z

    On Mon, Jul 23, 2018 at 8:52 AM, Victor Yegorov <vyegorov@gmail.com> wrote:
    > вс, 22 июл. 2018 г. в 21:31, PG Bug reporting form <noreply@postgresql.org>:
    >>
    >> The following bug has been logged on the website:
    >>
    >> Bug reference:      15290
    >> Logged by:          Victor Yegorov
    >> Email address:      vyegorov@gmail.com
    >> PostgreSQL version: 10.4
    >> Operating system:   Debian GNU/Linux 8.7 (jessie)
    
    Thanks for the detailed report!  To summarise your back traces, we have:
    
    PID 2877 is the master process and has decided to abort and is waiting
    for the workers to exit:
    
    WaitLatch
    WaitForBackgroundWorkerShutdown
    WaitForParallelWorkersToExit
    DestroyParallelContext
    AtEOXact_Parallel
    AbortTransaction
    AbortCurrentTransaction
    PostgresMain
    
    PIDs 3416, 3417, 3418, 3419 meanwhile are waiting to seize the scan head:
    
    WaitEventSetWaitBlock
    ConditionVariableSleep
    _bt_parallel_seize
    _bt_readnextpage
    
    Presumably 2877 has it (?), but aborted (do you have an error message
    in the server log?), and the workers have somehow survived
    TerminateBackgroundWorker() (called by DestroyParallelContext()).
    Hmm.
    
    TerminateBackgroundWorker() sets a shm flag and signals the
    postmaster, the postmaster signals the worker with SIGTERM, the worker
    handles SIGTERM in die() by setting ProcDiePending = true,
    InterruptPending= true and then setting its own latch, and the
    ConditionVarableSleep() loop should wake up, go around its loop again
    and run CHECK_FOR_INTERRUPTS() which sees InterruptPending and runs
    ProcessInterrupts(), which sees ProcDiePending and reports FATAL.  So
    which domino fell short here?
    
    -- 
    Thomas Munro
    http://www.enterprisedb.com
    
    
    
  5. Re: BUG #15290: Stuck Parallel Index Scan query

    Andres Freund <andres@anarazel.de> — 2018-07-23T04:35:44Z

    On 2018-07-23 16:30:28 +1200, Thomas Munro wrote:
    > TerminateBackgroundWorker() sets a shm flag and signals the
    > postmaster, the postmaster signals the worker with SIGTERM, the worker
    > handles SIGTERM in die() by setting ProcDiePending = true,
    > InterruptPending= true and then setting its own latch, and the
    > ConditionVarableSleep() loop should wake up, go around its loop again
    > and run CHECK_FOR_INTERRUPTS() which sees InterruptPending and runs
    > ProcessInterrupts(), which sees ProcDiePending and reports FATAL.  So
    > which domino fell short here?
    
    I've not looked at this, but is it possible that interrupts aren't
    accepted because they're held (e.g. because of an lwlock)?
    
    Greetings,
    
    Andres Freund
    
    
    
  6. Re: BUG #15290: Stuck Parallel Index Scan query

    Victor Yegorov <vyegorov@gmail.com> — 2018-07-23T07:57:55Z

    пн, 23 июл. 2018 г. в 7:31, Thomas Munro <thomas.munro@enterprisedb.com>:
    
    > PID 2877 is the master process and has decided to abort and is waiting
    > for the workers to exit:
    >
    > WaitLatch
    > WaitForBackgroundWorkerShutdown
    > WaitForParallelWorkersToExit
    > DestroyParallelContext
    > AtEOXact_Parallel
    > AbortTransaction
    > AbortCurrentTransaction
    > PostgresMain
    >
    > PIDs 3416, 3417, 3418, 3419 meanwhile are waiting to seize the scan head:
    >
    > WaitEventSetWaitBlock
    > ConditionVariableSleep
    > _bt_parallel_seize
    > _bt_readnextpage
    >
    > Presumably 2877 has it (?), but aborted (do you have an error message
    > in the server log?), and the workers have somehow survived
    > TerminateBackgroundWorker() (called by DestroyParallelContext()).
    >
    
    Query was stuck for 8 hours when we tried to terminate it. Makes me think,
    that master process was
    still waiting for bgworkers to finish, as test run finished in 11ms for me.
    As I mentioned, we've got this case re-appear while I was preparing
    the report (had to restart the DB second time). I think I might make it
    happen again, if necessary.
    
    There is not so much in the logs:
    - a bunch of `FATAL:  connection to client lost`, but from another (web)
    user (couple errors per hour)
    - `ERROR:  canceling statement due to conflict with recovery`, happened
    right when our problematic query started, same user
    - errors related to shutdown/startup of the DB.
    
    
    -- 
    Victor Yegorov
    
  7. Re: BUG #15290: Stuck Parallel Index Scan query

    Thomas Munro <thomas.munro@enterprisedb.com> — 2018-07-23T08:46:33Z

    On Mon, Jul 23, 2018 at 7:57 PM, Victor Yegorov <vyegorov@gmail.com> wrote:
    > - `ERROR:  canceling statement due to conflict with recovery`, happened
    > right when our problematic query started, same user
    
    Ok, so that would explain how the master was cancelled.  In 2877's
    stack we see that it was aborting here:
    
    #11 0x00007f539697ba5e in PostgresMain (argc=1,
    argv=argv@entry=0x7f5398d1bbc8, dbname=0x7f5398d1bb98 "coub",
    username=0x7f5398d1bbb0 "app") at
    /build/postgresql-10-U6N320/postgresql-10-10.4/build/../src/backend/tcop/postgres.c:3879
    
    That line calls AbortCurrentTransaction(), just after the call to
    EmitErrorReport() that wrote something in your log.  Andres's theory
    (interrupts 'held') seems promising... perhaps there could be a bug
    where parallel index scans leak a share-locked page or something like
    that.  I tried to reproduce this a bit, but no cigar so far.  I wonder
    if there could be something about your bloated index that reaches
    buggy behaviour...
    
    If you happen to have a core file for a worker that is waiting in
    ConditionVariableSleep(), or it happens again, you'd be able to see if
    an LWLock is causing this by printing num_held_lwlocks.
    
    -- 
    Thomas Munro
    http://www.enterprisedb.com
    
    
    
  8. Re: BUG #15290: Stuck Parallel Index Scan query

    Victor Yegorov <vyegorov@gmail.com> — 2018-07-23T11:42:52Z

    пн, 23 июл. 2018 г. в 11:47, Thomas Munro <thomas.munro@enterprisedb.com>:
    
    > On Mon, Jul 23, 2018 at 7:57 PM, Victor Yegorov <vyegorov@gmail.com>
    > wrote:
    > > - `ERROR:  canceling statement due to conflict with recovery`, happened
    > > right when our problematic query started, same user
    >
    > Ok, so that would explain how the master was cancelled.  In 2877's
    > stack we see that it was aborting here:
    >
    
    Right:
    
    ERROR:  canceling statement due to conflict with recovery
    DETAIL:  User was holding shared buffer pin for too long.
    
    
    >
    > #11 0x00007f539697ba5e in PostgresMain (argc=1,
    > argv=argv@entry=0x7f5398d1bbc8, dbname=0x7f5398d1bb98 "coub",
    > username=0x7f5398d1bbb0 "app") at
    >
    > /build/postgresql-10-U6N320/postgresql-10-10.4/build/../src/backend/tcop/postgres.c:3879
    >
    > That line calls AbortCurrentTransaction(), just after the call to
    > EmitErrorReport() that wrote something in your log.  Andres's theory
    > (interrupts 'held') seems promising... perhaps there could be a bug
    > where parallel index scans leak a share-locked page or something like
    > that.  I tried to reproduce this a bit, but no cigar so far.  I wonder
    > if there could be something about your bloated index that reaches
    > buggy behaviour...
    >
    > If you happen to have a core file for a worker that is waiting in
    > ConditionVariableSleep(), or it happens again, you'd be able to see if
    > an LWLock is causing this by printing num_held_lwlocks.
    >
    
    No, we do not have core files around. And so far I was not able to
    reproduce this situation.
    I will keep monitoring. In case I'll hit it again — what else (except for
    num_held_lwlocks)
    should I check for?
    
    -- 
    Victor Yegorov
    
  9. Re: BUG #15290: Stuck Parallel Index Scan query

    Thomas Munro <thomas.munro@enterprisedb.com> — 2018-07-25T02:04:11Z

    Ok, I see it:
    
                            /* check for interrupts while we're not
    holding any buffer lock */
                            CHECK_FOR_INTERRUPTS();
                            /* step right one page */
                            so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ);
                            ...
                            /* nope, keep going */
                            if (scan->parallel_scan != NULL)
                            {
                                    status = _bt_parallel_seize(scan, &blkno);
    
    That leads to a condition variable wait, while we still hold that
    buffer lock.  That prevents interrupts.  Oops.
    
    -- 
    Thomas Munro
    http://www.enterprisedb.com
    
    
    
  10. Re: BUG #15290: Stuck Parallel Index Scan query

    Andres Freund <andres@anarazel.de> — 2018-07-25T02:08:05Z

    On 2018-07-25 14:04:11 +1200, Thomas Munro wrote:
    > Ok, I see it:
    > 
    >                         /* check for interrupts while we're not
    > holding any buffer lock */
    >                         CHECK_FOR_INTERRUPTS();
    >                         /* step right one page */
    >                         so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ);
    >                         ...
    >                         /* nope, keep going */
    >                         if (scan->parallel_scan != NULL)
    >                         {
    >                                 status = _bt_parallel_seize(scan, &blkno);
    > 
    > That leads to a condition variable wait, while we still hold that
    > buffer lock.  That prevents interrupts.  Oops.
    
    Heh, guessed right.  I kinda wonder if we shouldn't add a
    CHECK_FOR_INTERRUPTS_FOR_REALZ() that asserts if interrupts aren't
    held. There are plenty places where we rely on that being the case, for
    correctness.
    
    Greetings,
    
    Andres Freund
    
    
    
  11. Re: BUG #15290: Stuck Parallel Index Scan query

    Thomas Munro <thomas.munro@enterprisedb.com> — 2018-07-25T02:59:07Z

    On Wed, Jul 25, 2018 at 2:08 PM, Andres Freund <andres@anarazel.de> wrote:
    > On 2018-07-25 14:04:11 +1200, Thomas Munro wrote:
    >> Ok, I see it:
    >>
    >>                         /* check for interrupts while we're not
    >> holding any buffer lock */
    >>                         CHECK_FOR_INTERRUPTS();
    >>                         /* step right one page */
    >>                         so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ);
    >>                         ...
    >>                         /* nope, keep going */
    >>                         if (scan->parallel_scan != NULL)
    >>                         {
    >>                                 status = _bt_parallel_seize(scan, &blkno);
    >>
    >> That leads to a condition variable wait, while we still hold that
    >> buffer lock.  That prevents interrupts.  Oops.
    >
    > Heh, guessed right.  I kinda wonder if we shouldn't add a
    > CHECK_FOR_INTERRUPTS_FOR_REALZ() that asserts if interrupts aren't
    > held. There are plenty places where we rely on that being the case, for
    > correctness.
    
    Yeah, I was wondering something similar: perhaps WaitEventSetWait()
    should assert that interrupts are not held, unless you explicitly told
    it somehow that it's OK?  You couldn't do it unconditionally, because
    we sometimes do that on purpose:
    
        HOLD_INTERRUPTS();
        WaitForParallelWorkersToExit(pcxt);
        RESUME_INTERRUPTS();
    
    Here's a reproducer (adjust timeout to suit your machine):
    
    drop table if exists t;
    create table t as select generate_series(1, 1000000)::int i;
    create index on t(i);
    alter table t set (autovacuum_enabled = false);
    delete from t; -- 100% bloat please
    
    set max_parallel_workers_per_gather = 2;
    set min_parallel_index_scan_size = 0;
    set enable_seqscan = false;
    set enable_bitmapscan = false;
    set parallel_tuple_cost = 0;
    set parallel_setup_cost = 0;
    
    set statement_timeout = '100ms'; -- enough to fork, not enough to complete
    
    explain analyze select count(*) from t;
    
    -- 
    Thomas Munro
    http://www.enterprisedb.com
    
    
    
  12. Re: BUG #15290: Stuck Parallel Index Scan query

    Amit Kapila <amit.kapila16@gmail.com> — 2018-07-25T03:13:04Z

    On Wed, Jul 25, 2018 at 8:29 AM, Thomas Munro
    <thomas.munro@enterprisedb.com> wrote:
    > On Wed, Jul 25, 2018 at 2:08 PM, Andres Freund <andres@anarazel.de> wrote:
    >> On 2018-07-25 14:04:11 +1200, Thomas Munro wrote:
    >>> Ok, I see it:
    >>>
    >>>                         /* check for interrupts while we're not
    >>> holding any buffer lock */
    >>>                         CHECK_FOR_INTERRUPTS();
    >>>                         /* step right one page */
    >>>                         so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ);
    >>>                         ...
    >>>                         /* nope, keep going */
    >>>                         if (scan->parallel_scan != NULL)
    >>>                         {
    >>>                                 status = _bt_parallel_seize(scan, &blkno);
    >>>
    >>> That leads to a condition variable wait, while we still hold that
    >>> buffer lock.  That prevents interrupts.  Oops.
    >>
    
    Well spotted.  I think here we can release the current page lock
    before calling _bt_parallel_seize as we don't need it to get the next
    page.  See the backward scan case, in particular, I am referring to
    the below code:
    
    _bt_readnextpage()
    {
    ..
    * For parallel scans, get the last page scanned as it is quite
    * possible that by the time we try to seize the scan, some other
    * worker has already advanced the scan to a different page.  We
    * must continue based on the latest page scanned by any worker.
    */
    if (scan->parallel_scan != NULL)
    {
    _bt_relbuf(rel, so->currPos.buf);
    status = _bt_parallel_seize(scan, &blkno);
    ..
    }
    
    This needs some more analysis.  I will continue the analysis and
    shared findings.
    
    Thanks, Thomas for pinging me offlist and including me here.
    
    -- 
    With Regards,
    Amit Kapila.
    EnterpriseDB: http://www.enterprisedb.com
    
    
    
  13. Re: BUG #15290: Stuck Parallel Index Scan query

    Amit Kapila <amit.kapila16@gmail.com> — 2018-07-25T05:46:55Z

    On Wed, Jul 25, 2018 at 8:43 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
    > On Wed, Jul 25, 2018 at 8:29 AM, Thomas Munro
    > <thomas.munro@enterprisedb.com> wrote:
    >> On Wed, Jul 25, 2018 at 2:08 PM, Andres Freund <andres@anarazel.de> wrote:
    >>> On 2018-07-25 14:04:11 +1200, Thomas Munro wrote:
    >>>> Ok, I see it:
    >>>>
    >>>>                         /* check for interrupts while we're not
    >>>> holding any buffer lock */
    >>>>                         CHECK_FOR_INTERRUPTS();
    >>>>                         /* step right one page */
    >>>>                         so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ);
    >>>>                         ...
    >>>>                         /* nope, keep going */
    >>>>                         if (scan->parallel_scan != NULL)
    >>>>                         {
    >>>>                                 status = _bt_parallel_seize(scan, &blkno);
    >>>>
    >>>> That leads to a condition variable wait, while we still hold that
    >>>> buffer lock.  That prevents interrupts.  Oops.
    >>>
    >
    > Well spotted.  I think here we can release the current page lock
    > before calling _bt_parallel_seize as we don't need it to get the next
    > page.
    >
    
    I have written a patch on the above lines and manually verified (by
    reproducing the issue via debugger) that it fixes the issue.  Thomas,
    Victor, is it possible for you guys to see if the attached fixes the
    issue for you?
    
    -- 
    With Regards,
    Amit Kapila.
    EnterpriseDB: http://www.enterprisedb.com
    
  14. Re: BUG #15290: Stuck Parallel Index Scan query

    Thomas Munro <thomas.munro@enterprisedb.com> — 2018-07-25T09:55:57Z

    On Wed, Jul 25, 2018 at 5:46 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:
    >> I think here we can release the current page lock
    >> before calling _bt_parallel_seize as we don't need it to get the next
    >> page.
    >
    > I have written a patch on the above lines and manually verified (by
    > reproducing the issue via debugger) that it fixes the issue.  Thomas,
    > Victor, is it possible for you guys to see if the attached fixes the
    > issue for you?
    
    The patch looks good to me.  It fixes the problem for my reproducer
    (BTW that works better with an extra zero on the generate_series
    number).
    
    -- 
    Thomas Munro
    http://www.enterprisedb.com
    
    
    
  15. Re: BUG #15290: Stuck Parallel Index Scan query

    Victor Yegorov <vyegorov@gmail.com> — 2018-07-26T10:23:01Z

    ср, 25 июл. 2018 г. в 8:46, Amit Kapila <amit.kapila16@gmail.com>:
    
    > On Wed, Jul 25, 2018 at 8:43 AM, Amit Kapila <amit.kapila16@gmail.com>
    > wrote:
    > > Well spotted.  I think here we can release the current page lock
    > > before calling _bt_parallel_seize as we don't need it to get the next
    > > page.
    > >
    >
    > I have written a patch on the above lines and manually verified (by
    > reproducing the issue via debugger) that it fixes the issue.  Thomas,
    > Victor, is it possible for you guys to see if the attached fixes the
    > issue for you?
    >
    
    First, I failed to reproduce Thomas' case on the VM with just 1 core.
    But it is reproduced just fine on an 8-core machine, make sure to warm up
    the index first though.
    
    Patch fixes the issue for me here.
    
    
    -- 
    Victor Yegorov
    
  16. Re: BUG #15290: Stuck Parallel Index Scan query

    Amit Kapila <amit.kapila16@gmail.com> — 2018-07-27T05:47:49Z

    On Thu, Jul 26, 2018 at 3:53 PM, Victor Yegorov <vyegorov@gmail.com> wrote:
    > ср, 25 июл. 2018 г. в 8:46, Amit Kapila <amit.kapila16@gmail.com>:
    >>
    >> On Wed, Jul 25, 2018 at 8:43 AM, Amit Kapila <amit.kapila16@gmail.com>
    >> wrote:
    >> > Well spotted.  I think here we can release the current page lock
    >> > before calling _bt_parallel_seize as we don't need it to get the next
    >> > page.
    >> >
    >>
    >> I have written a patch on the above lines and manually verified (by
    >> reproducing the issue via debugger) that it fixes the issue.  Thomas,
    >> Victor, is it possible for you guys to see if the attached fixes the
    >> issue for you?
    >
    >
    > First, I failed to reproduce Thomas' case on the VM with just 1 core.
    > But it is reproduced just fine on an 8-core machine, make sure to warm up
    > the index first though.
    >
    > Patch fixes the issue for me here.
    >
    
    Thanks, pushed the fix.  I have not included Thomas's test as it is
    timing dependent and it seems risky to include such a test.
    
    -- 
    With Regards,
    Amit Kapila.
    EnterpriseDB: http://www.enterprisedb.com