tursodatabase / libsql-client-go

Go client API for libSQL

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Guarantee read-your-write with replicas

penberg opened this issue · comments

We have reports that the following scenario breaks read-your-write guarantee with replicas:

// CREATE TABLE companies (id INT PRIMARY KEY, score INT);

package main

import (
	"context"
	"database/sql"
	"fmt"
	_ "github.com/tursodatabase/libsql-client-go/libsql"
	"log"
	"math/rand"
	"os"
)

func main() {
	ctx := context.Background()
	dbUrl := os.Getenv("LIBSQL_URL")
	authToken := os.Getenv("LIBSQL_AUTH_TOKEN")
	db, err := sql.Open("libsql", dbUrl+"?authToken="+authToken)
	if err != nil {
		log.Fatal(err)
	}
	err = db.Ping()
	if err != nil {
		log.Fatal(err)
	}
	for i := 0; i < 100; i++ {
		fmt.Printf(".")
		id := rand.Intn(10)
		origScore := rand.Intn(100)
		updateSql := "INSERT INTO companies (id, score) VALUES (?, ?) ON CONFLICT (id) DO UPDATE SET score = ?"
		_, err = db.ExecContext(ctx, updateSql, id, origScore, origScore)
		if err != nil {
			log.Fatal(err)
		}
		var newScore int
		if err = db.QueryRowContext(ctx, "SELECT score FROM companies WHERE id = ?", id).Scan(&newScore); err != nil {
			if err == sql.ErrNoRows {
				log.Fatal(fmt.Errorf("no such company"))
			}
			log.Fatal(err)
		}
		if origScore != newScore {
			log.Fatal(fmt.Errorf("read your write violation"))
		}
	}
}

@MarinPostma pointed out that we need to request a read at the same replication index as the last write in the client to guarantee read-your-write. The replication index support in the protocol is undocumented, but here are some pointers:

https://github.com/tursodatabase/libsql/blob/main/libsql-server/src/hrana/proto.rs#L16-L35

https://github.com/tursodatabase/libsql/blob/main/libsql-server/src/hrana/proto.rs#L95

Depending on the Hrana API you are using, you may be missing the response replication index. It's there for batch response, but may be missing in other places, let me know, and I'll make it a priority to fix it for you