Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Option to start without migration? #30

Closed
adoublef opened this issue Jun 27, 2024 · 8 comments
Closed

Option to start without migration? #30

adoublef opened this issue Jun 27, 2024 · 8 comments

Comments

@adoublef
Copy link

adoublef commented Jun 27, 2024

As the title suggests, would be interested if the API can be modified to allow users to open a connection and run the initial table creations separately.

The use-case is a tool such as LiteFS, a SQLite replication package. This currently limits one writer to the database on server creation. A problem arises with this package when spinning up a replica

2024-06-27 23:19:11 failed to create kv client: disk I/O error: permission denied
2024-06-27 23:19:11 level=INFO msg="E6A5861BC03876BC: disconnected from primary with error, retrying: next frame: context canceled"

This could likely be resolved by giving an option to ignore calling sqlx.*DB[T].createSchema pass by through the redka.Options struct.

A package level function could likely be exported instead to run the function:

// Up as an example
func Up[T any](db *DB[T]) error {
    return d.createSchema()
}

giving a bit more flexibility to the application developer.

@nalgeon
Copy link
Owner

nalgeon commented Jun 27, 2024

Not sure if I understand the use case correctly. Are you running Redka on the read-only database replica? Do you plan to only use API methods like Get, and not methods like Set or Delete?

@adoublef
Copy link
Author

Yes, when a cluster of Go binaries are up, the leader will be granted read-write access, while all the replicas will be in read-only. The LiteFS package intercepts all sqlite operations to handle this.

Having checked through the API of Redka again, I realise it would additionally require modifying the *DB.startBgManager functionality, as this would have read-only replicas, attempt writes, resulting in a permission denied.

Similarly, if a leader is changed (via consensus) then that will now be promoted to perform read & writes. This is not something I considered before making the issue, and so unsure if this can be achieved without injecting very LiteFS specific code (they offer a way for a node to know if its a parent, but I'd expect this is not something that could be added to a general purpose library), so something I need to rethink.

Here is an example project on Fly's profile using the standard database/sql interface.

@adoublef
Copy link
Author

adoublef commented Jun 28, 2024

Regarding needing to know if a DB has been promoted, if the logic could be in application space and either an atomic value or a public field can be dynamically updated, then this could allow the application to run a go routine to look for these changes, reducing the work this library would need to do.

// pseudo-code
go func () {
  // user will look for changes in the filesystem
  // can be accomplished with an interval or hooking into litefs events
  // but this would be all within application space, not the library
  isPrimary, _  := nodeIsPrimary(ctx)
  if isPrimary { 
    // modify the value.
    // or modify a package level atomic value.
    kv.Mode(redka.Write)
  }
} () 

I do recognise this is niche & such a change needs to not break expectations for current users.

@nalgeon
Copy link
Owner

nalgeon commented Jun 28, 2024

I see, thanks for the detailed explanation! I'll think about it over the weekend and hopefully come back with something.

@nalgeon
Copy link
Owner

nalgeon commented Jun 29, 2024

Here is the proposed design:

  1. The client uses the redka.OpenRead function to open the database in read-only mode:
db, err := redka.OpenRead("data.db", nil)
  1. When a database has been promoted to read-write mode, the client should close and reopen the database in read-write mode:
db.Close()
db, err := redka.Open("data.db", nil)

What do you think?

@adoublef
Copy link
Author

adoublef commented Jun 30, 2024

I think this could work, I have an example project setup with litefs that I can experiment with. To me, I should be able to open a go routine in main as example, and have it just listen into the event stream that litefs offers.

Would this OpenRead be a different type or just one that doesn't create the schema (or runs the purge).

This would likely mean reassigning the db variable, something I've not had to do before, so will be willing to test this doesn't cause any unexpected behaviours.

nalgeon added a commit that referenced this issue Jul 1, 2024
@nalgeon
Copy link
Owner

nalgeon commented Jul 1, 2024

Please try using OpenRead from the read-only-mode branch. You can install it using go get as follows:

go get github.com/nalgeon/redka@read-only-mode

OpenRead returns the same *redka.DB value as Open, only the write operations won't work:

// open a writable database
db, err := redka.Open("data.db", nil)
if err != nil {
    panic(err)
}
db.Str().Set("name", "alice")
db.Close()

// open a read-only database
db, err = redka.OpenRead("data.db", nil)
if err != nil {
    panic(err)
}

// read operations work fine
name, _ := db.Str().Get("name")
fmt.Println(name)

// write operations will fail
err = db.Str().Set("name", "bob")
fmt.Println(err)
db.Close()
alice
attempt to write a readonly database

@nalgeon
Copy link
Owner

nalgeon commented Jul 3, 2024

Any luck trying OpenRead?

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

No branches or pull requests

2 participants