You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Umbrella tracking issue for bringing SMF onto the org unified data-layer standard. SMF is the last and largest migration in the epic and is benchmark-gated (per the standard §7). This issue describes exactly what to do and the standard sections that apply. The narrow stub #3 (smf_aaa raw-ETS → smf_db) is Phase 2 of this plan.
Epic:#6 Org standard (authoritative):next-nf/next-nf → plugins/nf-conventions/skills/nf-architecture/reference/database.md (skill: nf-conventions:nf-architecture). Section references below point into it. Reference implementations (already migrated — use as templates):udr (reference impl), pcf, chf (chf Phase 1: next-nf/chf#13).
Why SMF is special (read first)
The standard has SMF-specific carve-outs:
§5.3 node-local carve-out — SMF's hot per-node runtime indices are node-local by design; they SKIP the syn cluster lock and use plain cas_put/dirty ops. Only cluster-wide identity entities (subscribers, sessions) use the lock.
§7 — SMF migrates last and is the largest migration; do not begin until chf's conformance passes (chf Phase 1 done).
§8.5 performance gate — before migrating the hot registry ops through smf_db, benchmark Mnesia-ram vs raw-ETS on those ops; the acceptable regression threshold must be documented and committed BEFORE migration begins.
P7 dirty-read fast path — even hot per-node registries use the API, but reads may be lock-free; the call site documents the concern.
Current state (what exists vs the gaps)
Already present:
An smf_db behaviour (apps/smf_core/src/smf_db.erl) with two backends: smf_db_ets (raw ETS) and smf_db_mnesia (scope=local→ETS, scope=global→Mnesia ram_copies dirty ops). Backend selected via application:get_env(smf_core, db_backend, …), cached in persistent_term.
The node-local hot registries already route through smf_db and are the §5.3 carve-out (correct to keep ETS-backed): gtp_context_reg (local bag — TEID/SEID/context-key index), gtp_path_reg, smf_socket_reg, smf_sx_node_reg, smf_local_pool_reg.
Gaps vs the standard (the work):
smf_db is ETS-shaped, not the document contract. Its API is create/insert/lookup/select/fold/… — no version-CAS, no binary keys, no collections, no update/3 functional CAS. The standard's §2.2 contract is the 11 generic callbacks (child_spec/1, ensure_collection/2, get/2→{ok,Doc,Vsn}, put/3, cas_put/4, delete/2, take/2, find/2, find_by/3, fold/4, count/2) + the §2.3 facade (update/3, create/3, ready/0, await_ready/1).
No syn, no with_entity, no CAS.smf_cluster only coordinates Mnesia startup. The standard's §5.1 Layer-1 lock does not exist. syn is not a dep.
Cluster-wide state is unsafe.gtp_path_db:cas_restart_counter/3 is a home-grown read-modify-write over dirty_read/dirty_write with no serialization (a race under the current backend). gtp_context_global (IMSI/IMEI registration) and smf_global are cluster-wide with no lock/CAS.
Session state is process-only (biggest gap). The PDU-session/bearer/tunnel/PFCP/PCC state (#context/#tunnel/#bearer/#pfcp_ctx/#pcc_ctx) lives solely in gtp_context gen_statem heaps — no write-through to any store, no recovery (in-flight sessions are lost on node restart). These records are the cluster-wide "entities" the document store is meant to hold (P2/P3).
No §8.5 microbenchmark.bench_SUITE:contexts_at_scale is an integration benchmark; it does NOT isolate ets:lookup vs smf_db:lookup latency on the hot path.
Minor: gtp_context_regupdate/4 has a raw ets:insert bypass (~line 75) — fix to go through smf_db.
Replace jsx with OTP json; binary-keyed payloads #6 Replace jsx with OTP json; binary-keyed payloads. The standard requires binary-keyed documents; SBI/provisioning payloads are currently atom-keyed. Needed before the session document schema can be defined. Blocks Phase 4 (not the ETS-routing phases).
Independent (do NOT block this migration): #4 (OTEL), #5 (HTTP app split), #7 (Diameter request_errors).
Plan (phased — each phase is an independently testable deliverable)
Phase 0 — Performance-gate harness (§8.5) — GATES all hot-path work
Write a microbenchmark (extend apps/smf_core/test/bench_SUITE.erl) comparing raw ets:lookup/insert vs smf_db:lookup/write on both backends for the hot registry ops (gtp_context_reg:lookup, smf_aaa_session_reg:lookup), reported µs/op at P50/P99.
Document + commit the acceptable regression threshold before any hot-path migration (§8.5 mandate).
Accept: benchmark case committed; threshold doc committed; baseline numbers recorded.
Phase 1 — Reshape smf_db to the org document contract (§2.2, §2.3, P2, P9, §8.1)
Implement the 11 generic callbacks + facade update/3/create/3/ready/0/await_ready/1; version-as-CAS-metadata (get→{ok,Doc,Vsn}); binary-keyed map documents in named collections; schema_version + upgrade-on-read (§4.5). Mnesia generic envelope per §3.1; disc-schema-at-boot (lesson from pcf/chf).
Decide extend-in-place vs parallel-module-then-swap; keep the node-local fast path (P7; §5.3 — no syn).
Add the backend-agnostic conformance suite (§8.1) on Mnesia ram+disc. Port from udr/pcf/chf.
Accept: conformance green on both Mnesia tiers; error taxonomy §6.1; dialyzer clean.
Phase 2 — Route smf_aaa_session_reg through smf_db, benchmark-gated (this is issue #3)
Replace the 6 raw ets:* in smf_aaa_session_reg.erl with smf_db; keep smf_aaa_session_seq atomic counter as raw ETS (exception class — atomic increment is not a db op; document it).
Define the session document schema (binary-keyed maps + schema_version): decide which #context/#tunnel/#bearer/#pfcp_ctx/#pcc_ctx fields form which aggregate/collection (P3 — one aggregate = one document; e.g. session, bearer).
Accept: a session survives a node restart (recovery test); document conformance; readiness gate in place.
Biggest risk
The §8.5 perf gate: gtp_context_reg and smf_aaa_session_reg are on the per-packet hot path (every GTP-C message, every AAA request). If Mnesia-ram adds unacceptable lookup latency vs raw ETS, the approach for those tables needs revisiting (they may stay ETS-backed under the §5.3 carve-out). This is why Phase 0's microbenchmark + committed threshold is the first deliverable and gates everything downstream.
Umbrella tracking issue for bringing SMF onto the org unified data-layer standard. SMF is the last and largest migration in the epic and is benchmark-gated (per the standard §7). This issue describes exactly what to do and the standard sections that apply. The narrow stub #3 (smf_aaa raw-ETS → smf_db) is Phase 2 of this plan.
Epic: #6
Org standard (authoritative):
next-nf/next-nf→plugins/nf-conventions/skills/nf-architecture/reference/database.md(skill:nf-conventions:nf-architecture). Section references below point into it.Reference implementations (already migrated — use as templates):
udr(reference impl),pcf,chf(chf Phase 1: next-nf/chf#13).Why SMF is special (read first)
The standard has SMF-specific carve-outs:
syncluster lock and use plaincas_put/dirty ops. Only cluster-wide identity entities (subscribers, sessions) use the lock.smf_db, benchmark Mnesia-ram vs raw-ETS on those ops; the acceptable regression threshold must be documented and committed BEFORE migration begins.Current state (what exists vs the gaps)
Already present:
smf_dbbehaviour (apps/smf_core/src/smf_db.erl) with two backends:smf_db_ets(raw ETS) andsmf_db_mnesia(scope=local→ETS,scope=global→Mnesiaram_copiesdirty ops). Backend selected viaapplication:get_env(smf_core, db_backend, …), cached inpersistent_term.smf_dband are the §5.3 carve-out (correct to keep ETS-backed):gtp_context_reg(local bag — TEID/SEID/context-key index),gtp_path_reg,smf_socket_reg,smf_sx_node_reg,smf_local_pool_reg.Gaps vs the standard (the work):
smf_dbis ETS-shaped, not the document contract. Its API iscreate/insert/lookup/select/fold/…— no version-CAS, no binary keys, no collections, noupdate/3functional CAS. The standard's §2.2 contract is the 11 generic callbacks (child_spec/1,ensure_collection/2,get/2→{ok,Doc,Vsn},put/3,cas_put/4,delete/2,take/2,find/2,find_by/3,fold/4,count/2) + the §2.3 facade (update/3,create/3,ready/0,await_ready/1).syn, nowith_entity, no CAS.smf_clusteronly coordinates Mnesia startup. The standard's §5.1 Layer-1 lock does not exist.synis not a dep.gtp_path_db:cas_restart_counter/3is a home-grown read-modify-write overdirty_read/dirty_writewith no serialization (a race under the current backend).gtp_context_global(IMSI/IMEI registration) andsmf_globalare cluster-wide with no lock/CAS.#context/#tunnel/#bearer/#pfcp_ctx/#pcc_ctx) lives solely ingtp_contextgen_statem heaps — no write-through to any store, no recovery (in-flight sessions are lost on node restart). These records are the cluster-wide "entities" the document store is meant to hold (P2/P3).smf_aaa_session_regstill uses raw ETS (6ets:*sites) — bypassessmf_dbentirely (issue Route smf_aaa raw-ETS through smf_db (benchmark-gated) #3).bench_SUITE:contexts_at_scaleis an integration benchmark; it does NOT isolateets:lookupvssmf_db:lookuplatency on the hot path.gtp_context_regupdate/4has a rawets:insertbypass (~line 75) — fix to go throughsmf_db.Prerequisites — must land first (Blocked by)
diameter_make. SMF is onminimum_otp_vsn "26.2". OTP-29 is the standard's floor and the target for benchmarks/CI. Hard blocker.json; binary-keyed payloads. The standard requires binary-keyed documents; SBI/provisioning payloads are currently atom-keyed. Needed before the session document schema can be defined. Blocks Phase 4 (not the ETS-routing phases).Independent (do NOT block this migration): #4 (OTEL), #5 (HTTP app split), #7 (Diameter
request_errors).Plan (phased — each phase is an independently testable deliverable)
Phase 0 — Performance-gate harness (§8.5) — GATES all hot-path work
apps/smf_core/test/bench_SUITE.erl) comparing rawets:lookup/insertvssmf_db:lookup/write on both backends for the hot registry ops (gtp_context_reg:lookup,smf_aaa_session_reg:lookup), reported µs/op at P50/P99.Phase 1 — Reshape
smf_dbto the org document contract (§2.2, §2.3, P2, P9, §8.1)update/3/create/3/ready/0/await_ready/1; version-as-CAS-metadata (get→{ok,Doc,Vsn}); binary-keyed map documents in named collections;schema_version+ upgrade-on-read (§4.5). Mnesia generic envelope per §3.1; disc-schema-at-boot (lesson from pcf/chf).Phase 2 — Route
smf_aaa_session_regthroughsmf_db, benchmark-gated (this is issue #3)ets:*insmf_aaa_session_reg.erlwithsmf_db; keepsmf_aaa_session_seqatomic counter as raw ETS (exception class — atomic increment is not a db op; document it).ets:*in smf_aaa (except the documented counter); works on both backends; hot-path latency within the committed bound or documented.Phase 3 — Cluster-wide CAS safety +
synwith_entity(§5.1, P4)syndep andsmf_cluster:with_entity(Scope, Key, Fun)(the standard's Layer-1 lock) — port the shape from udr/pcf/chf<comp>_cluster.gtp_path_db:cas_restart_counterto useupdate/3CAS (+with_entityfor the multi-step procedure).gtp_context_globalIMSI/IMEI registration andsmf_global: wrap cluster-wide mutations inwith_entity+ CAS.Phase 4 — Session/bearer document schema + write-through + recovery (P2, P3, §4.4, §6.4) — LARGEST
schema_version): decide which#context/#tunnel/#bearer/#pfcp_ctx/#pcc_ctxfields form which aggregate/collection (P3 — one aggregate = one document; e.g.session,bearer).from_doc/1/to_doc/1, invariant Funs, upgrade-on-read.smf_db(global tables) from thegtp_contextgen_statem.with_entityfor the subscriber/session entity.smf_dbreports ready.Biggest risk
The §8.5 perf gate:
gtp_context_regandsmf_aaa_session_regare on the per-packet hot path (every GTP-C message, every AAA request). If Mnesia-ram adds unacceptablelookuplatency vs raw ETS, the approach for those tables needs revisiting (they may stay ETS-backed under the §5.3 carve-out). This is why Phase 0's microbenchmark + committed threshold is the first deliverable and gates everything downstream.Checklist (§9 of the standard, SMF-adapted)
smf_dbimplements the 11-callback contract +update/3; conformance green (Mnesia ram+disc)synwith_entity; cluster-wide CAS safe (gtp_path_db, gtp_context_global, smf_global)