Skip to content

Commit 200b210

Browse files
committed
docs: improve on documentation
1 parent 3903186 commit 200b210

File tree

5 files changed

+61
-11
lines changed

5 files changed

+61
-11
lines changed

docs/adrs/00009-re-process-documents.md renamed to docs/adrs/00011-re-process-documents.md

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# 00009. Re-process documents
1+
# 00011. Re-process documents
22

33
Date: 2025-08-08
44

@@ -28,7 +28,7 @@ any stored values in the database. It therefore is necessary to re-process docum
2828
This ADR makes the following assumptions:
2929

3030
* All documents are stored in the storage
31-
* It is expected that the step of upgrading has to be performed by someone
31+
* It is expected that the step of upgrading has to be performed by someone, it is not magically happening
3232
* Running such migrations is expected to take a long time
3333
* The management of infrastructure (PostgreSQL) is not in the scope of Trustify
3434

@@ -52,7 +52,7 @@ We could provide an endpoint to the UI, reporting the fact that the system is in
5252
* 👍 Can fully migrate database (create mandatory field as optional -> re-process -> make mandatory)
5353
* 👍 Might allow for an out-of-band migration of data, before running the upgrade (even on a staging env)
5454
* 👍 Would allow to continue serving data while the process is running
55-
* 👎 Might be tricky to create a combined re-processing of multiple ones
55+
* 👎 Might be tricky to create a combined re-processing of multiple ones at the same time
5656
* 👎 Might block an upgrade if re-processing fails
5757

5858
We do want to support different approaches of this migration. Depending on the needs of the user, the size of the
@@ -77,8 +77,14 @@ transaction mode of read-only.
7777

7878
Migrations which do re-process data have to be written in a way, that they can be run and re-run without failing
7979
during the migration of the schema (e.g. add "if not exist"). In this case, the data migration job can be run
80-
"out of band" (beforehand) and the data be processed. Then, the actual upgrade and schema migration can run.
80+
"out of band" (beforehand) and the data be processed. Then, the actual upgrade and schema migration can run, keeping
81+
the SeaORM process.
8182

83+
* The migration will block the upgrade process until it is finished
84+
* Ansible and the operator will need to handle this as well
85+
* The system will become read-only during a migration
86+
* The UI should let the user know the system is in read-only mode. This is a feature which has to be rolled out before
87+
the data migration can be used.
8288

8389
## Open items
8490

@@ -124,10 +130,3 @@ Have the operator orchestrate the process of switching the database into read-on
124130

125131
This adds a lot of user-friendliness. However, it also is rather complex and so we should, as a first step, have this
126132
as a manual step.
127-
128-
## Consequences
129-
130-
* The migration will block the upgrade process until it is finished
131-
* Ansible and the operator will need to handle this as well
132-
* The system will become read-only during a migration
133-
* The UI needs to provide a page for monitoring the migration state. The backend needs to provide appropriate APIs.

migration/src/data/document/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use trustify_entity::source_document;
1414
use trustify_module_storage::service::{StorageBackend, StorageKey};
1515
use uuid::Uuid;
1616

17+
/// A document eligible for re-processing.
1718
#[allow(async_fn_in_trait)]
1819
pub trait Document: Sized + Send + Sync {
1920
type Model: Partitionable + Send;

migration/src/data/migration.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::{ffi::OsString, ops::Deref, sync::LazyLock};
1010
use tokio::task_local;
1111
use trustify_module_storage::{config::StorageConfig, service::dispatch::DispatchBackend};
1212

13+
/// A migration which also processes data.
1314
pub struct MigrationWithData {
1415
pub storage: DispatchBackend,
1516
pub options: Options,
@@ -87,6 +88,7 @@ where
8788
}
8889
}
8990

91+
/// A [`SchemaManager`], extended with data migration features.
9092
pub struct SchemaDataManager<'c> {
9193
pub manager: &'c SchemaManager<'c>,
9294
storage: &'c DispatchBackend,

migration/src/data/mod.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ impl From<()> for Options {
8080
}
8181

8282
impl Options {
83+
/// Check if we should skip a data migration. Returns `true` if it should be skipped.
84+
///
85+
/// Skipping means that the "data" part of the migration should not be processes. The schema
86+
/// part still will be processes.
8387
pub fn should_skip(&self, name: &str) -> bool {
8488
if self.skip_all {
8589
// we skip all migration
@@ -104,6 +108,7 @@ impl From<&Options> for Partition {
104108
}
105109
}
106110

111+
/// A trait for processing documents
107112
pub trait DocumentProcessor {
108113
fn process<D>(
109114
&self,
@@ -116,6 +121,42 @@ pub trait DocumentProcessor {
116121
}
117122

118123
impl<'c> DocumentProcessor for SchemaManager<'c> {
124+
/// Process documents for a schema *data* migration.
125+
///
126+
/// ## Pre-requisites
127+
///
128+
/// The database should be maintenance mode. Meaning that the actual application should be
129+
/// running from a read-only clone for the time of processing.
130+
///
131+
/// ## Partitioning
132+
///
133+
/// This will partition documents and only process documents selected for *this* partition.
134+
/// The partition configuration normally comes from outside, as configuration through env-vars.
135+
///
136+
/// This means that there may be other instances of this processor running in a different
137+
/// process instance. However, not touching documents of our partition.
138+
///
139+
/// ## Transaction strategy
140+
///
141+
/// The processor will identify all documents, filtering out all which are not part of this
142+
/// partition. This is done in a dedicated transaction. As the database is supposed to be in
143+
/// read-only mode for the running instance, this is ok as no additional documents will be
144+
/// created during the time of processing.
145+
///
146+
/// Next, it is processing all found documents, in a concurrent way. Meaning, this single
147+
/// process instance, will process multiple documents in parallel.
148+
///
149+
/// Each document is loaded and processed within a dedicated transaction. Commiting the
150+
/// transaction at the end each step and before moving on the next document.
151+
///
152+
/// As handlers are intended to be idempotent, there's no harm in re-running them, in case
153+
/// things go wrong.
154+
///
155+
/// ## Caveats
156+
///
157+
/// However, this may lead to a situation where only a part of the documents is processed.
158+
/// But, this is ok, as the migration is supposed to run on a clone of the database and so the
159+
/// actual system is still running from the read-only clone of the original data.
119160
async fn process<D>(
120161
&self,
121162
storage: &DispatchBackend,
@@ -188,6 +229,9 @@ impl<'c> DocumentProcessor for SchemaManager<'c> {
188229
}
189230

190231
/// A handler for data migration of documents.
232+
///
233+
/// Handlers have to be written in a way that they can be re-run multiple times on the same
234+
/// document without failing and creating the exact same output state.
191235
#[macro_export]
192236
macro_rules! handler {
193237
(async | $doc:ident: $doc_ty:ty, $model:ident, $tx:ident | $body:block) => {{

migration/src/data/partition.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::{
55
};
66
use trustify_entity::{advisory, sbom};
77

8+
/// Information required for partitioning data
89
#[derive(Debug, Copy, Clone)]
910
pub struct Partition {
1011
pub current: u64,
@@ -43,6 +44,9 @@ impl Default for Partition {
4344
}
4445

4546
impl Partition {
47+
/// Create a new partition of one.
48+
///
49+
/// This will be one processor processing everything.
4650
pub const fn new_one() -> Self {
4751
Self {
4852
current: 0,

0 commit comments

Comments
 (0)