Skip to main content

Forward NQE includes several under-the-hood optimizations that can improve query performance. This post focuses on the _lookup optimization, which can reduce execution time when filtering on a single key field using an exact equality comparison. Leveraging _lookup is considered a best practice whenever possible.

In a recent query I wrote joining device CVE information with the EPSS database in a network containing 10,000 devices, re-writing the NQE to take advantage of _lookup reduced the execution time by over 50%. While this kind of optimization isn’t as dramatic as parallelization—where a multi-minute query might drop to seconds—it can still offer a significant and worthwhile improvement, particularly in queries with repeated key-based lookups over large lists.

 

What _lookup Does

Normally, when you write a loop in NQE that scans a list for a match, the query engine will iterate over every item in that list and check the condition. This can be slow if the list or number of devices in the network is large.

When a query uses a foreach on a list of records followed by a where clause that filters that list using an exact equality comparison on a single field, NQE may rewrite the loop into a _lookup operation. This changes the behavior from scanning the entire list for each match to performing a direct, indexed lookup.

 The index used for this optimization comes from two possible sources:

  • Data model lists that already have a predefined index on that field (key fields are not exposed in the UI).
  • User-defined lists that are saved to a variable and iterated over more than once by that same field, in which case NQE will automatically build an index.

Building the index requires one initial pass over the list (O(n)), but all subsequent accesses are O(1). This makes repeated key-based lookups significantly faster, especially when the list is large or the same field is queried multiple times.

How to Tell If _lookup Is Happening

You can see whether _lookup is being used by looking at the Query Debug Panel by holding either alt(windows) or option(mac) and left clicking on Execute from the NQE IDE. If you see _lookup(...) in the Query Debug Panel, the optimization is being leveraged for that section of the query.

AD_4nXdVnzL4a7qzloEPHdacOwdqITj4_Y4Grzcm7pPfUGfrDF_bRBv5A75j1Vi6K2GMUCxERz4BpoB-k9LSFYs-lAp7VMsBT1ErP4PXIFl1_uKTf1wi-1ZDaFn4HtOj5NK3Gq13YGCGQA?key=6ajxsR9OTrrxDOCbMpbbog

The Query Debug Panel also displays useful details such as execution duration, allowing you to see the exact NQE run time and compare how different changes to the query impact performance.

If you don’t see _lookup, then the engine is performing a full scan.

 

Simple Example: No _lookup Optimization

Using a function like matches() or any non-exact match breaks the optimization:

Syntax

foreach device in network.devices

foreach interface in device.interfaces

where matches(interface.name, "ge-0/0/0")

select { device: device.name, interface: interface.name }

AD_4nXcJ9K8yusCUakmhR-RANF9QluMBAdSzWjR212uvJDF1fUjakph0xOBB_IEMg-Aj4uYsbItGCKDMjGB8mUd1yHtTFh4jHrteMLzTzbgbgvnJkDCr7mLcd5A9hv1cHmG-W3Koj55ovA?key=6ajxsR9OTrrxDOCbMpbbogAD_4nXferTKBMTDBliEHEUonUGix9ko8fx5viYe9QDxKR4v3Y7xQzr1PbAxKBxt9N-XIbtqfRwREG6sMsdBr6REmGQ1P1vl3pMIPSR6fBzSTKjR7sXIxyEoerIFupWmzUyvFAT4-i-_m?key=6ajxsR9OTrrxDOCbMpbbog

Query Debug Panel

_lookup does not exist in the below syntax, indicating that the optimization is not being leveraged.

@juniper(INDENTATION);



parallel_foreach device in network.devices

⟪foreach interface in ⟪device.interfaces⟫

 where @builtin/matches(interface.name, "ge-0/0/0")

 select ⟪{ device: ⟪⟪device⟫.name⟫, interface: ⟪⟪interface⟫.name⟫ }⟫⟫

AD_4nXc9NTXj4WF9gEzajyXzm-o9zeMJXunTyJJu5d8mQBaoJcs1r0706M0WwdG6o0VpZg3I1Kr5-k-b1fyhu2OGneu5cnrvYCBpKNPykMve9oAO6g_NTRYXjqkWOgiXinXvF51ihCHV?key=6ajxsR9OTrrxDOCbMpbbogAD_4nXel_N4twTekdy4IPc6ezQvfFjanLa4iV9cyGRIYkqC4lc4oeJiwBjU6d7HHmXs5jYvi68TfHhUiQmvg6vZAWJqF9Gr0IxI2nOTK0Gb2Mva5kEB0c2VWAC-iCcVzM_SgrE6DqtzvoQ?key=6ajxsR9OTrrxDOCbMpbbog

