Skip to content

Conversation

niclaflamme
Copy link
Contributor

@niclaflamme niclaflamme commented Aug 8, 2025

Replace the dead-simple in_transaction flag with a real LogicalTransaction state machine

Why

  • A bool only express very simple state.
  • It can’t say which shard a statement hit, what phase we’re in, or whether we can safely cross shards.
  • The new LogicalTransaction struct carries that context.
  • This will allow us to do more sophisticated thing, like tracking 2PC status across.

What changed

  • Added LogicalTransaction and TransactionStatus (Idle → BeginPending → InProgress → Committed / RolledBack). (old pr)
  • Includes .in_transaction() and shard tracking.
  • Replaced every in_transaction call across EngineContext, Inner, router, and tests to a borrow of LogicalTransaction.
  • start_transaction() / end_transaction() now drive the state machine and push backend messages with SmallVec.
  • integration tests prewarm() uses real BEGIN…COMMIT; this was an error in the logic.
  • switched docker-compose → docker compose to quiet the deprecation warning.
  • smallvec = 1.15.1 shows up in Cargo.lock, but SmallVec is already in a bunch other crates—this isn’t a real new dependency.

Refresher on logical transactions

Merge as standalone file in PR #313: a logical transaction makes a sharded cluster look like a single Postgres instance.
Basically, single-node postgres have Transactions.
PgDog can only have LogicalTransacion, and must orchestrate true Transactions across shards.
It tracks shards, phases, and errors early so we can later block illegal cross-shard writes and bolt on 2PC enventually.

TODO

LogicalTransaction::touch() is wired to throw if the client tries to touch multiple shards in the same txn (might need to track backend_ids instead—TBD). It’s not used yet; hook it up next.

export PGPASSWORD=postgres

docker-compose up -d
docker compose up -d
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docker-compose is deprecated.
docker compose has replaced it.

docker-compose does not work on my recent version of docker desktop.
docker compose works on the version of docker in our CI.

win-win?
I suppose it will work for a every engineer's machine too. Can rollback this change if that is not the case

@niclaflamme niclaflamme changed the title WIP : LogicalTranasactions in client LogicalTransactions in frontend Client Aug 8, 2025
prepared_statements: &mut PreparedStatements,
params: &Parameters,
in_transaction: bool,
logical_transaction: &LogicalTransaction,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the root change, followed the compiler from here basically.

@niclaflamme niclaflamme requested a review from levkk August 8, 2025 20:07
let in_transaction = self.in_transaction();

// 3) Reconcile against our LogicalTransaction
if should_be_tx && !in_transaction {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd probably clarify that the query parser isn't always active and therefore we don't catch BEGIN statements always. So sometimes, the only way to know we're inside a transaction is because the server told us.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right, I also think it might be cheap to check twice. I'll check 👀

Copy link
Collaborator

@levkk levkk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool!

Some(Command::StartTransaction(query)) => {
if let BufferedQuery::Query(_) = query {
self.start_transaction().await?;
inner.start_transaction = Some(query.clone());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is important to record. BEGIN can have multiple arguments that affect what kind of transaction we're starting: https://www.postgresql.org/docs/current/sql-begin.html

/// Execute a BEGIN on all servers
/// TODO: Block mutli-shard BEGINs as transaction should not occur on multiple shards
pub async fn begin(&mut self) -> Result<(), Error> {
let query = Query::new("BEGIN");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See other comment. Client can pass options to BEGIN, changing transaction type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants