Skip to content

Commit

Permalink
Enable parallel query with SERIALIZABLE isolation.
Browse files Browse the repository at this point in the history
Previously, the SERIALIZABLE isolation level prevented parallel query
from being used.  Allow the two features to be used together by
sharing the leader's SERIALIZABLEXACT with parallel workers.

An extra per-SERIALIZABLEXACT LWLock is introduced to make it safe to
share, and new logic is introduced to coordinate the early release
of the SERIALIZABLEXACT required for the SXACT_FLAG_RO_SAFE
optimization, as follows:

The first backend to observe the SXACT_FLAG_RO_SAFE flag (set by
some other transaction) will 'partially release' the SERIALIZABLEXACT,
meaning that the conflicts and locks it holds are released, but the
SERIALIZABLEXACT itself will remain active because other backends
might still have a pointer to it.

Whenever any backend notices the SXACT_FLAG_RO_SAFE flag, it clears
its own MySerializableXact variable and frees local resources so that
it can skip SSI checks for the rest of the transaction.  In the
special case of the leader process, it transfers the SERIALIZABLEXACT
to a new variable SavedSerializableXact, so that it can be completely
released at the end of the transaction after all workers have exited.

Remove the serializable_okay flag added to CreateParallelContext() by
commit 9da0cc3, because it's now redundant.

Author: Thomas Munro
Reviewed-by: Haribabu Kommi, Robert Haas, Masahiko Sawada, Kevin Grittner
Discussion: https://postgr.es/m/CAEepm=0gXGYhtrVDWOTHS8SQQy_=S9xo+8oCxGLWZAOoeJ=yzQ@mail.gmail.com
  • Loading branch information
macdice committed Mar 15, 2019
1 parent 13e8643 commit bb16aba
Show file tree
Hide file tree
Showing 19 changed files with 429 additions and 67 deletions.
7 changes: 6 additions & 1 deletion doc/src/sgml/monitoring.sgml
Expand Up @@ -861,7 +861,7 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser

<tbody>
<row>
<entry morerows="63"><literal>LWLock</literal></entry>
<entry morerows="64"><literal>LWLock</literal></entry>
<entry><literal>ShmemIndexLock</literal></entry>
<entry>Waiting to find or allocate space in shared memory.</entry>
</row>
Expand Down Expand Up @@ -1121,6 +1121,11 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
<entry><literal>predicate_lock_manager</literal></entry>
<entry>Waiting to add or examine predicate lock information.</entry>
</row>
<row>
<entry><literal>serializable_xact</literal></entry>
<entry>Waiting to perform an operation on a serializable transaction
in a parallel query.</entry>
</row>
<row>
<entry><literal>parallel_query_dsa</literal></entry>
<entry>Waiting for parallel query dynamic shared memory allocation lock.</entry>
Expand Down
17 changes: 0 additions & 17 deletions doc/src/sgml/parallel.sgml
Expand Up @@ -184,13 +184,6 @@ EXPLAIN SELECT * FROM pgbench_accounts WHERE filler LIKE '%x%';
using a very large number of processes.
</para>
</listitem>

<listitem>
<para>
The transaction isolation level is serializable. This is
a limitation of the current implementation.
</para>
</listitem>
</itemizedlist>

<para>
Expand Down Expand Up @@ -233,16 +226,6 @@ EXPLAIN SELECT * FROM pgbench_accounts WHERE filler LIKE '%x%';
that may be suboptimal when run serially.
</para>
</listitem>

<listitem>
<para>
The transaction isolation level is serializable. This situation
does not normally arise, because parallel query plans are not
generated when the transaction isolation level is serializable.
However, it can happen if the transaction isolation level is changed to
serializable after the plan is generated and before it is executed.
</para>
</listitem>
</itemizedlist>
</sect1>

Expand Down
2 changes: 1 addition & 1 deletion src/backend/access/nbtree/nbtsort.c
Expand Up @@ -1265,7 +1265,7 @@ _bt_begin_parallel(BTBuildState *buildstate, bool isconcurrent, int request)
EnterParallelMode();
Assert(request > 0);
pcxt = CreateParallelContext("postgres", "_bt_parallel_build_main",
request, true);
request);
scantuplesortstates = leaderparticipates ? request + 1 : request;

