Skip to content
This repository was archived by the owner on Feb 6, 2026. It is now read-only.

Commit faa35ec

Browse files
merge: #3458
3458: feat(*): move workspace snapshots into layerdb r=zacharyhamm a=zacharyhamm Moves storage and fetch of the workspace snapshot into its own layer cache and database (WorkspaceSnapshotDb). This enables us to use an Arc without extra copying when performing read-only graph operations. Some changes were required to the blocking_commit and commit internals to ensure we perform a rebase even if no transaction has been opened in the Dal Postgres transactions state. Co-authored-by: Zachary Hamm <zack@systeminit.com>
2 parents a6dca45 + d7a9e96 commit faa35ec

26 files changed

Lines changed: 576 additions & 206 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/dal-test/src/helpers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ pub async fn create_change_set_and_update_ctx(
154154
.update_pointer(
155155
ctx,
156156
base_change_set
157-
.workspace_snapshot_id
157+
.workspace_snapshot_address
158158
.expect("no workspace snapshot set on base change set"),
159159
)
160160
.await

lib/dal/src/change_set_pointer.rs

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ use std::sync::{Arc, Mutex};
55
use chrono::{DateTime, Utc};
66
use serde::{Deserialize, Serialize};
77
use si_data_pg::{PgError, PgRow};
8+
use si_events::WorkspaceSnapshotAddress;
89
use telemetry::prelude::*;
910
use thiserror::Error;
1011
use ulid::{Generator, Ulid};
1112

1213
use crate::context::RebaseRequest;
1314
use crate::workspace_snapshot::vector_clock::VectorClockId;
14-
use crate::workspace_snapshot::WorkspaceSnapshotId;
1515
use crate::{
1616
id, ChangeSetStatus, DalContext, HistoryEvent, HistoryEventError, TransactionsError, Workspace,
1717
WorkspacePk, WsEvent, WsEventError,
@@ -78,7 +78,7 @@ pub struct ChangeSetPointer {
7878
pub name: String,
7979
pub status: ChangeSetStatus,
8080
pub base_change_set_id: Option<ChangeSetId>,
81-
pub workspace_snapshot_id: Option<WorkspaceSnapshotId>,
81+
pub workspace_snapshot_address: Option<WorkspaceSnapshotAddress>,
8282
pub workspace_id: Option<WorkspacePk>,
8383

8484
#[serde(skip)]
@@ -98,7 +98,7 @@ impl TryFrom<PgRow> for ChangeSetPointer {
9898
name: value.try_get("name")?,
9999
status,
100100
base_change_set_id: value.try_get("base_change_set_id")?,
101-
workspace_snapshot_id: value.try_get("workspace_snapshot_id")?,
101+
workspace_snapshot_address: value.try_get("workspace_snapshot_address")?,
102102
workspace_id: value.try_get("workspace_id")?,
103103
generator: Arc::new(Mutex::new(Default::default())),
104104
})
@@ -116,7 +116,7 @@ impl ChangeSetPointer {
116116
updated_at: Utc::now(),
117117
generator: Arc::new(Mutex::new(generator)),
118118
base_change_set_id: None,
119-
workspace_snapshot_id: None,
119+
workspace_snapshot_address: None,
120120
workspace_id: None,
121121
name: "".to_string(),
122122
status: ChangeSetStatus::Open,
@@ -126,7 +126,7 @@ impl ChangeSetPointer {
126126
pub fn editing_changeset(&self) -> ChangeSetPointerResult<Self> {
127127
let mut new_local = Self::new_local()?;
128128
new_local.base_change_set_id = self.base_change_set_id;
129-
new_local.workspace_snapshot_id = self.workspace_snapshot_id;
129+
new_local.workspace_snapshot_address = self.workspace_snapshot_address;
130130
new_local.workspace_id = self.workspace_id;
131131
new_local.name = self.name.to_owned();
132132
new_local.status = self.status.to_owned();
@@ -187,7 +187,7 @@ impl ChangeSetPointer {
187187
change_set_pointer
188188
.update_pointer(
189189
ctx,
190-
base_change_set_pointer.workspace_snapshot_id.ok_or(
190+
base_change_set_pointer.workspace_snapshot_address.ok_or(
191191
ChangeSetPointerError::DefaultChangeSetNoWorkspaceSnapshotPointer(
192192
workspace.default_change_set_id(),
193193
),
@@ -233,18 +233,18 @@ impl ChangeSetPointer {
233233
pub async fn update_pointer(
234234
&mut self,
235235
ctx: &DalContext,
236-
workspace_snapshot_id: WorkspaceSnapshotId,
236+
workspace_snapshot_address: WorkspaceSnapshotAddress,
237237
) -> ChangeSetPointerResult<()> {
238238
ctx.txns()
239239
.await?
240240
.pg()
241241
.query_none(
242-
"UPDATE change_set_pointers SET workspace_snapshot_id = $2 WHERE id = $1",
243-
&[&self.id, &workspace_snapshot_id],
242+
"UPDATE change_set_pointers SET workspace_snapshot_address = $2 WHERE id = $1",
243+
&[&self.id, &workspace_snapshot_address],
244244
)
245245
.await?;
246246

247-
self.workspace_snapshot_id = Some(workspace_snapshot_id);
247+
self.workspace_snapshot_address = Some(workspace_snapshot_address);
248248

249249
Ok(())
250250
}
@@ -318,11 +318,11 @@ impl ChangeSetPointer {
318318
let to_rebase_change_set_id = self
319319
.base_change_set_id
320320
.ok_or(ChangeSetPointerError::NoBaseChangeSet(self.id))?;
321-
let onto_workspace_snapshot_id = self
322-
.workspace_snapshot_id
321+
let onto_workspace_snapshot_address = self
322+
.workspace_snapshot_address
323323
.ok_or(ChangeSetPointerError::NoWorkspaceSnapshot(self.id))?;
324324
let rebase_request = RebaseRequest {
325-
onto_workspace_snapshot_id,
325+
onto_workspace_snapshot_address,
326326
onto_vector_clock_id: self.vector_clock_id(),
327327
to_rebase_change_set_id,
328328
};
@@ -366,8 +366,10 @@ impl std::fmt::Debug for ChangeSetPointer {
366366
&self.base_change_set_id.map(|bcsid| bcsid.to_string()),
367367
)
368368
.field(
369-
"workspace_snapshot_id",
370-
&self.workspace_snapshot_id.map(|wsid| wsid.to_string()),
369+
"workspace_snapshot_address",
370+
&self
371+
.workspace_snapshot_address
372+
.map(|wsaddr| wsaddr.to_string()),
371373
)
372374
.finish()
373375
}

lib/dal/src/context.rs

Lines changed: 64 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize};
88
use si_crypto::SymmetricCryptoService;
99
use si_data_nats::{NatsClient, NatsError, NatsTxn};
1010
use si_data_pg::{InstrumentedClient, PgError, PgPool, PgPoolError, PgPoolResult, PgTxn};
11+
use si_events::WorkspaceSnapshotAddress;
1112
use si_layer_cache::db::LayerDb;
1213
use telemetry::prelude::*;
1314
use thiserror::Error;
@@ -16,10 +17,9 @@ use tokio::time::Instant;
1617
use veritech_client::{Client as VeritechClient, CycloneEncryptionKey};
1718

1819
use crate::layer_db_types::ContentTypes;
19-
use crate::workspace_snapshot::conflict::Conflict;
20-
use crate::workspace_snapshot::update::Update;
21-
use crate::workspace_snapshot::vector_clock::VectorClockId;
22-
use crate::workspace_snapshot::WorkspaceSnapshotId;
20+
use crate::workspace_snapshot::{
21+
conflict::Conflict, graph::WorkspaceSnapshotGraph, update::Update, vector_clock::VectorClockId,
22+
};
2323
use crate::Workspace;
2424
use crate::{
2525
change_set_pointer::{ChangeSetId, ChangeSetPointer},
@@ -34,7 +34,7 @@ use crate::{
3434
WorkspacePk, WorkspaceSnapshot,
3535
};
3636

37-
pub type DalLayerDb = LayerDb<ContentTypes>;
37+
pub type DalLayerDb = LayerDb<ContentTypes, WorkspaceSnapshotGraph>;
3838

3939
/// A context type which contains handles to common core service dependencies.
4040
///
@@ -207,17 +207,29 @@ impl ConnectionState {
207207
tenancy: &Tenancy,
208208
rebase_request: Option<RebaseRequest>,
209209
) -> Result<(Self, Option<Conflicts>), TransactionsError> {
210-
match self {
211-
Self::Connections(_) => {
212-
trace!("no active transactions present when commit was called, taking no action");
213-
Ok((self, None))
210+
let (conns, nats_conn, rebaser_config) = match self {
211+
Self::Connections(conns) => {
212+
trace!("no active transactions present when commit was called");
213+
let rebaser_config = conns.rebaser_config.clone();
214+
let nats_conn = conns.nats_conn.clone();
215+
Ok((Self::Connections(conns), nats_conn, rebaser_config))
214216
}
215217
Self::Transactions(txns) => {
216-
let (conns, conflicts) = txns.commit_into_conns(tenancy, rebase_request).await?;
217-
Ok((Self::Connections(conns), conflicts))
218+
let conns = txns.commit_into_conns().await?;
219+
let rebaser_config = conns.rebaser_config.clone();
220+
let nats_conn = conns.nats_conn.clone();
221+
Ok((Self::Connections(conns), nats_conn, rebaser_config))
218222
}
219223
Self::Invalid => Err(TransactionsError::TxnCommit),
220-
}
224+
}?;
225+
226+
let conflicts = if let Some(rebase_request) = rebase_request {
227+
rebase(tenancy, nats_conn, rebaser_config, rebase_request).await?
228+
} else {
229+
None
230+
};
231+
232+
Ok((conns, conflicts))
221233
}
222234

223235
async fn blocking_commit(
@@ -226,9 +238,24 @@ impl ConnectionState {
226238
rebase_request: Option<RebaseRequest>,
227239
) -> Result<(Self, Option<Conflicts>), TransactionsError> {
228240
match self {
229-
Self::Connections(_) => {
230-
trace!("no active transactions present when commit was called, taking no action");
231-
Ok((self, None))
241+
Self::Connections(conns) => {
242+
trace!("no active transactions present when commit was called, but we will still attempt rebase");
243+
244+
// Even if there are no open dal transactions, we may have written to the layer db
245+
// and we need to perform a rebase if one is requested
246+
let conflicts = if let Some(rebase_request) = rebase_request {
247+
rebase(
248+
tenancy,
249+
conns.nats_conn.clone(),
250+
conns.rebaser_config.clone(),
251+
rebase_request,
252+
)
253+
.await?
254+
} else {
255+
None
256+
};
257+
258+
Ok((Self::Connections(conns), conflicts))
232259
}
233260
Self::Transactions(txns) => {
234261
let (conns, conflicts) = txns
@@ -345,7 +372,9 @@ impl DalContext {
345372
Ok(())
346373
}
347374

348-
pub async fn write_snapshot(&self) -> Result<Option<WorkspaceSnapshotId>, TransactionsError> {
375+
pub async fn write_snapshot(
376+
&self,
377+
) -> Result<Option<WorkspaceSnapshotAddress>, TransactionsError> {
349378
if let Some(snapshot) = &self.workspace_snapshot {
350379
let vector_clock_id = self.change_set_pointer()?.vector_clock_id();
351380

@@ -359,11 +388,11 @@ impl DalContext {
359388

360389
fn get_rebase_request(
361390
&self,
362-
onto_workspace_snapshot_id: WorkspaceSnapshotId,
391+
onto_workspace_snapshot_address: WorkspaceSnapshotAddress,
363392
) -> Result<RebaseRequest, TransactionsError> {
364393
let vector_clock_id = self.change_set_pointer()?.vector_clock_id();
365394
Ok(RebaseRequest {
366-
onto_workspace_snapshot_id,
395+
onto_workspace_snapshot_address,
367396
// the vector clock id of the current change set is just the id
368397
// of the current change set
369398
to_rebase_change_set_id: self.change_set_id(),
@@ -427,7 +456,9 @@ impl DalContext {
427456
/// Consumes all inner transactions and committing all changes made within them.
428457
pub async fn commit(&self) -> Result<Option<Conflicts>, TransactionsError> {
429458
let rebase_request = match self.write_snapshot().await? {
430-
Some(workspace_snapshot_id) => Some(self.get_rebase_request(workspace_snapshot_id)?),
459+
Some(workspace_snapshot_address) => {
460+
Some(self.get_rebase_request(workspace_snapshot_address)?)
461+
}
431462
None => None,
432463
};
433464

@@ -510,9 +541,10 @@ impl DalContext {
510541
/// Consumes all inner transactions, committing all changes made within them, and
511542
/// blocks until all queued jobs have reported as finishing.
512543
pub async fn blocking_commit(&self) -> Result<Option<Conflicts>, TransactionsError> {
513-
info!("blocking_commit");
514544
let rebase_request = match self.write_snapshot().await? {
515-
Some(workspace_snapshot_id) => Some(self.get_rebase_request(workspace_snapshot_id)?),
545+
Some(workspace_snapshot_address) => {
546+
Some(self.get_rebase_request(workspace_snapshot_address)?)
547+
}
516548
None => None,
517549
};
518550

@@ -1001,7 +1033,7 @@ pub enum TransactionsError {
10011033
#[error(transparent)]
10021034
PgPool(#[from] PgPoolError),
10031035
#[error("rebase of snapshot {0} change set id {1} failed {2}")]
1004-
RebaseFailed(WorkspaceSnapshotId, ChangeSetId, String),
1036+
RebaseFailed(WorkspaceSnapshotAddress, ChangeSetId, String),
10051037
#[error(transparent)]
10061038
RebaserClient(#[from] RebaserClientError),
10071039
#[error(transparent)]
@@ -1098,7 +1130,7 @@ pub struct Transactions {
10981130
#[derive(Clone, Debug)]
10991131
pub struct RebaseRequest {
11001132
pub to_rebase_change_set_id: ChangeSetId,
1101-
pub onto_workspace_snapshot_id: WorkspaceSnapshotId,
1133+
pub onto_workspace_snapshot_address: WorkspaceSnapshotAddress,
11021134
pub onto_vector_clock_id: VectorClockId,
11031135
}
11041136

@@ -1127,7 +1159,7 @@ async fn rebase(
11271159
let response = rebaser_client
11281160
.request_rebase(
11291161
rebase_request.to_rebase_change_set_id.into(),
1130-
rebase_request.onto_workspace_snapshot_id.into(),
1162+
rebase_request.onto_workspace_snapshot_address,
11311163
rebase_request.onto_vector_clock_id.into(),
11321164
)
11331165
.await?;
@@ -1136,7 +1168,7 @@ async fn rebase(
11361168
match response {
11371169
ReplyRebaseMessage::Success { .. } => Ok(None),
11381170
ReplyRebaseMessage::Error { message } => Err(TransactionsError::RebaseFailed(
1139-
rebase_request.onto_workspace_snapshot_id,
1171+
rebase_request.onto_workspace_snapshot_address,
11401172
rebase_request.to_rebase_change_set_id,
11411173
message,
11421174
)),
@@ -1190,33 +1222,18 @@ impl Transactions {
11901222
skip_all,
11911223
fields()
11921224
)]
1193-
pub async fn commit_into_conns(
1194-
self,
1195-
tenancy: &Tenancy,
1196-
rebase_request: Option<RebaseRequest>,
1197-
) -> Result<(Connections, Option<Conflicts>), TransactionsError> {
1225+
pub async fn commit_into_conns(self) -> Result<Connections, TransactionsError> {
11981226
let pg_conn = self.pg_txn.commit_into_conn().await?;
11991227
let nats_conn = self.nats_txn.commit_into_conn().await?;
12001228

1201-
let conflicts = if let Some(rebase_request) = rebase_request {
1202-
let start = Instant::now();
1203-
let conflicts = rebase(
1204-
tenancy,
1205-
nats_conn.clone(),
1206-
self.rebaser_config.clone(),
1207-
rebase_request,
1208-
)
1209-
.await?;
1210-
info!("rebase took: {:?}", start.elapsed());
1211-
conflicts
1212-
} else {
1213-
None
1214-
};
1215-
12161229
self.job_processor.process_queue(self.job_queue).await?;
1217-
let conns = Connections::new(pg_conn, nats_conn, self.job_processor, self.rebaser_config);
12181230

1219-
Ok((conns, conflicts))
1231+
Ok(Connections::new(
1232+
pg_conn,
1233+
nats_conn,
1234+
self.job_processor,
1235+
self.rebaser_config,
1236+
))
12201237
}
12211238

12221239
/// Consumes all inner transactions, committing all changes made within them, and returns
@@ -1236,7 +1253,6 @@ impl Transactions {
12361253
let nats_conn = self.nats_txn.commit_into_conn().await?;
12371254

12381255
let conflicts = if let Some(rebase_request) = rebase_request {
1239-
info!("rebase request");
12401256
rebase(
12411257
tenancy,
12421258
nats_conn.clone(),
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
CREATE TABLE change_set_pointers
22
(
3-
id ident primary key NOT NULL DEFAULT ident_create_v1(),
4-
created_at timestamp with time zone NOT NULL DEFAULT CLOCK_TIMESTAMP(),
5-
updated_at timestamp with time zone NOT NULL DEFAULT CLOCK_TIMESTAMP(),
6-
name text NOT NULL,
7-
base_change_set_id ident,
8-
status text NOT NULL,
3+
id ident primary key NOT NULL DEFAULT ident_create_v1(),
4+
created_at timestamp with time zone NOT NULL DEFAULT CLOCK_TIMESTAMP(),
5+
updated_at timestamp with time zone NOT NULL DEFAULT CLOCK_TIMESTAMP(),
6+
name text NOT NULL,
7+
base_change_set_id ident,
8+
status text NOT NULL,
99

10-
workspace_id ident REFERENCES workspaces (pk) DEFERRABLE,
11-
workspace_snapshot_id ident REFERENCES workspace_snapshots (id)
10+
workspace_id ident REFERENCES workspaces (pk) DEFERRABLE,
11+
workspace_snapshot_address text
1212
);

lib/dal/src/queries/workspace_snapshot/find_for_change_set.sql

Lines changed: 0 additions & 4 deletions
This file was deleted.

0 commit comments

Comments
 (0)