Improved documentation

This commit is contained in:
Eduard Urbach 2023-07-12 13:28:15 +02:00
parent 658cd6a8a3
commit 0c6db9d1f3
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
2 changed files with 27 additions and 17 deletions

View File

@ -11,12 +11,11 @@ go get git.akyoto.dev/go/ocean
## Example ## Example
```go ```go
// User type // Define the User type
type User struct { Name string } type User struct { Name string }
// Create a new collection // Create a collection in ~/.ocean/myapp/User.dat
todolist := ocean.New("todolist") users := ocean.New[User]("myapp", &storage.File[User]{})
users := todolist.NewCollection[User](&storage.File[User]{})
// Store some data // Store some data
users.Set("1", &User{Name: "User 1"}) users.Set("1", &User{Name: "User 1"})
@ -32,9 +31,7 @@ for user := range users.All() {
} }
``` ```
Data will be stored in `~/.ocean/todolist/User.dat`. ## File format
## Format
```json ```json
1 1
@ -45,6 +42,23 @@ Data will be stored in `~/.ocean/todolist/User.dat`.
{"name":"User 3"} {"name":"User 3"}
``` ```
## Storage systems
### storage.File
`storage.File` uses a single file to store all records.
Writes using `Set(key, value)` are async and only mark the collection as "dirty" which is very quick.
The sync to disk happens shortly afterwards.
Every collection uses one goroutine to check the "dirty" flag, write the new contents to disk and reset the flag.
The biggest advantage of `storage.File` is that it scales well with the number of requests:
Suppose `n` is the number of write requests and `io` is the time it takes for one write. Immediate storage would require `O(n * io)` time to complete all writes but the async behavior makes it `O(n)`.
You should use `storage.File` if you have a permanently running process such as a web server where end users expect quick responses and background work can happen after the user request has already been dealt with.
Make sure you `defer collection.Sync()` to ensure that queued writes will be handled when the process ends.
## Benchmarks ## Benchmarks
``` ```
@ -53,10 +67,3 @@ BenchmarkSet-12 4796011 251.0 ns/op 32 B/op
BenchmarkDelete-12 471913158 2.530 ns/op 0 B/op 0 allocs/op BenchmarkDelete-12 471913158 2.530 ns/op 0 B/op 0 allocs/op
BenchmarkNew-12 48838576 22.89 ns/op 80 B/op 1 allocs/op BenchmarkNew-12 48838576 22.89 ns/op 80 B/op 1 allocs/op
``` ```
## Usage
1. Create all the collections you need at the start
2. `defer users.Sync()` to ensure queued writes will be handled on exit
3. Start your web server
4. Retrieve and update your data using `Get` and `Set` calls

View File

@ -14,7 +14,10 @@ import (
"git.akyoto.dev/go/ocean" "git.akyoto.dev/go/ocean"
) )
const diskWriteInterval = 100 * time.Millisecond const (
diskWriteInterval = 100 * time.Millisecond
fileExtension = ".dat"
)
type File[T any] struct { type File[T any] struct {
collection ocean.StorageData collection ocean.StorageData
@ -28,7 +31,7 @@ func (fs *File[T]) Init(c ocean.StorageData) error {
go fs.flushWorker() go fs.flushWorker()
fileName := filepath.Join(c.Root(), c.Name()+".dat") fileName := filepath.Join(c.Root(), c.Name()+fileExtension)
file, err := os.Open(fileName) file, err := os.Open(fileName)
if os.IsNotExist(err) { if os.IsNotExist(err) {
@ -79,7 +82,7 @@ func (fs *File[T]) flushWorker() {
} }
func (fs *File[T]) flush() error { func (fs *File[T]) flush() error {
oldPath := filepath.Join(fs.collection.Root(), fs.collection.Name()+".dat") oldPath := filepath.Join(fs.collection.Root(), fs.collection.Name()+fileExtension)
newPath := oldPath + ".tmp" newPath := oldPath + ".tmp"
file, err := os.OpenFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) file, err := os.OpenFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)