LiveReactIslands.SSR.ETSCache (LiveReactIslands v0.1.1)

View Source

ETS-backed cache implementation for SSR rendering.

Uses the Winner/Waiter pattern for performance and thundering herd protection:

  • Cache hits: Direct ETS read (~1-2 microseconds, no GenServer overhead)
  • Cache misses: Atomic lock via :ets.insert_new/2 prevents duplicate renders
  • Winner: First process to acquire lock renders and caches result
  • Waiters: Other processes poll ETS for winner's result
  • GenServer: Only used for cleanup, stats, and configuration (never in hot path)

Configuration

config :live_react_islands,
  ssr_cache: LiveReactIslands.SSR.ETSCache,
  cache_default_ttl: :timer.minutes(5),
  cache_cleanup_interval: :timer.minutes(1)

Opt-In Caching

Caching is opt-in per component. Components must explicitly enable caching:

# Simple opt-in with defaults
defmodule MyApp.Components.ProductCard do
  use LiveReactIslands.Component,
    component: "ProductCard",
    ssr_strategy: :overwrite,
    ssr_cache: true  # Uses default TTL from config
end

# Custom cache options
defmodule MyApp.Components.ExpensiveChart do
  use LiveReactIslands.Component,
    component: "ExpensiveChart",
    ssr_strategy: :overwrite,
    ssr_cache: [ttl: :timer.minutes(10), id_in_key: true]
end

# Not cached (no ssr_cache option)
defmodule MyApp.Components.OtherComponent do
  use LiveReactIslands.Component,
    component: "OtherComponent",
    ssr_strategy: :overwrite
end

Cache options:

ssr_cache: true                       # Enable with defaults
ssr_cache: [
  ttl: :timer.minutes(10),            # Override default TTL
  id_in_key: true,                    # Boolean: include ID in cache key?
  ssr_props: %{},                     # Override props for SSR (shell caching)
  ssr_globals: %{},                   # Override globals for SSR (shell caching)
  cache_key_fn: &my_key_fn/5          # Custom cache key function (advanced)
]

Shell Caching (Skeleton SSR)

You can cache a "shell" or "skeleton" by providing placeholder data for SSR:

defmodule MyApp.Components.UserProfile do
  use LiveReactIslands.Component,
    component: "UserProfile",
    ssr_strategy: :hydrate_root,
    ssr_cache: [
      ttl: :timer.minutes(10),
      ssr_props: %{email: "", name: "Loading..."}  # Placeholders for SSR
    ]
end

The component renders with placeholder data (cached), but the real data is pushed to the client and rendered in a second pass after hydration is completed (to prevent hydration mismatches). This allows caching the HTML structure while supporting user-specific data.

Performance Characteristics

  • Cache hit latency: 1-2 microseconds (direct ETS lookup)
  • Thundering herd: 500 requests → 1 render + 499 waits
  • No GenServer bottleneck on render path
  • Atomic locking prevents duplicate renders
  • Read concurrency enabled for parallel cache lookups

Summary

Functions

Returns a specification to start this module under a supervisor.

Start the cache GenServer.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

start_link(opts \\ [])

Start the cache GenServer.

Options

  • :default_ttl - Default time-to-live in milliseconds (default: 5 minutes)
  • :cleanup_interval - How often to cleanup expired entries (default: 1 minute)

Component-specific cache options are configured on the component itself via the ssr_cache option.