The catalog

The catalog is what the scanners write and what every screen reads. It is a SQLite database replicated by Corrosion, one cluster per namespace, with one standing member and a member in every pod that reads or writes it. This guide describes the Catalog resource that creates it, how a screen gets its copy, and how the counts reach a Library’s status.

The Catalog resource

One Catalog per namespace creates the catalog pod, sizes every catalog claim in the namespace, and owns the Service the cluster’s members find each other through:

apiVersion: library.liken.sh/v1alpha1
kind: Catalog
metadata:
  name: catalog
  namespace: media
spec:
  storage:
    size: 1Gi
    storageClassName: local-path
  screens:
    storageClassName: local-path

Every member holds the whole namespace’s catalog, because the cluster gossips every row to every peer. So one size covers the catalog pod, every scan Job, and every screen. spec.storage.claimName names an existing claim for the catalog pod in place of the one the operator provisions. Catalog describes every field.

The listing shows the size and whether the pod runs:

$ kubectl -n media get catalogs
NAME      SIZE   READY   AGE
catalog   1Gi    True    3d

Ready follows the catalog pod alone. A dark screen does not report the namespace’s catalog as down. status.members lists the pods in the cluster, and status.screens lists each screen with its claim, its node, and its phase.

The catalog pod

The pod is named <catalog>-catalog, and it runs two containers. The catalog container is the Corrosion agent, on a ReadWriteOnce claim named the same way, so a restarted pod keeps its database. The reporter container reads that agent and publishes one retained report per Library on the bus, rebuilt whenever the catalog changes, at most once a second. The report is where a Library’s counts, gaps, and runs come from. The reporter holds no Kubernetes credential. The operator alone writes status.

The agent’s API binds to loopback, so nothing on the pod network can reach it, and the pod’s probes run SELECT 1 through the agent’s own binary inside the container. The startup probe allows ninety seconds for the agent to open its database.

How the members find each other

The operator writes a headless Service named catalog in the namespace, on UDP port 8787, and writes its EndpointSlice itself. The slice holds every pod in the namespace that carries the member label: the catalog pod, every running scan, enrich, and cleanup Job, and every screen. A starting agent is published before it is ready, because it is a gossip peer as soon as it starts. Every agent bootstraps to catalog:8787 and keeps re-resolving it for its whole life.

How a Job confirms its rows landed

Every worker Job writes a runs row when it starts and one when it finishes, then subscribes to its Library’s report and waits until the reporter echoes that run back with the counts the Job itself held. The echo is the proof, because a version can apply before the rows behind it finish replicating. A Job that waits more than two minutes fails, and Kubernetes retries it. The rows stay safe on the Job’s own claim.

How a screen syncs

A screen pod runs the same agent as a native sidecar, and the browser starts only after the agent’s startup probe passes. The agent’s claim is named <screen-pod>-catalog, sized from the Catalog, and classed by spec.screens.storageClassName. A screen pod is pinned to the machine that holds its display, so a node-local class fits.

The claim is what makes a restart fast. On an emptyDir, a restarted screen synced the whole catalog again every time. On a claim, the sync happens once, when the claim is fresh:

Screen restart On an emptyDir On a claim
Time to the full catalog 157 s 0 to 1 s
Agent memory after 211 MiB 11 MiB
First start on a fresh claim every start once, 152 s

A screen in a namespace with no Catalog runs on an emptyDir and pays the sync on every start.

A node-local class binds the claim to the node the pod first landed on. If the display moves to another machine, the pod cannot schedule there. After five minutes unschedulable, the operator deletes the pod and its claim together, and the next pass creates both on the new node.

Reading the catalog by hand

Neither image in the catalog pod has a shell. The agent’s own binary answers queries and lists the cluster’s members:

kubectl -n media exec catalog-catalog -c catalog -- /corrosion query "SELECT COUNT(*) FROM movies"
kubectl -n media exec catalog-catalog -c catalog -- /corrosion cluster members

The tables are in the repository at corrosion/schema/catalog.sql, with a comment on each. Every table is keyed by its library first, so two libraries in one namespace never share a row.