Skip to content
quarry Artifact [cbf721b36a]
Login

Artifact [cbf721b36a]

Artifact cbf721b36a900c3401e01350798cbe50da742118ec154cd324d08c9b19844135:


// finfowarm.go — build the file-history index off the request path.
//
// zit answers `log CHECKIN PATH` from an index it derives on first use
// (zit#188, shipped 0.3.14, made durable across commits in 0.3.15).
// Measured on this fleet's SQLite mirror: 104s to build, then ~0.6s for
// ANY path, because the index is per repository rather than per path.
//
// 104 seconds is not a page. So the build is done here, beside the
// listing warm and under the same guard, rather than by whichever
// reader happens to open a file's history first.
package main

import (
	"context"
	"log"
	"sync"
	"time"

	"craftdesign.group/quarry/internal/obs"
	"craftdesign.group/quarry/internal/zitcli"
)

// warmFinfoPath is the path the warming query asks about.
//
// Deliberately one that cannot exist. A PATH query builds the whole
// index before it discovers the path is not in the tree — verified on
// this fleet — so the index is warmed without reading any real file's
// history, and without quarry needing to know a tracked path to name.
// The leading dot and the name say what it is if it ever shows up in a
// log or a trace.
const warmFinfoPath = ".quarry-warm-file-index"

// warmFinfoTimeout bounds the build.
//
// The build is not resumable: zit writes the index only once the whole
// walk finishes, so a warm that is cut off leaves nothing behind and the
// next attempt starts from zero. That makes an UNBOUNDED warm the worst
// of both worlds — it can hold a core for as long as the process lives
// and still have nothing to show for it. This ran on a bare
// context.Background() until now, which is how the pkgsrc mirror came to
// pay for a full build on every one of roughly fifteen deploys in a day
// and still have no index at the end of it.
//
// Measured on that mirror (218,444 check-ins, 1.2M log frames): 34.3 min
// under zit 0.3.16, 6.8 min under 0.3.17 on zit's box and about 11 here.
// SQLite, for contrast, is ~104s. Fifteen minutes lets every store that
// CAN converge do so with room to spare, and stops the one that cannot
// from spending the box on it indefinitely.
// A var, not a const, so the test can prove the deadline fires.
var warmFinfoTimeout = 15 * time.Minute

// finfoGaveUp remembers the stores whose index did not build inside the
// deadline.
//
// Per process, deliberately. A restart is exactly the moment the
// circumstances that made it impossible may have changed — a newer zit,
// a packed store — so one attempt per start is worth making. One
// attempt per TIP MOVE is not: that is a core for a quarter of an hour,
// repeated, for a result the last attempt already showed is out of
// reach.
var finfoGaveUp sync.Map // repo name -> struct{}

// warmFileHistory builds or extends the index, and says how long it took
// when that was long enough to matter.
//
// A failure that is not the deadline is dropped on purpose: the query
// names a path that does not exist, so "no such path" IS the success
// case, and a store that cannot answer at all has already announced
// itself through ingest. The only cost is that the next reader of a
// file's history pays what this would have.
func warmFileHistory(cli *zitcli.Client, tip, repo string) {
	if _, done := finfoGaveUp.Load(repo); done {
		return
	}
	ctx, cancel := context.WithTimeout(
		obs.WithReason(context.Background(), obs.ReasonWarm), warmFinfoTimeout)
	defer cancel()
	start := time.Now()
	_, _ = cli.FileHistory(ctx, tip, warmFinfoPath, 1)
	// The deadline is read from the context rather than from the error,
	// because FileHistory turns a killed subprocess into an ordinary
	// failure and "no such path" into no error at all. The context is
	// the only thing here that knows WHY the call ended.
	if ctx.Err() != nil {
		finfoGaveUp.Store(repo, struct{}{})
		log.Printf("file-history index for %s did not build within %s; "+
			"a file's history there stays slow until one path can be answered "+
			"without the whole-repo index (zit#198)", repo, warmFinfoTimeout)
		return
	}
	// A warm index answers in well under a second; anything near the
	// build cost means this repository had none, which is worth one
	// line saying where the time went.
	if d := time.Since(start); d > 5*time.Second {
		log.Printf("file-history index for %s built in %s", repo, d.Round(time.Second))
	}
}