/src/postgres/src/backend/replication/logical/logicalctl.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * logicalctl.c |
3 | | * Functionality to control logical decoding status online. |
4 | | * |
5 | | * This module enables dynamic control of logical decoding availability. |
6 | | * Logical decoding becomes active under two conditions: when the wal_level |
7 | | * parameter is set to 'logical', or when at least one valid logical replication |
8 | | * slot exists with wal_level set to 'replica'. The system disables logical |
9 | | * decoding when neither condition is met. Therefore, the dynamic control |
10 | | * of logical decoding availability is required only when wal_level is set |
11 | | * to 'replica'. Logical decoding is always enabled when wal_level='logical' |
12 | | * and always disabled when wal_level='minimal'. |
13 | | * |
14 | | * The core concept of dynamically enabling and disabling logical decoding |
15 | | * is to separately control two aspects: writing information required for |
16 | | * logical decoding to WAL records, and using logical decoding itself. During |
17 | | * activation, we first enable logical WAL writing while keeping logical |
18 | | * decoding disabled. This change is reflected in the read-only |
19 | | * effective_wal_level GUC parameter. Once we ensure that all processes have |
20 | | * updated to the latest effective_wal_level value, we then enable logical |
21 | | * decoding. Deactivation follows a similar careful, multi-step process |
22 | | * in reverse order. |
23 | | * |
24 | | * While activation occurs synchronously right after creating the first |
25 | | * logical slot, deactivation happens asynchronously through the checkpointer |
26 | | * process. This design avoids a race condition at the end of recovery; see |
27 | | * the comments in UpdateLogicalDecodingStatusEndOfRecovery() for details. |
28 | | * Asynchronous deactivation also avoids excessive toggling of the logical |
29 | | * decoding status in workloads that repeatedly create and drop a single |
30 | | * logical slot. On the other hand, this lazy approach can delay changes |
31 | | * to effective_wal_level and the disabling of logical decoding, especially |
32 | | * when the checkpointer is busy with other tasks. We chose this lazy approach |
33 | | * in all deactivation paths to keep the implementation simple, even though |
34 | | * laziness is strictly required only for end-of-recovery cases. Future work |
35 | | * might address this limitation either by using a dedicated worker instead |
36 | | * of the checkpointer, or by implementing synchronous waiting during slot |
37 | | * drops if workloads are significantly affected by the lazy deactivation |
38 | | * of logical decoding. |
39 | | * |
40 | | * Standby servers use the primary server's effective_wal_level and logical |
41 | | * decoding status. Unlike normal activation and deactivation, these |
42 | | * are updated simultaneously without status change coordination, solely by |
43 | | * replaying XLOG_LOGICAL_DECODING_STATUS_CHANGE records. The local wal_level |
44 | | * setting has no effect during this time. Upon promotion, we update the |
45 | | * logical decoding status based on local conditions: the wal_level value and |
46 | | * the presence of logical slots. |
47 | | * |
48 | | * In the future, we could extend support to include automatic transitions |
49 | | * of effective_wal_level between 'minimal' and 'logical' WAL levels. However, |
50 | | * this enhancement would require additional coordination mechanisms and |
51 | | * careful implementation of operations such as terminating walsenders and |
52 | | * archiver processes while carefully considering the sequence of operations |
53 | | * to ensure system stability during these transitions. |
54 | | * |
55 | | * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group |
56 | | * Portions Copyright (c) 1994, Regents of the University of California |
57 | | * |
58 | | * IDENTIFICATION |
59 | | * src/backend/replication/logical/logicalctl.c |
60 | | * |
61 | | *------------------------------------------------------------------------- |
62 | | */ |
63 | | |
64 | | #include "postgres.h" |
65 | | |
66 | | #include "access/xloginsert.h" |
67 | | #include "catalog/pg_control.h" |
68 | | #include "miscadmin.h" |
69 | | #include "replication/slot.h" |
70 | | #include "storage/ipc.h" |
71 | | #include "storage/lmgr.h" |
72 | | #include "storage/proc.h" |
73 | | #include "storage/procarray.h" |
74 | | #include "storage/procsignal.h" |
75 | | #include "storage/subsystems.h" |
76 | | #include "utils/injection_point.h" |
77 | | |
78 | | /* |
79 | | * Struct for controlling the logical decoding status. |
80 | | * |
81 | | * This struct is protected by LogicalDecodingControlLock. |
82 | | */ |
83 | | typedef struct LogicalDecodingCtlData |
84 | | { |
85 | | /* |
86 | | * This is the authoritative value used by all processes to determine |
87 | | * whether to write additional information required by logical decoding to |
88 | | * WAL. Since this information could be checked frequently, each process |
89 | | * caches this value in XLogLogicalInfo for better performance. |
90 | | */ |
91 | | bool xlog_logical_info; |
92 | | |
93 | | /* True if logical decoding is available in the system */ |
94 | | bool logical_decoding_enabled; |
95 | | |
96 | | /* True if logical decoding might need to be disabled */ |
97 | | bool pending_disable; |
98 | | } LogicalDecodingCtlData; |
99 | | |
100 | | static LogicalDecodingCtlData *LogicalDecodingCtl = NULL; |
101 | | |
102 | | static void LogicalDecodingCtlShmemRequest(void *arg); |
103 | | |
104 | | const ShmemCallbacks LogicalDecodingCtlShmemCallbacks = { |
105 | | .request_fn = LogicalDecodingCtlShmemRequest, |
106 | | }; |
107 | | |
108 | | /* |
109 | | * A process-local cache of LogicalDecodingCtl->xlog_logical_info. This is |
110 | | * initialized at process startup, and updated when processing the process |
111 | | * barrier signal in ProcessBarrierUpdateXLogLogicalInfo(). If the process |
112 | | * is in an XID-assigned transaction, the cache update is delayed until the |
113 | | * transaction ends. See the comments for XLogLogicalInfoUpdatePending for details. |
114 | | */ |
115 | | bool XLogLogicalInfo = false; |
116 | | |
117 | | /* |
118 | | * When receiving the PROCSIGNAL_BARRIER_UPDATE_XLOG_LOGICAL_INFO signal, if |
119 | | * an XID is assigned to the current transaction, the process sets this flag and |
120 | | * delays the XLogLogicalInfo update until the transaction ends. This ensures |
121 | | * that the XLogLogicalInfo value (typically accessed via XLogLogicalInfoActive) |
122 | | * remains consistent throughout the transaction. |
123 | | */ |
124 | | static bool XLogLogicalInfoUpdatePending = false; |
125 | | |
126 | | static void update_xlog_logical_info(void); |
127 | | static void abort_logical_decoding_activation(int code, Datum arg); |
128 | | static void write_logical_decoding_status_update_record(bool status); |
129 | | |
130 | | static void |
131 | | LogicalDecodingCtlShmemRequest(void *arg) |
132 | 0 | { |
133 | 0 | ShmemRequestStruct(.name = "Logical decoding control", |
134 | 0 | .size = sizeof(LogicalDecodingCtlData), |
135 | 0 | .ptr = (void **) &LogicalDecodingCtl, |
136 | 0 | ); |
137 | 0 | } |
138 | | |
139 | | /* |
140 | | * Initialize the logical decoding status in shmem at server startup. This |
141 | | * must be called ONCE during postmaster or standalone-backend startup. |
142 | | */ |
143 | | void |
144 | | StartupLogicalDecodingStatus(bool last_status) |
145 | 0 | { |
146 | | /* Logical decoding is always disabled when 'minimal' WAL level */ |
147 | 0 | if (wal_level == WAL_LEVEL_MINIMAL) |
148 | 0 | return; |
149 | | |
150 | | /* |
151 | | * Set the initial logical decoding status based on the last status. If |
152 | | * logical decoding was enabled before the last shutdown, it remains |
153 | | * enabled as we might have set wal_level='logical' or have at least one |
154 | | * logical slot. |
155 | | */ |
156 | 0 | LogicalDecodingCtl->xlog_logical_info = last_status; |
157 | 0 | LogicalDecodingCtl->logical_decoding_enabled = last_status; |
158 | 0 | } |
159 | | |
160 | | /* |
161 | | * Update the XLogLogicalInfo cache. |
162 | | */ |
163 | | static inline void |
164 | | update_xlog_logical_info(void) |
165 | 0 | { |
166 | 0 | XLogLogicalInfo = IsXLogLogicalInfoEnabled(); |
167 | 0 | } |
168 | | |
169 | | /* |
170 | | * Initialize XLogLogicalInfo backend-private cache. This routine is called |
171 | | * during process initialization. |
172 | | */ |
173 | | void |
174 | | InitializeProcessXLogLogicalInfo(void) |
175 | 0 | { |
176 | 0 | update_xlog_logical_info(); |
177 | 0 | } |
178 | | |
179 | | /* |
180 | | * This routine is called when we are told to update XLogLogicalInfo |
181 | | * by a ProcSignalBarrier. |
182 | | */ |
183 | | bool |
184 | | ProcessBarrierUpdateXLogLogicalInfo(void) |
185 | 0 | { |
186 | 0 | if (GetTopTransactionIdIfAny() != InvalidTransactionId) |
187 | 0 | { |
188 | | /* Delay updating XLogLogicalInfo until the transaction end */ |
189 | 0 | XLogLogicalInfoUpdatePending = true; |
190 | 0 | } |
191 | 0 | else |
192 | 0 | update_xlog_logical_info(); |
193 | |
|
194 | 0 | return true; |
195 | 0 | } |
196 | | |
197 | | /* |
198 | | * Check the shared memory state and return true if logical decoding is |
199 | | * enabled on the system. |
200 | | */ |
201 | | bool |
202 | | IsLogicalDecodingEnabled(void) |
203 | 0 | { |
204 | 0 | bool enabled; |
205 | |
|
206 | 0 | LWLockAcquire(LogicalDecodingControlLock, LW_SHARED); |
207 | 0 | enabled = LogicalDecodingCtl->logical_decoding_enabled; |
208 | 0 | LWLockRelease(LogicalDecodingControlLock); |
209 | |
|
210 | 0 | return enabled; |
211 | 0 | } |
212 | | |
213 | | /* |
214 | | * Returns true if logical WAL logging is enabled based on the shared memory |
215 | | * status. |
216 | | */ |
217 | | bool |
218 | | IsXLogLogicalInfoEnabled(void) |
219 | 0 | { |
220 | 0 | bool xlog_logical_info; |
221 | |
|
222 | 0 | LWLockAcquire(LogicalDecodingControlLock, LW_SHARED); |
223 | 0 | xlog_logical_info = LogicalDecodingCtl->xlog_logical_info; |
224 | 0 | LWLockRelease(LogicalDecodingControlLock); |
225 | |
|
226 | 0 | return xlog_logical_info; |
227 | 0 | } |
228 | | |
229 | | /* |
230 | | * Reset the local cache at end of the transaction. |
231 | | */ |
232 | | void |
233 | | AtEOXact_LogicalCtl(void) |
234 | 0 | { |
235 | | /* Update the local cache if there is a pending update */ |
236 | 0 | if (XLogLogicalInfoUpdatePending) |
237 | 0 | { |
238 | 0 | update_xlog_logical_info(); |
239 | 0 | XLogLogicalInfoUpdatePending = false; |
240 | 0 | } |
241 | 0 | } |
242 | | |
243 | | /* |
244 | | * Writes an XLOG_LOGICAL_DECODING_STATUS_CHANGE WAL record with the given |
245 | | * status. |
246 | | */ |
247 | | static void |
248 | | write_logical_decoding_status_update_record(bool status) |
249 | 0 | { |
250 | 0 | XLogRecPtr recptr; |
251 | |
|
252 | 0 | XLogBeginInsert(); |
253 | 0 | XLogRegisterData(&status, sizeof(bool)); |
254 | 0 | recptr = XLogInsert(RM_XLOG_ID, XLOG_LOGICAL_DECODING_STATUS_CHANGE); |
255 | 0 | XLogFlush(recptr); |
256 | 0 | } |
257 | | |
258 | | /* |
259 | | * A PG_ENSURE_ERROR_CLEANUP callback for activating logical decoding. |
260 | | * |
261 | | * Rather than directly reverting xlog_logical_info here, we request |
262 | | * that the checkpointer handle it via the normal disable path. This |
263 | | * avoids race conditions when multiple backends attempt concurrent |
264 | | * activation: the checkpointer will reset xlog_logical_info when |
265 | | * no valid logical slots exist. |
266 | | */ |
267 | | static void |
268 | | abort_logical_decoding_activation(int code, Datum arg) |
269 | | { |
270 | | elog(DEBUG1, "aborting logical decoding activation process"); |
271 | | RequestDisableLogicalDecoding(); |
272 | | } |
273 | | |
274 | | /* |
275 | | * Enable logical decoding if disabled. |
276 | | * |
277 | | * If this function is called during recovery, it just checks that logical |
278 | | * decoding is still enabled, since the logical decoding status cannot be |
279 | | * changed during this time. The logical decoding status depends on the |
280 | | * status on the primary. |
281 | | * |
282 | | * Note that there is no interlock between logical decoding activation |
283 | | * and slot creation. To ensure enabling logical decoding, the caller |
284 | | * needs to call this function after creating a logical slot before |
285 | | * initializing the logical decoding context. |
286 | | */ |
287 | | void |
288 | | EnsureLogicalDecodingEnabled(void) |
289 | 0 | { |
290 | 0 | Assert(MyReplicationSlot); |
291 | 0 | Assert(wal_level >= WAL_LEVEL_REPLICA); |
292 | | |
293 | | /* Logical decoding is always enabled */ |
294 | 0 | if (wal_level >= WAL_LEVEL_LOGICAL) |
295 | 0 | return; |
296 | | |
297 | 0 | if (RecoveryInProgress()) |
298 | 0 | { |
299 | | /* |
300 | | * The caller has already checked that logical decoding is enabled via |
301 | | * CheckLogicalDecodingRequirements(), but the status could have been |
302 | | * disabled concurrently before we created our slot: either by |
303 | | * replaying an XLOG_LOGICAL_DECODING_STATUS_CHANGE record, or by |
304 | | * UpdateLogicalDecodingStatusEndOfRecovery() upon promotion. We |
305 | | * cannot enable logical decoding during recovery, so raise an error. |
306 | | * |
307 | | * Our slot has already been created, so its in_use flag is set and |
308 | | * the slot scans performed by a deactivation can see it. It |
309 | | * guarantees that this check doesn't miss a concurrent deactivation: |
310 | | * UpdateLogicalDecodingStatusEndOfRecovery() won't disable logical |
311 | | * decoding since CheckLogicalSlotExists() finds our valid slot, and |
312 | | * replaying a status change record after this check invalidates our |
313 | | * slot, so this slot creation fails afterwards anyway (by a recovery |
314 | | * conflict or the requirement re-check in |
315 | | * CreateInitDecodingContext()). Hence, this check only needs to catch |
316 | | * deactivations that completed before our slot's in_use flag was set. |
317 | | */ |
318 | 0 | if (!IsLogicalDecodingEnabled()) |
319 | 0 | ereport(ERROR, |
320 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
321 | 0 | errmsg("logical decoding on standby requires \"effective_wal_level\" >= \"logical\" on the primary"), |
322 | 0 | errdetail("Logical decoding was concurrently disabled during the logical replication slot creation."))); |
323 | | |
324 | 0 | return; |
325 | 0 | } |
326 | | |
327 | | /* |
328 | | * Ensure to abort the activation process in cases where there is an |
329 | | * interruption during the wait. |
330 | | */ |
331 | 0 | PG_ENSURE_ERROR_CLEANUP(abort_logical_decoding_activation, (Datum) 0); |
332 | 0 | { |
333 | 0 | EnableLogicalDecoding(); |
334 | 0 | } |
335 | 0 | PG_END_ENSURE_ERROR_CLEANUP(abort_logical_decoding_activation, (Datum) 0); |
336 | 0 | } |
337 | | |
338 | | /* |
339 | | * A workhorse function to enable logical decoding. |
340 | | */ |
341 | | void |
342 | | EnableLogicalDecoding(void) |
343 | | { |
344 | | bool in_recovery; |
345 | | |
346 | | LWLockAcquire(LogicalDecodingControlLock, LW_EXCLUSIVE); |
347 | | |
348 | | /* Return if it is already enabled */ |
349 | | if (LogicalDecodingCtl->logical_decoding_enabled) |
350 | | { |
351 | | LogicalDecodingCtl->pending_disable = false; |
352 | | LWLockRelease(LogicalDecodingControlLock); |
353 | | return; |
354 | | } |
355 | | |
356 | | /* |
357 | | * Set logical info WAL logging in shmem. All processes starting after |
358 | | * this point will include the information required by logical decoding in |
359 | | * WAL records. |
360 | | */ |
361 | | LogicalDecodingCtl->xlog_logical_info = true; |
362 | | |
363 | | LWLockRelease(LogicalDecodingControlLock); |
364 | | |
365 | | /* |
366 | | * Tell all running processes to reflect the xlog_logical_info update, and |
367 | | * wait. This ensures that all running processes have enabled logical |
368 | | * information WAL logging. |
369 | | */ |
370 | | WaitForProcSignalBarrier( |
371 | | EmitProcSignalBarrier(PROCSIGNAL_BARRIER_UPDATE_XLOG_LOGICAL_INFO)); |
372 | | |
373 | | INJECTION_POINT("logical-decoding-activation", NULL); |
374 | | |
375 | | in_recovery = RecoveryInProgress(); |
376 | | |
377 | | /* |
378 | | * There could be some transactions that might have started with the old |
379 | | * status, but we don't need to wait for these transactions to complete as |
380 | | * long as they have valid XIDs. These transactions will appear in the |
381 | | * xl_running_xacts record and therefore the snapshot builder will not try |
382 | | * to decode the transaction during the logical decoding initialization. |
383 | | * |
384 | | * There is a theoretical case where a transaction decides whether to |
385 | | * include logical-info in WAL records before getting an XID. In this |
386 | | * case, the transaction won't appear in xl_running_xacts. |
387 | | * |
388 | | * For operations that do not require an XID assignment, the process |
389 | | * starts including logical-info immediately upon receiving the signal |
390 | | * (barrier). If such an operation checks the effective_wal_level multiple |
391 | | * times within a single execution, the resulting WAL records might be |
392 | | * inconsistent (i.e., logical-info is included in some records but not in |
393 | | * others). However, this is harmless because logical decoding generally |
394 | | * ignores WAL records that are not associated with an assigned XID. |
395 | | * |
396 | | * One might think we need to wait for all running transactions, including |
397 | | * those without XIDs and read-only transactions, to finish before |
398 | | * enabling logical decoding. However, such a requirement would force the |
399 | | * slot creation to wait for a potentially very long time due to |
400 | | * long-running read queries, which is practically unacceptable. |
401 | | */ |
402 | | |
403 | | LWLockAcquire(LogicalDecodingControlLock, LW_EXCLUSIVE); |
404 | | |
405 | | /* |
406 | | * Re-check whether logical decoding got enabled while we waited for the |
407 | | * barrier above. |
408 | | */ |
409 | | if (LogicalDecodingCtl->logical_decoding_enabled) |
410 | | { |
411 | | LogicalDecodingCtl->pending_disable = false; |
412 | | LWLockRelease(LogicalDecodingControlLock); |
413 | | return; |
414 | | } |
415 | | |
416 | | START_CRIT_SECTION(); |
417 | | |
418 | | /* |
419 | | * We enable logical decoding first, followed by writing the WAL record. |
420 | | * This sequence ensures logical decoding becomes available on the primary |
421 | | * first. |
422 | | */ |
423 | | LogicalDecodingCtl->logical_decoding_enabled = true; |
424 | | |
425 | | if (!in_recovery) |
426 | | write_logical_decoding_status_update_record(true); |
427 | | |
428 | | LogicalDecodingCtl->pending_disable = false; |
429 | | |
430 | | END_CRIT_SECTION(); |
431 | | |
432 | | LWLockRelease(LogicalDecodingControlLock); |
433 | | |
434 | | /* |
435 | | * We log the activation message after releasing the slot lock. This is |
436 | | * safe because the activation is performed while holding a logical slot, |
437 | | * meaning, a concurrent deactivation cannot interleave its log message |
438 | | * ahead of ours. |
439 | | */ |
440 | | if (!in_recovery) |
441 | | ereport(LOG, |
442 | | errmsg("logical decoding is enabled upon creating a new logical replication slot")); |
443 | | } |
444 | | |
445 | | /* |
446 | | * Initiate a request for disabling logical decoding. |
447 | | * |
448 | | * Note that this function does not verify whether logical slots exist. The |
449 | | * checkpointer will verify if logical decoding should actually be disabled. |
450 | | * |
451 | | * This may be called during recovery, for example when a standby invalidates |
452 | | * its last valid logical slot. That is safe because the queued request is only |
453 | | * acted upon outside recovery. See the RecoveryInProgress() check in |
454 | | * DisableLogicalDecodingIfNecessary(). |
455 | | */ |
456 | | void |
457 | | RequestDisableLogicalDecoding(void) |
458 | | { |
459 | | if (wal_level != WAL_LEVEL_REPLICA) |
460 | | return; |
461 | | |
462 | | /* |
463 | | * It's possible that we might not actually need to disable logical |
464 | | * decoding if someone creates a new logical slot concurrently. We set the |
465 | | * flag anyway and the checkpointer will check it and disable logical |
466 | | * decoding if necessary. |
467 | | */ |
468 | | LWLockAcquire(LogicalDecodingControlLock, LW_EXCLUSIVE); |
469 | | LogicalDecodingCtl->pending_disable = true; |
470 | | LWLockRelease(LogicalDecodingControlLock); |
471 | | |
472 | | WakeupCheckpointer(); |
473 | | |
474 | | elog(DEBUG1, "requested disabling logical decoding"); |
475 | | } |
476 | | |
477 | | /* |
478 | | * Disable logical decoding if necessary. |
479 | | * |
480 | | * This function disables logical decoding upon a request initiated by |
481 | | * RequestDisableLogicalDecoding(). Otherwise, it performs no action. |
482 | | */ |
483 | | void |
484 | | DisableLogicalDecodingIfNecessary(void) |
485 | 0 | { |
486 | 0 | bool pending_disable; |
487 | |
|
488 | 0 | if (wal_level != WAL_LEVEL_REPLICA) |
489 | 0 | return; |
490 | | |
491 | | /* |
492 | | * Sanity check as we cannot disable logical decoding while holding a |
493 | | * logical slot. |
494 | | */ |
495 | 0 | Assert(!MyReplicationSlot); |
496 | | |
497 | | /* |
498 | | * During recovery the logical decoding status follows the primary via WAL |
499 | | * replay, so we must not disable it here. A pending_disable request |
500 | | * queued during recovery, for example by a local slot invalidation, is |
501 | | * intentionally left for the end-of-recovery transition or the |
502 | | * post-promotion checkpointer to act on. See |
503 | | * UpdateLogicalDecodingStatusEndOfRecovery(). |
504 | | */ |
505 | 0 | if (RecoveryInProgress()) |
506 | 0 | return; |
507 | | |
508 | 0 | LWLockAcquire(LogicalDecodingControlLock, LW_SHARED); |
509 | 0 | pending_disable = LogicalDecodingCtl->pending_disable; |
510 | 0 | LWLockRelease(LogicalDecodingControlLock); |
511 | | |
512 | | /* Quick return if no pending disable request */ |
513 | 0 | if (!pending_disable) |
514 | 0 | return; |
515 | | |
516 | 0 | DisableLogicalDecoding(); |
517 | 0 | } |
518 | | |
519 | | /* |
520 | | * A workhorse function to disable logical decoding. |
521 | | */ |
522 | | void |
523 | | DisableLogicalDecoding(void) |
524 | | { |
525 | | bool in_recovery = RecoveryInProgress(); |
526 | | bool was_enabled; |
527 | | |
528 | | LWLockAcquire(LogicalDecodingControlLock, LW_EXCLUSIVE); |
529 | | |
530 | | /* |
531 | | * Check if we can disable logical decoding. |
532 | | * |
533 | | * Nothing to do if both flags are already off, or if valid slots exist |
534 | | * (skip the slot check during recovery because the existing slots will be |
535 | | * invalidated after disabling logical decoding.) |
536 | | */ |
537 | | if ((!LogicalDecodingCtl->logical_decoding_enabled && |
538 | | !LogicalDecodingCtl->xlog_logical_info) || |
539 | | (!in_recovery && CheckLogicalSlotExists())) |
540 | | { |
541 | | LogicalDecodingCtl->pending_disable = false; |
542 | | LWLockRelease(LogicalDecodingControlLock); |
543 | | return; |
544 | | } |
545 | | |
546 | | /* |
547 | | * Remember if logical decoding was enabled. An interrupted activation can |
548 | | * leave xlog_logical_info=true while logical_decoding_enabled remains |
549 | | * false. |
550 | | */ |
551 | | was_enabled = LogicalDecodingCtl->logical_decoding_enabled; |
552 | | |
553 | | START_CRIT_SECTION(); |
554 | | |
555 | | /* |
556 | | * We need to disable logical decoding first and then disable logical |
557 | | * information WAL logging in order to ensure that no logical decoding |
558 | | * processes WAL records with insufficient information. |
559 | | */ |
560 | | LogicalDecodingCtl->logical_decoding_enabled = false; |
561 | | |
562 | | /* Write the WAL to disable logical decoding on standbys too */ |
563 | | if (!in_recovery && was_enabled) |
564 | | write_logical_decoding_status_update_record(false); |
565 | | |
566 | | /* Now disable logical information WAL logging */ |
567 | | LogicalDecodingCtl->xlog_logical_info = false; |
568 | | LogicalDecodingCtl->pending_disable = false; |
569 | | |
570 | | END_CRIT_SECTION(); |
571 | | |
572 | | /* |
573 | | * Logging under the lock guarantees our "is disabled" message appears in |
574 | | * the server log before its eventual "is enabled", making server log |
575 | | * diagnostics easy. |
576 | | */ |
577 | | if (!in_recovery && was_enabled) |
578 | | ereport(LOG, |
579 | | errmsg("logical decoding is disabled because there are no valid logical replication slots")); |
580 | | |
581 | | LWLockRelease(LogicalDecodingControlLock); |
582 | | |
583 | | /* |
584 | | * Tell all running processes to reflect the xlog_logical_info update. |
585 | | * Unlike when enabling logical decoding, we don't need to wait for all |
586 | | * processes to complete it in this case. We already disabled logical |
587 | | * decoding and it's always safe to write logical information to WAL |
588 | | * records, even when not strictly required. Therefore, we don't need to |
589 | | * wait for all running transactions to finish either. |
590 | | */ |
591 | | EmitProcSignalBarrier(PROCSIGNAL_BARRIER_UPDATE_XLOG_LOGICAL_INFO); |
592 | | } |
593 | | |
594 | | /* |
595 | | * Updates the logical decoding status at end of recovery, and ensures that |
596 | | * all running processes have the updated XLogLogicalInfo status. This |
597 | | * function must be called before accepting writes. |
598 | | */ |
599 | | void |
600 | | UpdateLogicalDecodingStatusEndOfRecovery(void) |
601 | 0 | { |
602 | 0 | bool new_status = false; |
603 | |
|
604 | 0 | Assert(RecoveryInProgress()); |
605 | |
|
606 | 0 | LWLockAcquire(LogicalDecodingControlLock, LW_EXCLUSIVE); |
607 | | |
608 | | /* |
609 | | * With 'minimal' WAL level, no logical replication slot can exist (see |
610 | | * RestoreSlotFromDisk()), so the new status is always false. However, |
611 | | * logical decoding could have been enabled during recovery by replaying |
612 | | * an XLOG_LOGICAL_DECODING_STATUS_CHANGE record from WAL generated with a |
613 | | * higher wal_level, e.g. if the server crashed right after the last |
614 | | * logical slot was dropped and then restarted with wal_level='minimal'. |
615 | | * The code below disables logical decoding in that case. |
616 | | */ |
617 | 0 | if (wal_level == WAL_LEVEL_LOGICAL || CheckLogicalSlotExists()) |
618 | 0 | new_status = true; |
619 | | |
620 | | /* |
621 | | * When recovery ends, we need to either enable or disable logical |
622 | | * decoding based on the wal_level setting and the presence of logical |
623 | | * slots. We need to note that concurrent slot creation and deletion could |
624 | | * happen but WAL writes are still not permitted until recovery fully |
625 | | * completes. Here's how we handle concurrent toggling of logical |
626 | | * decoding: |
627 | | * |
628 | | * For 'enable' case, if there's a concurrent disable request before |
629 | | * recovery fully completes, the checkpointer will handle it after |
630 | | * recovery is done. This means there might be a brief period after |
631 | | * recovery where logical decoding remains enabled even with no logical |
632 | | * replication slots present. This temporary state is not new - it can |
633 | | * already occur due to the checkpointer's asynchronous deactivation |
634 | | * process. |
635 | | * |
636 | | * For 'disable' case, a backend concurrently creating a logical slot on a |
637 | | * standby could have passed its CheckLogicalDecodingRequirements() check |
638 | | * when creating its slot only after our slot check above. Such a backend |
639 | | * rechecks the status after creating the slot in |
640 | | * EnsureLogicalDecodingEnabled() and raises an error if logical decoding |
641 | | * has been disabled meanwhile, so it cannot end up with a logical slot |
642 | | * while logical decoding remains disabled. (If recovery has already ended |
643 | | * by the time of the recheck, the backend instead enables logical |
644 | | * decoding by itself, which is fine after promotion.) |
645 | | */ |
646 | 0 | if (new_status != LogicalDecodingCtl->logical_decoding_enabled) |
647 | 0 | { |
648 | | /* |
649 | | * Update both the logical decoding status and logical WAL logging |
650 | | * status. Unlike toggling these status during non-recovery, we don't |
651 | | * need to worry about the operation order as WAL writes are still not |
652 | | * permitted. |
653 | | */ |
654 | 0 | LogicalDecodingCtl->xlog_logical_info = new_status; |
655 | 0 | LogicalDecodingCtl->logical_decoding_enabled = new_status; |
656 | |
|
657 | 0 | elog(DEBUG1, |
658 | 0 | "update logical decoding status to %d at the end of recovery", |
659 | 0 | new_status); |
660 | | |
661 | | /* |
662 | | * Now that we updated the logical decoding status, clear the pending |
663 | | * disable flag. It's possible that a concurrent process drops the |
664 | | * last logical slot and initiates the pending disable again. The |
665 | | * checkpointer process will check it. |
666 | | */ |
667 | 0 | LogicalDecodingCtl->pending_disable = false; |
668 | |
|
669 | 0 | LWLockRelease(LogicalDecodingControlLock); |
670 | |
|
671 | 0 | write_logical_decoding_status_update_record(new_status); |
672 | 0 | } |
673 | 0 | else |
674 | 0 | LWLockRelease(LogicalDecodingControlLock); |
675 | | |
676 | | /* |
677 | | * Ensure all running processes have the updated status. We don't need to |
678 | | * wait for running transactions to finish as we don't accept any writes |
679 | | * yet. On the other hand, we need to wait for synchronizing |
680 | | * XLogLogicalInfo even if we've not updated the status above as the |
681 | | * status have been turned on and off during recovery, having running |
682 | | * processes have different status on their local caches. |
683 | | */ |
684 | 0 | if (IsUnderPostmaster) |
685 | 0 | WaitForProcSignalBarrier( |
686 | 0 | EmitProcSignalBarrier(PROCSIGNAL_BARRIER_UPDATE_XLOG_LOGICAL_INFO)); |
687 | |
|
688 | 0 | INJECTION_POINT("startup-logical-decoding-status-change-end-of-recovery", NULL); |
689 | 0 | } |