Skip to content
Skip to content

The subscriber source#

An appliance can take its subscribers from two places at once: the ones declared in its own configuration (subscribers ...), and a subscriber source — a read-only view in your platform's database that the appliance reads whole, on a timer. This page describes how the appliance keeps live sessions in step with that view, and what it does when the view cannot be trusted.

The source is configured under auth database: connection-string, source (the view), cache-refresh-interval (300 seconds by default). Without a connection string the appliance serves only the subscribers in its configuration and nothing on this page applies.

What a refresh does#

Every interval the appliance reads the view, replaces what it knew from the last read, and then checks every live session against what the view says about it now:

The view now says The session
the subscriber is active, blocked or suspended, or has new rates is brought into line at once — blocked, restricted, restored or re-rated without a reconnect
the subscriber is disabled is disconnected at once
nothing — the subscriber's row has gone is disconnected, but only when two refreshes in a row say so

A subscriber declared in the appliance's own configuration is never disconnected because the view does not list it. The appliance records, for each session, whether a row of the view is what authorised it; only those sessions depend on the view.

Sessions that were identified by their access circuit rather than a username follow the same rules, with some more of their own: see access NNIs.

Why a removed row takes two refreshes#

A view can load successfully and still be wrong — incomplete while the platform rebuilds it, or missing a row for a moment. Disconnecting on the first sight of a missing row would turn every such moment into dropped customers. So the first refresh that finds a row gone only notes it, and the session is ended when the next refresh agrees. With the default interval a removal takes effect within ten minutes. To stop a subscriber now, set their status to disabled (or blocked) instead of deleting the row — that is acted on at the next refresh — or disconnect them yourself.

If many rows go at once, one refresh disconnects at most 50 sessions, or a tenth of the sessions of that kind if that is more, and carries the rest over to the refreshes that follow. The alarm subscribers.reconcile_limited tells you this is happening. If the removal was a mistake, putting the rows back before the next refresh keeps every session that has not yet been ended.

A disconnect that fails is tried again at every refresh until it succeeds. A row removed while the appliance was restarting is noticed at the first refresh afterwards.

When the view comes back empty, or much smaller#

The most damaging thing a subscriber view can do is load successfully with nothing in it — after a view was rewritten, a table was dropped from a join, or a permission changed. Taken at face value that says every subscriber has been removed.

The appliance does not take it at face value. A load is not applied when:

  • it has no rows at all, and the last load had some (or subscribers from the view are online); or
  • too much of it has changed for the worse since the last load that was applied: the rows that have gone, plus the rows that have turned from active to disabled, blocked or suspended, are more than half of it — more than auth database max-row-loss-percent (default 50) and more than auth database min-row-loss rows (default 10). A view in which every subscriber is suddenly disabled is doubted exactly like one that is suddenly empty.

The appliance remembers the size of the last view it accepted across a restart, so the first read after an upgrade is judged the same way.

When that happens nothing from the new view is applied. Nobody is disconnected — and new subscribers, and changes to existing ones, in the view that is not believed do not take effect either. The appliance keeps serving the last view it accepted, and logins are answered from that. It raises the alarm subscribers.source_implausible, and show subscribers source says so:

State:           LOAD NOT BELIEVED
Subscribers:     451 (the last load accepted)
Last load:       0 row(s)
Not believed:    it returned no rows; the last load accepted had 451
Held load:       read 2026-10-07 09:14:02Z (0d 00:03:10 ago), 0 row(s) - the load an accept applies
                 Not applied: nobody is disconnected, and new and changed subscribers are NOT being applied.
                 If the view is right: request subscribers source accept

Held load is the load the appliance read and did not believe, with the time it was read: that is exactly what request subscribers source accept would apply. Last attempt is the most recent read of any kind, and moves on when later reads fail; the held load does not.

The appliance reads the view again every minute, and the alarm clears by itself as soon as a load looks right.

If the smaller view is correct#

Sometimes it is: a wholesale customer has left and taken four hundred lines with them. Tell the appliance:

request subscribers source accept

It applies the load it showed you — the one it read and did not believe, which it has kept — and not a fresh read of the view, which could be different again by now. The answer says which load that was (when it was read, how many rows). The sessions whose rows have gone are then disconnected by the ordinary rule — at the second refresh that agrees, at most 50 (or a tenth) per refresh — so you still have one interval in which to change your mind by restoring the rows.

