Files
DenozordecandCursor f8e0520817 feat(httpapi): harden control plane and speed up prefix refresh
Очередь job получила lease и fencing, телеметрия и прогресс deploy сохраняются, ревизия пишет manifest входов. Обновление префиксов не переписывает неизменённый снимок, большие наборы идут через COPY, а устаревшая ревизия не попадает в deploy. Бандл ноды кэшируется, prune и постановка job больше не держат горячий путь.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-02 17:23:59 +07:00

128 lines
3.9 KiB
Go

package repository
import (
"context"
"encoding/json"
"errors"
"fmt"
"evobgp/internal/store"
"github.com/jackc/pgx/v5"
)
func (p *Postgres) GetRenderGeneration(ctx context.Context, tenantID string) (int64, error) {
var generation int64
err := p.pool.QueryRow(ctx, `SELECT generation FROM tenant_render_generation WHERE tenant_id=$1::uuid`, tenantID).Scan(&generation)
if errors.Is(err, pgx.ErrNoRows) {
return 0, store.ErrNotFound
}
if err != nil {
return 0, fmt.Errorf("repository: render generation: %w", err)
}
return generation, nil
}
func (p *Postgres) ListModuleSnapshotHeaders(ctx context.Context, tenantID string) ([]store.ModuleSnapshotUse, error) {
rows, err := p.pool.Query(ctx, `
SELECT module_id::text, snapshot_version, COALESCE(input_hash, '')
FROM module_prefix_snapshot
WHERE tenant_id = $1::uuid
ORDER BY module_id`, tenantID)
if err != nil {
return nil, fmt.Errorf("repository: list snapshot headers: %w", err)
}
defer rows.Close()
var out []store.ModuleSnapshotUse
for rows.Next() {
var item store.ModuleSnapshotUse
if err := rows.Scan(&item.ModuleID, &item.SnapshotVersion, &item.InputHash); err != nil {
return nil, fmt.Errorf("repository: scan snapshot header: %w", err)
}
out = append(out, item)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("repository: iterate snapshot headers: %w", err)
}
return out, nil
}
func (p *Postgres) LatestRenderManifest(ctx context.Context, tenantID string) (*store.RenderManifestRecord, bool, error) {
var revID string
var gen *int64
var raw []byte
err := p.pool.QueryRow(ctx, `
SELECT cr.id::text, m.config_generation, m.manifest_json
FROM config_revision cr
LEFT JOIN render_revision_manifest m ON m.revision_id = cr.id
WHERE cr.tenant_id = $1::uuid
ORDER BY cr.created_at DESC, cr.id DESC
LIMIT 1`, tenantID).Scan(&revID, &gen, &raw)
if errors.Is(err, pgx.ErrNoRows) {
return nil, false, nil
}
if err != nil {
return nil, false, fmt.Errorf("repository: latest render manifest: %w", err)
}
if gen == nil || len(raw) == 0 {
return nil, false, nil
}
var doc struct {
Snapshots []store.ModuleSnapshotUse `json:"snapshots"`
}
if err := json.Unmarshal(raw, &doc); err != nil {
return nil, false, fmt.Errorf("repository: decode render manifest: %w", err)
}
return &store.RenderManifestRecord{
RevisionID: revID,
Generation: *gen,
Snapshots: doc.Snapshots,
}, true, nil
}
func insertRenderManifestTx(ctx context.Context, tx pgx.Tx, revisionID, tenantID string) error {
var gen int64
err := tx.QueryRow(ctx, `
SELECT generation FROM tenant_render_generation
WHERE tenant_id = $1::uuid
FOR UPDATE`, tenantID).Scan(&gen)
if errors.Is(err, pgx.ErrNoRows) {
gen = 1
} else if err != nil {
return fmt.Errorf("repository: lock render generation: %w", err)
}
rows, err := tx.Query(ctx, `
SELECT module_id::text, snapshot_version, COALESCE(input_hash, '')
FROM module_prefix_snapshot
WHERE tenant_id = $1::uuid
ORDER BY module_id`, tenantID)
if err != nil {
return fmt.Errorf("repository: read snapshot versions: %w", err)
}
defer rows.Close()
var snaps []store.ModuleSnapshotUse
for rows.Next() {
var item store.ModuleSnapshotUse
if err := rows.Scan(&item.ModuleID, &item.SnapshotVersion, &item.InputHash); err != nil {
return fmt.Errorf("repository: scan snapshot version: %w", err)
}
snaps = append(snaps, item)
}
if err := rows.Err(); err != nil {
return fmt.Errorf("repository: iterate snapshot versions: %w", err)
}
body, err := json.Marshal(struct {
Snapshots []store.ModuleSnapshotUse `json:"snapshots"`
}{Snapshots: snaps})
if err != nil {
return fmt.Errorf("repository: encode render manifest: %w", err)
}
if _, err := tx.Exec(ctx, `
INSERT INTO render_revision_manifest (revision_id, tenant_id, config_generation, manifest_json)
VALUES ($1::uuid, $2::uuid, $3, $4::jsonb)`,
revisionID, tenantID, gen, body); err != nil {
return fmt.Errorf("repository: insert render manifest: %w", err)
}
return nil
}