perf(relay): cache parsed metadata and pre-load HTML template

- Add ParsedMetadata struct to LeaseEntry for O(1) access - Parse metadata once in updateLeaseInternal instead of per-request - Pre-load portal.html template with sync.Once for efficient SSR - Remove sdk import from view.go (no longer needed) Eliminates O(N) JSON parsing and file I/O per HTTP request.

cognitive-glitch committed Dec 9, 2025 at 13:11 UTC dfa34b7dc95d0a2d29ba33001a819125ab26e2f0
3 files changed +69 -26
cmd/relay-server/serve.go
+22 -7
@@ -13,6 +13,18 @@ import (
13 "gosuda.org/portal/utils"
14 )
15
16 +// Cached portal.html template for efficient SSR
17 +var (
18 + cachedPortalHTML []byte
19 + cachedPortalHTMLOnce sync.Once
20 +)
21 +
22 +func initPortalHTMLCache() error {
23 + var err error
24 + cachedPortalHTML, err = distFS.ReadFile("dist/app/portal.html")
25 + return err
26 +}
27 +
28 func serveAsset(mux *http.ServeMux, route, assetPath, contentType string) {
29 mux.HandleFunc(route, func(w http.ResponseWriter, r *http.Request) {
30 // Read from dist/app subdirectory of the embedded FS
@@ -34,17 +46,20 @@ func serveAsset(mux *http.ServeMux, route, assetPath, contentType string) {
46 func servePortalHTMLWithSSR(w http.ResponseWriter, r *http.Request, serv *portal.RelayServer) {
47 utils.SetCORSHeaders(w)
48
37 - // Read portal.html from embedded FS
38 - fullPath := path.Join("dist", "app", "portal.html")
39 - htmlContent, err := distFS.ReadFile(fullPath)
40 - if err != nil {
41 - log.Error().Err(err).Msg("Failed to read portal.html")
49 + // Initialize cache on first use
50 + cachedPortalHTMLOnce.Do(func() {
51 + if err := initPortalHTMLCache(); err != nil {
52 + log.Error().Err(err).Msg("Failed to cache portal.html")
53 + }
54 + })
55 +
56 + if cachedPortalHTML == nil {
57 http.NotFound(w, r)
58 return
59 }
60
46 - // Inject SSR data
47 - injectedHTML := injectServerData(string(htmlContent), serv)
61 + // Inject SSR data into cached template
62 + injectedHTML := injectServerData(string(cachedPortalHTML), serv)
63
64 // Set headers
65 w.Header().Set("Content-Type", "text/html; charset=utf-8")
cmd/relay-server/view.go
+5 -11
@@ -17,7 +17,6 @@ import (
17 "github.com/rs/zerolog/log"
18
19 "gosuda.org/portal/portal"
20 - "gosuda.org/portal/sdk"
20 "gosuda.org/portal/utils"
21 )
22
@@ -344,10 +343,6 @@ func convertLeaseEntriesToAdminRows(serv *portal.RelayServer) []leaseRow {
343 lease := leaseEntry.Lease
344 identityID := string(lease.Identity.Id)
345
347 - // Metadata parsing
348 - var meta sdk.Metadata
349 - _ = json.Unmarshal([]byte(lease.Metadata), &meta)
350 -
346 // Calculate TTL
347 ttl := time.Until(leaseEntry.Expires)
348 ttlStr := ""
@@ -430,7 +425,7 @@ func convertLeaseEntriesToAdminRows(serv *portal.RelayServer) []leaseRow {
425 TTL: ttlStr,
426 Link: link,
427 StaleRed: !connected && since >= 15*time.Second,
433 - Hide: meta.Hide,
428 + Hide: leaseEntry.ParsedMetadata != nil && leaseEntry.ParsedMetadata.Hide,
429 Metadata: lease.Metadata,
430 BPS: bps,
431 }
@@ -466,10 +461,8 @@ func convertLeaseEntriesToRows(serv *portal.RelayServer) []leaseRow {
461 continue
462 }
463
469 - // Metadata parsing
470 - var meta sdk.Metadata
471 - _ = json.Unmarshal([]byte(lease.Metadata), &meta)
472 - if meta.Hide {
464 + // Use cached parsed metadata
465 + if leaseEntry.ParsedMetadata != nil && leaseEntry.ParsedMetadata.Hide {
466 continue
467 }
468
@@ -557,10 +550,11 @@ func convertLeaseEntriesToRows(serv *portal.RelayServer) []leaseRow {
550 TTL: ttlStr,
551 Link: link,
552 StaleRed: !connected && since >= 15*time.Second,
560 - Hide: meta.Hide,
553 + Hide: leaseEntry.ParsedMetadata != nil && leaseEntry.ParsedMetadata.Hide,
554 Metadata: lease.Metadata,
555 }
556
557 + // Hidden entries are already filtered above, but keep check for safety
558 if !row.Hide {
559 rows = append(rows, row)
560 }
portal/lease.go
+42 -8
@@ -1,6 +1,7 @@
1 package portal
2
3 import (
4 + "encoding/json"
5 "regexp"
6 "sync"
7 "time"
@@ -9,12 +10,23 @@ import (
10 "gosuda.org/portal/portal/core/proto/rdverb"
11 )
12
13 +// ParsedMetadata contains pre-parsed lease metadata fields.
14 +// Defined locally to avoid sdk dependency in core package.
15 +type ParsedMetadata struct {
16 + Description string
17 + Tags []string
18 + Thumbnail string
19 + Owner string
20 + Hide bool
21 +}
22 +
23 // LeaseEntry represents a registered lease with expiration tracking.
24 type LeaseEntry struct {
14 - Lease *rdverb.Lease
15 - Expires time.Time
16 - LastSeen time.Time
17 - ConnectionID int64 // Store the connection ID
25 + Lease *rdverb.Lease
26 + Expires time.Time
27 + LastSeen time.Time
28 + ConnectionID int64 // Store the connection ID
29 + ParsedMetadata *ParsedMetadata // Cached parsed metadata
30 }
31
32 // leaseCmd is the command interface for LeaseManager event loop.
@@ -320,11 +332,33 @@ func (lm *LeaseManager) updateLeaseInternal(lease *rdverb.Lease, connectionID in
332 }
333 }
334
335 + // Parse metadata once for cached access
336 + var parsedMeta *ParsedMetadata
337 + if lease.Metadata != "" {
338 + var meta struct {
339 + Description string `json:"description"`
340 + Tags []string `json:"tags"`
341 + Thumbnail string `json:"thumbnail"`
342 + Owner string `json:"owner"`
343 + Hide bool `json:"hide"`
344 + }
345 + if err := json.Unmarshal([]byte(lease.Metadata), &meta); err == nil {
346 + parsedMeta = &ParsedMetadata{
347 + Description: meta.Description,
348 + Tags: meta.Tags,
349 + Thumbnail: meta.Thumbnail,
350 + Owner: meta.Owner,
351 + Hide: meta.Hide,
352 + }
353 + }
354 + }
355 +
356 lm.leases[identityID] = &LeaseEntry{
324 - Lease: lease,
325 - Expires: expires,
326 - LastSeen: time.Now(),
327 - ConnectionID: connectionID,
357 + Lease: lease,
358 + Expires: expires,
359 + LastSeen: time.Now(),
360 + ConnectionID: connectionID,
361 + ParsedMetadata: parsedMeta,
362 }
363
364 return true