Skip to content

Commit 7fe9180

Browse files
committed
perf(postgres): bulk cache session ownership
Identity preparation previously issued serialized owner lookups for each candidate and repeated many of them during alias and relationship resolution. The latency dominated full pushes to remote PostgreSQL even when later fingerprint comparison skipped the session.\n\nPreload every candidate, legacy-prefix, canonical, and alias owner in one array query. Cache both rows and absences for the full push so out-of-window relationship lookups are queried at most once.
1 parent a200adc commit 7fe9180

2 files changed

Lines changed: 246 additions & 16 deletions

File tree

internal/postgres/push.go

Lines changed: 152 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"slices"
1515
"sort"
1616
"strings"
17+
"sync"
1718
"time"
1819

1920
"go.kenn.io/agentsview/internal/artifact"
@@ -354,6 +355,13 @@ func (s *Sync) Push(
354355
if err != nil {
355356
return result, err
356357
}
358+
ctx, err = s.preloadPGSessionOwners(ctx, s.pushIdentityOwnerCandidateIDs(
359+
sessionByID, artifactImportedSessions, localArtifactOrigin,
360+
markerID, legacyMarkerMachines,
361+
))
362+
if err != nil {
363+
return result, err
364+
}
357365
for id, sess := range sessionByID {
358366
_, artifactImported := artifactImportedSessions[sess.ID]
359367
identity, err := s.resolvePushedSessionIdentity(
@@ -1855,6 +1863,31 @@ func (s *Sync) markRelationshipConflicts(
18551863
return nil
18561864
}
18571865

1866+
func initialPushedSessionIdentity(
1867+
sess db.Session,
1868+
fallbackMachine string,
1869+
localArtifactOrigin string,
1870+
artifactImported bool,
1871+
markerID string,
1872+
) (pushedSessionIdentity, bool) {
1873+
identity := pushedSessionIdentity{
1874+
ID: sess.ID,
1875+
Machine: pushedSessionMachine(sess, fallbackMachine),
1876+
}
1877+
if id, machine, ownerMarker, ok := artifactPushIdentity(
1878+
sess, localArtifactOrigin, artifactImported,
1879+
); ok {
1880+
identity.ID = id
1881+
identity.Machine = machine
1882+
identity.OwnerMarker = ownerMarker
1883+
identity.LegacyOwnerMarkers = []string{markerID}
1884+
identity.ArtifactReplica = artifactImported
1885+
identity.AliasIDs = artifactPushAliasIDs(sess, id, machine)
1886+
return identity, true
1887+
}
1888+
return identity, false
1889+
}
1890+
18581891
// resolvePushedSessionIdentity decides the PG id a local session is stored
18591892
// under. A session this sync owns -- by matching push marker, or an adoptable
18601893
// legacy/ownerless row (see sameSessionOwner) -- is updated in place: an
@@ -1873,22 +1906,9 @@ func (s *Sync) resolvePushedSessionIdentity(
18731906
markerID string,
18741907
legacyMarkerMachines []string,
18751908
) (pushedSessionIdentity, error) {
1876-
identity := pushedSessionIdentity{
1877-
ID: sess.ID,
1878-
Machine: pushedSessionMachine(sess, s.machine),
1879-
}
1880-
artifactIdentity := false
1881-
if id, machine, ownerMarker, ok := artifactPushIdentity(
1882-
sess, localArtifactOrigin, artifactImported,
1883-
); ok {
1884-
artifactIdentity = true
1885-
identity.ID = id
1886-
identity.Machine = machine
1887-
identity.OwnerMarker = ownerMarker
1888-
identity.LegacyOwnerMarkers = []string{markerID}
1889-
identity.ArtifactReplica = artifactImported
1890-
identity.AliasIDs = artifactPushAliasIDs(sess, id, machine)
1891-
}
1909+
identity, artifactIdentity := initialPushedSessionIdentity(
1910+
sess, s.machine, localArtifactOrigin, artifactImported, markerID,
1911+
)
18921912
canonicalID := identity.ID
18931913
id, err := s.resolveOwnedPushIdentityID(
18941914
ctx, identity.ID, identity, markerID, legacyMarkerMachines,
@@ -1920,6 +1940,42 @@ func (s *Sync) resolvePushedSessionIdentity(
19201940
return identity, nil
19211941
}
19221942

1943+
func (s *Sync) pushIdentityOwnerCandidateIDs(
1944+
sessionByID map[string]db.Session,
1945+
artifactImportedSessions map[string]struct{},
1946+
localArtifactOrigin string,
1947+
markerID string,
1948+
legacyMarkerMachines []string,
1949+
) []string {
1950+
ids := make(map[string]struct{}, len(sessionByID)*3)
1951+
for _, sess := range sessionByID {
1952+
_, artifactImported := artifactImportedSessions[sess.ID]
1953+
identity, _ := initialPushedSessionIdentity(
1954+
sess, s.machine, localArtifactOrigin, artifactImported, markerID,
1955+
)
1956+
ids[identity.ID] = struct{}{}
1957+
for _, machine := range pushIDMachinePrefixes(
1958+
identity.Machine, legacyMarkerMachines,
1959+
) {
1960+
candidateID := prefixedSessionID(machine, identity.ID)
1961+
if candidateID != identity.ID {
1962+
ids[candidateID] = struct{}{}
1963+
}
1964+
}
1965+
for _, aliasID := range uniqueNonEmptyStrings(identity.AliasIDs) {
1966+
ids[aliasID] = struct{}{}
1967+
}
1968+
}
1969+
result := make([]string, 0, len(ids))
1970+
for id := range ids {
1971+
if id != "" {
1972+
result = append(result, id)
1973+
}
1974+
}
1975+
sort.Strings(result)
1976+
return result
1977+
}
1978+
19231979
// artifactLegacyDuplicateCandidate recognizes the narrow upgrade state where
19241980
// an importer already created the stable artifact id while this origin still
19251981
// owns its pre-artifact bare row. Both rows must already have the exact owners
@@ -2076,13 +2132,85 @@ func pushIDMachinePrefixes(machine string, legacyMarkerMachines []string) []stri
20762132
return prefixes
20772133
}
20782134

2135+
type pgSessionOwnerRecord struct {
2136+
machine string
2137+
ownerMarker string
2138+
exists bool
2139+
}
2140+
2141+
type pgSessionOwnerCache struct {
2142+
mu sync.Mutex
2143+
entries map[string]pgSessionOwnerRecord
2144+
}
2145+
2146+
type pgSessionOwnerCacheContextKey struct{}
2147+
2148+
func (c *pgSessionOwnerCache) get(id string) (pgSessionOwnerRecord, bool) {
2149+
c.mu.Lock()
2150+
defer c.mu.Unlock()
2151+
record, ok := c.entries[id]
2152+
return record, ok
2153+
}
2154+
2155+
func (c *pgSessionOwnerCache) put(id string, record pgSessionOwnerRecord) {
2156+
c.mu.Lock()
2157+
defer c.mu.Unlock()
2158+
c.entries[id] = record
2159+
}
2160+
2161+
// preloadPGSessionOwners resolves a candidate set in one PG round trip and
2162+
// records both hits and misses. pgSessionOwner reuses this cache and memoizes
2163+
// any relationship targets discovered later in the same push.
2164+
func (s *Sync) preloadPGSessionOwners(
2165+
ctx context.Context, ids []string,
2166+
) (context.Context, error) {
2167+
unique := uniqueNonEmptyStrings(ids)
2168+
cache := &pgSessionOwnerCache{
2169+
entries: make(map[string]pgSessionOwnerRecord, len(unique)),
2170+
}
2171+
for _, id := range unique {
2172+
cache.entries[id] = pgSessionOwnerRecord{}
2173+
}
2174+
if len(unique) == 0 {
2175+
return context.WithValue(ctx, pgSessionOwnerCacheContextKey{}, cache), nil
2176+
}
2177+
rows, err := s.pg.QueryContext(ctx, `
2178+
SELECT id, machine, owner_marker
2179+
FROM sessions
2180+
WHERE id = ANY($1)`, unique)
2181+
if err != nil {
2182+
return ctx, fmt.Errorf("preloading pg session owners: %w", err)
2183+
}
2184+
defer rows.Close()
2185+
for rows.Next() {
2186+
var id, machine string
2187+
var ownerMarker sql.NullString
2188+
if err := rows.Scan(&id, &machine, &ownerMarker); err != nil {
2189+
return ctx, fmt.Errorf("scanning pg session owner: %w", err)
2190+
}
2191+
cache.entries[id] = pgSessionOwnerRecord{
2192+
machine: machine, ownerMarker: ownerMarker.String, exists: true,
2193+
}
2194+
}
2195+
if err := rows.Err(); err != nil {
2196+
return ctx, fmt.Errorf("iterating pg session owners: %w", err)
2197+
}
2198+
return context.WithValue(ctx, pgSessionOwnerCacheContextKey{}, cache), nil
2199+
}
2200+
20792201
// pgSessionOwner returns the machine and owner_marker of a PG session row, and
20802202
// whether it exists. owner_marker is empty for legacy rows pushed before the
20812203
// marker model.
20822204
func (s *Sync) pgSessionOwner(
20832205
ctx context.Context,
20842206
id string,
20852207
) (string, string, bool, error) {
2208+
cache, _ := ctx.Value(pgSessionOwnerCacheContextKey{}).(*pgSessionOwnerCache)
2209+
if cache != nil {
2210+
if record, ok := cache.get(id); ok {
2211+
return record.machine, record.ownerMarker, record.exists, nil
2212+
}
2213+
}
20862214
var machine string
20872215
var ownerMarker sql.NullString
20882216
err := s.pg.QueryRowContext(ctx,
@@ -2091,13 +2219,21 @@ func (s *Sync) pgSessionOwner(
20912219
).Scan(&machine, &ownerMarker)
20922220
if err != nil {
20932221
if errors.Is(err, sql.ErrNoRows) {
2222+
if cache != nil {
2223+
cache.put(id, pgSessionOwnerRecord{})
2224+
}
20942225
return "", "", false, nil
20952226
}
20962227
return "", "", false, fmt.Errorf(
20972228
"reading pg session owner for %s: %w",
20982229
id, err,
20992230
)
21002231
}
2232+
if cache != nil {
2233+
cache.put(id, pgSessionOwnerRecord{
2234+
machine: machine, ownerMarker: ownerMarker.String, exists: true,
2235+
})
2236+
}
21012237
return machine, ownerMarker.String, true, nil
21022238
}
21032239

internal/postgres/push_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,6 +1194,13 @@ type pushSessionProbeState struct {
11941194
aliases map[string]string
11951195
excludedIDs map[string]bool
11961196
existingExcluded map[string]bool
1197+
ownerQueries int
1198+
owners map[string]pushSessionProbeOwner
1199+
}
1200+
1201+
type pushSessionProbeOwner struct {
1202+
machine string
1203+
marker string
11971204
}
11981205

11991206
var (
@@ -1310,7 +1317,29 @@ func (c *pushSessionProbeConn) QueryContext(
13101317
defer c.state.mu.Unlock()
13111318

13121319
switch {
1320+
case strings.Contains(normalized, "select id, machine, owner_marker"):
1321+
c.state.ownerQueries++
1322+
values := [][]driver.Value{}
1323+
for _, id := range namedValueStrings(args) {
1324+
if owner, ok := c.state.owners[id]; ok {
1325+
values = append(values, []driver.Value{id, owner.machine, owner.marker})
1326+
}
1327+
}
1328+
return &pushSessionProbeRows{
1329+
columns: []string{"id", "machine", "owner_marker"},
1330+
values: values,
1331+
}, nil
13131332
case strings.Contains(normalized, "select machine, owner_marker"):
1333+
c.state.ownerQueries++
1334+
if len(args) > 0 {
1335+
id, _ := args[0].Value.(string)
1336+
if owner, ok := c.state.owners[id]; ok {
1337+
return &pushSessionProbeRows{
1338+
columns: []string{"machine", "owner_marker"},
1339+
values: [][]driver.Value{{owner.machine, owner.marker}},
1340+
}, nil
1341+
}
1342+
}
13141343
return &pushSessionProbeRows{
13151344
columns: []string{"machine", "owner_marker"},
13161345
}, nil
@@ -1347,6 +1376,71 @@ func (c *pushSessionProbeConn) QueryContext(
13471376
}
13481377
}
13491378

1379+
func TestPreloadPGSessionOwnersUsesOneQueryAndCachesMisses(t *testing.T) {
1380+
state := &pushSessionProbeState{owners: map[string]pushSessionProbeOwner{
1381+
"owned-a": {machine: "desk", marker: "marker-a"},
1382+
"owned-b": {machine: "laptop", marker: "marker-b"},
1383+
}}
1384+
sync := &Sync{pg: newPushSessionProbeDB(t, state)}
1385+
1386+
ctx, err := sync.preloadPGSessionOwners(
1387+
context.Background(), []string{"owned-a", "owned-b", "missing"},
1388+
)
1389+
require.NoError(t, err)
1390+
for _, tc := range []struct {
1391+
id string
1392+
machine string
1393+
marker string
1394+
exists bool
1395+
}{
1396+
{id: "owned-a", machine: "desk", marker: "marker-a", exists: true},
1397+
{id: "owned-b", machine: "laptop", marker: "marker-b", exists: true},
1398+
{id: "missing"},
1399+
} {
1400+
machine, marker, exists, lookupErr := sync.pgSessionOwner(ctx, tc.id)
1401+
require.NoError(t, lookupErr)
1402+
assert.Equal(t, tc.machine, machine)
1403+
assert.Equal(t, tc.marker, marker)
1404+
assert.Equal(t, tc.exists, exists)
1405+
}
1406+
assert.Equal(t, 1, state.ownerQueries,
1407+
"preloaded hits and misses must use one owner query")
1408+
1409+
_, _, exists, err := sync.pgSessionOwner(ctx, "late-miss")
1410+
require.NoError(t, err)
1411+
assert.False(t, exists)
1412+
_, _, exists, err = sync.pgSessionOwner(ctx, "late-miss")
1413+
require.NoError(t, err)
1414+
assert.False(t, exists)
1415+
assert.Equal(t, 2, state.ownerQueries,
1416+
"an owner first discovered after preload must be memoized")
1417+
}
1418+
1419+
func TestPushIdentityOwnerCandidateIDsCoverLegacyAndArtifactAliases(t *testing.T) {
1420+
sync := &Sync{machine: "desk"}
1421+
sessions := map[string]db.Session{
1422+
"plain": {
1423+
ID: "plain", Machine: "local",
1424+
},
1425+
"remote-a1b2c3~imported": {
1426+
ID: "remote-a1b2c3~imported", Machine: "remote-a1b2c3",
1427+
},
1428+
}
1429+
imported := map[string]struct{}{"remote-a1b2c3~imported": {}}
1430+
1431+
got := sync.pushIdentityOwnerCandidateIDs(
1432+
sessions, imported, "desk-origin", "marker", []string{"old-desk"},
1433+
)
1434+
assert.ElementsMatch(t, []string{
1435+
"desk-origin~plain",
1436+
"old-desk~desk-origin~plain",
1437+
"plain",
1438+
"remote-a1b2c3~imported",
1439+
"old-desk~remote-a1b2c3~imported",
1440+
"imported",
1441+
}, got)
1442+
}
1443+
13501444
func (pushSessionProbeTx) Commit() error { return nil }
13511445

13521446
func (pushSessionProbeTx) Rollback() error { return nil }

0 commit comments

Comments
 (0)