Skip to content

Commit 5136031

Browse files
committed
Improve publish cleanup perf when sources share most of their packages
The cleanup phase needs to list out all the files in each component in order to determine what's still in use. When there's a large number of sources (e.g. from having many snapshots), the time spent just loading the package information becomes substantial. However, in many cases, most of the packages being loaded are actually shared across the sources; if you're taking frequent snapshots, for instance, most of the packages in each snapshot will be the same as other snapshots. In these cases, re-reading the packages repeatedly is just a waste of time. To improve this, we maintain a list of refs that we know were processed for each component. When listing the refs from a source, only the ones that have not yet been processed will be examined. Some tests were also added specifically to check listing the files in a component. With this change, listing the files in components on a copy of our production database went from >10 minutes to ~10 seconds, and the newly added benchmark went from ~300ms to ~43ms. Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
1 parent 2ca9fbc commit 5136031

4 files changed

Lines changed: 211 additions & 19 deletions

File tree

deb/publish.go

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1141,18 +1141,10 @@ func (collection *PublishedRepoCollection) Len() int {
11411141
return len(collection.list)
11421142
}
11431143

1144-
// CleanupPrefixComponentFiles removes all unreferenced files in published storage under prefix/component pair
1145-
func (collection *PublishedRepoCollection) CleanupPrefixComponentFiles(prefix string, components []string,
1146-
publishedStorage aptly.PublishedStorage, collectionFactory *CollectionFactory, progress aptly.Progress) error {
1147-
1148-
collection.loadList()
1149-
1150-
var err error
1144+
func (collection *PublishedRepoCollection) listReferencedFilesByComponent(prefix string, components []string,
1145+
collectionFactory *CollectionFactory, progress aptly.Progress) (map[string][]string, error) {
11511146
referencedFiles := map[string][]string{}
1152-
1153-
if progress != nil {
1154-
progress.Printf("Cleaning up prefix %#v components %s...\n", prefix, strings.Join(components, ", "))
1155-
}
1147+
processedComponentRefs := map[string]*PackageRefList{}
11561148

11571149
for _, r := range collection.list {
11581150
if r.Prefix == prefix {
@@ -1171,16 +1163,28 @@ func (collection *PublishedRepoCollection) CleanupPrefixComponentFiles(prefix st
11711163
continue
11721164
}
11731165

1174-
err = collection.LoadComplete(r, collectionFactory)
1175-
if err != nil {
1176-
return err
1166+
if err := collection.LoadComplete(r, collectionFactory); err != nil {
1167+
return nil, err
11771168
}
11781169

11791170
for _, component := range components {
11801171
if utils.StrSliceHasItem(repoComponents, component) {
1181-
packageList, err := NewPackageListFromRefList(r.RefList(component), collectionFactory.PackageCollection(), progress)
1172+
unseenRefs := r.RefList(component)
1173+
processedRefs := processedComponentRefs[component]
1174+
if processedRefs != nil {
1175+
unseenRefs = unseenRefs.Subtract(processedRefs)
1176+
} else {
1177+
processedRefs = NewPackageRefList()
1178+
}
1179+
1180+
if unseenRefs.Len() == 0 {
1181+
continue
1182+
}
1183+
processedComponentRefs[component] = processedRefs.Merge(unseenRefs, false, true)
1184+
1185+
packageList, err := NewPackageListFromRefList(unseenRefs, collectionFactory.PackageCollection(), progress)
11821186
if err != nil {
1183-
return err
1187+
return nil, err
11841188
}
11851189

11861190
packageList.ForEach(func(p *Package) error {
@@ -1200,6 +1204,24 @@ func (collection *PublishedRepoCollection) CleanupPrefixComponentFiles(prefix st
12001204
}
12011205
}
12021206

1207+
return referencedFiles, nil
1208+
}
1209+
1210+
// CleanupPrefixComponentFiles removes all unreferenced files in published storage under prefix/component pair
1211+
func (collection *PublishedRepoCollection) CleanupPrefixComponentFiles(prefix string, components []string,
1212+
publishedStorage aptly.PublishedStorage, collectionFactory *CollectionFactory, progress aptly.Progress) error {
1213+
1214+
collection.loadList()
1215+
1216+
if progress != nil {
1217+
progress.Printf("Cleaning up prefix %#v components %s...\n", prefix, strings.Join(components, ", "))
1218+
}
1219+
1220+
referencedFiles, err := collection.listReferencedFilesByComponent(prefix, components, collectionFactory, progress)
1221+
if err != nil {
1222+
return err
1223+
}
1224+
12031225
for _, component := range components {
12041226
sort.Strings(referencedFiles[component])
12051227

deb/publish_bench_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package deb
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"sort"
7+
"testing"
8+
9+
"github.com/aptly-dev/aptly/database/goleveldb"
10+
)
11+
12+
func BenchmarkListReferencedFiles(b *testing.B) {
13+
const defaultComponent = "main"
14+
const repoCount = 16
15+
const repoPackagesCount = 1024
16+
const uniqPackagesCount = 64
17+
18+
tmpDir, err := os.MkdirTemp("", "aptly-bench")
19+
if err != nil {
20+
b.Fatal(err)
21+
}
22+
defer os.RemoveAll(tmpDir)
23+
24+
db, err := goleveldb.NewOpenDB(tmpDir)
25+
if err != nil {
26+
b.Fatal(err)
27+
}
28+
defer db.Close()
29+
30+
factory := NewCollectionFactory(db)
31+
packageCollection := factory.PackageCollection()
32+
repoCollection := factory.LocalRepoCollection()
33+
publishCollection := factory.PublishedRepoCollection()
34+
35+
sharedRefs := NewPackageRefList()
36+
{
37+
transaction, err := db.OpenTransaction()
38+
if err != nil {
39+
b.Fatal(err)
40+
}
41+
42+
for pkgIndex := 0; pkgIndex < repoPackagesCount-uniqPackagesCount; pkgIndex++ {
43+
p := &Package{
44+
Name: fmt.Sprintf("pkg-shared_%d", pkgIndex),
45+
Version: "1",
46+
Architecture: "amd64",
47+
}
48+
p.UpdateFiles(PackageFiles{PackageFile{
49+
Filename: fmt.Sprintf("pkg-shared_%d.deb", pkgIndex),
50+
}})
51+
52+
packageCollection.UpdateInTransaction(p, transaction)
53+
sharedRefs.Refs = append(sharedRefs.Refs, p.Key(""))
54+
}
55+
56+
sort.Sort(sharedRefs)
57+
58+
if err := transaction.Commit(); err != nil {
59+
b.Fatal(err)
60+
}
61+
}
62+
63+
for repoIndex := 0; repoIndex < repoCount; repoIndex++ {
64+
refs := NewPackageRefList()
65+
66+
transaction, err := db.OpenTransaction()
67+
if err != nil {
68+
b.Fatal(err)
69+
}
70+
71+
for pkgIndex := 0; pkgIndex < uniqPackagesCount; pkgIndex++ {
72+
p := &Package{
73+
Name: fmt.Sprintf("pkg%d_%d", repoIndex, pkgIndex),
74+
Version: "1",
75+
Architecture: "amd64",
76+
}
77+
p.UpdateFiles(PackageFiles{PackageFile{
78+
Filename: fmt.Sprintf("pkg%d_%d.deb", repoIndex, pkgIndex),
79+
}})
80+
81+
packageCollection.UpdateInTransaction(p, transaction)
82+
refs.Refs = append(refs.Refs, p.Key(""))
83+
}
84+
85+
if err := transaction.Commit(); err != nil {
86+
b.Fatal(err)
87+
}
88+
89+
sort.Sort(refs)
90+
91+
repo := NewLocalRepo(fmt.Sprintf("repo%d", repoIndex), "comment")
92+
repo.DefaultDistribution = fmt.Sprintf("dist%d", repoIndex)
93+
repo.DefaultComponent = defaultComponent
94+
repo.UpdateRefList(refs.Merge(sharedRefs, false, true))
95+
repoCollection.Add(repo)
96+
97+
publish, err := NewPublishedRepo("", "test", "", nil, []string{defaultComponent}, []interface{}{repo}, factory)
98+
if err != nil {
99+
b.Fatal(err)
100+
}
101+
publishCollection.Add(publish)
102+
}
103+
104+
db.CompactDB()
105+
106+
b.ResetTimer()
107+
for i := 0; i < b.N; i++ {
108+
_, err := publishCollection.listReferencedFilesByComponent("test", []string{defaultComponent}, factory, nil)
109+
if err != nil {
110+
b.Fatal(err)
111+
}
112+
}
113+
}

deb/publish_test.go

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"io/ioutil"
88
"os"
99
"path/filepath"
10+
"sort"
1011

1112
"github.com/aptly-dev/aptly/aptly"
1213
"github.com/aptly-dev/aptly/database"
@@ -450,13 +451,22 @@ type PublishedRepoCollectionSuite struct {
450451
var _ = Suite(&PublishedRepoCollectionSuite{})
451452

452453
func (s *PublishedRepoCollectionSuite) SetUpTest(c *C) {
454+
s.SetUpPackages()
455+
453456
s.db, _ = goleveldb.NewOpenDB(c.MkDir())
454457
s.factory = NewCollectionFactory(s.db)
455458

456459
s.snapshotCollection = s.factory.SnapshotCollection()
457460

458-
s.snap1 = NewSnapshotFromPackageList("snap1", []*Snapshot{}, NewPackageList(), "desc1")
459-
s.snap2 = NewSnapshotFromPackageList("snap2", []*Snapshot{}, NewPackageList(), "desc2")
461+
snap1Refs := NewPackageRefList()
462+
snap1Refs.Refs = [][]byte{s.p1.Key(""), s.p2.Key("")}
463+
sort.Sort(snap1Refs)
464+
s.snap1 = NewSnapshotFromRefList("snap1", []*Snapshot{}, snap1Refs, "desc1")
465+
466+
snap2Refs := NewPackageRefList()
467+
snap2Refs.Refs = [][]byte{s.p3.Key("")}
468+
sort.Sort(snap2Refs)
469+
s.snap2 = NewSnapshotFromRefList("snap2", []*Snapshot{}, snap2Refs, "desc2")
460470

461471
s.snapshotCollection.Add(s.snap1)
462472
s.snapshotCollection.Add(s.snap2)
@@ -534,7 +544,7 @@ func (s *PublishedRepoCollectionSuite) TestUpdateLoadComplete(c *C) {
534544
c.Assert(r.sourceItems["main"].snapshot, IsNil)
535545
c.Assert(s.collection.LoadComplete(r, s.factory), IsNil)
536546
c.Assert(r.Sources["main"], Equals, s.repo1.sourceItems["main"].snapshot.UUID)
537-
c.Assert(r.RefList("main").Len(), Equals, 0)
547+
c.Assert(r.RefList("main").Len(), Equals, 2)
538548

539549
r, err = collection.ByStoragePrefixDistribution("", "ppa", "precise")
540550
c.Assert(err, IsNil)
@@ -625,6 +635,51 @@ func (s *PublishedRepoCollectionSuite) TestByLocalRepo(c *C) {
625635
c.Check(s.collection.ByLocalRepo(s.localRepo), DeepEquals, []*PublishedRepo{s.repo4, s.repo5})
626636
}
627637

638+
func (s *PublishedRepoCollectionSuite) TestListReferencedFiles(c *C) {
639+
c.Check(s.factory.PackageCollection().Update(s.p1), IsNil)
640+
c.Check(s.factory.PackageCollection().Update(s.p2), IsNil)
641+
c.Check(s.factory.PackageCollection().Update(s.p3), IsNil)
642+
643+
c.Check(s.collection.Add(s.repo1), IsNil)
644+
c.Check(s.collection.Add(s.repo2), IsNil)
645+
c.Check(s.collection.Add(s.repo4), IsNil)
646+
c.Check(s.collection.Add(s.repo5), IsNil)
647+
648+
files, err := s.collection.listReferencedFilesByComponent(".", []string{"main", "contrib"}, s.factory, nil)
649+
c.Assert(err, IsNil)
650+
for _, v := range files {
651+
sort.Strings(v)
652+
}
653+
c.Check(files, DeepEquals, map[string][]string{
654+
"contrib": {
655+
"a/alien-arena/alien-arena-common_7.40-2_i386.deb",
656+
"a/alien-arena/mars-invaders_7.40-2_i386.deb",
657+
},
658+
"main": {"a/alien-arena/lonely-strangers_7.40-2_i386.deb"},
659+
})
660+
661+
snap3 := NewSnapshotFromRefList("snap3", []*Snapshot{}, s.snap2.RefList(), "desc3")
662+
s.snapshotCollection.Add(snap3)
663+
664+
// Ensure that adding a second publish point with matching files doesn't give duplicate results.
665+
repo3, err := NewPublishedRepo("", "", "anaconda-2", []string{}, []string{"main"}, []interface{}{snap3}, s.factory)
666+
c.Check(err, IsNil)
667+
c.Check(s.collection.Add(repo3), IsNil)
668+
669+
files, err = s.collection.listReferencedFilesByComponent(".", []string{"main", "contrib"}, s.factory, nil)
670+
c.Assert(err, IsNil)
671+
for _, v := range files {
672+
sort.Strings(v)
673+
}
674+
c.Check(files, DeepEquals, map[string][]string{
675+
"contrib": {
676+
"a/alien-arena/alien-arena-common_7.40-2_i386.deb",
677+
"a/alien-arena/mars-invaders_7.40-2_i386.deb",
678+
},
679+
"main": {"a/alien-arena/lonely-strangers_7.40-2_i386.deb"},
680+
})
681+
}
682+
628683
type PublishedRepoRemoveSuite struct {
629684
PackageListMixinSuite
630685
db database.Storage

deb/remote_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,11 @@ func (s *PackageListMixinSuite) SetUpPackages() {
6161
s.p1 = NewPackageFromControlFile(packageStanza.Copy())
6262
stanza := packageStanza.Copy()
6363
stanza["Package"] = "mars-invaders"
64+
stanza["Filename"] = "pool/contrib/m/mars-invaders/mars-invaders_7.40-2_i386.deb"
6465
s.p2 = NewPackageFromControlFile(stanza)
6566
stanza = packageStanza.Copy()
6667
stanza["Package"] = "lonely-strangers"
68+
stanza["Filename"] = "pool/contrib/l/lonely-strangers/lonely-strangers_7.40-2_i386.deb"
6769
s.p3 = NewPackageFromControlFile(stanza)
6870

6971
s.list.Add(s.p1)

0 commit comments

Comments
 (0)