Why does a UDT template subscribe every tag in the UDT?

Brian Holt9 min read
HMI / SCADAOther ManufacturerTroubleshooting
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Measure the subscription load before changing anything

Symptom: an overview screen with about 50 valve templates, each showing only open/closed, pushes a large multi-core server into saturation. Each valve is an object-style UDT carrying indications, controls, setpoints, actuation timers and alarms, and some objects exceed 200 members.

The usual night-shift fixes fail for these reasons:

  • Adding server hardware. The problem appeared on a 32-core server. The subscription count grows with every valve dropped on a screen, so extra cores only raise the ceiling.
  • Slowing the scan or poll rate. Every member still gets subscribed. You reduce how often the members update, not how many there are, and the operator loses indication response.
  • Splitting the overview into smaller screens. This holds until someone opens two of them or builds another overview.

Here is the mechanism. When a template parameter is typed as a UDT, the template binds to the whole instance. The client subscribes to every member of that structure, including members that no component on the template references. Referencing one boolean pulls in the full object.

Scale it with the numbers from this case. Assume 200 members per instance, which is the low end because some objects exceed it. 50 valves × 200 members gives at least 10,000 subscriptions to display 50 booleans. The indication alone needs 50.

Check: Record the tag or subscription count for one client session in the gateway's session diagnostics. Take one reading with the overview closed and one with it open. The difference divided by the valve count should be close to the full member count of your UDT. Also confirm the window is actually closed. A window left open or cached behind the active one keeps all of its subscriptions, which explains reports that UDT tags stay subscribed whether or not they are displayed.

Pick the fix per template

Three workarounds work in production. They are not exclusive. Most projects use inheritance for overview templates and string paths for popups.

Approach Drop target kept? What gets subscribed Use for Cost
UDT inheritance: status-only base type, full child type Yes Base-type members only Overview and status templates repeated many times per screen UDT restructure; instances retyped to the child
String path parameter with tag indirection or expression No Only members actually bound Popups, faceplates, detail windows Paths are built by hand; typos fail quietly
Trim UDT: static metadata moved to SQL Yes Smaller full structure Any project where UDTs have grown too large Schema plus a query on popup open

Get the overview running first with inheritance. Then fix the detail windows properly with string paths and SQL.

Check: List every template that takes a UDT parameter. Mark each one as either repeated on overviews (use inheritance) or opened one at a time (use a string path). Do not start editing until the list is complete.

Split the valve UDT into a status base and a full child

  1. Back up the project and tag export. Do the restructure on a copy first.
  2. Create a base UDT that holds only what the overview shows: open/closed indications and any summary status the symbol animates. Keep it small.
  3. Create the full valve UDT with the base UDT as its parent type. Add the controls, setpoints, actuation timers, alarms and remaining members there.
  4. Point existing valve instances at the new child type. Inherited members appear in the child at the same relative position, so bindings to status members should still resolve. Verify this on the copy before you touch production.
  5. Check any instances that had member overrides. Retyping can reset or orphan overrides, so re-apply them where needed.

Check: Open one retyped instance in the tag browser. The status members should show as inherited from the base type, and their live values should match the field device. Confirm that alarms and history on the child members are still active.

Stop here if instances lose values or alarms after retyping. Restore the backup and review the override list before trying again.

Rebind overview templates to the base type

  1. Open each status-only template and change its UDT parameter type from the full valve UDT to the status base UDT.
  2. Remove any binding in the template that references a member that no longer exists in the base type. If the overview needs that member, move it into the base type. Do not route around it.
  3. Save the template and open an overview that uses it.
  4. Drag a full valve instance onto a screen. Because the child is also of the base type, the drop offers the status-only template. The drop-target workflow stays intact.

This is the main advantage of inheritance over string paths. The operator-screen build workflow does not change, and the overview subscribes only the base members.

Check: Repeat the session measurement from the first section. Per-valve subscriptions should now equal the base-type member count, not the full count. With 50 valves and a base type of a few members, the overview total should fall from five figures to a few hundred or less.

Drive popups and detail templates from a string path

