package granary
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
Pure-OCaml SQL engine
Install
dune-project
Dependency
Authors
Maintainers
Sources
0.0.3.tar.gz
sha256=8b18780ea373be48301d9f333925860a2f9110fc0ac28684295118d72b65a67e
sha512=25ca3c9c5e2b528704a542502e0f37dc33ba003f65622d969b8c2b800778585f8ef0cf89b36e6679832e3993e8303aecddfc662742baf7044d6afe4a796b8f11
doc/src/granary.replication/standby.ml.html
Source file standby.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209(** Standby follower driver (#172) implementation. *) open Lwt.Syntax module Store = Granary_store.Store module Wal = Granary_storage.Wal module Pager = Granary_storage.Pager type follower_mode = | Following | Promoted type acked_position = { epoch : int64 ; frame_idx : int } type t = { store : Store.t ; pager : Pager.t ; wal : Wal.t ; mutable mode : follower_mode ; mutable last_epoch : int64 ; mutable last_frame_idx : int ; apply_mutex : Lwt_mutex.t ; reader_gate : target:int -> unit Lwt.t ; on_standby_ack : (int -> unit) option } let create ~store ~pager ~wal ?on_standby_ack () = { store ; pager ; wal ; mode = Following ; last_epoch = 0L ; last_frame_idx = -1 ; apply_mutex = Lwt_mutex.create () ; reader_gate = Store.wait_for_readers_past store ; on_standby_ack } ;; let mode t = t.mode let acked_position t = { epoch = t.last_epoch; frame_idx = t.last_frame_idx } let pp fmt t = Format.fprintf fmt "Standby.t { mode = %s; epoch = %Ld; frame_idx = %d }" (match t.mode with | Following -> "Following" | Promoted -> "Promoted") t.last_epoch t.last_frame_idx ;; let promote t = match t.mode with | Promoted -> Lwt.return_unit | Following -> (* Take the apply mutex so any in-flight [apply_frames_epoch_aware] from the follower loop completes before we drain and reset. Re-check the mode under the lock: a concurrent [promote] may have won the race. *) Lwt_mutex.with_lock t.apply_mutex (fun () -> match t.mode with | Promoted -> Lwt.return_unit | Following -> (* Drain buffered committed frames to the main DB before recycling the WAL — otherwise the fresh engine view opened on promotion (which recovers from main) would lose every frame applied since the last epoch-change checkpoint. [checkpoint_wal_to_main] also bumps the local WAL epoch via [Wal.reset], so locally-generated writes start with a fresh epoch distinct from the master's. *) let* r = Replication.checkpoint_wal_to_main ~wal:t.wal ~pager:t.pager ~reader_gate:t.reader_gate in (match r with | Error (`Apply_error msg) -> Lwt.fail_with ("Standby.promote: drain failed: " ^ msg) | Ok () -> t.mode <- Promoted; Store.set_follower t.store false; t.last_epoch <- Wal.epoch t.wal; (* Nothing applied in the new (local) epoch yet — same sentinel as [create]. *) t.last_frame_idx <- -1; Lwt.return_unit)) ;; let start_following t stream = match t.mode with | Promoted -> (* Never re-enter follower mode on a promoted node: doing so would leave the store rejecting writes (the exit clause below only clears follower mode when still [Following]). Promotion is terminal. *) Lwt.return (Ok ()) | Following -> Store.set_follower t.store true; let* result = Lwt.catch (fun () -> let rec loop () = let* next = Lwt_stream.get stream in match next with | None -> Lwt.return (Ok ()) | Some frames -> (* [with_lock] releases the mutex even if [apply_frames_epoch_aware] raises, so a faulting batch can never wedge a later [promote]. Re-check the mode under the lock: a [promote] may have won the race while we were blocked, in which case the WAL has been recycled and we must not apply this batch into the promoted node. *) let* outcome = Lwt_mutex.with_lock t.apply_mutex (fun () -> match t.mode with | Promoted -> Lwt.return (`Stop (Ok ())) | Following -> let* r = Replication.apply_frames_epoch_aware ~wal:t.wal ~pager:t.pager ~last_epoch:t.last_epoch ~last_idx:t.last_frame_idx ~reader_gate:t.reader_gate frames in (match r with | Error _ as e -> Lwt.return (`Stop e) | Ok (epoch, idx) -> t.last_epoch <- epoch; t.last_frame_idx <- idx; let acked = Wal.committed_frames t.wal in Store.set_follower_ack_position t.store ~frames:acked; (match t.on_standby_ack with | Some cb -> cb acked | None -> ()); Lwt.return `Continue)) in (match outcome with | `Stop result -> Lwt.return result | `Continue -> loop ()) in loop ()) (fun exn -> Lwt.return (Error (`Apply_error (Printexc.to_string exn)))) in (* Clear follower mode on any exit path unless we have been promoted. *) if t.mode = Following then Store.set_follower t.store false; Lwt.return result ;; let rebase t segments = match t.mode with | Promoted -> Lwt.return (Error (`Apply_error "Standby.rebase: cannot rebase a promoted standby")) | Following -> (* Take the apply mutex so a rebase never races an in-flight apply from a (stopped, but possibly still draining) follower loop. *) Lwt_mutex.with_lock t.apply_mutex (fun () -> match t.mode with | Promoted -> Lwt.return (Error (`Apply_error "Standby.rebase: cannot rebase a promoted standby")) | Following -> (* The application has overwritten the main DB (the pager passed to [create]) with a fresh, consistent base snapshot from the object store. The standby's existing WAL index therefore describes a superseded state and must be DISCARDED — not drained, which would corrupt the fresh base. [Wal.reset] drops the index and bumps the local WAL epoch. *) Wal.reset t.wal; t.last_epoch <- Wal.epoch t.wal; t.last_frame_idx <- -1; (* Replay the object-store WAL segments in order through the epoch-aware apply primitive, threading the position forward. The segments may span several master epochs; [apply_frames_epoch_aware] checkpoints the local WAL at each transition (#209), so on completion the main DB holds the base plus all-but-the-last epoch and the WAL holds the live tail's epoch — exactly the steady-state a following standby maintains. *) let rec loop () = let* next = Lwt_stream.get segments in match next with | None -> Lwt.return (Ok { epoch = t.last_epoch; frame_idx = t.last_frame_idx }) | Some frames -> let* r = Replication.apply_frames_epoch_aware ~wal:t.wal ~pager:t.pager ~last_epoch:t.last_epoch ~last_idx:t.last_frame_idx ~reader_gate:t.reader_gate frames in (match r with | Error _ as e -> Lwt.return e | Ok (epoch, idx) -> t.last_epoch <- epoch; t.last_frame_idx <- idx; let acked = Wal.committed_frames t.wal in Store.set_follower_ack_position t.store ~frames:acked; (match t.on_standby_ack with | Some cb -> cb acked | None -> ()); loop ()) in loop ()) ;;
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>