Skip to main content

Optimistic concurrency

Two people load the same row, and both save. By default the second save wins for the columns it changed. A version field makes it fail instead, so nobody overwrites work they never saw.

A version field​

ant:"version" starts at 1 and goes up with every save. A save only goes through if the row still has the version you loaded.

type Page struct {
ID int64
Title string
Version int64 `ant:"version"`
}

When writers collide​

one, two := ant.Session(db), ant.Session(db)

a, err := one.Pages.Find(ctx, id)
b, err := two.Pages.Find(ctx, id)

a.Title = "first"
err = one.SaveChanges(ctx) // fine

b.Title = "second"
err = two.SaveChanges(ctx) // ant.ErrConcurrency

Bulk updates and deletes count as writes, and a delete checks the version too.

Handling a conflict​

ant.ConcurrencyError tells you which row it was. Reload it to see what's there now, and try again.

err := db.SaveChanges(ctx)

var conflict *ant.ConcurrencyError

if errors.As(err, &conflict) {
fmt.Println(conflict.Model, conflict.Key)

return db.Pages.Reload(ctx, page)
}

A token you manage​

ant:"token" works the same way with a value your application sets, such as an ETag. A NULL token compares correctly too.

type Stock struct {
ID int64
Quantity int
ETag *string `ant:"token"`
}