Faceplates need controls, setpoints and timers, but only one opens at a time. Keeping a full-UDT parameter there is tolerable. Switching to a string path means the popup subscribes only what it displays.

  1. Replace the UDT parameter with a string parameter that carries the instance path, for example a tagname parameter.
  2. Rebind each component with an indirect tag binding. The binding substitutes the string parameter into the path and appends the member name.
  3. For read-only values, an expression that reads the specific tag.value you need works just as well.
  4. For writable members such as setpoints and commands, use an indirect binding with bidirectional write enabled. A read-only expression will not write back.
  5. Update the window-open calls on the overview templates so they pass the instance path string into the popup parameter.

Watch for these pitfalls:

  • No drop target. A string parameter cannot accept a dragged UDT instance. Screen builders have to type or script the path.
  • Silent path errors. A typo in the path or member name produces bad-quality or blank values, not a design-time error.
  • Folder moves. Relocating instances in the tag tree breaks every hard-coded path string. Build the path from the instance the overview already knows, not from a literal.

Check: Open one popup and read the session count. It should rise by exactly the number of members bound on the popup. Close it, and the count should return to the baseline. Then pass a deliberately wrong path and confirm the popup shows bad quality, so operators can recognize a broken link.

Move static metadata out of the UDT into SQL

A 200-member valve object usually carries descriptive data that never changes at runtime, stored as tags. Each of those members is a subscription and a scan entry for no process benefit.

  1. Sort every UDT member into three groups: needs alarming, needs history, or is a live process value. Anything else is a candidate for removal.
  2. Create a SQL table keyed on the instance name or path. Move the candidates into it: descriptions, equipment references, fixed configuration text.
  3. On popup open, run one query using the same path string the popup already receives, and bind the display fields to the result.
  4. Delete the moved members from the UDT definition only after the popup shows the SQL values correctly.

Check: The member count of the full child UDT falls to alarms, history and process values only. The popup shows the metadata from the query, and there is no rise in tag subscriptions when it loads.

Separate gateway-side scanning from client subscriptions

Converting every template to string paths removes client-side subscriptions. It does not stop gateway-side work. The gateway evaluates tag history and alarms for their members regardless of which screens are open, so those members are scanned even with zero clients connected.

It is still open whether one historized member causes its non-historized siblings in the same UDT to be polled from the device. Measure it on your system instead of assuming:

  1. Close all clients.
  2. Record the request or tag count on the device connection diagnostics for the driver serving the valves.
  3. Disable history on the only historized member of one test instance, then record the count again.
  4. Compare. If the count drops by more than that one member, the whole structure was being held on scan.

Whatever the result, apply the same principle as the SQL trim. Only members that need alarms or history should sit on a fast gateway scan. Put indication-only members on a slower scan class if the screens tolerate it.

Check: With no clients open, the device request count and gateway CPU stay flat, and they match what historized and alarmed members alone should produce.

Verify end to end under full screen load

  1. Record the baseline with no clients open: gateway CPU, device request count, and total subscriptions.
  2. Open one client on the 50-valve overview. Confirm per-valve subscriptions equal the base-type member count.
  3. Open and close three different valve popups in sequence. After each close, the count should return to the overview figure.
  4. Operate one valve from its popup. Confirm the command write reaches the device and the overview indication follows.
  5. Open the overview on as many clients as run on a normal shift. CPU should scale with the base-type count, not the full UDT count.
  6. Trigger one alarm and confirm it annunciates and historizes with no clients connected.

Keep the before and after figures. They are the evidence you will need if the load comes back after the next round of screen builds.

FAQ

What happens if a template references only one member of a UDT parameter?

The client subscribes to the entire UDT instance, not just that member. A valve object with 200+ members costs 200+ subscriptions per template, so 50 valves showing open/closed load at least 10,000 subscriptions.

What happens if I change a template parameter from a UDT type to a string path?

The template subscribes only the members bound through indirect bindings or tag.value expressions, but you lose the drag-and-drop target. A wrong path fails quietly as bad quality, so test a deliberate bad path before release. Use UDT inheritance instead if the drop target matters.

What happens if server load stays high after converting every template?

Look for windows left open or cached in the background, then measure device requests with all clients closed to separate history and alarm scanning from client load. If the counts still exceed what bound, historized and alarmed members explain, stop there and open a case with the platform vendor's official support. Include the before and after subscription counts, device request counts, and one exported UDT definition.

Back to blog