-
Notifications
You must be signed in to change notification settings - Fork 64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
🚀 [Feature]: ObjectBox storage support #1531
Comments
I have make some small implementation package middleware
import (
"math/rand/v2"
"time"
"github.com/objectbox/objectbox-go/objectbox"
)
//go:generate go run github.com/objectbox/objectbox-go/cmd/objectbox-gogen
type CacheEntry struct {
Id uint64 `objectbox:"id"`
Key string `objectbox:"index"`
Value []byte
ExpiresAt int64
}
type ObjectBoxStorage struct {
ob *objectbox.ObjectBox
box *CacheEntryBox
}
func NewObjectBoxStorage() (*ObjectBoxStorage, error) {
ob, err := objectbox.NewBuilder().Model(ObjectBoxModel()).Build()
if err != nil {
return nil, err
}
storage := &ObjectBoxStorage{
ob: ob,
box: BoxForCacheEntry(ob),
}
// Run cleanup every hour
go func() {
ticker := time.NewTicker(1 * time.Hour)
for range ticker.C {
storage.cleanupExpired()
}
}()
return storage, nil
}
func (s *ObjectBoxStorage) Get(key string) ([]byte, error) {
if rand.Float32() < 0.1 {
s.cleanupExpired()
}
query := s.box.Query(CacheEntry_.Key.Equals(key, true), CacheEntry_.ExpiresAt.GreaterThan(time.Now().Unix()))
entries, err := query.Find()
if err != nil {
return nil, err
}
if len(entries) == 0 {
return nil, nil
}
return entries[0].Value, nil
}
func (s *ObjectBoxStorage) Set(key string, val []byte, exp time.Duration) error {
entry := &CacheEntry{
Key: key,
Value: val,
ExpiresAt: time.Now().Add(exp).Unix(),
}
_, err := s.box.Put(entry)
return err
}
func (s *ObjectBoxStorage) Delete(key string) error {
query := s.box.Query(CacheEntry_.Key.Equals(key, true))
entries, err := query.Find()
if err != nil {
return err
}
for _, entry := range entries {
if err := s.box.Remove(entry); err != nil {
return err
}
}
return nil
}
func (s *ObjectBoxStorage) Reset() error {
return s.box.RemoveAll()
}
func (s *ObjectBoxStorage) Close() error {
s.ob.Close()
return nil
}
func (s *ObjectBoxStorage) cleanupExpired() {
query := s.box.Query(CacheEntry_.ExpiresAt.LessThan(time.Now().Unix()))
entries, err := query.Find()
if err != nil {
return
}
s.box.ObjectBox.RunInWriteTx(func() error {
for _, entry := range entries {
s.box.Remove(entry)
}
return nil
})
} |
I will take a look later to see how much effort is this. |
I couldn't wait so after looking for other storage implementation, I write this. package objectbox
import "time"
// Config defines the configuration options for ObjectBox storage.
type Config struct {
// Directory is the path where the database is stored.
// Optional, defaults to "objectbox"
Directory string
// MaxSizeInKb sets the maximum size of the database in kilobytes.
// Optional, defaults to 1GB (1024 * 1024 * 1024)
MaxSizeInKb uint64
// MaxReaders defines the maximum number of concurrent readers.
// Optional, defaults to 126
MaxReaders uint
// Reset determines if existing keys should be cleared on startup.
// Optional, defaults to false
Reset bool
// CleanerInterval sets the frequency for deleting expired keys.
// Optional, defaults to 60 seconds
CleanerInterval time.Duration
}
var DefaultConfig = Config{
Directory: "objectbox_db",
MaxSizeInKb: 1024 * 1024, // 1GByte
MaxReaders: 126,
Reset: false,
CleanerInterval: 60 * time.Second,
}
func getConfig(config ...Config) Config {
if len(config) < 1 {
return DefaultConfig
}
cfg := config[0]
// Set default values
if cfg.Directory == "" {
cfg.Directory = DefaultConfig.Directory
}
if cfg.MaxSizeInKb == 0 {
cfg.MaxSizeInKb = DefaultConfig.MaxSizeInKb
}
if cfg.MaxReaders == 0 {
cfg.MaxReaders = DefaultConfig.MaxReaders
}
if int(cfg.CleanerInterval.Seconds()) == 0 {
cfg.CleanerInterval = DefaultConfig.CleanerInterval
}
return cfg
} package objectbox
import (
"time"
"github.com/objectbox/objectbox-go/objectbox"
)
//go:generate go run github.com/objectbox/objectbox-go/cmd/objectbox-gogen
// Cache represents a single cache entry in the storage.
type Cache struct {
Id uint64 `objectbox:"id"`
Key string `objectbox:"index,unique"`
Value []byte
ExpiresAt int64 `objectbox:"index"`
}
// Storage handles the ObjectBox database operations and cleanup routines.
type Storage struct {
ob *objectbox.ObjectBox
box *CacheBox
done chan struct{}
}
// New creates a new Storage instance with the provided configuration.
// It initializes the ObjectBox database and starts the cleanup routine.
func New(config ...Config) *Storage {
cfg := getConfig(config...)
ob, err := objectbox.NewBuilder().Model(ObjectBoxModel()).MaxSizeInKb(cfg.MaxSizeInKb).MaxReaders(cfg.MaxReaders).Directory(cfg.Directory).Build()
if err != nil {
return nil
}
if cfg.Reset {
box := BoxForCache(ob)
box.RemoveAll()
}
storage := &Storage{
ob: ob,
box: BoxForCache(ob),
done: make(chan struct{}),
}
go storage.cleanerTicker(cfg.CleanerInterval)
return storage
}
// Get retrieves a value from cache by its key.
// Returns nil if key doesn't exist or has expired.
func (s *Storage) Get(key string) ([]byte, error) {
if len(key) < 1 {
return nil, nil
}
query := s.box.Query(Cache_.Key.Equals(key, true),
objectbox.Any(
Cache_.ExpiresAt.Equals(0),
Cache_.ExpiresAt.GreaterThan(time.Now().Unix()),
))
caches, err := query.Find()
if err != nil {
return nil, err
}
if len(caches) < 1 {
return nil, nil
}
return caches[0].Value, nil
}
// Set stores a value in cache with the specified key and expiration.
// If expiration is 0, the entry won't expire.
func (s *Storage) Set(key string, value []byte, exp time.Duration) error {
if len(key) <= 0 || len(value) <= 0 {
return nil
}
// Since objectbox go doen't support conflict strategy,
// we need to check if the key already exists
// and update the value if it does. Thus we need to
// get the id of the cache first and then update the cache
// with the new value with the same id.
query := s.box.Query(Cache_.Key.Equals(key, true))
cachesIds, err := query.FindIds()
if err != nil {
return err
}
// if the id is 0 it will create new cache
// otherwise it will update the existing entry
var id uint64 = 0
if len(cachesIds) > 0 {
id = cachesIds[0]
}
var expAt int64
if exp > 0 { // Changed from exp != 0 to exp > 0
expAt = time.Now().Add(exp).Unix()
}
cache := &Cache{
Id: id,
Key: key,
Value: value,
ExpiresAt: expAt,
}
_, err = s.box.Put(cache)
if err != nil {
return err
}
return nil
}
// Delete removes an entry from cache by its key.
func (s *Storage) Delete(key string) error {
if len(key) <= 0 {
return nil
}
query := s.box.Query(Cache_.Key.Equals(key, true))
cachesIds, err := query.FindIds()
if err != nil {
return err
}
if len(cachesIds) < 1 {
return nil
}
if err := s.box.RemoveId(cachesIds[0]); err != nil {
return err
}
return nil
}
// Reset removes all entries from the cache.
func (s *Storage) Reset() error {
return s.box.RemoveAll()
}
// Close shuts down the storage, stopping the cleanup routine
// and closing the database connection.
func (s *Storage) Close() error {
close(s.done)
s.ob.Close()
return nil
}
// cleaneStorage removes all expired cache entries.
func (s *Storage) cleaneStorage() {
s.box.Query(Cache_.ExpiresAt.LessThan(time.Now().Unix())).Remove()
}
// cleanerTicker runs periodic cleanup of expired entries.
func (s *Storage) cleanerTicker(interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
s.cleaneStorage()
case <-s.done:
return
}
}
} |
@karnadii Awesome, do you allow us to use this implementation to make an official driver for it on this repo? |
@gaby yes please, the updated code is here https://github.com/karnadii/storage/tree/objectbox/objectbox |
Will do this week, thanks! 💪 |
Feature Description
implement driver for ObjectBox from github.com/objectbox/objectbox-go
Additional Context (optional)
No response
Code Snippet (optional)
Checklist:
The text was updated successfully, but these errors were encountered: