changes_amit_2.patch
application/octet-stream
Filename: changes_amit_2.patch
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
| File | + | − |
|---|---|---|
| src/backend/replication/logical/applyparallelworker.c | 107 | 42 |
| src/backend/replication/logical/worker.c | 9 | 45 |
diff --git a/src/backend/replication/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c index df3aad43e1..13513f2e57 100644 --- a/src/backend/replication/logical/applyparallelworker.c +++ b/src/backend/replication/logical/applyparallelworker.c @@ -7,43 +7,98 @@ * IDENTIFICATION * src/backend/replication/logical/applyparallelworker.c * - * This file contains routines that are intended to support setting up, using, - * and tearing down a ParallelApplyWorkerInfo. + * This file contains the code to launch, set up, and teardown parallel apply + * worker which receives the changes from the leader worker and invokes routines + * to apply those on the subscriber database. * - * Refer to the comments in the file header of logical/worker.c to see more - * information about parallel apply workers. + * This file contains routines that are intended to support setting up, using + * and tearing down a ParallelApplyWorkerInfo which is required to communicate + * among leader and parallel apply workers. + * + * The parallel apply workers are assigned (if available) as soon as xact's + * first stream is received for subscriptions that have set their 'streaming' + * option as parallel. The leader apply worker will send changes to this new + * worker via shared memory. We keep this worker assigned till the transaction + * commit is received and also wait for the worker to finish at commit. This + * preserves commit ordering and avoid file I/O in most cases, although we + * still need to spill to a file if there is no worker available. See comments + * atop logical/worker to know more about streamed xacts whose changes are + * spilled to disk. It is important to maintain commit order to avoid failures + * due to (a) transaction dependencies, say if we insert a row in the first + * transaction and update it in the second transaction on publisher then + * allowing the subscriber to apply both in parallel can lead to failure in the + * update. (b) deadlocks, allowing transactions that update the same set of + * rows/tables in the opposite order to be applied in parallel can lead to + * deadlocks. + * + * We maintain a worker pool to avoid restarting workers for each streaming + * transaction. We maintain each worker's information in the + * ParallelApplyWorkersList. After successfully launching a new worker, its + * information is added to the ParallelApplyWorkersList. Once the worker + * finishes applying the transaction, we mark it available for re-use. Now, + * before starting a new worker to apply the streaming transaction, we check + * the list for any available worker. Note that we maintain a maximum of half + * the max_parallel_apply_workers_per_subscription workers in the pool and + * after that, we simply exit the worker after applying the transaction. + * + * XXX This worker pool threshold is a bit arbitrary and we can provide a GUC + * variable for this in the future if required. + * + * The leader apply worker will create a separate dynamic shared memory segment + * when each parallel apply worker starts. The reason for this design is that + * we cannot count how many workers will be started. It may be possible to + * allocate enough shared memory in one segment based on the maximum number of + * parallel apply workers (max_parallel_apply_workers_per_subscription), but + * this would waste memory if no process is actually started. + * + * The dynamic shared memory segment will contain (a) a shm_mq that is used to + * send changes in the transaction from leader apply worker to parallel apply + * worker (b) another shm_mq that is used to send errors (and other messages + * reported via elog/ereport) from the parallel apply worker to leader apply + * worker (c) necessary information to be shared among parallel apply workers + * and leader apply worker (i.e. members of ParallelApplyWorkerShared). * * Locking Considerations - * ------------------ - * Since the database structure (schema of subscription tables, etc.) of - * publisher and subscriber may be different, applying transactions in parallel - * mode on the subscriber side may cause some deadlock problems that do not - * occur on the publisher side. + * ---------------------- + * Since the database structure (schema of subscription tables, constraints, + * etc.) of the publisher and subscriber could be different, applying + * transactions in parallel mode on the subscriber side can cause some + * deadlocks that do not occur on the publisher side which is expected and can + * happen even without parallel mode. In order to detect the deadlocks among + * leader and parallel apply workers, we need to ensure that we wait using lmgr + * locks, otherwise, such deadlocks won't be detected. The other approach was + * to not allow parallelism when the schema of tables is different between the + * publisher and subscriber but that would be too restrictive and would require + * the publisher to send much more information than it is currently sending. * - * Suppose a subscribed table does not have a unique key on the publisher and - * has a unique key on the subscriber. + * Consider a case where the subscribed table does not have a unique key on the + * publisher and has a unique key on the subscriber. * * 1) Deadlock between the leader apply worker and a parallel apply worker * * The parallel apply worker (PA) is executing TX-1 and the leader apply worker * (LA) is executing TX-2 concurrently on the subscriber. Now, LA is waiting * for PA because of the unique key of the subscribed table while PA is waiting - * for LA to send further messages. + * for LA to send the next stream of changes or transaction finish command + * message. * * In order for lmgr to detect this, we have LA acquire a session lock on the * remote transaction (by pa_lock_stream()) and have PA wait on the lock before - * trying to receive messages. In other words, LA acquires the lock before - * sending STREAM_STOP and releases it if already acquired before sending - * STREAM_START, STREAM_ABORT (for toplevel transaction), STREAM_PREPARE and - * STREAM_COMMIT. For PA, it always needs to acquire the lock after processing - * STREAM_STOP and STREAM_ABORT (for subtransaction) and then release - * immediately after acquiring it. That way, when PA is waiting for LA, we can - * have a wait-edge from PA to LA in lmgr, which will make a deadlock in lmgr - * like: + * trying to receive the next stream of changes. Specifically, LA will acquire + * the lock before sending the STREAM_STOP and will release it if already + * acquired before sending the STREAM_START, STREAM_ABORT (for toplevel + * transaction), STREAM_PREPARE, and STREAM_COMMIT. The PA will acquire the + * lock after processing STREAM_STOP and STREAM_ABORT (for subtransaction) and + * then release the lock immediately after acquiring it. * + * The lock graph for the above example will look as follows: * LA (waiting to acquire the lock on the unique index) -> PA (waiting to * acquire the lock on the remote transaction) -> LA * + * This way, when PA is waiting for LA for the next stream of changes, we can + * have a wait-edge from PA to LA in lmgr, which will make us detect the + * deadlock between LA and PA. + * * 2) Deadlock between the leader apply worker and parallel apply workers * * This scenario is similar to the first case but TX-1 and TX-2 are executed by @@ -51,38 +106,46 @@ * PA-2 is waiting for PA-1 to complete its transaction while PA-1 is waiting * for subsequent input from LA. Also, LA is waiting for PA-2 to complete its * transaction in order to preserve the commit order. There is a deadlock among - * three processes. + * the three processes. + * + * In order for lmgr to detect this, we have PA acquire a session lock (this is + * a different lock than referred in previous case, see + * pa_lock_transaction()) on the transaction being applied and have LA wait on + * the lock before proceeding in the transaction finish commands. Specifically, + * PA will acquire this lock in Share mode before executing the first message + * of the transaction and release it at the xact end. LA will acquire this lock + * in AccessExclusive mode at transaction finish commands (STREAM_COMMIT and + * STREAM_PREAPRE) and release it immediately. * - * In order for lmgr to detect this, we have PA acquire a session lock on the - * transaction being applied and have LA wait on the lock before proceeding in - * transaction finish commands. That way, we can have a wait-edge from LA to PA - * in lmgr, which will make a deadlock in lmgr like: + * The lock graph for the above example will look as follows: + * LA (waiting to acquire the transaction lock) -> PA-2 (waiting to acquire the + * lock due to unique index constraint) -> PA-1 (waiting to acquire the stream + * lock) -> LA * - * LA (waiting to acquire the local transaction lock) -> PA-2 (waiting to - * acquire the lock on the unique index) -> PA-1 (waiting to acquire the lock - * on the remote transaction) -> LA + * This way when LA is waiting to finish the transaction end command to preserve + * the commit order, we will be able to detect deadlock, if any. * * One might think we can use XactLockTableWait(), but XactLockTableWait() * considers PREPARED TRANSACTION as still in progress which means the lock - * won't be released even if the parallel apply worker prepared the transaction. + * won't be released even after the parallel apply worker has prepared the + * transaction. * * 3) Deadlock when the shm_mq buffer is full * - * The scenario where LA has to wait is when the shm_mq buffer is full. In the - * above scenario (ie. PA-1 and PA-2 are executing transactions concurrently), - * if the shm_mq buffer between LA and PA-2 is full, LA has to wait to send - * messages, and this wait doesn't appear in lmgr. + * In the previous scenario (ie. PA-1 and PA-2 are executing transactions + * concurrently), if the shm_mq buffer between LA and PA-2 is full, LA has to + * wait to send messages, and this wait doesn't appear in lmgr. * * To resolve this issue, we use non-blocking write and wait with a timeout. If - * timeout is exceeded, the LA reports an error and restarts logical + * the timeout is exceeded, the LA reports an error and restarts logical * replication. * - * 4) Lock type + * 4) Lock types * * Both the stream lock and the transaction lock mentioned above are - * session-level locks, because both locks could be acquired outside the + * session-level locks because both locks could be acquired outside the * transaction, and the stream lock in the leader need to persist across - * transaction boundaries until the end of the streaming transaction. + * transaction boundaries i.e until the end of the streaming transaction. *------------------------------------------------------------------------- */ @@ -158,7 +221,7 @@ static HTAB *ParallelApplyWorkersHash = NULL; * list entry is removed if there are already enough workers in the worker * pool either at the end of the transaction or while trying to find a free * worker for applying the transaction. For more information about the worker - * pool, see comments atop worker.c. + * pool, see comments atop this file. */ static List *ParallelApplyWorkersList = NIL; @@ -365,7 +428,7 @@ pa_find_worker(TransactionId xid) * and frees the corresponding info. Otherwise it just marks the worker as * available for reuse. * - * For more information about the worker pool, see comments atop worker.c. + * For more information about the worker pool, see comments atop this file. * * Returns true if the worker is stopped, false otherwise. */ @@ -726,9 +789,11 @@ ParallelApplyWorkerMain(Datum main_arg) LogicalParallelApplyLoop(mqh); /* - * The parallel apply worker should not get here, because the parallel - * apply worker should only stop when it receives a SIGTERM or SIGINT from - * the leader or detects a change in subscription information. + * The parallel apply worker must not get here because the parallel + * apply worker will only stop when it receives a SIGTERM or SIGINT from + * the leader, detects a change in subscription information, or when there + * there is an error. None of these cases will allow the code to reach + * here. */ Assert(false); } diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c index 56e3c6c1a2..d9ef50864e 100644 --- a/src/backend/replication/logical/worker.c +++ b/src/backend/replication/logical/worker.c @@ -24,52 +24,10 @@ * Streamed transactions (large transactions exceeding a memory limit on the * upstream) are applied using one of two approaches: * - * 1) Parallel apply workers + * 1) Write to temporary files and apply when the final commit arrives * - * If streaming = parallel, we assign a new parallel apply worker (if - * available) as soon as the xact's first stream is received. The leader apply - * worker will send changes to this new worker via shared memory. We keep this - * worker assigned till the transaction commit is received and also wait for - * the worker to finish at commit. This preserves commit ordering and avoids - * file I/O in most cases, although we still need to spill to a file if there - * is no worker available. It is important to maintain commit order to avoid - * failures due to (a) transaction dependencies, say if we insert a row in the - * first transaction and update it in the second transaction then allowing to - * apply both in parallel can lead to failure in the update. (b) deadlocks, - * allowing transactions that update the same set of rows/tables in opposite - * order to be applied in parallel can lead to deadlocks. - * - * We maintain a worker pool to avoid restarting workers for each streaming - * transaction. We maintain each worker's information in the - * ParallelApplyWorkersList. After successfully launching a new worker, its - * information is added to the ParallelApplyWorkersList. Once the worker - * finishes applying the transaction, we mark it available for re-use. Now, - * before starting a new worker to apply the streaming transaction, we check - * the list for any available worker. Note that we maintain a maximum of half - * the max_parallel_apply_workers_per_subscription workers in the pool and - * after that, we simply exit the worker after applying the transaction. - * - * XXX This worker pool threshold is a bit arbitrary and we can provide a GUC - * variable for this in the future if required. - * - * The leader apply worker will create separate dynamic shared memory segment - * when each parallel apply worker starts. The reason for this design is that - * we cannot count how many workers will be started. It may be possible to - * allocate enough shared memory in one segment based on the maximum number of - * parallel apply workers (max_parallel_apply_workers_per_subscription), but this - * would waste memory if no process is actually started. - * - * The dynamic shared memory segment will contain (a) a shm_mq that is used to - * send changes in the transaction from leader apply worker to parallel apply - * worker (b) another shm_mq that is used to send errors (and other messages - * reported via elog/ereport) from the parallel apply worker to leader apply - * worker (c) necessary information to be shared among parallel apply workers - * and leader apply worker (i.e. members of ParallelApplyWorkerShared). - * - * If no parallel apply worker is available to handle the streamed transaction - * we follow approach 2. - * - * 2) Write to temporary files and apply when the final commit arrives + * This approach is used when user has set subscription's streaming option as + * on. * * Unlike the regular (non-streamed) case, handling streamed transactions has * to handle aborts of both the toplevel transaction and subtransactions. This @@ -96,6 +54,12 @@ * the file we desired across multiple stream-open calls for the same * transaction. * + * 2) Parallel apply workers. + * + * This approach is used when user has set subscription's streaming option as + * parallel. See logical/applyparallelworker.c for information about this + * approach. + * * TWO_PHASE TRANSACTIONS * ---------------------- * Two phase transactions are replayed at prepare and then committed or