When an NQE runs in parallel, it executes the same logic across all devices in the snapshot inventory simultaneously, rather than one device at a time in sequence. In practice, this can be the difference between a query that risks hitting the 20-minute timeout and one that completes in a fraction of the time.
When Parallelization Happens
A query will run in parallel if:
- It starts with an iteration over network.devices
- It does not access network.devices in any other way (only one iteration)
- It does not access network.endpoints
- It does not use the group … by qualifier
How to Check
From the NQE IDE:
- Windows: Alt + left-click Execute
- Mac: Option + left-click Execute
In the debug panel, look for:
parallel_foreach device in network.devices
If you only see foreach device in network.devices, it’s not parallelized.
Example 1 — Pass the Device Object, Not Its Name
Broken (serial)
Passing device.name into a function and then re-looking up the device causes a second iteration over network.devices.
getDeviceVersion(deviceName) =
foreach device in network.devices
where device.name == deviceName
select device.platform.osVersion;
foreach device in network.devices
foreach interface in device.interfaces
foreach subinterface in interface.subinterfaces
foreach arp in subinterface.ipv4.neighbors
select {
device: device.name,
deviceVersion: getDeviceVersion(device.name),
interface: interface.name,
arp: arp.ip,
mac: arp.linkLayerAddress
}
Debug (abridged)
foreach device in network.devices
Result: No parallel_foreach — not parallelized.
Fixed (parallel)
Pass the entire device object to the function to keep a single pass over network.devices.
getDeviceVersion(device) =
foreach device in >device]
select device.platform.osVersion;
foreach device in network.devices
foreach interface in device.interfaces
foreach subinterface in interface.subinterfaces
foreach arp in subinterface.ipv4.neighbors
select {
device: device.name,
deviceVersion: getDeviceVersion(device),
interface: interface.name,
arp: arp.ip,
mac: arp.linkLayerAddress
}
Debug (abridged)
parallel_foreach device in network.devices
Result: Parallelized
Example 2 — Avoid Building Multiple Device Lists
Broken (serial)
Constructing multiple device lists invokes network.devices more than once, which results in the query being executed in serial.
ciscoDevices =
foreach device in network.devices
where device.platform.vendor == Vendor.CISCO
select device;
paloDevices =
foreach device in network.devices
where device.platform.vendor == Vendor.PALO_ALTO_NETWORKS
select device;
foreach device in ciscoDevices + paloDevices
foreach interface in device.interfaces
foreach subinterface in interface.subinterfaces
foreach arp in subinterface.ipv4.neighbors
select {
device: device.name,
deviceVersion: device.platform.osVersion,
interface: interface.name,
arp: arp.ip,
mac: arp.linkLayerAddress
}
Debug (abridged)
foreach device in (ciscoDevices + paloDevices)
(No parallel_foreach — not parallelized.)
Fixed (parallel)
Filter in a single pass over network.devices.
foreach device in network.devices
where device.platform.vendor in eVendor.CISCO, Vendor.PALO_ALTO_NETWORKS]
foreach interface in device.interfaces
foreach subinterface in interface.subinterfaces
foreach arp in subinterface.ipv4.neighbors
select {
device: device.name,
deviceVersion: device.platform.osVersion,
interface: interface.name,
arp: arp.ip,
mac: arp.linkLayerAddress
}
Debug (abridged)
parallel_foreach device in network.devices
Result: Parallelized
Example 3 — Multiple Checks, One Device Loop
Broken (serial)
Running separate checks that each iterate over network.devices invokes multiple passes and prevents parallelization.
lineVtyPattern = ```
line vty 0 4
session-timeout
access-class
```;
vtpModePattern = ```
vtp mode transparent
```;
check1Results =
foreach device in network.devices
where device.platform.os == OS.IOS_XE
let result = blockDiff(device.files.config, vtpModePattern)
select {
device: device.name,
version: device.platform.osVersion,
check: "VTP Mode Is Transparent",
violation: result.diffCount > 0,
diff: result.blocks
};
check2Results =
foreach device in network.devices
where device.platform.os == OS.IOS_XE
let result = blockDiff(device.files.config, lineVtyPattern)
select {
device: device.name,
version: device.platform.osVersion,
check: "Line VTY Configuration contains access-class and timeout",
violation: result.diffCount > 0,
diff: result.blocks
};
foreach result in check1Results + check2Results
select result
Debug (abridged)
foreach device in network.devices // in check1Results
foreach device in network.devices // in check2Results
Result: Not parallelized
Fixed (parallel)
Keep “one function per check,” but pass the device into each check so there’s only one iteration over network.devices.
lineVtyPattern = ```
line vty 0 4
session-timeout
access-class
```;
vtpModePattern = ```
vtp mode transparent
```;
check1Results(device) =
foreach device in bdevice]
where device.platform.os == OS.IOS_XE
let result = blockDiff(device.files.config, vtpModePattern)
select {
device: device.name,
version: device.platform.osVersion,
check: "VTP Mode Is Transparent",
violation: result.diffCount > 0,
diff: result.blocks
};
check2Results(device) =
foreach device in tdevice]
where device.platform.os == OS.IOS_XE
let result = blockDiff(device.files.config, lineVtyPattern)
select {
device: device.name,
version: device.platform.osVersion,
check: "Line VTY Configuration contains access-class and timeout",
violation: result.diffCount > 0,
diff: result.blocks
};
foreach device in network.devices
foreach result in check1Results(device) + check2Results(device)
select result
Debug (abridged)
parallel_foreach device in network.devices
Result: Parallelized
Takeaways
- Golden rule: iterate over network.devices once
- Prefer using the data model when possible over manually parsing information
- Pass the device object into helper functions; don’t re-look it up by name.
- Combine filters with where instead of building multiple device lists.
- Use the debug panel and confirm you see parallel_foreach device in network.devices.
Final Notes
There are cases where writing an NQE to execute in serial is unavoidable, such as in the Forward Library examples Uncollected CDP-LLDP Neighbors or IP Address Uniqueness. NQE fully supports serial queries and can often process very complex logic across large datasets in serial without issue. The takeaway should not be that a serial query is inherently bad practice. Rather, advanced NQE authors should understand how parallelization works, know how to leverage it when possible, and reserve serial execution for situations where it is the only viable way to solve the problem.