Skip to content


This page introduces the MQL operators, including a syntax summary, usage examples, and a brief description of each operator.

For the most part these operations use their RxJava analogs:

MQL operator RxJava operator
select map
window window
where filter
group by groupBy
order by n/a
limit take

For this reason you may find the ReactiveX documentation useful as well, though it is a goal of MQL to provide you with a layer of abstraction such that you will not need to use or think about ReactiveX when writing queries.

MQL attempts to stay true to the SQL syntax in order to reduce friction for new users writing queries and to allow them to leverage their experiences with SQL.

An essential concept in MQL is the “property,” which can take on one of several forms:

  • e["this"]["accesses"]["nested"]["properties"]
  • 15
  • "string literal"

Most of the MQL operators use these forms to refer to properties within the event stream, as well as string and numeric literals.

The last detail of note concerns unbounded vs. discrete streams. ReactiveX Observables that are not expected to close are an unbounded stream and must be bounded/discretized in order for certain operators to function with them. The window operator is useful for partitioning unbounded streams into discrete bounded streams for operators such as aggregate, group by, or order by.


Syntax: select property from source


  • "select * from servo"
  • "select nf.node from servo"
  • "select e["tags"]["nf.cluster"] from servo"

The select operator allows you to project data into a new object by specifying which properties should be carried forward into the output. Properties will bear the same name as was used to select them. In the case of numbers and string literals their value will also be their name. In the case of nested properties the result will be a top-level object joined with dots.

For example, the following select example…

  • "select "string literal", 45, e["tags"]["cluster"], nf.node from servo"

…would result in an object like:

  "string literal": "string literal",
  45: 45,
  "tags.cluster": "mantisagent",
  "nf.node": "i-123456"


Be careful to avoid collisions between the top-level objects with dotted names and the nested objects that result in top-level properties with dotted names.


Syntax: "select aggregate(property) from source"


  • "select count(e["node"]) from servo window 60"
  • "select count(e["node"]) from servo window 60 where e["metrics"]["latency"] > 350"
  • "select average(e["metrics"]["latency"]), e["node"] from servo window 10"

Supported Aggregates:

  • Min
  • Max
  • Average
  • Count
  • Sum

Aggregates add analysis capabilities to MQL in order to allow you to answer interesting questions about data in real-time. They can be intermixed with regular properties in order to select properties and compute information about those properties, such as the last example above which computes average latency on a per-node basis in 10 second windows.


Aggregates require that the stream on which they operate be discretized. You can ensure this either by feeding MQL a cold Observable in its context or by using the window operator on an unbounded stream.

You can use the distinct operator on a property to operate only on unique items within the window. This is particularly useful with the count aggregate if you want to count the number of items with some distinct property value in that window. For example:

  • "select count(distinct esn) from stream window 10 1"


Syntax: "select something from source"


  • "select * from servo"

The from clause indicates to MQL from which Observable it should draw data. This requires some explanation, as it bears different meaning in different contexts.

Directly against queryable sources the from clause refers to the source Observable no matter which name is given. The operator is in fact optional, and the name of the source is arbitrary in this context, and the clause will be inserted for you if you omit it.

When you use MQL as a library, the source corresponds with the names in the context map parameter. The second parameter to eval-mql() is a Map<String, Observable<T>> and the from clause will attempt to fetch the Observable from this Map.


Syntax: "WINDOW integer"


  • "select node, latency from servo window 10"
  • "select MAX(latency) from servo window 60"

The window clause divides an otherwise unbounded stream of data into discrete time-bounded streams. The integer parameter is the number of seconds over which to perform this bounding. For example "select * from observable window 10" will produce 10-second windows of data.

This discretization of streams is important for use with aggregate operations as well as group-by and order-by clauses which cannot be executed on, and will hang on, unbounded streams.


Syntax: "select property from source where predicate"


  • "select * from servo where node == "i-123456" AND e["metrics"]["latency"] > 350"
  • "select * from servo where (node == "i-123456" AND e["metrics"]["latency"] > 350) OR node == "1-abcdef""
  • "select * from servo where node ==~ /i-123/"
  • "select * from servo where e["metrics"]["latency"] != null
  • "select * from servo where e["list_of_requests"][*]["status"] == "success"

The where clause filters any events out of the stream which do not match a given predictate. Predicates support AND and OR operations. Binary operators supported are =, ==, <>, !=, <, <=, >, >=, ==~. The first two above are both equality, and either of the next two represent not-equal. You can use the last of those operators, ==~, with a regular expression as in: "where property ==~ /regex/" (any Java regular expression will suffice). To take an event with certain attribute, use e["{{key}}"] != null.

If the event contains a list field, you can use the [*] operator to match objects inside that list. For example, say the events have a field called list_of_requests and each item in the list has a field called status. Then, the condition e["list_of_requests"][*]["status"] == "success" will return true if at least 1 item has status equals success. Further, you can combine multiple conditions on the list. For example,

e["list_of_requests"][*]["status"] == "success" AND e["list_of_requests"][*]["url"] == "abc"

This condition returns true if at least 1 item has status equals success and url equals abc.

group by

Syntax: "GROUP BY property"


  • "select node, latency from servo where latency > 300.0 group by node"
  • "select MAX(latency), e["node"] from servo group by node"

group by groups values in the output according to some property. This is particularly useful in conjunction with aggregate operators in which one can compute aggregate values over a group. For example, the following query calculates the maximum latency observed for each node in 60-second windows:

  • "select MAX(latency), e["node"] from servo window 60 group by node"


The group by clause requires that the stream on which it operates be discretized. You can ensure this either by feeding MQL a cold Observable in its context or by using the window operator on an unbounded stream.

order by

Syntax: "ORDER BY property"


  • "select node, latency from servo group by node order by latency"

The order by operator orders the results in the inner-most Observable by the specified property. For example, the query "select * from observable window 5 group by nf.node order by latency" would produce an Observable of Observables (windows) of Observables (groups). The events within the groups would be ordered by their latency property.


The order by clause requires that the stream on which it operates be discretized. You can ensure this either by feeding MQL a cold Observable in its context or by using the window operator on an unbounded stream.


Syntax: "LIMIT integer"


  • "select * from servo limit 10"
  • "select AVERAGE(latency) from servo window 60 limit 10"

The limit operator takes as a parameter a single integer and bounds the number of results to ≤ integer.


Limit does not discretize the stream for earlier operators such as group by, order by, aggregates.