mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-06-08 05:50:47 +00:00
Reimplement DB collections for mirrors, repos and snapshots
See #765, #761 Collections were relying on keeping in-memory list of all the objects for any kind of operation which doesn't scale well the number of objects in the database. With this rewrite, objects are loaded only on demand which might be pessimization in some edge cases but should improve performance and memory footprint signifcantly.
This commit is contained in:
@@ -13,7 +13,7 @@ RUN_LONG_TESTS?=yes
|
|||||||
|
|
||||||
GO_1_10_AND_HIGHER=$(shell (printf '%s\n' go1.10 $(GOVERSION) | sort -cV >/dev/null 2>&1) && echo "yes")
|
GO_1_10_AND_HIGHER=$(shell (printf '%s\n' go1.10 $(GOVERSION) | sort -cV >/dev/null 2>&1) && echo "yes")
|
||||||
|
|
||||||
all: test check system-test
|
all: test bench check system-test
|
||||||
|
|
||||||
prepare:
|
prepare:
|
||||||
go get -u github.com/alecthomas/gometalinter
|
go get -u github.com/alecthomas/gometalinter
|
||||||
@@ -57,6 +57,9 @@ else
|
|||||||
go test -v `go list ./... | grep -v vendor/` -gocheck.v=true
|
go test -v `go list ./... | grep -v vendor/` -gocheck.v=true
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
bench:
|
||||||
|
go test -v ./deb -run=nothing -bench=. -benchmem
|
||||||
|
|
||||||
mem.png: mem.dat mem.gp
|
mem.png: mem.dat mem.gp
|
||||||
gnuplot mem.gp
|
gnuplot mem.gp
|
||||||
open mem.png
|
open mem.png
|
||||||
|
|||||||
+70
-56
@@ -2,6 +2,7 @@ package deb
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -93,8 +94,8 @@ func (repo *LocalRepo) RefKey() []byte {
|
|||||||
// LocalRepoCollection does listing, updating/adding/deleting of LocalRepos
|
// LocalRepoCollection does listing, updating/adding/deleting of LocalRepos
|
||||||
type LocalRepoCollection struct {
|
type LocalRepoCollection struct {
|
||||||
*sync.RWMutex
|
*sync.RWMutex
|
||||||
db database.Storage
|
db database.Storage
|
||||||
list []*LocalRepo
|
cache map[string]*LocalRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLocalRepoCollection loads LocalRepos from DB and makes up collection
|
// NewLocalRepoCollection loads LocalRepos from DB and makes up collection
|
||||||
@@ -102,43 +103,59 @@ func NewLocalRepoCollection(db database.Storage) *LocalRepoCollection {
|
|||||||
return &LocalRepoCollection{
|
return &LocalRepoCollection{
|
||||||
RWMutex: &sync.RWMutex{},
|
RWMutex: &sync.RWMutex{},
|
||||||
db: db,
|
db: db,
|
||||||
|
cache: make(map[string]*LocalRepo),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (collection *LocalRepoCollection) loadList() {
|
func (collection *LocalRepoCollection) search(filter func(*LocalRepo) bool, unique bool) []*LocalRepo {
|
||||||
if collection.list != nil {
|
result := []*LocalRepo(nil)
|
||||||
return
|
for _, r := range collection.cache {
|
||||||
}
|
if filter(r) {
|
||||||
|
result = append(result, r)
|
||||||
blobs := collection.db.FetchByPrefix([]byte("L"))
|
|
||||||
collection.list = make([]*LocalRepo, 0, len(blobs))
|
|
||||||
|
|
||||||
for _, blob := range blobs {
|
|
||||||
r := &LocalRepo{}
|
|
||||||
if err := r.Decode(blob); err != nil {
|
|
||||||
log.Printf("Error decoding repo: %s\n", err)
|
|
||||||
} else {
|
|
||||||
collection.list = append(collection.list, r)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if unique && len(result) > 0 {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.db.ProcessByPrefix([]byte("L"), func(key, blob []byte) error {
|
||||||
|
r := &LocalRepo{}
|
||||||
|
if err := r.Decode(blob); err != nil {
|
||||||
|
log.Printf("Error decoding local repo: %s\n", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter(r) {
|
||||||
|
if _, exists := collection.cache[r.UUID]; !exists {
|
||||||
|
collection.cache[r.UUID] = r
|
||||||
|
result = append(result, r)
|
||||||
|
if unique {
|
||||||
|
return errors.New("abort")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add appends new repo to collection and saves it
|
// Add appends new repo to collection and saves it
|
||||||
func (collection *LocalRepoCollection) Add(repo *LocalRepo) error {
|
func (collection *LocalRepoCollection) Add(repo *LocalRepo) error {
|
||||||
collection.loadList()
|
_, err := collection.ByName(repo.Name)
|
||||||
|
|
||||||
for _, r := range collection.list {
|
if err == nil {
|
||||||
if r.Name == repo.Name {
|
return fmt.Errorf("local repo with name %s already exists", repo.Name)
|
||||||
return fmt.Errorf("local repo with name %s already exists", repo.Name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := collection.Update(repo)
|
err = collection.Update(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
collection.list = append(collection.list, repo)
|
collection.cache[repo.UUID] = repo
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,8 +176,6 @@ func (collection *LocalRepoCollection) Update(repo *LocalRepo) error {
|
|||||||
|
|
||||||
// LoadComplete loads additional information for local repo
|
// LoadComplete loads additional information for local repo
|
||||||
func (collection *LocalRepoCollection) LoadComplete(repo *LocalRepo) error {
|
func (collection *LocalRepoCollection) LoadComplete(repo *LocalRepo) error {
|
||||||
collection.loadList()
|
|
||||||
|
|
||||||
encoded, err := collection.db.Get(repo.RefKey())
|
encoded, err := collection.db.Get(repo.RefKey())
|
||||||
if err == database.ErrNotFound {
|
if err == database.ErrNotFound {
|
||||||
return nil
|
return nil
|
||||||
@@ -175,26 +190,39 @@ func (collection *LocalRepoCollection) LoadComplete(repo *LocalRepo) error {
|
|||||||
|
|
||||||
// ByName looks up repository by name
|
// ByName looks up repository by name
|
||||||
func (collection *LocalRepoCollection) ByName(name string) (*LocalRepo, error) {
|
func (collection *LocalRepoCollection) ByName(name string) (*LocalRepo, error) {
|
||||||
collection.loadList()
|
result := collection.search(func(r *LocalRepo) bool { return r.Name == name }, true)
|
||||||
|
if len(result) == 0 {
|
||||||
for _, r := range collection.list {
|
return nil, fmt.Errorf("local repo with name %s not found", name)
|
||||||
if r.Name == name {
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("local repo with name %s not found", name)
|
|
||||||
|
return result[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ByUUID looks up repository by uuid
|
// ByUUID looks up repository by uuid
|
||||||
func (collection *LocalRepoCollection) ByUUID(uuid string) (*LocalRepo, error) {
|
func (collection *LocalRepoCollection) ByUUID(uuid string) (*LocalRepo, error) {
|
||||||
collection.loadList()
|
if r, ok := collection.cache[uuid]; ok {
|
||||||
|
return r, nil
|
||||||
for _, r := range collection.list {
|
|
||||||
if r.UUID == uuid {
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("local repo with uuid %s not found", uuid)
|
|
||||||
|
key := (&LocalRepo{UUID: uuid}).Key()
|
||||||
|
|
||||||
|
value, err := collection.db.Get(key)
|
||||||
|
if err == database.ErrNotFound {
|
||||||
|
return nil, fmt.Errorf("local repo with uuid %s not found", uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &LocalRepo{}
|
||||||
|
err = r.Decode(value)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
collection.cache[r.UUID] = r
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForEach runs method for each repository
|
// ForEach runs method for each repository
|
||||||
@@ -212,30 +240,16 @@ func (collection *LocalRepoCollection) ForEach(handler func(*LocalRepo) error) e
|
|||||||
|
|
||||||
// Len returns number of remote repos
|
// Len returns number of remote repos
|
||||||
func (collection *LocalRepoCollection) Len() int {
|
func (collection *LocalRepoCollection) Len() int {
|
||||||
collection.loadList()
|
return len(collection.db.KeysByPrefix([]byte("L")))
|
||||||
|
|
||||||
return len(collection.list)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drop removes remote repo from collection
|
// Drop removes remote repo from collection
|
||||||
func (collection *LocalRepoCollection) Drop(repo *LocalRepo) error {
|
func (collection *LocalRepoCollection) Drop(repo *LocalRepo) error {
|
||||||
collection.loadList()
|
if _, err := collection.db.Get(repo.Key()); err == database.ErrNotFound {
|
||||||
|
|
||||||
repoPosition := -1
|
|
||||||
|
|
||||||
for i, r := range collection.list {
|
|
||||||
if r == repo {
|
|
||||||
repoPosition = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if repoPosition == -1 {
|
|
||||||
panic("local repo not found!")
|
panic("local repo not found!")
|
||||||
}
|
}
|
||||||
|
|
||||||
collection.list[len(collection.list)-1], collection.list[repoPosition], collection.list =
|
delete(collection.cache, repo.UUID)
|
||||||
nil, collection.list[len(collection.list)-1], collection.list[:len(collection.list)-1]
|
|
||||||
|
|
||||||
err := collection.db.Delete(repo.Key())
|
err := collection.db.Delete(repo.Key())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -124,6 +124,11 @@ func (s *LocalRepoCollectionSuite) TestByUUID(c *C) {
|
|||||||
|
|
||||||
r, err := s.collection.ByUUID(repo.UUID)
|
r, err := s.collection.ByUUID(repo.UUID)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(r, Equals, repo)
|
||||||
|
|
||||||
|
collection := NewLocalRepoCollection(s.db)
|
||||||
|
r, err = collection.ByUUID(repo.UUID)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
c.Assert(r.String(), Equals, repo.String())
|
c.Assert(r.String(), Equals, repo.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+69
-54
@@ -3,6 +3,7 @@ package deb
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
gocontext "context"
|
gocontext "context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -654,8 +655,8 @@ func (repo *RemoteRepo) RefKey() []byte {
|
|||||||
// RemoteRepoCollection does listing, updating/adding/deleting of RemoteRepos
|
// RemoteRepoCollection does listing, updating/adding/deleting of RemoteRepos
|
||||||
type RemoteRepoCollection struct {
|
type RemoteRepoCollection struct {
|
||||||
*sync.RWMutex
|
*sync.RWMutex
|
||||||
db database.Storage
|
db database.Storage
|
||||||
list []*RemoteRepo
|
cache map[string]*RemoteRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRemoteRepoCollection loads RemoteRepos from DB and makes up collection
|
// NewRemoteRepoCollection loads RemoteRepos from DB and makes up collection
|
||||||
@@ -663,43 +664,59 @@ func NewRemoteRepoCollection(db database.Storage) *RemoteRepoCollection {
|
|||||||
return &RemoteRepoCollection{
|
return &RemoteRepoCollection{
|
||||||
RWMutex: &sync.RWMutex{},
|
RWMutex: &sync.RWMutex{},
|
||||||
db: db,
|
db: db,
|
||||||
|
cache: make(map[string]*RemoteRepo),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (collection *RemoteRepoCollection) loadList() {
|
func (collection *RemoteRepoCollection) search(filter func(*RemoteRepo) bool, unique bool) []*RemoteRepo {
|
||||||
if collection.list != nil {
|
result := []*RemoteRepo(nil)
|
||||||
return
|
for _, r := range collection.cache {
|
||||||
}
|
if filter(r) {
|
||||||
|
result = append(result, r)
|
||||||
blobs := collection.db.FetchByPrefix([]byte("R"))
|
|
||||||
collection.list = make([]*RemoteRepo, 0, len(blobs))
|
|
||||||
|
|
||||||
for _, blob := range blobs {
|
|
||||||
r := &RemoteRepo{}
|
|
||||||
if err := r.Decode(blob); err != nil {
|
|
||||||
log.Printf("Error decoding mirror: %s\n", err)
|
|
||||||
} else {
|
|
||||||
collection.list = append(collection.list, r)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if unique && len(result) > 0 {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.db.ProcessByPrefix([]byte("R"), func(key, blob []byte) error {
|
||||||
|
r := &RemoteRepo{}
|
||||||
|
if err := r.Decode(blob); err != nil {
|
||||||
|
log.Printf("Error decoding remote repo: %s\n", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter(r) {
|
||||||
|
if _, exists := collection.cache[r.UUID]; !exists {
|
||||||
|
collection.cache[r.UUID] = r
|
||||||
|
result = append(result, r)
|
||||||
|
if unique {
|
||||||
|
return errors.New("abort")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add appends new repo to collection and saves it
|
// Add appends new repo to collection and saves it
|
||||||
func (collection *RemoteRepoCollection) Add(repo *RemoteRepo) error {
|
func (collection *RemoteRepoCollection) Add(repo *RemoteRepo) error {
|
||||||
collection.loadList()
|
_, err := collection.ByName(repo.Name)
|
||||||
|
|
||||||
for _, r := range collection.list {
|
if err == nil {
|
||||||
if r.Name == repo.Name {
|
return fmt.Errorf("mirror with name %s already exists", repo.Name)
|
||||||
return fmt.Errorf("mirror with name %s already exists", repo.Name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := collection.Update(repo)
|
err = collection.Update(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
collection.list = append(collection.list, repo)
|
collection.cache[repo.UUID] = repo
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -734,26 +751,38 @@ func (collection *RemoteRepoCollection) LoadComplete(repo *RemoteRepo) error {
|
|||||||
|
|
||||||
// ByName looks up repository by name
|
// ByName looks up repository by name
|
||||||
func (collection *RemoteRepoCollection) ByName(name string) (*RemoteRepo, error) {
|
func (collection *RemoteRepoCollection) ByName(name string) (*RemoteRepo, error) {
|
||||||
collection.loadList()
|
result := collection.search(func(r *RemoteRepo) bool { return r.Name == name }, true)
|
||||||
|
if len(result) == 0 {
|
||||||
for _, r := range collection.list {
|
return nil, fmt.Errorf("mirror with name %s not found", name)
|
||||||
if r.Name == name {
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("mirror with name %s not found", name)
|
|
||||||
|
return result[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ByUUID looks up repository by uuid
|
// ByUUID looks up repository by uuid
|
||||||
func (collection *RemoteRepoCollection) ByUUID(uuid string) (*RemoteRepo, error) {
|
func (collection *RemoteRepoCollection) ByUUID(uuid string) (*RemoteRepo, error) {
|
||||||
collection.loadList()
|
if r, ok := collection.cache[uuid]; ok {
|
||||||
|
return r, nil
|
||||||
for _, r := range collection.list {
|
|
||||||
if r.UUID == uuid {
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("mirror with uuid %s not found", uuid)
|
|
||||||
|
key := (&RemoteRepo{UUID: uuid}).Key()
|
||||||
|
|
||||||
|
value, err := collection.db.Get(key)
|
||||||
|
if err == database.ErrNotFound {
|
||||||
|
return nil, fmt.Errorf("mirror with uuid %s not found", uuid)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &RemoteRepo{}
|
||||||
|
err = r.Decode(value)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
collection.cache[r.UUID] = r
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForEach runs method for each repository
|
// ForEach runs method for each repository
|
||||||
@@ -771,30 +800,16 @@ func (collection *RemoteRepoCollection) ForEach(handler func(*RemoteRepo) error)
|
|||||||
|
|
||||||
// Len returns number of remote repos
|
// Len returns number of remote repos
|
||||||
func (collection *RemoteRepoCollection) Len() int {
|
func (collection *RemoteRepoCollection) Len() int {
|
||||||
collection.loadList()
|
return len(collection.db.KeysByPrefix([]byte("R")))
|
||||||
|
|
||||||
return len(collection.list)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drop removes remote repo from collection
|
// Drop removes remote repo from collection
|
||||||
func (collection *RemoteRepoCollection) Drop(repo *RemoteRepo) error {
|
func (collection *RemoteRepoCollection) Drop(repo *RemoteRepo) error {
|
||||||
collection.loadList()
|
if _, err := collection.db.Get(repo.Key()); err == database.ErrNotFound {
|
||||||
|
|
||||||
repoPosition := -1
|
|
||||||
|
|
||||||
for i, r := range collection.list {
|
|
||||||
if r == repo {
|
|
||||||
repoPosition = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if repoPosition == -1 {
|
|
||||||
panic("repo not found!")
|
panic("repo not found!")
|
||||||
}
|
}
|
||||||
|
|
||||||
collection.list[len(collection.list)-1], collection.list[repoPosition], collection.list =
|
delete(collection.cache, repo.UUID)
|
||||||
nil, collection.list[len(collection.list)-1], collection.list[:len(collection.list)-1]
|
|
||||||
|
|
||||||
err := collection.db.Delete(repo.Key())
|
err := collection.db.Delete(repo.Key())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -651,6 +651,11 @@ func (s *RemoteRepoCollectionSuite) TestByUUID(c *C) {
|
|||||||
|
|
||||||
r, err := s.collection.ByUUID(repo.UUID)
|
r, err := s.collection.ByUUID(repo.UUID)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(r, Equals, repo)
|
||||||
|
|
||||||
|
collection := NewRemoteRepoCollection(s.db)
|
||||||
|
r, err = collection.ByUUID(repo.UUID)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
c.Assert(r.String(), Equals, repo.String())
|
c.Assert(r.String(), Equals, repo.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+100
-104
@@ -173,8 +173,8 @@ func (s *Snapshot) Decode(input []byte) error {
|
|||||||
// SnapshotCollection does listing, updating/adding/deleting of Snapshots
|
// SnapshotCollection does listing, updating/adding/deleting of Snapshots
|
||||||
type SnapshotCollection struct {
|
type SnapshotCollection struct {
|
||||||
*sync.RWMutex
|
*sync.RWMutex
|
||||||
db database.Storage
|
db database.Storage
|
||||||
list []*Snapshot
|
cache map[string]*Snapshot
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSnapshotCollection loads Snapshots from DB and makes up collection
|
// NewSnapshotCollection loads Snapshots from DB and makes up collection
|
||||||
@@ -182,43 +182,23 @@ func NewSnapshotCollection(db database.Storage) *SnapshotCollection {
|
|||||||
return &SnapshotCollection{
|
return &SnapshotCollection{
|
||||||
RWMutex: &sync.RWMutex{},
|
RWMutex: &sync.RWMutex{},
|
||||||
db: db,
|
db: db,
|
||||||
}
|
cache: map[string]*Snapshot{},
|
||||||
}
|
|
||||||
|
|
||||||
func (collection *SnapshotCollection) loadList() {
|
|
||||||
if collection.list != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
blobs := collection.db.FetchByPrefix([]byte("S"))
|
|
||||||
collection.list = make([]*Snapshot, 0, len(blobs))
|
|
||||||
|
|
||||||
for _, blob := range blobs {
|
|
||||||
s := &Snapshot{}
|
|
||||||
if err := s.Decode(blob); err != nil {
|
|
||||||
log.Printf("Error decoding snapshot: %s\n", err)
|
|
||||||
} else {
|
|
||||||
collection.list = append(collection.list, s)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add appends new repo to collection and saves it
|
// Add appends new repo to collection and saves it
|
||||||
func (collection *SnapshotCollection) Add(snapshot *Snapshot) error {
|
func (collection *SnapshotCollection) Add(snapshot *Snapshot) error {
|
||||||
collection.loadList()
|
_, err := collection.ByName(snapshot.Name)
|
||||||
|
if err == nil {
|
||||||
for _, s := range collection.list {
|
return fmt.Errorf("snapshot with name %s already exists", snapshot.Name)
|
||||||
if s.Name == snapshot.Name {
|
|
||||||
return fmt.Errorf("snapshot with name %s already exists", snapshot.Name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := collection.Update(snapshot)
|
err = collection.Update(snapshot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
collection.list = append(collection.list, snapshot)
|
collection.cache[snapshot.UUID] = snapshot
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,70 +225,96 @@ func (collection *SnapshotCollection) LoadComplete(snapshot *Snapshot) error {
|
|||||||
return snapshot.packageRefs.Decode(encoded)
|
return snapshot.packageRefs.Decode(encoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ByName looks up snapshot by name
|
func (collection *SnapshotCollection) search(filter func(*Snapshot) bool, unique bool) []*Snapshot {
|
||||||
func (collection *SnapshotCollection) ByName(name string) (*Snapshot, error) {
|
result := []*Snapshot(nil)
|
||||||
collection.loadList()
|
for _, s := range collection.cache {
|
||||||
|
if filter(s) {
|
||||||
for _, s := range collection.list {
|
result = append(result, s)
|
||||||
if s.Name == name {
|
|
||||||
return s, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if unique && len(result) > 0 {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.db.ProcessByPrefix([]byte("S"), func(key, blob []byte) error {
|
||||||
|
s := &Snapshot{}
|
||||||
|
if err := s.Decode(blob); err != nil {
|
||||||
|
log.Printf("Error decoding snapshot: %s\n", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter(s) {
|
||||||
|
if _, exists := collection.cache[s.UUID]; !exists {
|
||||||
|
collection.cache[s.UUID] = s
|
||||||
|
result = append(result, s)
|
||||||
|
if unique {
|
||||||
|
return errors.New("abort")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByName looks up snapshot by name
|
||||||
|
func (collection *SnapshotCollection) ByName(name string) (*Snapshot, error) {
|
||||||
|
result := collection.search(func(s *Snapshot) bool { return s.Name == name }, true)
|
||||||
|
if len(result) > 0 {
|
||||||
|
return result[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("snapshot with name %s not found", name)
|
return nil, fmt.Errorf("snapshot with name %s not found", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ByUUID looks up snapshot by UUID
|
// ByUUID looks up snapshot by UUID
|
||||||
func (collection *SnapshotCollection) ByUUID(uuid string) (*Snapshot, error) {
|
func (collection *SnapshotCollection) ByUUID(uuid string) (*Snapshot, error) {
|
||||||
collection.loadList()
|
if s, ok := collection.cache[uuid]; ok {
|
||||||
|
return s, nil
|
||||||
for _, s := range collection.list {
|
|
||||||
if s.UUID == uuid {
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("snapshot with uuid %s not found", uuid)
|
|
||||||
|
key := (&Snapshot{UUID: uuid}).Key()
|
||||||
|
|
||||||
|
value, err := collection.db.Get(key)
|
||||||
|
if err == database.ErrNotFound {
|
||||||
|
return nil, fmt.Errorf("snapshot with uuid %s not found", uuid)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &Snapshot{}
|
||||||
|
err = s.Decode(value)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
collection.cache[s.UUID] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ByRemoteRepoSource looks up snapshots that have specified RemoteRepo as a source
|
// ByRemoteRepoSource looks up snapshots that have specified RemoteRepo as a source
|
||||||
func (collection *SnapshotCollection) ByRemoteRepoSource(repo *RemoteRepo) []*Snapshot {
|
func (collection *SnapshotCollection) ByRemoteRepoSource(repo *RemoteRepo) []*Snapshot {
|
||||||
collection.loadList()
|
return collection.search(func(s *Snapshot) bool {
|
||||||
|
return s.SourceKind == SourceRemoteRepo && utils.StrSliceHasItem(s.SourceIDs, repo.UUID)
|
||||||
var result []*Snapshot
|
}, false)
|
||||||
|
|
||||||
for _, s := range collection.list {
|
|
||||||
if s.SourceKind == SourceRemoteRepo && utils.StrSliceHasItem(s.SourceIDs, repo.UUID) {
|
|
||||||
result = append(result, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ByLocalRepoSource looks up snapshots that have specified LocalRepo as a source
|
// ByLocalRepoSource looks up snapshots that have specified LocalRepo as a source
|
||||||
func (collection *SnapshotCollection) ByLocalRepoSource(repo *LocalRepo) []*Snapshot {
|
func (collection *SnapshotCollection) ByLocalRepoSource(repo *LocalRepo) []*Snapshot {
|
||||||
collection.loadList()
|
return collection.search(func(s *Snapshot) bool {
|
||||||
|
return s.SourceKind == SourceLocalRepo && utils.StrSliceHasItem(s.SourceIDs, repo.UUID)
|
||||||
var result []*Snapshot
|
}, false)
|
||||||
|
|
||||||
for _, s := range collection.list {
|
|
||||||
if s.SourceKind == SourceLocalRepo && utils.StrSliceHasItem(s.SourceIDs, repo.UUID) {
|
|
||||||
result = append(result, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BySnapshotSource looks up snapshots that have specified snapshot as a source
|
// BySnapshotSource looks up snapshots that have specified snapshot as a source
|
||||||
func (collection *SnapshotCollection) BySnapshotSource(snapshot *Snapshot) []*Snapshot {
|
func (collection *SnapshotCollection) BySnapshotSource(snapshot *Snapshot) []*Snapshot {
|
||||||
collection.loadList()
|
return collection.search(func(s *Snapshot) bool {
|
||||||
|
return s.SourceKind == "snapshot" && utils.StrSliceHasItem(s.SourceIDs, snapshot.UUID)
|
||||||
var result []*Snapshot
|
}, false)
|
||||||
|
|
||||||
for _, s := range collection.list {
|
|
||||||
if s.SourceKind == "snapshot" && utils.StrSliceHasItem(s.SourceIDs, snapshot.UUID) {
|
|
||||||
result = append(result, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForEach runs method for each snapshot
|
// ForEach runs method for each snapshot
|
||||||
@@ -326,15 +332,25 @@ func (collection *SnapshotCollection) ForEach(handler func(*Snapshot) error) err
|
|||||||
|
|
||||||
// ForEachSorted runs method for each snapshot following some sort order
|
// ForEachSorted runs method for each snapshot following some sort order
|
||||||
func (collection *SnapshotCollection) ForEachSorted(sortMethod string, handler func(*Snapshot) error) error {
|
func (collection *SnapshotCollection) ForEachSorted(sortMethod string, handler func(*Snapshot) error) error {
|
||||||
collection.loadList()
|
blobs := collection.db.FetchByPrefix([]byte("S"))
|
||||||
|
list := make([]*Snapshot, 0, len(blobs))
|
||||||
|
|
||||||
sorter, err := newSnapshotSorter(sortMethod, collection)
|
for _, blob := range blobs {
|
||||||
|
s := &Snapshot{}
|
||||||
|
if err := s.Decode(blob); err != nil {
|
||||||
|
log.Printf("Error decoding snapshot: %s\n", err)
|
||||||
|
} else {
|
||||||
|
list = append(list, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sorter, err := newSnapshotSorter(sortMethod, list)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, i := range sorter.list {
|
for _, s := range sorter.list {
|
||||||
err = handler(collection.list[i])
|
err = handler(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -346,30 +362,16 @@ func (collection *SnapshotCollection) ForEachSorted(sortMethod string, handler f
|
|||||||
// Len returns number of snapshots in collection
|
// Len returns number of snapshots in collection
|
||||||
// ForEach runs method for each snapshot
|
// ForEach runs method for each snapshot
|
||||||
func (collection *SnapshotCollection) Len() int {
|
func (collection *SnapshotCollection) Len() int {
|
||||||
collection.loadList()
|
return len(collection.db.KeysByPrefix([]byte("S")))
|
||||||
|
|
||||||
return len(collection.list)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drop removes snapshot from collection
|
// Drop removes snapshot from collection
|
||||||
func (collection *SnapshotCollection) Drop(snapshot *Snapshot) error {
|
func (collection *SnapshotCollection) Drop(snapshot *Snapshot) error {
|
||||||
collection.loadList()
|
if _, err := collection.db.Get(snapshot.Key()); err == database.ErrNotFound {
|
||||||
|
|
||||||
snapshotPosition := -1
|
|
||||||
|
|
||||||
for i, s := range collection.list {
|
|
||||||
if s == snapshot {
|
|
||||||
snapshotPosition = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if snapshotPosition == -1 {
|
|
||||||
panic("snapshot not found!")
|
panic("snapshot not found!")
|
||||||
}
|
}
|
||||||
|
|
||||||
collection.list[len(collection.list)-1], collection.list[snapshotPosition], collection.list =
|
delete(collection.cache, snapshot.UUID)
|
||||||
nil, collection.list[len(collection.list)-1], collection.list[:len(collection.list)-1]
|
|
||||||
|
|
||||||
err := collection.db.Delete(snapshot.Key())
|
err := collection.db.Delete(snapshot.Key())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -386,13 +388,12 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type snapshotSorter struct {
|
type snapshotSorter struct {
|
||||||
list []int
|
list []*Snapshot
|
||||||
collection *SnapshotCollection
|
|
||||||
sortMethod int
|
sortMethod int
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSnapshotSorter(sortMethod string, collection *SnapshotCollection) (*snapshotSorter, error) {
|
func newSnapshotSorter(sortMethod string, list []*Snapshot) (*snapshotSorter, error) {
|
||||||
s := &snapshotSorter{collection: collection}
|
s := &snapshotSorter{list: list}
|
||||||
|
|
||||||
switch sortMethod {
|
switch sortMethod {
|
||||||
case "time", "Time":
|
case "time", "Time":
|
||||||
@@ -403,11 +404,6 @@ func newSnapshotSorter(sortMethod string, collection *SnapshotCollection) (*snap
|
|||||||
return nil, fmt.Errorf("sorting method \"%s\" unknown", sortMethod)
|
return nil, fmt.Errorf("sorting method \"%s\" unknown", sortMethod)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.list = make([]int, len(collection.list))
|
|
||||||
for i := range s.list {
|
|
||||||
s.list[i] = i
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(s)
|
sort.Sort(s)
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
@@ -420,9 +416,9 @@ func (s *snapshotSorter) Swap(i, j int) {
|
|||||||
func (s *snapshotSorter) Less(i, j int) bool {
|
func (s *snapshotSorter) Less(i, j int) bool {
|
||||||
switch s.sortMethod {
|
switch s.sortMethod {
|
||||||
case SortName:
|
case SortName:
|
||||||
return s.collection.list[s.list[i]].Name < s.collection.list[s.list[j]].Name
|
return s.list[i].Name < s.list[j].Name
|
||||||
case SortTime:
|
case SortTime:
|
||||||
return s.collection.list[s.list[i]].CreatedAt.Before(s.collection.list[s.list[j]].CreatedAt)
|
return s.list[i].CreatedAt.Before(s.list[j].CreatedAt)
|
||||||
}
|
}
|
||||||
panic("unknown sort method")
|
panic("unknown sort method")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,3 +36,63 @@ func BenchmarkSnapshotCollectionForEach(b *testing.B) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkSnapshotCollectionByUUID(b *testing.B) {
|
||||||
|
const count = 1024
|
||||||
|
|
||||||
|
tmpDir := os.TempDir()
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
db, _ := database.NewOpenDB(tmpDir)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
collection := NewSnapshotCollection(db)
|
||||||
|
|
||||||
|
uuids := []string{}
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
snapshot := NewSnapshotFromRefList(fmt.Sprintf("snapshot%d", i), nil, NewPackageRefList(), fmt.Sprintf("Snapshot number %d", i))
|
||||||
|
if collection.Add(snapshot) != nil {
|
||||||
|
b.FailNow()
|
||||||
|
}
|
||||||
|
uuids = append(uuids, snapshot.UUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
collection = NewSnapshotCollection(db)
|
||||||
|
|
||||||
|
if _, err := collection.ByUUID(uuids[i%len(uuids)]); err != nil {
|
||||||
|
b.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSnapshotCollectionByName(b *testing.B) {
|
||||||
|
const count = 1024
|
||||||
|
|
||||||
|
tmpDir := os.TempDir()
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
db, _ := database.NewOpenDB(tmpDir)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
collection := NewSnapshotCollection(db)
|
||||||
|
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
snapshot := NewSnapshotFromRefList(fmt.Sprintf("snapshot%d", i), nil, NewPackageRefList(), fmt.Sprintf("Snapshot number %d", i))
|
||||||
|
if collection.Add(snapshot) != nil {
|
||||||
|
b.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
collection = NewSnapshotCollection(db)
|
||||||
|
|
||||||
|
if _, err := collection.ByName(fmt.Sprintf("snapshot%d", i%count)); err != nil {
|
||||||
|
b.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+27
-1
@@ -2,6 +2,7 @@ package deb
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/aptly-dev/aptly/database"
|
"github.com/aptly-dev/aptly/database"
|
||||||
|
|
||||||
@@ -158,6 +159,10 @@ func (s *SnapshotCollectionSuite) TestAddByNameByUUID(c *C) {
|
|||||||
snapshot, err = collection.ByUUID(s.snapshot1.UUID)
|
snapshot, err = collection.ByUUID(s.snapshot1.UUID)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(snapshot.String(), Equals, s.snapshot1.String())
|
c.Assert(snapshot.String(), Equals, s.snapshot1.String())
|
||||||
|
|
||||||
|
snapshot, err = collection.ByUUID(s.snapshot2.UUID)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(snapshot.String(), Equals, s.snapshot2.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SnapshotCollectionSuite) TestUpdateLoadComplete(c *C) {
|
func (s *SnapshotCollectionSuite) TestUpdateLoadComplete(c *C) {
|
||||||
@@ -193,6 +198,23 @@ func (s *SnapshotCollectionSuite) TestForEachAndLen(c *C) {
|
|||||||
c.Assert(err, Equals, e)
|
c.Assert(err, Equals, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SnapshotCollectionSuite) TestForEachSorted(c *C) {
|
||||||
|
s.collection.Add(s.snapshot2)
|
||||||
|
s.collection.Add(s.snapshot1)
|
||||||
|
s.collection.Add(s.snapshot4)
|
||||||
|
s.collection.Add(s.snapshot3)
|
||||||
|
|
||||||
|
names := []string{}
|
||||||
|
|
||||||
|
err := s.collection.ForEachSorted("name", func(snapshot *Snapshot) error {
|
||||||
|
names = append(names, snapshot.Name)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
c.Check(sort.StringsAreSorted(names), Equals, true)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SnapshotCollectionSuite) TestFindByRemoteRepoSource(c *C) {
|
func (s *SnapshotCollectionSuite) TestFindByRemoteRepoSource(c *C) {
|
||||||
c.Assert(s.collection.Add(s.snapshot1), IsNil)
|
c.Assert(s.collection.Add(s.snapshot1), IsNil)
|
||||||
c.Assert(s.collection.Add(s.snapshot2), IsNil)
|
c.Assert(s.collection.Add(s.snapshot2), IsNil)
|
||||||
@@ -230,7 +252,11 @@ func (s *SnapshotCollectionSuite) TestFindSnapshotSource(c *C) {
|
|||||||
c.Assert(s.collection.Add(snapshot4), IsNil)
|
c.Assert(s.collection.Add(snapshot4), IsNil)
|
||||||
c.Assert(s.collection.Add(snapshot5), IsNil)
|
c.Assert(s.collection.Add(snapshot5), IsNil)
|
||||||
|
|
||||||
c.Check(s.collection.BySnapshotSource(s.snapshot1), DeepEquals, []*Snapshot{snapshot3, snapshot4})
|
list := s.collection.BySnapshotSource(s.snapshot1)
|
||||||
|
sorter, _ := newSnapshotSorter("name", list)
|
||||||
|
sort.Sort(sorter)
|
||||||
|
|
||||||
|
c.Check(sorter.list, DeepEquals, []*Snapshot{snapshot3, snapshot4})
|
||||||
c.Check(s.collection.BySnapshotSource(s.snapshot2), DeepEquals, []*Snapshot{snapshot3})
|
c.Check(s.collection.BySnapshotSource(s.snapshot2), DeepEquals, []*Snapshot{snapshot3})
|
||||||
c.Check(s.collection.BySnapshotSource(snapshot5), DeepEquals, []*Snapshot(nil))
|
c.Check(s.collection.BySnapshotSource(snapshot5), DeepEquals, []*Snapshot(nil))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user