Check cached file sizes at startup, not per request (closes #252)
check / check (push) Waiting to run

Lookup and LookupSource are as on next again: requests compare no
sizes and a hit reads no extra row. The reconciliation pass, at startup
and every five minutes, compares each cached source and variant file
with the size its row records and removes one that is empty or of
another size, with its rows and a warning naming the file; an empty
variant file without a row is removed rather than adopted. Source,
variant and .meta files all go through writeFileSynced (temp file,
sync, rename, directory sync), the .meta file before the variant, and
a failed directory sync removes the renamed file.

#252

Model: opus-5-5
This commit is contained in:
2026-10-15 16:50:05 +00:00
parent 6f3fea68c3
commit e97673c7f2
6 changed files with 157 additions and 235 deletions
+36 -100
View File
@@ -152,24 +152,8 @@ func (s *ContentStorage) Exists(hash ContentHash) bool {
return err == nil
}
// Size returns the size in bytes of the content with the given hash.
func (s *ContentStorage) Size(hash ContentHash) (int64, error) {
info, err := os.Stat(s.hashToPath(hash))
if err != nil {
if os.IsNotExist(err) {
return 0, ErrNotFound
}
return 0, fmt.Errorf("failed to stat content: %w", err)
}
return info.Size(), nil
}
// writeIfAbsent writes data to the path derived from hash, unless a
// file of the data's size is already there. It writes a temp file,
// syncs it to disk and renames it into place, so neither a concurrent
// reader nor a crash finds a partial file under that path.
// writeIfAbsent writes data with writeFileSynced to the path derived
// from hash, unless a file of the data's size is already there.
func (s *ContentStorage) writeIfAbsent(hash ContentHash, data []byte) error {
// Build path: <basedir>/<ab>/<cd>/<hash>
path := s.hashToPath(hash)
@@ -180,15 +164,23 @@ func (s *ContentStorage) writeIfAbsent(hash ContentHash, data []byte) error {
return nil
}
// Create directory structure
dir := filepath.Dir(path)
err := os.MkdirAll(dir, StorageDirPerm)
err := os.MkdirAll(filepath.Dir(path), StorageDirPerm)
if err != nil {
return fmt.Errorf("failed to create directory: %w", err)
}
// Write to temp file first, then rename for atomicity
return writeFileSynced(path, data)
}
// writeFileSynced writes data to path through a temp file in the same
// directory: the temp file is synced to disk, renamed to path, and the
// directory synced after, so that neither a concurrent reader nor a
// crash finds a partial file under path. When the directory sync fails,
// the file is removed from path, so that a failed write leaves no file
// there.
func writeFileSynced(path string, data []byte) error {
dir := filepath.Dir(path)
tmpFile, err := os.CreateTemp(dir, ".tmp-*")
if err != nil {
return fmt.Errorf("failed to create temp file: %w", err)
@@ -196,18 +188,12 @@ func (s *ContentStorage) writeIfAbsent(hash ContentHash, data []byte) error {
tmpPath := tmpFile.Name()
// Each failure path below unlinks the temp file explicitly. This
// replaces a deferred cleanup that read a named result, which the
// canonical config does not permit; the set of paths that remove
// tmpPath, and the order relative to Close, is unchanged. This
// mirrors how MetadataStorage.Store and VariantStorage.Store below
// already express the same cleanup.
_, err = tmpFile.Write(data)
if err != nil {
_ = tmpFile.Close()
_ = os.Remove(tmpPath)
return fmt.Errorf("failed to write content: %w", err)
return fmt.Errorf("failed to write temp file: %w", err)
}
err = tmpFile.Sync()
@@ -225,7 +211,6 @@ func (s *ContentStorage) writeIfAbsent(hash ContentHash, data []byte) error {
return fmt.Errorf("failed to close temp file: %w", err)
}
// Atomic rename
err = os.Rename(filepath.Clean(tmpPath), filepath.Clean(path))
if err != nil {
_ = os.Remove(tmpPath)
@@ -233,7 +218,14 @@ func (s *ContentStorage) writeIfAbsent(hash ContentHash, data []byte) error {
return fmt.Errorf("failed to rename temp file: %w", err)
}
return syncDirectory(dir)
err = syncDirectory(dir)
if err != nil {
_ = os.Remove(path)
return err
}
return nil
}
// syncDirectory syncs the directory dir to disk, so that a file just
@@ -468,7 +460,9 @@ func NewVariantStorage(baseDir string, log *slog.Logger) (*VariantStorage, error
return &VariantStorage{baseDir: baseDir, log: log}, nil
}
// Store writes content and metadata to storage at the given key.
// Store writes content and metadata to storage at the given key, each
// with writeFileSynced. The .meta file is written first, so that a crash
// cannot leave the content in place without it.
func (s *VariantStorage) Store(
key VariantKey, r io.Reader, contentType string,
) (int64, error) {
@@ -481,61 +475,11 @@ func (s *VariantStorage) Store(
path := s.keyToPath(key)
metaPath := path + ".meta"
// Create directory structure
dir := filepath.Dir(path)
err = os.MkdirAll(dir, StorageDirPerm)
err = os.MkdirAll(filepath.Dir(path), StorageDirPerm)
if err != nil {
return 0, fmt.Errorf("failed to create directory: %w", err)
}
// Write content to temp file first, sync it to disk, then rename for
// atomicity, so neither a concurrent reader nor a crash finds a
// partial file under the key's path
tmpFile, err := os.CreateTemp(dir, ".tmp-*")
if err != nil {
return 0, fmt.Errorf("failed to create temp file: %w", err)
}
tmpPath := tmpFile.Name()
_, err = tmpFile.Write(data)
if err != nil {
_ = tmpFile.Close()
_ = os.Remove(tmpPath)
return 0, fmt.Errorf("failed to write content: %w", err)
}
err = tmpFile.Sync()
if err != nil {
_ = tmpFile.Close()
_ = os.Remove(tmpPath)
return 0, fmt.Errorf("failed to sync temp file: %w", err)
}
err = tmpFile.Close()
if err != nil {
_ = os.Remove(tmpPath)
return 0, fmt.Errorf("failed to close temp file: %w", err)
}
// Atomic rename content
err = os.Rename(filepath.Clean(tmpPath), filepath.Clean(path))
if err != nil {
_ = os.Remove(tmpPath)
return 0, fmt.Errorf("failed to rename temp file: %w", err)
}
err = syncDirectory(dir)
if err != nil {
return 0, err
}
// Write metadata
meta := VariantMeta{
ContentType: contentType,
Size: size,
@@ -547,13 +491,19 @@ func (s *VariantStorage) Store(
return 0, fmt.Errorf("failed to marshal metadata: %w", err)
}
// Metadata write failure is non-fatal; content is already stored.
err = os.WriteFile(metaPath, metaData, StorageFilePerm)
// Metadata write failure is non-fatal: the content is then served as
// application/octet-stream.
err = writeFileSynced(metaPath, metaData)
if err != nil {
s.log.Warn("failed to write variant metadata sidecar",
"path", metaPath, "error", err)
}
err = writeFileSynced(path, data)
if err != nil {
return 0, err
}
return size, nil
}
@@ -631,20 +581,6 @@ func (s *VariantStorage) Exists(key VariantKey) bool {
return err == nil
}
// Size returns the size in bytes of the content at the given key.
func (s *VariantStorage) Size(key VariantKey) (int64, error) {
info, err := os.Stat(s.keyToPath(key))
if err != nil {
if os.IsNotExist(err) {
return 0, ErrNotFound
}
return 0, fmt.Errorf("failed to stat content: %w", err)
}
return info.Size(), nil
}
// Delete removes the content at the given key together with its .meta
// sidecar file. A missing file is not an error.
func (s *VariantStorage) Delete(key VariantKey) error {