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/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

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

Index state changes for the activity feed

4d938fe894d56ff29b87fa0f2ebec50a744f2993 · Keenan Nemetz · 10d ago · parent e2a58f3a

Index state changes for the activity feed

The activity feed found closed, reopened and merged events by reading the
last 200 metadata commit subjects on every render: one git process per
repository, so the instance home with N repositories ran N of them. It was
the last derived read still going to git.

- A `state_changes` table is fed by the same refresh: a delta reads the
  commits in its range, a rebuild reads the whole metadata branch. Rows are
  keyed by the commit, so a retried or overlapping refresh cannot duplicate
  an event, and an ordinal keeps two changes in one second in branch order
  (commit times are seconds; a close and a reopen often share one).
- `Store.StateChanges` returns the events newest first; the feed reads it
  and keeps the metadata log as the fallback for a server without an index
  or a failed read.
- `git.Repo.Log` and `CommitsBetween` treat a limit of zero as no limit,
  which the rebuild needs, and `metadata.ParseStateSubject` is exported so
  the index derives the events without a second copy of the format.

The activity page now starts no metadata log; the instance home and the
instance activity page lose one process per visible repository. On the
load harness with three repositories: activity 496 req/s (p50 7 ms),
instance activity 230 req/s (p50 17 ms), home 202 req/s (p50 20 ms).

Schema version 7 rebuilds each repository on its first read after upgrade.

forge/index/index.go +22−3

@@ -179,7 +179,25 @@ CREATE TABLE IF NOT EXISTS artifacts (
179 179 oid TEXT NOT NULL,
180 180 size INTEGER NOT NULL DEFAULT 0,
181 181 PRIMARY KEY (repo_id, tag, name)
182 );`); err != nil {
182 );
183
184 -- state_changes is the closed/reopened/merged history the activity feed shows.
185 -- It is derived from the metadata commit subjects, and keyed by the commit so a
186 -- refresh that sees the same range twice cannot duplicate an event.
187 CREATE TABLE IF NOT EXISTS state_changes (
188 repo_id TEXT NOT NULL,
189 sha TEXT NOT NULL,
190 kind TEXT NOT NULL,
191 id TEXT NOT NULL,
192 state TEXT NOT NULL,
193 actor TEXT NOT NULL,
194 at INTEGER NOT NULL,
195 -- ordinal is the order the commits entered the branch. Commit times are
196 -- seconds, so two changes in one second need this to keep their order.
197 ordinal INTEGER NOT NULL DEFAULT 0,
198 PRIMARY KEY (repo_id, sha)
199 );
200 CREATE INDEX IF NOT EXISTS state_changes_recent ON state_changes(repo_id, ordinal DESC);`); err != nil {
183 201 db.Close()
184 202 return nil, err
185 203 }
@@ -215,7 +233,7 @@ CREATE VIRTUAL TABLE IF NOT EXISTS search USING fts5(
215 233 // and leaves every repo with an empty issues_sha, which makes the next read
216 234 // rebuild that repo from git. A stale row is worse than a rebuild, and a rebuild is the
217 235 // operation the index already has to survive.
218 const schemaVersion = 6
236 const schemaVersion = 7
219 237
220 238 func (s *Store) migrate() error {
221 239 var v int
@@ -235,6 +253,7 @@ DELETE FROM request_closes;
235 253 DELETE FROM request_approvals;
236 254 DELETE FROM comments;
237 255 DELETE FROM artifacts;
256 DELETE FROM state_changes;
238 257 DELETE FROM search;
239 258 UPDATE repos SET issues_sha = '';`); err != nil {
240 259 return err
@@ -280,7 +299,7 @@ func (s *Store) Rebuild(st *repo.Store) (int, error) {
280 299 // longer exists must not survive it.
281 300 for _, table := range []string{
282 301 "issues", "issue_labels", "issue_assignees", "requests", "request_closes",
283 "request_approvals", "comments", "artifacts", "search", "entity_numbers",
302 "request_approvals", "comments", "artifacts", "state_changes", "search", "entity_numbers",
284 303 } {
285 304 if _, err := tx.Exec(`DELETE FROM ` + table); err != nil {
286 305 return 0, err