Simple Example: _lookup Optimization Enabled

Switching to a simple equality check on a unique key enables _lookup:

Query

foreach device in network.devices

foreach interface in device.interfaces

where interface.name == "ge-0/0/0"

select { device: device.name, interface: interface.name }

AD_4nXe_Qy2p7_hbTr7aWCTwqcLbmY1EW-z0QD6sQDtR2UXkvqC4C_aikojs_YBB07ygRBGf9ToUajQg2wJY0ASSFWNhJGdzfkG0izqt1T5L88zU61kuOZZ_trOGh19zxTmBMnjZtlyi_g?key=6ajxsR9OTrrxDOCbMpbbogAD_4nXfsgijl0jziDXzOAt_Po438eCmhVqsrw94NgTy2aRkwRUvN7HxIOhKmd5tiHX8KSczK5aATDr5jRJ2xyDHE63Al_lDovXe0Mz9crLJkzv2ThIy-MdNmbTE_bRlRDKHp56mrthnYPQ?key=6ajxsR9OTrrxDOCbMpbbog

Query Debug Panel

@juniper(INDENTATION);



parallel_foreach device in network.devices

⟪foreach interface in _lookup(device.interfaces, "name", "ge-0/0/0")

 select ⟪{ device: ⟪⟪device⟫.name⟫, interface: ⟪⟪interface⟫.name⟫ }⟫⟫

AD_4nXcvIXNOx58ieB38gK5v_YqUr1lm_xZXbMYrfaWSvZLda3vclQ7fX5ocIwbSZf2SA08jtWNxDfANZ84NccwDPxAoOW3PtpzgqLwe9_ySRS7_xKqMPCpX5a5GMWtTbzuidhAS9XOdgA?key=6ajxsR9OTrrxDOCbMpbbogAD_4nXe01dVkLoo0J9OyO8CSDx1ILcSGtW6-0SDe9Bdy_Miz6zBmaj_ntBkkuf9sMWJcyiX74JDADWVZOS0c5d6EFP7b-eWdb_5QHG9ZEr68-SWVm-GNqUI9ge0sm0-i-sAARGbQmndl?key=6ajxsR9OTrrxDOCbMpbbog

Here, _lookup means the engine jumps directly to the matching interface instead of scanning all interfaces.

Advanced Example: _lookup Optimization in Larger Queries

The _lookup optimization isn’t just for simple interface-name matches — it also applies in larger, more complex queries when you’re joining one dataset to another via a unique key using ==. In fact, it can make a huge difference in scenarios where you’re performing the same lookup for many records.

Here’s an example using EPSS data which is a list 

Optimized Version (with _lookup)

EPSS = network.externalSources.first_org.ePSS_LATEST;



foreach device in network.devices

foreach cveFinding in device.cveFindings

let epssRecord = max(foreach epssRecord in EPSS

                     where epssRecord.cve == cveFinding.cveId

                     select epssRecord)

select {

  deviceName: device.name,

  cveFindingCveId: cveFinding.cveId,

  isVulnerable: cveFinding.isVulnerable,

  basis: cveFinding.basis,

  "EPSS Score": epssRecord?.epss,

  "EPSS Percentile": epssRecord?.percentile,

  "EPSS Date": epssRecord?.date

}

AD_4nXeOPxfV5j9CPdaqEgEX7BzjYjCxximheTCgipPX9Z-TSHhQkcNvclHsWmYhVbOG2xLc0yIp6hTL4-WL9yeiFsFYG7JBK-NrPnaSo8ycaA3MvQQpo1j1RHTbhRCfRMGF89mOxLx6eQ?key=6ajxsR9OTrrxDOCbMpbbogAD_4nXeEkVBHhNH4IMldj4PCIaGZdqfM3TfeWjpgpLziYPPVHJ5Tzknbvi0_dgnYqc1lyl_rl-k1iC1yDVGjnsnVNL_I6ext6jt1TQNl7xxo5HlNUGH7VcjP6nawVpdcZdzVjvqeZoCnlw?key=6ajxsR9OTrrxDOCbMpbbog

Query Debug Panel

parallel_foreach device in network.devices