If there is no load waiting to be accepted, the command reads the view again and tells you so. A load it believes is simply applied; one it does not believe is kept and reported, and is applied only if you give the command again.

The appliance never accepts such a load by itself, however many times it sees it. A view that is wrong is usually wrong the same way every time.

After a restart#

The appliance keeps the last accepted view on disk, so that the first load after a restart is judged against real history, and so that it has something to serve if the database cannot be reached, or its first load is not believed, when it starts. show subscribers source then says Serving: the view kept on disk.

A PPPoE or L2TP login that arrives before that first load has been attempted is held for it — for at most auth database startup-hold-seconds (20 by default; 0 turns the hold off) — and then answered from the load, or from the kept view if the load failed. show subscribers source has a Start-up hold: line: how many logins are held now, and of those held since the control plane started, how many the first load released, how many ran out of time, and how many were abandoned because the line hung up first. One line is logged when the first load releases them, with how long the longest had waited.

That copy contains no passwords and no L2TP tunnel secrets. It is a fixed list of fields — who the subscriber is, their status, addresses, plan and circuit — and, for a line relayed to another LNS, only each tunnel endpoint's address, port, VRF, assignment id and priority. Anything else in the view's TunnelEndpoints column is left out. While it is being served:

  • subscribers who are identified by their line (IPoE, PPPoE by circuit, L2TP by calling number) connect normally;
  • a subscriber who has to present a password (PPP with PAP or CHAP) cannot connect until the database has been read — unless auth database credential-check is log-only, which never checks the password. With enforce, an appliance restarted while the database is unreachable serves no password-presenting line from the copy. Each attempt is ended without an authentication failure — the appliance closes the link (LCP Terminate-Request; for L2TP the call is cleared as a temporary condition) rather than telling the router its password is wrong — and the router redials until the database is back;
  • a line that is relayed to another LNS over a tunnel with a shared secret is not relayed;
  • a subscriber added since the copy was kept is not answered either.

The copy is used to decide new connections only. A subscriber who is already online is never disconnected, restarted, re-policed or turned into a different subscriber because of what the copy says: their DHCP renewals are answered from the session they have, exactly as while the database cannot be asked. Changes to subscribers who are online take effect when the database has been read again.

A copy older than auth database kept-view-max-age-days (7 by default) is not used; nor is one dated in the future. Set it to 0 and the copy is never used. The limit is applied once, when the appliance starts: a copy that was young enough then goes on being served for as long as the database stays unreachable, however long that is.

Refreshing now#

request subscribers refresh

reads the view immediately instead of waiting for the timer, and reports what it did:

Loaded 451 row(s) from SQL (bng.BroadbandSubscribers); applied.
Sessions: 1 re-policed, 0 ended, 0 adopted.
Not ended: 2 awaiting a second refresh that agrees, 0 over the per-refresh limit, 0 with a row that could not be read, 0 whose disconnect failed (tried again next refresh).

Over the API it is POST /api/v1/config/cache-refresh, which returns the same counts. request system cache-refresh is the older name for the same command.

Alarms#

Alarm Severity Raised when What to do
subscribers.source_unavailable major the view cannot be read Check the database and the path to it. The last view accepted is still served.
subscribers.source_implausible major the view was read and came back empty, or with most of its rows gone or turned disabled, blocked or suspended Check the view. Nothing from it is applied: nobody is disconnected, and new and changed subscribers are not applied either. If the view is right, request subscribers source accept.
subscribers.reconcile_limited major a refresh disconnected as many sessions as one refresh may, and more are waiting. The instance is the access NNI, logins for sessions found by username, or guests when the guest budget (200 adopted or ended per refresh) ran out If the removal is intended, nothing: it completes over the next refreshes. If not, restore the rows before the next refresh.

Settings#

Setting Default Meaning
auth database cache-refresh-interval 300 Seconds between refreshes
auth database max-row-loss-percent 50 A load in which more than this share of the last accepted load's rows have gone, or turned from active to disabled, blocked or suspended, is not applied
auth database min-row-loss 10 …provided more than this many rows have gone or turned, so that a small view changing is not an alarm

All three are read when the control plane starts; the commit that changes one says so.