Transactions

yesterday & today

Szymon Kulec

@Scooletz

http://scooletz.com

?

Example

extracted from MSDN magazine

Publish example
public void Publish(object o) { var factory = new ConnectionFactory(); var conn = factory.CreateConnection(); using ( var channel = conn.CreateModel()) { // code to define the RabbitMQ channel var json = SerializeToJSON(o); var messageBodyBytes = Encoding.UTF8.GetBytes(json); channel.BasicPublish("CustomerUpdate", "", props, messageBodyBytes); } }
Save example
public bool Save(Customer c) { using ( var ctx = new MyDbCtx()) { ctx.Customers.Add(c); int response = ctx.SaveChanges(); if (response > 0) { Publish(c); return true; } return false; } }
Is this Save better?
public bool Save(Customer c) { using (var ctx = new MyDbCtx()) { ctx.Customers.Add(c); Publish(c); return ctx.SaveChanges() > 0; } }
Simplified
public bool Save(Customer c) { DoDatabase(c); DoQueue(c); }
Simplified with some tweaks
public bool Save(Customer c) { PreDoDatabase(c); PreDoQueue(c); PostDoDatabase(c); }

Example outcome

  • The example shows not only queue-db integration but service-db & others as well
  • Even with different ordering, we cannot ensure the transaction between queue & database
  • It's possible to get into an inconsistent state because of exceptions, processes being killed, etc.

Distributed transactions

Distributed transactions

  • A transaction across more than one system, database, queue, resource
  • Simulates good old-fashioned ACID transaction
  • Requires a transaction manager
  • May be used across the network

Distributed transactions - 2PC

  • 2PC - two phase commit
  • 1st prepares all the transactions across systems being used in the transaction
  • 2nd goes through the systems and ACKing the transaction

TransactionScope

  • .NET high-level abstraction over transaction
  • When used only with a single resource, it's just a wrap around transaction
  • Can propagate to a distributed transaction when another transactional resource used
  • The process of registering a transaction in a scope is called enlistement
TransactionScope applied
public bool Save(Customer c) { using( var ts = new TransactionScope()) { PreDoDatabase(c); PreDoQueue(c); PostDoDatabase(c); ts.Complete(); } }

Distributed transactions are the solution !

NOT!

Disadvantages of distributed transactions

  • Not always supported (try RabbitMQ or REST services or Cassandra)
  • Latency - you need additional network hops
  • Longer calls - longer locks - worse throughput
  • Orphans - state is unknown (no, they won't become 007)

If not distributed

then what? :(

Going local

Simplified example once again but with a ctx passed to Publish...
public bool Save(Customer c) { using (var ctx = new MyDbCtx()) { ctx.Customers.Add(c); Publish(c, ctx); return ctx.SaveChanges() > 0; } }
With the Publish defined now as...
public void Publish(Customer c, MyDbCtx ctx) { var publishIntent = PublishIntent.CreateFrom (c); ctx.PublishIntents.Add (publishIntent); }

Going local

  • Transaction is local
  • A different modelling saved us from going distributed
  • A different modelling saved us from dropping system in a unknown state
  • ... but left with unpublished intents
  • Now all we should do is just published intents via queue and delete them in a tx
  • ... but this requires distributed transaction again :/
  • Is happiness really possible???

Delivery guarantees

Delivery guarantees

  • at-most-once
  • exactly-once
  • at-least-once
This method provides at-most-once delivery without getting into distributed problems
public void RealPublish(MyDbCtx ctx, RabbitMqSender sender) { var intent = ctx.PublishIntents.FirstOrDefault(); ctx.PublishIntents.Remove(intent); ctx.SaveChanges(); sender.Send(intent); }
This method provides at-least-once delivery without getting into distributed problems
public void RealPublish(MyDbCtx ctx, RabbitMqSender sender) { var intent = ctx.PublishIntents.FirstOrDefault(); sender.Send(intent); ctx.PublishIntents.Remove(intent); ctx.SaveChanges(); }

Delivery guarantees

  • It's easy to at-least/at-most once deliver the message
  • Can we make it exactly-once then?
  • YES!

Exactly-once delivery

  • We need to be ensured that receiver gets the message
  • ... hence at-least-once is required.
  • Under some conditions duplicates can appear on the receiver side
  • What can be done?

Exactly-once delivery with idempotent receiver

  • Mark every message with a unique id
  • On the receiver side use local transaction to process the message and store some state
  • Additonally, in the very same transaction store the message id
  • Process only messages previously not marked as processed
This method handles the message being sent and assumes that the message has Id property with a unique message id
public void ReceiveAtLeastOnce(OtherDbCtx ctx, Message msg) { using(var tx = ctx.Database.BeginTransaction()) { var done = ctx.ProcessedIds.Find(msg.Id); if (done != null) return; ProcessAndSaveState(ctx); ctx.ProcessedIds.Add (new ProcessedId {Id = msg.Id}); tx.Commit(); } }

Delivery guarantees summary

exactly-once = at-least-once + idempotent-receiver

Transactions in new databases

Transactions in new databases

  • So many trends, approaches - this will not cover everything
  • Many of them does not imply any transactions
  • It's common to have transactions only for the given key
  • There's no notion of a transaction locking rows for reads

Transactions in new databases - examples

Name Features
Azure Table Storage partition key + row key, tx only across partition
RavenDB transactions spans across documents, but indeces are not transactional!
Riak key-value, values are replace atomically but can use multi-write as well
EventStore transaction only for a given stream, idempotent receiver in the db
The error that drained many bitcoin wallets (improper use of MongoDb)
mybalance = database.read("account-number") newbalance = mybalance - amount database.write("account-number", newbalance) dispense_cash(amount) // or send bitcoins to customer

Modelling question - money transfer

  • The database can hold transaction only on one key
  • We are allowed to have a debit on an account
  • How would you model a transfer across accounts?
  • We can use EventStore for example

Modelling question - money transfer

  • There is no account but only its number
  • The atomic operation of a transfer is the thing that we save
  • An account is a sum of all transfers from-to

Summary

  • Be aware of the possible failures
  • Try to model towards local transactions
  • exactly-once = at-least-once + idempotent-receiver
  • Model is only a model: choose wisely its first class citizens
  • Use error-injection if you cannot see the whole picture yet. This will drive you.
  • Don't drop the ball. There's a big chance that you're dealing with money :P

TO DO

Questions?

Szymon Kulec

@Scooletz

http://blog.scooletz.com