⟪foreach cveFinding in ⟪device.cveFindings⟫

 let epssRecord = @builtin/max(

   foreach epssRecord

     in _lookup(./EPSS, "cve", cveFinding.cveId)

   select epssRecord)

 select { ... }

AD_4nXeaGskADVrNTN-YJfHoqoWab-vhmwaqclWG2_cZP9M021REQcRNyVXKhOD1YI1nxucR7fkCqlSlhWjuaqYSC1skYYm2_43CS2C1nr1cPN9Uc8fr2At_6jtbc49MNa8aCSLARRzm4w?key=6ajxsR9OTrrxDOCbMpbbogAD_4nXc3OUbpn8y-9Mc56-J7fjp1x_5LyhnK-tTUXhzSUQExm0qmRzE-CvrkurS0YqhwcnW43rECjtJcI0SB6zo2tNv_catSKmonSxo4TztHsEODhFQEw-F-A_Y9M9nYdnY_QqaI62Rfsw?key=6ajxsR9OTrrxDOCbMpbbog

Not Optimized

If you wrap the same logic in a function:

EPSS = network.externalSources.first_org.ePSS_LATEST;

getEPSSRecord(cveId) =
max(foreach epssRecord in EPSS
where epssRecord.cve == cveId
select epssRecord);

foreach device in network.devices
foreach cveFinding in device.cveFindings
let epssRecord = getEPSSRecord(cveFinding.cveId)
select {
deviceName: device.name,
cveFindingCveId: cveFinding.cveId,
isVulnerable: cveFinding.isVulnerable,
basis: cveFinding.basis,
"EPSS Score": epssRecord?.epss,
"EPSS Percentile": epssRecord?.percentile,
"EPSS Date": epssRecord?.date
}

AD_4nXeeNVm8jSmd-o27Xati5Fr9pWc2x3GRUg1EbLOkOWkjlCl4iUdH-taIHGeFiNUDOr8m0anoNyIeRF-qAB37y1HwASglqil8gB0M75xKXK1o7UPZP5KQdNpYSnibSNd1r2GXR0UCLQ?key=6ajxsR9OTrrxDOCbMpbbogAD_4nXcggXilbQsw4ui6D44M3K9o14RA6aNJSLJhfLTFJrP2tyjBsMo94uvyB_bzxaMPDkFERq384IT6M5tvGkvHrSbMEj3Q-KNYDkE3Ugc_M395WE6c_ZWe-yq9PmWSWL-Y25xPhJgOzg?key=6ajxsR9OTrrxDOCbMpbbog

There is no _lookup optimization — even though the logic is the same as the inline version. The optimizer is conservative and when the lookup logic is wrapped in a separate function, the optimizer can’t verify there’s no other logic or side effects, so it won’t rewrite it to a _lookup.

 

Query Debug Panel

parallel_foreach device in network.devices

⟪foreach cveFinding in ⟪device.cveFindings⟫

 let epssRecord = ./getEPSSRecord(cveFinding.cveId)

 select { ... }

AD_4nXdB8isB8WiaxCdM-HTRKTy7Nkwt6_mAM8IA5r6WaZvzGIYHYNHDTTrM1osirgczLnREeg8_CqPx7DNCkpECQT6cMxhbNyWkrWSkfD8kS4H_7cskrprV85Yk0wsr2yh3EG9pybjKPw?key=6ajxsR9OTrrxDOCbMpbbogAD_4nXcCRCOSGyB6RGbZupZqVZUi1zIXlfC11vZC_H0MQmTUIgxyoeH0NmYPVwivQ5tneIN5urm-3FzyH-nFqZat6HnYYWAuC1tCkE1sxKhLupn9X62j308d-r8J2XV5S9Mddj0iLycCVw?key=6ajxsR9OTrrxDOCbMpbbog

Practical Implications

  • In the optimized version, each CVE lookup is an O(1) key access into the EPSS list.
  • In the non-optimized version, each lookup scans the entire EPSS list, making it O(n) per finding.
  • On large datasets (e.g., hundreds of thousands of EPSS records × thousands of device findings), this difference can mean minutes of runtime saved.

 

Best Practice Takeaways

 Inline the loop when possible if you want _lookup optimization.

  1. Inline the loop when possible if you want _lookup optimization.
  2. Avoid wrapping direct key lookups in helper functions unless you’re willing to trade speed for reuse/readability.
  3. Always check your debug output for _lookup when performance matters.
  4. Use exact equality on unique keys (==) to trigger the optimization — no regex or pattern match.

 

Be the first to reply!

Reply