/*
Expand Down
18 changes: 7 additions & 11 deletions src/backend/access/transam/parallel.c
Expand Up @@ -31,6 +31,7 @@
#include "optimizer/optimizer.h"
#include "pgstat.h"
#include "storage/ipc.h"
#include "storage/predicate.h"
#include "storage/sinval.h"
#include "storage/spin.h"
#include "tcop/tcopprot.h"
Expand Down Expand Up @@ -91,6 +92,7 @@ typedef struct FixedParallelState
BackendId parallel_master_backend_id;
TimestampTz xact_ts;
TimestampTz stmt_ts;
SerializableXactHandle serializable_xact_handle;

/* Mutex protects remaining fields. */
slock_t mutex;
Expand Down Expand Up @@ -155,7 +157,7 @@ static void ParallelWorkerShutdown(int code, Datum arg);
*/
ParallelContext *
CreateParallelContext(const char *library_name, const char *function_name,
int nworkers, bool serializable_okay)
int nworkers)
{
MemoryContext oldcontext;
ParallelContext *pcxt;
Expand All @@ -166,16 +168,6 @@ CreateParallelContext(const char *library_name, const char *function_name,
/* Number of workers should be non-negative. */
Assert(nworkers >= 0);

/*
* If we are running under serializable isolation, we can't use parallel
* workers, at least not until somebody enhances that mechanism to be
* parallel-aware. Utility statement callers may ask us to ignore this
* restriction because they're always able to safely ignore the fact that
* SIREAD locks do not work with parallelism.
*/
if (IsolationIsSerializable() && !serializable_okay)
nworkers = 0;

/* We might be running in a short-lived memory context. */
oldcontext = MemoryContextSwitchTo(TopTransactionContext);

Expand Down Expand Up @@ -327,6 +319,7 @@ InitializeParallelDSM(ParallelContext *pcxt)
fps->parallel_master_backend_id = MyBackendId;
fps->xact_ts = GetCurrentTransactionStartTimestamp();
fps->stmt_ts = GetCurrentStatementStartTimestamp();
fps->serializable_xact_handle = ShareSerializableXact();
SpinLockInit(&fps->mutex);
fps->last_xlog_end = 0;
shm_toc_insert(pcxt->toc, PARALLEL_KEY_FIXED, fps);
Expand Down Expand Up @@ -1422,6 +1415,9 @@ ParallelWorkerMain(Datum main_arg)
false);
RestoreEnumBlacklist(enumblacklistspace);

/* Attach to the leader's serializable transaction, if SERIALIZABLE. */
AttachSerializableXact(fps->serializable_xact_handle);

/*
* We've initialized all of our state now; nothing should change
* hereafter.
Expand Down
7 changes: 5 additions & 2 deletions src/backend/access/transam/xact.c
Expand Up @@ -2024,9 +2024,12 @@ CommitTransaction(void)
/*
* Mark serializable transaction as complete for predicate locking
* purposes. This should be done as late as we can put it and still allow
* errors to be raised for failure patterns found at commit.
* errors to be raised for failure patterns found at commit. This is not
* appropriate in a parallel worker however, because we aren't committing
* the leader's transaction and its serializable state will live on.
*/
PreCommit_CheckForSerializationFailure();
if (!is_parallel_worker)
PreCommit_CheckForSerializationFailure();

/*
* Insert notifications sent by NOTIFY commands into the queue. This
Expand Down
2 changes: 1 addition & 1 deletion src/backend/executor/execParallel.c
Expand Up @@ -604,7 +604,7 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
pstmt_data = ExecSerializePlan(planstate->plan, estate);

/* Create a parallel context. */
pcxt = CreateParallelContext("postgres", "ParallelQueryMain", nworkers, false);
pcxt = CreateParallelContext("postgres", "ParallelQueryMain", nworkers);
pei->pcxt = pcxt;

/*
Expand Down
11 changes: 1 addition & 10 deletions src/backend/optimizer/plan/planner.c
Expand Up @@ -337,22 +337,13 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
* parallel worker. We might eventually be able to relax this
* restriction, but for now it seems best not to have parallel workers
* trying to create their own parallel workers.
*
* We can't use parallelism in serializable mode because the predicate
* locking code is not parallel-aware. It's not catastrophic if someone
* tries to run a parallel plan in serializable mode; it just won't get
* any workers and will run serially. But it seems like a good heuristic
* to assume that the same serialization level will be in effect at plan
* time and execution time, so don't generate a parallel plan if we're in
* serializable mode.
*/
if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
IsUnderPostmaster &&
parse->commandType == CMD_SELECT &&
!parse->hasModifyingCTE &&
max_parallel_workers_per_gather > 0 &&
!IsParallelWorker() &&
!IsolationIsSerializable())
!IsParallelWorker())
{
/* all the cheap tests pass, so scan the query tree */
glob->maxParallelHazard = max_parallel_hazard(parse);
Expand Down
1 change: 1 addition & 0 deletions src/backend/storage/lmgr/lwlock.c
Expand Up @@ -521,6 +521,7 @@ RegisterLWLockTranches(void)
LWLockRegisterTranche(LWTRANCHE_TBM, "tbm");
LWLockRegisterTranche(LWTRANCHE_PARALLEL_APPEND, "parallel_append");
LWLockRegisterTranche(LWTRANCHE_PARALLEL_HASH_JOIN, "parallel_hash_join");
LWLockRegisterTranche(LWTRANCHE_SXACT, "serializable_xact");

/* Register named tranches. */
for (i = 0; i < NamedLWLockTrancheRequests; i++)
Expand Down

0 comments on commit bb16aba

Please sign in to comment.