Files
k8s-manifests/shortsmith/deployment.yaml
T
chemavxandClaude Opus 5 aeb31eae59 shortsmith: recursos medidos en el pod, no en el host
El primer render real murio OOMKilled con el limite de 1Gi que salia de medir en
la maquina de desarrollo. El host lleva ffmpeg 4.4.2 y la imagen 7.1.5, y 7.0
reescribio la transcodificacion sobre un planificador con colas de fotogramas
decodificados entre componentes: pasarle el WAV como segunda entrada dejaba al
decodificador de video correr por delante y llenar una cola con fotogramas de
6,2 MB. Arreglado partiendo el encode en dos pasos (repo shortsmith), y aqui van
las cifras nuevas, medidas dentro del pod.

requests 768Mi (pico anon 640 MB), limits 1536Mi. El limite no es 1Gi por el
caso techo de 180 s: ahi el pico era 940 MB de 1024 y sobrevivia solo porque
quedaba cache reclamable que soltar. A 1.5Gi memory.events da max 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 16:40:24 +00:00

149 lines
5.6 KiB
YAML

---
apiVersion: v1
kind: Namespace
metadata:
name: shortsmith
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
annotations:
# Never let ArgoCD prune this PVC: it holds the job store and the rendered videos.
argocd.argoproj.io/sync-options: Prune=false
name: shortsmith-data
namespace: shortsmith
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: shortsmith
namespace: shortsmith
labels:
app: shortsmith
spec:
replicas: 1
# Single replica on a RWO local-path volume holding a SQLite job store in WAL mode:
# Recreate keeps two pods from opening /data/shortsmith.db during a roll. The queue
# is per-process anyway, so a second replica would not share work, only the file.
strategy:
type: Recreate
selector:
matchLabels:
app: shortsmith
template:
metadata:
labels:
app: shortsmith
spec:
containers:
- name: shortsmith
image: git.chemavx.xyz/chemavx/shortsmith:46b39256
imagePullPolicy: Always
ports:
- name: http
containerPort: 8080
env:
- name: SHORTSMITH_DATA_DIR
value: "/data"
volumeMounts:
- name: data
mountPath: /data
# Measured IN THIS POD, sampling the cgroup's own memory.stat every 30 ms.
# An earlier set of numbers taken on the dev host said 522 MB and produced a
# 1Gi limit; the first real render was OOMKilled at 1483 MB. The host runs
# ffmpeg 4.4.2 and the image ships 7.1.5, and 7.0 rewrote transcoding onto a
# threaded scheduler whose inter-component queues hold decoded frames. So:
# numbers measured outside the deployment target are a guess about it.
#
# Rendering is this service's normal mode, not a burst, so the request comes
# from the peak and not from the resting figure.
#
# at rest (imports + FastAPI app) 40 MB current
# frames, 3 workers, 1260 of them +145 MB anon
# audio synthesis and WAV +82 MB anon
# encode pass 1, video only, -threads 3 +562 MB anon <- the cost
# encode pass 2, mux the audio in +15 MB anon
# two renders back to back, peak 640 MB anon / 716 current
# one render at the spec's 180 s ceiling 620 MB anon / 923 current
#
# anon is flat with duration; what grows is the page cache holding the PNG
# frames (70 MB at 42 s, 287 MB at the 180 s ceiling). That is reclaimable,
# so the request is sized against anon and the limit against current.
#
# 768Mi covers the 640 MB anon peak with ~20% headroom. The limit is 1.5Gi
# and not 1Gi because of the ceiling case: at 1Gi that render peaked at
# 940 MB of 1024, i.e. 84 MB of margin, surviving only because the kernel
# had reclaimable cache to drop. Verified at 1.5Gi with memory.events
# reading `max 0` — the limit was never reached, not merely never fatal.
#
# The CPU limit is what sizes the worker pool, not the request — the code
# reads cpu.max, since cpu_count() reports the node's 16. At limits.cpu 3
# that is 3 workers, verified in this pod. 42 s of video renders in ~31 s,
# the 180 s ceiling in ~135 s.
#
# Consequence, deliberate: under sustained contention the pod runs 3 workers
# on the 1 CPU it is guaranteed and a render takes about 3x longer. That is
# benign here — POST /render returns a job id, the queue runs at concurrency
# 1 and the caller polls, so a slow render is a later `done`, never a
# timeout. Do not "fix" it by sizing the pool from the request: that would
# trade an occasional slowdown for a permanent 3x on every render, including
# the ones on an idle node, which is the common case.
resources:
requests:
cpu: "1"
memory: "768Mi"
limits:
cpu: "3"
memory: "1536Mi"
# The render runs off the event loop (asyncio.to_thread, and the frame
# workers are separate processes), so /healthz answers while a job is in
# flight. A probe timing out means the process is actually wedged.
readinessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 15
periodSeconds: 30
failureThreshold: 3
volumes:
- name: data
persistentVolumeClaim:
claimName: shortsmith-data
imagePullSecrets:
- name: gitea-registry-infisical
---
apiVersion: v1
kind: Service
metadata:
# Must end in -svc. researchowl hit this: a Service named `searxng` collided with
# the SEARXNG_* env vars k8s injects into every pod in the namespace and returned
# 403s until it was renamed.
name: shortsmith-svc
namespace: shortsmith
spec:
selector:
app: shortsmith
ports:
- name: http
port: 8080
targetPort: http
# No Ingress on purpose: this is an internal API with no authentication, reached at
# shortsmith-svc.shortsmith.svc.cluster.local:8080 from inside the cluster. Ollama was
# exposed without auth once and had to be retrofitted with Authentik forward-auth.