← Articles
OPC UA/7 min read/ views

Why Your OPC UA Client Drops Fast Tag Changes — and Which Queue Settings Fix It

How monitored item queues, discard policy and the Overflow status bit decide whether your client sees every fast tag change or only the latest one.

OPC UASCADAHMITagsTroubleshooting

A reject counter jumped from 12 to 19 on the PLC, and the SCADA trend showed it going 12 → 19 in one step. Nobody lost data on the wire — the client just never asked to keep the values in between. That behavior is decided by three numbers on the monitored item, and most of the time nobody sets them on purpose.

OPC UA Part 4 (the Services spec) calls these the MonitoringParameters: samplingInterval, queueSize, and the discardOldest flag, plus the DataChangeFilter. Sampling and publishing are the settings people argue about; the queue is the one that silently eats your fast transitions.

Three numbers that are not the same number

Sampling, publishing, and queue size get collapsed into "make it faster," and that's where the reject-counter problem comes from. They answer different questions:

ParameterWhat it controlsThe question it answers
samplingInterval (per item)How often the server checks the source valueCan the server/PLC actually sample this fast?
Publishing interval (per subscription)How often the subscription flushes notificationsDoes the client get steady updates or bursts?
queueSize (per item)How many notifications wait between publishesDo I need the intermediate values?
discardOldest (per item)Which value gets dropped when the queue is fullKeep the newest, or preserve the sequence?
DataChangeFilterWhich changes become notifications at allHave I separated noise from real movement?

Dropping the publishing interval to "catch missed changes" while queueSize stays at 1 does nothing for a burst between publishes — the queue still holds one value. You've only made the client publish more often. If ten changes land between two publish cycles and the queue holds one, nine are gone regardless of how fast you publish.

Note the "per item" tags above: samplingInterval and queueSize are set on each monitored item, not on the subscription. You can and should give a fast counter different parameters than a tank level in the same subscription.

Queue size 1 is the right default for most live values

Most SCADA points are live status: the operator wants the current state and its quality, not every value since the last screen refresh. For those, queueSize 1 is correct — anything larger just costs server memory. This covers:

  • A tank level on an overview screen.
  • Motor running feedback when only the present state is used.
  • A slow-moving temperature.
  • Any HMI-only indicator that a historian is collecting separately anyway.

One thing that still bites you at queueSize 1: quality. A fresh Good value arriving after a five-second comm gap should not silently erase the fact that the value was Bad or Uncertain during the gap. If the historian is your record, make sure it's subscribed with StatusCode changes treated as notifications — the DataChangeFilter Trigger setting (StatusValue vs StatusValueTimestamp) decides that.

Raise the queue only where order actually matters

A handful of tags need more than "the latest value." Give those the deeper queue instead of inflating every point:

  • A counter that spikes during a reject burst (the one from the intro).
  • A step number that walks through several states during auto cycle start.
  • A diagnostic word that goes warning → fault → reset in under a second.
  • A batch phase transition that MES or reporting logic reads.
  • A high-speed test tag you're watching during commissioning.

For each, write down the worst-case burst as a number. If a machine can throw 20 state changes in 2 seconds and you publish once a second, queueSize 2 won't hold it — you need roughly one slot per expected change between publishes, plus margin. And check what the server actually gave you: CreateMonitoredItems returns a RevisedQueueSize and RevisedSamplingInterval, and the server is free to hand back less than you asked for. Log the revised values, not the requested ones.

Discard policy: discardOldest is a real decision

When the queue fills, the server drops something. discardOldest = true (the default) drops the oldest queued notification; false drops the incoming one.

discardOldestGood forThe cost
true (default)Live displays chasing the current valueIntermediate history is lost
falsePreserving the first changes in a sequenceThe client can lag the real process

For most live HMI values, true is easier to reason about — the screen converges on the newest state. For sequence-of-events work, false preserves that first fault transition, but if recovery is slow the client looks stale. Either way, don't leave it at the vendor default on a critical tag without writing down why.

Here's the part people miss: the server tells you when it discarded. When a data change is dropped, OPC UA sets the Overflow InfoBit in the StatusCode of the next value delivered from that queue (Part 4, the DataChangeNotification handling). Your client can detect loss per monitored item without any special server counter — but only if it actually reads the InfoBits instead of masking the StatusCode down to Good/Bad. If your driver throws away everything but the severity, you'll never see the overflow flag and dropped values will look like clean data.

Diagnostics that turn "sometimes values disappear" into a real ticket

Queue trouble shows up as intermittent, unreproducible data loss. Put these on the commissioning screen or in the maintenance notes so it's traceable:

  • Subscription count and monitored item count (against the server's advertised MaxMonitoredItemsPerSubscription).
  • Revised publishing interval and keep-alive count.
  • Late PublishResponse or a growing publish request backlog.
  • Any server-side queue overflow counter, plus how many values carried the Overflow InfoBit.
  • Rejected CreateMonitoredItems requests (server hit MaxMonitoredItemsPerCall or a per-session limit).
  • Client-side receive queue depth or processing delay.
  • Gap between source timestamp and client receive time.

Load-test with the real combination, not the tag in isolation: drive a fast test value while opening your heaviest HMI screen and running historian collection at the same time. If overflow only appears once several screens are open, the PLC tag is fine — it's the client's whole processing path that can't keep up.

Alarms are not fast Booleans

Subscribing to an alarm's active/inactive Boolean as a data change gets you the state, not the event. It won't carry the condition ID, the acknowledgment, or the transition detail an alarm record needs — those live in the Event model (Part 9, Alarms & Conditions), delivered through an event monitored item with an EventFilter, not a DataChangeFilter.

For an alarm-heavy site, confirm:

  • Event subscriptions exist separately from the data subscriptions.
  • Acknowledge, return-to-normal, and shelving all land with timestamps.
  • Event queue behavior and historical events survive a disconnect (test it).
  • The HMI alarm summary isn't built on a single sampled Boolean.

If alarm sequence accuracy matters, test the alarm path as an alarm path.

A commissioning test that produces a record, not a vibe

Two test tags catch most of this:

  1. A tag that increments once per second.
  2. A tag that bursts ten increments as fast as it can.
  3. Subscribe with the exact production parameters (revised values, not requested).
  4. Log source timestamp, value, StatusCode including InfoBits, and receive time.
  5. Open the heaviest HMI screen and the historian collector simultaneously.
  6. If reconnect recovery is a requirement, disconnect and reconnect the client mid-test.
  7. Compare expected increments to received increments, and count how many arrived with the Overflow bit set.

The output should state which changes were dropped on purpose and which weren't. "It looked OK on the screen" is not a commissioning record.

The whole game is matching queue behavior to what each tag is for. Live display, historian feed, alarm/event, and sequence diagnostics want different parameters — split them into classes and load-test each class, because the defaults were chosen by the server vendor, not for your process.