Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Materialise the activity feed in an events table

bca9ec24cbbb938f2d349d5f06e2c1f4fca06fcb · Keenan Nemetz · 9d ago · parent 73a732b1

Materialise the activity feed in an events table

The previous commit read the feed as a UNION over the source tables. It
stopped materialising events in Go, but SQLite still scanned and sorted
the union to find a page, so a page cost the history.

This replaces it with the design the feed actually wants: an `events`
table with one row per event — issue and change opened, comment,
approval, state change, release — carrying the time, actor, title and
assignees the feed renders. Rows are indexed by (repository, time, id),
so a repository page is an index range read; the instance page walks the
global time index and filters by repository, and neither sorts history.
An unfiltered page total is a stored per-repository count.

Maintenance is part of the refresh that already exists:

- an entity's events are replaced when the entity is reloaded, which is
  how a title or assignee edit reaches old events;
- state changes are rebuilt into events for the entities the commits
  name, which also catches a close-and-reopen that leaves the tree where
  it started and an endpoint diff cannot see;
- releases are refreshed from the tags after every push (post-receive)
  and after a release written in the browser, because a tag is a ref and
  no metadata commit announces it;
- the stored count moves with the rows a refresh adds or removes.

Measured with 100,000 events across 50 repositories: a repository page
takes 0.3-0.5 ms (page 1 or page 20), an instance page 0.5-0.9 ms, and an
unfiltered total 0.1 ms. A filtered total is the one query that scans,
at 12 ms for 100,000 events.

On the load harness, instance activity rises from 222 to 482 req/s and
p50 from 18 ms to 8 ms; activity from 577 to 762 req/s. The repository
feed no longer reads tags per request, and nothing reads the metadata
branch to render a page.

server/web/release_actions.go +14

@@ -28,6 +28,18 @@ type releaseFormView struct {
28 28 Error string
29 29 }
30 30
31 // refreshReleaseEvents brings the feed's release rows up to date after the
32 // browser writes a tag directly on the server. A failure is logged, never
33 // reported over the release that already exists.
34 func (w *Web) refreshReleaseEvents(m *repo.Meta) {
35 if w.Index == nil {
36 return
37 }
38 if err := w.Index.RefreshTags(m); err != nil && w.Logger != nil {
39 w.Logger("web: refresh release events for %s: %v", m.Name, err)
40 }
41 }
42
31 43 // canManageReleases is the read-side half of the release writes: the pages that
32 44 // offer the form and the handlers that accept it ask the same capability.
33 45 func (w *Web) canManageReleases(r *http.Request, m *repo.Meta) bool {
@@ -112,6 +124,7 @@ func (w *Web) CreateRelease(rw http.ResponseWriter, r *http.Request) {
112 124 fail(err.Error())
113 125 return
114 126 }
127 w.refreshReleaseEvents(m)
115 128 w.audit("web.release.create", ctx.Name, m.Name, tag)
116 129 http.Redirect(rw, r, "/r/"+m.Name+"/releases/"+pathURL(tag), http.StatusSeeOther)
117 130 }
@@ -187,6 +200,7 @@ func (w *Web) UpdateRelease(rw http.ResponseWriter, r *http.Request) {
187 200 fail(err.Error())
188 201 return
189 202 }
203 w.refreshReleaseEvents(m)
190 204 w.audit("web.release.edit", ctx.Name, m.Name, t.Name)
191 205 http.Redirect(rw, r, "/r/"+m.Name+"/releases/"+pathURL(t.Name), http.StatusSeeOther)
192 206 }