A data store with readReplicas becomes a replicated store. Writes, operator-written SQL and everything outside a read scope go to the primary. JMAP reads before a request's first write, IMAP LIST, STATUS, SEARCH, SORT and FETCH, POP3 RETR and TOP, DAV GET, PROPFIND and REPORT, and blob downloads run in a read scope. Only account data (properties, indexes, change logs, counters, ACLs, blobs, the search index) is read from a replica; the registry, in-memory values, the task queue and the rest stay on the primary. In a scope, the first read picks a replica round-robin among those up and under the lag limit, and only if it has every change this node has written or heard of for the scope's accounts: marks come from write results, the cluster's state-change broadcasts, a sinceState the client presents, and, with more than one node, Redis. A write inside the scope sends the rest of it to the primary. A miss on a replica is looked up on the primary, and a replica error retries the read there and marks the replica down. Each node samples lag every second (WAL positions on PostgreSQL; GTID sets or Seconds_Behind_Source on MySQL), stops reading from a replica over 5 s and starts again under 2.5 s, and probes a down replica every 10 s. At startup a replica is left out if it's the primary, isn't read-only, applies out of commit order, or doesn't show a marker written to the primary within six tries. replica_tests (postgres, STORE=PostgreSqlReplicated) runs a primary and a streaming hot standby in containers: tests 9, 10, 12, 13, 14 and 15 pass.
102 lines
2.3 KiB
Rust
102 lines
2.3 KiB
Rust
/*
|
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
|
*/
|
|
|
|
use ::store::query::log::Query;
|
|
use imap_proto::ResponseCode;
|
|
|
|
pub mod acl;
|
|
pub mod append;
|
|
pub mod authenticate;
|
|
pub mod capability;
|
|
pub mod close;
|
|
pub mod copy_move;
|
|
pub mod create;
|
|
pub mod delete;
|
|
pub mod enable;
|
|
pub mod expunge;
|
|
pub mod fetch;
|
|
pub mod idle;
|
|
pub mod list;
|
|
pub mod login;
|
|
pub mod logout;
|
|
pub mod namespace;
|
|
pub mod noop;
|
|
pub mod quota;
|
|
pub mod rename;
|
|
pub mod search;
|
|
pub mod select;
|
|
pub mod status;
|
|
pub mod store;
|
|
pub mod subscribe;
|
|
pub mod thread;
|
|
pub mod uidbatches;
|
|
|
|
trait FromModSeq {
|
|
fn from_modseq(modseq: u64) -> Self;
|
|
}
|
|
|
|
trait ToModSeq {
|
|
fn to_modseq(&self) -> u64;
|
|
}
|
|
|
|
impl FromModSeq for Query {
|
|
fn from_modseq(modseq: u64) -> Self {
|
|
if modseq > 0 {
|
|
Query::Since(modseq - 1)
|
|
} else {
|
|
Query::All
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ToModSeq for u64 {
|
|
fn to_modseq(&self) -> u64 {
|
|
if *self > 0 { *self + 1 } else { 0 }
|
|
}
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! spawn_op {
|
|
($data:expr, $($code:tt)*) => {
|
|
{
|
|
// inbuxa: ST-6: the operation keeps its read scope
|
|
tokio::spawn(store::backend::scaleout::replica::carry(async move {
|
|
let data = &($data);
|
|
|
|
if let Err(err) = (async {
|
|
$($code)*
|
|
})
|
|
.await
|
|
{
|
|
let _ = data.write_error(err).await;
|
|
}
|
|
}));
|
|
|
|
Ok(())}
|
|
};
|
|
}
|
|
pub trait ImapContext<T> {
|
|
fn imap_ctx(self, tag: &str, location: &'static str) -> trc::Result<T>;
|
|
}
|
|
|
|
impl<T> ImapContext<T> for trc::Result<T> {
|
|
fn imap_ctx(self, tag: &str, location: &'static str) -> trc::Result<T> {
|
|
match self {
|
|
Ok(value) => Ok(value),
|
|
Err(err) => Err(
|
|
if !err.matches(trc::EventType::Imap(trc::ImapEvent::Error)) {
|
|
err.ctx(trc::Key::Id, tag.to_string())
|
|
.ctx(trc::Key::Details, "Internal Server Error")
|
|
.ctx(trc::Key::Code, ResponseCode::ContactAdmin)
|
|
.ctx(trc::Key::CausedBy, location)
|
|
} else {
|
|
err.ctx(trc::Key::Id, tag.to_string())
|
|
},
|
|
),
|
|
}
|
|
}
|
|
}
|