EQL 语法参考

编辑

基本语法

编辑

EQL 查询需要一个事件类别和一个匹配条件。 where 关键字将它们连接起来。

event_category where condition

事件类别是 事件类别字段 的索引值。默认情况下,EQL 搜索 API 使用来自 Elastic Common Schema (ECS)event.category 字段。您可以使用 API 的 event_category_field 参数指定另一个事件类别字段。

例如,以下 EQL 查询匹配事件类别为 processprocess.namesvchost.exe 的事件

process where process.name == "svchost.exe"

匹配任何事件类别

编辑

要匹配任何类别的事件,请使用 any 关键字。您还可以使用 any 关键字搜索没有事件类别字段的文档。

例如,以下 EQL 查询匹配任何 network.protocol 字段值为 http 的文档

any where network.protocol == "http"

转义事件类别

编辑

使用包含的双引号 (") 或三个包含的双引号 (""") 来转义包含以下内容的事件类别:

  • 包含特殊字符,例如连字符 (-) 或点 (.)
  • 包含空格
  • 以数字开头
".my.event.category"
"my-event-category"
"my event category"
"6eventcategory"

""".my.event.category"""
"""my-event-category"""
"""my event category"""
"""6eventcategory"""

转义字段名

编辑

使用包含的反引号 (`) 来转义包含以下内容的字段名:

  • 包含连字符 (-)
  • 包含空格
  • 以数字开头
`my-field`
`my field`
`6myfield`

使用双反引号 (``) 来转义字段名中的任何反引号 (`)。

my`field -> `my``field`

条件

编辑

条件由事件必须匹配的一个或多个标准组成。您可以使用以下运算符指定和组合这些标准。大多数 EQL 运算符默认情况下区分大小写。

比较运算符

编辑
<   <=   ==   :   !=   >=   >
< (小于)
如果运算符左侧的值小于右侧的值,则返回 true。否则返回 false
<= (小于或等于)
如果运算符左侧的值小于或等于右侧的值,则返回 true。否则返回 false
== (等于,区分大小写)
如果运算符左侧和右侧的值相等,则返回 true。否则返回 false。不支持通配符。
: (等于,不区分大小写)
如果运算符左侧和右侧的字符串相等,则返回 true。否则返回 false。只能用于比较字符串。支持 通配符列表查找
!= (不等于,区分大小写)
如果运算符左侧和右侧的值不相等,则返回 true。否则返回 false。不支持通配符。
>= (大于或等于)
如果运算符左侧的值大于或等于右侧的值,则返回 true。否则返回 false。比较字符串时,运算符使用区分大小写的词典顺序。
> (大于)
如果运算符左侧的值大于右侧的值,则返回 true。否则返回 false。比较字符串时,运算符使用区分大小写的词典顺序。

= 不支持作为等于运算符。请改用 ==:

模式比较关键字

编辑
my_field like  "VALUE*"         // case-sensitive wildcard matching
my_field like~ "value*"         // case-insensitive wildcard matching

my_field regex  "VALUE[^Z].?"   // case-sensitive regex matching
my_field regex~ "value[^z].?"   // case-insensitive regex matching
like (区分大小写)
如果关键字左侧的字符串与右侧的 通配符模式 匹配,则返回 true。支持 列表查找。只能用于比较字符串。对于不区分大小写的匹配,请使用 like~
regex (区分大小写)
如果关键字左侧的字符串与右侧的正则表达式匹配,则返回 true。有关支持的正则表达式语法,请参见 正则表达式语法。支持 列表查找。只能用于比较字符串。对于不区分大小写的匹配,请使用 regex~
比较的限制
编辑

您不能链接比较。请改用比较之间的 逻辑运算符。例如,不支持 foo < bar <= baz。但是,您可以将表达式重写为 foo < bar and bar <= baz,这是受支持的。

您也不能将字段与另一个字段进行比较,即使字段已使用 函数 更改。

示例
以下 EQL 查询将 process.parent_name 字段值与静态值 foo 进行比较。此比较受支持。

但是,该查询还将 process.parent.name 字段值与 process.name 字段进行比较。此比较不受支持,并且将为整个查询返回错误。

process where process.parent.name == "foo" and process.parent.name == process.name

相反,您可以重写查询以将 process.parent.nameprocess.name 字段都与静态值进行比较。

process where process.parent.name == "foo" and process.name == "foo"

逻辑运算符

编辑
and  or  not
and
仅当左侧和右侧的条件返回 true 时,才返回 true。否则返回 false
or
如果左侧或右侧的条件之一为 true,则返回 true。否则返回 false
not
如果右侧的条件为 false,则返回 true

查找运算符

编辑
my_field in ("Value-1", "VALUE2", "VAL3")                 // case-sensitive
my_field in~ ("value-1", "value2", "val3")                // case-insensitive

my_field not in ("Value-1", "VALUE2", "VAL3")             // case-sensitive
my_field not in~ ("value-1", "value2", "val3")            // case-insensitive

my_field : ("value-1", "value2", "val3")                  // case-insensitive

my_field like  ("Value-*", "VALUE2", "VAL?")              // case-sensitive
my_field like~ ("value-*", "value2", "val?")              // case-insensitive

my_field regex  ("[vV]alue-[0-9]", "VALUE[^2].?", "VAL3") // case-sensitive
my_field regex~  ("value-[0-9]", "value[^2].?", "val3")   // case-insensitive
in (区分大小写)
如果该值包含在提供的列表中,则返回 true。对于不区分大小写的匹配,请使用 in~
not in (区分大小写)
如果该值不包含在提供的列表中,则返回 true。对于不区分大小写的匹配,请使用 not in~
: (不区分大小写)
如果字符串包含在提供的列表中,则返回 true。只能用于比较字符串。
like (区分大小写)
如果字符串与提供的列表中的 通配符模式 匹配,则返回 true。只能用于比较字符串。对于不区分大小写的匹配,请使用 like~
regex (区分大小写)
如果字符串与提供的列表中的正则表达式模式匹配,则返回 true。有关支持的正则表达式语法,请参见 正则表达式语法。只能用于比较字符串。对于不区分大小写的匹配,请使用 regex~

数学运算符

编辑
+  -  *  /  %
+ (加)
将运算符左侧和右侧的值相加。
- (减)
从左侧的值中减去运算符右侧的值。
* (乘)
将运算符左侧和右侧的值相乘。
/ (除)

将运算符左侧的值除以右侧的值。

如果被除数和除数都是整数,则除法 (\) 操作会向下舍入任何返回的浮点数到最接近的整数。要避免舍入,请将被除数或除数转换为浮点数。

示例
process.args_count 字段是一个 long 整数字段,包含进程参数的计数。

用户可能会期望以下 EQL 查询仅匹配 process.args_count 值为 4 的事件。

process where ( 4 / process.args_count ) == 1

但是,EQL 查询匹配 process.args_count 值为 34 的事件。

对于 process.args_count 值为 3 的事件,除法操作返回浮点数 1.333...,该浮点数向下舍入为 1

要仅匹配 process.args_count 值为 4 的事件,请将被除数或除数转换为浮点数。

以下 EQL 查询将整数 4 更改为等效的浮点数 4.0

process where ( 4.0 / process.args_count ) == 1
% (模)
将运算符左侧的值除以右侧的值。仅返回余数。

匹配任何条件

编辑

要仅根据事件类别匹配事件,请使用 where true 条件。

例如,以下 EQL 查询匹配任何 file 事件

file where true

要匹配任何事件,您可以将 any 关键字与 where true 条件结合使用

any where true

可选字段

编辑

默认情况下,EQL 查询只能包含搜索的数据集中存在的字段。如果字段具有 显式动态运行时 映射,则该字段存在于数据集中。如果 EQL 查询包含不存在的字段,则会返回错误。

如果您不确定字段是否存在于数据集中,请使用 ? 运算符将字段标记为可选。如果可选字段不存在,查询会将其替换为 null,而不是返回错误。

示例
在以下查询中,user.id 字段是可选的。

network where ?user.id != null

如果数据集包含user.id字段,则查询将匹配包含user.id值的任何network事件。如果数据集不包含user.id字段,则EQL会将查询解释为

network where null != null

在这种情况下,查询不匹配任何事件。

检查字段是否存在

编辑

要匹配包含任何字段值的事件,请使用!=运算符将该字段与null进行比较。

?my_field != null

要匹配不包含字段值的事件,请使用==运算符将该字段与null进行比较。

?my_field == null

字符串

编辑

字符串用双引号 (") 括起来。

"hello world"

不支持用单引号 (') 括起来的字符串。

字符串中的转义字符

编辑

在字符串中使用特殊字符(例如回车符或双引号 ("))时,必须在其前面加上反斜杠 (\) 进行转义。

"example \r of \" escaped \n characters"
转义序列 字面字符

\n

换行符(换行符)

\r

回车符

\t

制表符

\\

反斜杠 (\)

\"

双引号 (")

您可以使用十六进制\u{XXXXXXXX}转义序列转义Unicode字符。十六进制值可以是2到8个字符,并且不区分大小写。小于8个字符的值将用零填充。您可以使用这些转义序列在字符串中包含不可打印字符或从右到左 (RTL) 字符。例如,您可以将从右到左标记 (RLM)转义为\u{200f}\u{200F}\u{0000200f}

单引号 (') 字符保留供将来使用。您不能将转义的单引号 (\') 用于字面字符串。请改用转义的双引号 (\")。

原始字符串

编辑

原始字符串将特殊字符(如反斜杠 (\))视为字面字符。原始字符串用三个双引号 (""") 括起来。

"""Raw string with a literal double quote " and blackslash \ included"""

原始字符串不能包含三个连续的双引号 (""")。请改用带有\"转义序列的常规字符串。

"String containing \"\"\" three double quotes"

通配符

编辑

对于使用:运算符或like关键字的字符串比较,您可以使用*?通配符来匹配特定模式。*通配符匹配零个或多个字符。

my_field : "doc*"     // Matches "doc", "docs", or "document" but not "DOS"
my_field : "*doc"     // Matches "adoc" or "asciidoc"
my_field : "d*c"      // Matches "doc" or "disc"

my_field like "DOC*"  // Matches "DOC", "DOCS", "DOCs", or "DOCUMENT" but not "DOS"
my_field like "D*C"   // Matches "DOC", "DISC", or "DisC"

?通配符正好匹配一个字符。

my_field : "doc?"     // Matches "docs" but not "doc", "document", or "DOS"
my_field : "?doc"     // Matches "adoc" but not "asciidoc"
my_field : "d?c"      // Matches "doc" but not "disc"

my_field like "DOC?"  // Matches "DOCS" or "DOCs" but not "DOC", "DOCUMENT", or "DOS"
my_field like "D?c"   // Matches "DOC" but not "DISC"

:运算符和like关键字还支持列表查找中的通配符。

my_field : ("doc*", "f*o", "ba?", "qux")
my_field like ("Doc*", "F*O", "BA?", "QUX")

序列

编辑

您可以使用EQL序列来描述和匹配一系列有序事件。序列中的每个项目都是一个事件类别和事件条件,用方括号 ([ ]) 括起来。事件按升序时间顺序排列,最新的事件排在最后。

sequence
  [ event_category_1 where condition_1 ]
  [ event_category_2 where condition_2 ]
  ...

示例
以下EQL序列查询匹配此一系列有序事件

  1. 以一个具有以下特征的事件开始

    • 事件类别为file
    • file.extensionexe
  2. 随后是一个事件类别为process的事件
sequence
  [ file where file.extension == "exe" ]
  [ process where true ]

with maxspan语句

编辑

您可以使用with maxspan将序列限制在指定的时间范围内。匹配序列中的所有事件都必须在此持续时间内发生,从第一个事件的时间戳开始。

maxspan接受时间值参数。

sequence with maxspan=30s
  [ event_category_1 where condition_1 ] by field_baz
  [ event_category_2 where condition_2 ] by field_bar
  ...

示例
以下序列查询使用maxspan值为15m(15分钟)。匹配序列中的事件必须在第一个事件的时间戳后的15分钟内发生。

sequence with maxspan=15m
  [ file where file.extension == "exe" ]
  [ process where true ]

缺失事件

编辑

使用!匹配缺失事件:在时间范围内受约束的序列中不满足给定条件的事件。

sequence with maxspan=1h
  [ event_category_1 where condition_1 ]
  ![ event_category_2 where condition_2 ]
  [ event_category_3 where condition_3 ]
  ...

缺失事件子句可以在序列的开头、结尾和/或中间使用,可以与正事件子句任意组合。一个序列可以有多个缺失事件子句,但需要至少一个正子句。with maxspan在存在缺失事件子句时是必须的。

示例
以下序列查询查找登录事件,这些事件在5秒内没有后续的注销事件。

sequence by host.name, user.name with maxspan=5s
  [ authentication where event.code : "4624" ]
  ![ authentication where event.code : "4647" ]

by关键字

编辑

在序列查询中使用by关键字仅匹配共享相同值的事件,即使这些值位于不同的字段中。这些共享值称为连接键。如果连接键应跨所有事件位于同一字段中,请使用sequence by

sequence by field_foo
  [ event_category_1 where condition_1 ] by field_baz
  [ event_category_2 where condition_2 ] by field_bar
  ...

示例
以下序列查询使用by关键字将匹配事件限制为

  • 具有相同user.name值的事件
  • file事件的file.path值等于后续process事件的process.executable值。
sequence
  [ file where file.extension == "exe" ] by user.name, file.path
  [ process where true ] by user.name, process.executable

由于user.name字段在序列中的所有事件中共享,因此可以使用sequence by包含它。以下序列等效于前一个序列。

sequence by user.name
  [ file where file.extension == "exe" ] by file.path
  [ process where true ] by process.executable

您可以将sequence bywith maxspan组合起来,通过字段值和时间范围来约束序列。

sequence by field_foo with maxspan=30s
  [ event_category_1 where condition_1 ]
  [ event_category_2 where condition_2 ]
  ...

示例
以下序列查询使用sequence bywith maxspan仅匹配以下事件序列

  • 共享相同的user.name字段值
  • 在第一个匹配事件后的15m(15分钟)内发生
sequence by user.name with maxspan=15m
  [ file where file.extension == "exe" ]
  [ process where true ]

可选by字段

编辑

默认情况下,连接键必须是非null字段值。要允许null连接键,请使用?运算符将by字段标记为可选。如果您不确定要搜索的数据集是否包含by字段,这也很有用。

示例
以下序列查询使用sequence by将匹配事件限制为

  • 具有相同process.pid值的事件,不包括null值。如果要搜索的数据集不包含process.pid字段,则查询将返回错误。
  • 具有相同process.entity_id值的事件,包括null值。如果事件不包含process.entity_id字段,则其process.entity_id值被视为null。即使要搜索的数据集不包含process.pid字段,这也适用。
sequence by process.pid, ?process.entity_id
  [process where process.name == "regsvr32.exe"]
  [network where true]

until关键字

编辑

您可以使用until关键字为序列指定一个过期事件。如果此过期事件发生在序列中匹配事件之间,则序列将过期且不被视为匹配。如果过期事件发生在序列中匹配事件之后,则序列仍被视为匹配。结果中不包含过期事件。

sequence
  [ event_category_1 where condition_1 ]
  [ event_category_2 where condition_2 ]
  ...
until [ event_category_3 where condition_3 ]

示例
数据集包含以下事件序列,按共享ID分组

A, B
A, B, C
A, C, B

以下EQL查询搜索包含事件A后跟事件B的序列的数据集。事件C用作过期事件。

sequence by ID
  A
  B
until C

查询匹配序列A, BA, B, C,但不匹配A, C, B

until关键字在搜索Windows事件日志中的进程序列时非常有用。

在Windows中,进程ID (PID) 仅在进程运行时才唯一。进程终止后,其PID可以被重复使用。

您可以使用bysequence by关键字搜索具有相同PID值的事件序列。

示例
以下EQL查询使用sequence by关键字匹配共享相同process.pid值的事件序列。

sequence by process.pid
  [ process where event.type == "start" and process.name == "cmd.exe" ]
  [ process where file.extension == "exe" ]

但是,由于PID重复使用,这可能导致匹配序列包含跨无关进程的事件。为了防止误报,您可以使用until关键字在进程终止事件之前结束匹配序列。

以下EQL查询使用until关键字在process事件的event.typestop之前结束序列。这些事件表示进程已终止。

sequence by process.pid
  [ process where event.type == "start" and process.name == "cmd.exe" ]
  [ process where file.extension == "exe" ]
until [ process where event.type == "stop" ]

with runs语句

编辑

使用with runs语句在序列查询中连续运行相同的事件条件。例如

sequence
  [ process where event.type == "creation" ]
  [ library where process.name == "regsvr32.exe" ] with runs=3
  [ registry where true ]

等效于

sequence
  [ process where event.type == "creation" ]
  [ library where process.name == "regsvr32.exe" ]
  [ library where process.name == "regsvr32.exe" ]
  [ library where process.name == "regsvr32.exe" ]
  [ registry where true ]

runs值必须在1100(含)之间。

您可以将with runs语句与by关键字一起使用。例如

sequence
  [ process where event.type == "creation" ] by process.executable
  [ library where process.name == "regsvr32.exe" ] by dll.path with runs=3

示例

编辑

您可以使用EQL示例来描述和匹配一系列时间上无序的事件。示例中的所有事件都共享使用by关键字指定的字段的一个或多个相同值(连接键)。示例中的每个项目都是一个事件类别和事件条件,用方括号 ([ ]) 括起来。事件按其匹配的过滤器的顺序列出。

sample by join_key
  [ event_category_1 where condition_1 ]
  [ event_category_2 where condition_2 ]
  ...

示例
以下EQL示例查询返回最多10个具有host唯一值的示例。每个示例包含两个事件

  1. 以一个具有以下特征的事件开始

    • 事件类别为file
    • file.extensionexe
  2. 随后是一个事件类别为process的事件
sample by host
  [ file where file.extension == "exe" ]
  [ process where true ]

示例查询未考虑事件的时序顺序。不支持 with maxspanwith runs 语句以及 until 关键字。

函数

编辑

您可以使用 EQL 函数来转换数据类型、执行数学运算、操作字符串等等。有关支持函数的列表,请参阅 函数参考

不区分大小写的函数

编辑

默认情况下,大多数 EQL 函数都区分大小写。要使函数不区分大小写,请在函数名称后使用 ~ 运算符

stringContains(process.name,".exe")  // Matches ".exe" but not ".EXE" or ".Exe"
stringContains~(process.name,".exe") // Matches ".exe", ".EXE", or ".Exe"

函数如何影响搜索性能

编辑

在 EQL 查询中使用函数可能会导致搜索速度变慢。如果您经常使用函数转换索引数据,则可以通过在索引期间进行这些更改来加快搜索速度。但是,这通常意味着索引速度会变慢。

示例
索引包含 file.path 字段。file.path 包含文件的完整路径,包括文件扩展名。

在运行 EQL 搜索时,用户经常使用 file.path 字段中的 endsWith 函数来匹配文件扩展名

file where endsWith(file.path,".exe") or endsWith(file.path,".dll")

虽然这可以工作,但编写起来可能会很重复,并且会降低搜索速度。要加快搜索速度,您可以改为执行以下操作

  1. 添加新字段file.extension,到索引中。file.extension 字段将仅包含来自 file.path 字段的文件扩展名。
  2. 使用包含 grok 处理器或其他预处理器工具的 摄取管道 在索引之前从 file.path 字段提取文件扩展名。
  3. 将提取的文件扩展名索引到 file.extension 字段。

这些更改可能会降低索引速度,但可以加快搜索速度。用户可以使用 file.extension 字段,而不是多次调用 endsWith 函数

file where file.extension in ("exe", "dll")

我们建议在生产环境中部署任何索引更改之前进行测试和基准测试。请参阅 调整索引速度调整搜索速度

管道

编辑

EQL 管道过滤、聚合和后处理由 EQL 查询返回的事件。您可以使用管道缩小 EQL 查询结果的范围或使它们更具体。

管道使用管道 (|) 字符分隔。

event_category where condition | pipe

示例
以下 EQL 查询使用 tail 管道仅返回与查询匹配的 10 个最新的事件。

authentication where agent.id == 4624
| tail 10

您可以将管道的输出传递给另一个管道。这使您可以对单个查询使用多个管道。

有关支持管道的列表,请参阅 管道参考

限制

编辑

EQL 具有以下限制。

EQL 使用 fields 参数

编辑

EQL 使用搜索 API 的 fields 参数 检索字段值。fields 参数的任何限制也适用于 EQL 查询。例如,如果任何返回的字段或索引级别禁用了 _source,则无法检索值。

比较字段

编辑

您不能使用 EQL 比较运算符将一个字段与另一个字段进行比较。即使使用 函数更改了字段,也适用此规则。

不支持文本字段

编辑

EQL 搜索不支持 text 字段。要搜索 text 字段,请使用 EQL 搜索 API 的 Query DSL filter 参数。

EQL 在嵌套字段上的搜索

编辑

您不能使用 EQL 搜索 nested 字段的值或 nested 字段的子字段。但是,否则支持包含 nested 字段映射的数据流和索引。

与 Endgame EQL 语法的区别

编辑

Elasticsearch EQL 与 Elastic Endgame EQL 语法 的区别如下

  • 在 Elasticsearch EQL 中,大多数运算符都区分大小写。例如,process_name == "cmd.exe" 不等效于 process_name == "Cmd.exe"
  • 在 Elasticsearch EQL 中,函数区分大小写。要使函数不区分大小写,请使用 ~,例如 endsWith~(process_name, ".exe")
  • 对于不区分大小写的相等比较,请使用 : 运算符。 *? 都被识别为通配符字符。
  • ==!= 运算符不会扩展通配符字符。例如,process_name == "cmd*.exe"* 解释为文字星号,而不是通配符。
  • 对于通配符匹配,在区分大小写时使用 like 关键字,在不区分大小写时使用 like~: 运算符等效于 like~
  • 对于正则表达式匹配,请使用 regexregex~
  • = 不能替换 == 运算符。
  • 用单引号 (') 括起来的字符串不受支持。请改用双引号 (") 括起来字符串。
  • ?"?' 不表示原始字符串。请改用三个双引号 (""") 括起来原始字符串。
  • Elasticsearch EQL 不支持

序列查询如何处理匹配

编辑

序列查询 不会查找序列的所有潜在匹配项。对于大型事件数据集,这种方法速度太慢且成本太高。相反,序列查询将挂起的序列匹配项作为 状态机 处理

  • 序列查询中的每个事件项都是机器中的一个状态。
  • 一次只能在一个状态中存在一个挂起的序列。
  • 如果两个挂起的序列同时处于同一状态,则最新的序列会覆盖旧的序列。
  • 如果查询包含 by 字段,则查询会为每个唯一的 by 字段值使用一个单独的状态机。
示例

数据集按升序时序顺序包含以下 process 事件

{ "index" : { "_id": "1" } }
{ "user": { "name": "root" }, "process": { "name": "attrib" }, ...}
{ "index" : { "_id": "2" } }
{ "user": { "name": "root" }, "process": { "name": "attrib" }, ...}
{ "index" : { "_id": "3" } }
{ "user": { "name": "elkbee" }, "process": { "name": "bash" }, ...}
{ "index" : { "_id": "4" } }
{ "user": { "name": "root" }, "process": { "name": "bash" }, ...}
{ "index" : { "_id": "5" } }
{ "user": { "name": "root" }, "process": { "name": "bash" }, ...}
{ "index" : { "_id": "6" } }
{ "user": { "name": "elkbee" }, "process": { "name": "attrib" }, ...}
{ "index" : { "_id": "7" } }
{ "user": { "name": "root" }, "process": { "name": "attrib" }, ...}
{ "index" : { "_id": "8" } }
{ "user": { "name": "elkbee" }, "process": { "name": "bash" }, ...}
{ "index" : { "_id": "9" } }
{ "user": { "name": "root" }, "process": { "name": "cat" }, ...}
{ "index" : { "_id": "10" } }
{ "user": { "name": "elkbee" }, "process": { "name": "cat" }, ...}
{ "index" : { "_id": "11" } }
{ "user": { "name": "root" }, "process": { "name": "cat" }, ...}

EQL 序列查询搜索数据集

sequence by user.name
  [process where process.name == "attrib"]
  [process where process.name == "bash"]
  [process where process.name == "cat"]

查询的事件项对应于以下状态

  • 状态 A:[process where process.name == "attrib"]
  • 状态 B:[process where process.name == "bash"]
  • 完成:[process where process.name == "cat"]
sequence state machine

要查找匹配的序列,查询会为每个唯一的 user.name 值使用单独的状态机。根据数据集,您可以预期两个状态机:一个用于 root 用户,另一个用于 elkbee

separate state machines

挂起的序列匹配项将按以下方式遍历每个机器的状态

{ "index" : { "_id": "1" } }
{ "user": { "name": "root" }, "process": { "name": "attrib" }, ...}
// Creates sequence [1] in state A for the "root" user.
//
// +------------------------"root"------------------------+
// |  +-----------+     +-----------+     +------------+  |
// |  |  State A  |     |  State B  |     |  Complete  |  |
// |  +-----------+     +-----------+     +------------+  |
// |  |    [1]    |     |           |     |            |  |
// |  +-----------+     +-----------+     +------------+  |
// +------------------------------------------------------+

{ "index" : { "_id": "2" } }
{ "user": { "name": "root" }, "process": { "name": "attrib" }, ...}
// Creates sequence [2] in state A for "root", overwriting sequence [1].
//
// +------------------------"root"------------------------+
// |  +-----------+     +-----------+     +------------+  |
// |  |  State A  |     |  State B  |     |  Complete  |  |
// |  +-----------+     +-----------+     +------------+  |
// |  |    [2]    |     |           |     |            |  |
// |  +-----------+     +-----------+     +------------+  |
// +------------------------------------------------------+

{ "index" : { "_id": "3" } }
{ "user": { "name": "elkbee" }, "process": { "name": "bash" }, ...}
// Nothing happens. The "elkbee" user has no pending sequence to move
// from state A to state B.
//
// +-----------------------"elkbee"-----------------------+
// |  +-----------+     +-----------+     +------------+  |
// |  |  State A  |     |  State B  |     |  Complete  |  |
// |  +-----------+     +-----------+     +------------+  |
// |  |           |     |           |     |            |  |
// |  +-----------+     +-----------+     +------------+  |
// +------------------------------------------------------+

{ "index" : { "_id": "4" } }
{ "user": { "name": "root" }, "process": { "name": "bash" }, ...}
// Sequence [2] moves out of state A for "root".
// State B for "root" now contains [2, 4].
// State A for "root" is empty.
//
// +------------------------"root"------------------------+
// |  +-----------+     +-----------+     +------------+  |
// |  |  State A  |     |  State B  |     |  Complete  |  |
// |  +-----------+ --> +-----------+     +------------+  |
// |  |           |     |   [2, 4]  |     |            |  |
// |  +-----------+     +-----------+     +------------+  |
// +------------------------------------------------------+

{ "index" : { "_id": "5" } }
{ "user": { "name": "root" }, "process": { "name": "bash" }, ...}
// Nothing happens. State A is empty for "root".
//
// +------------------------"root"------------------------+
// |  +-----------+     +-----------+     +------------+  |
// |  |  State A  |     |  State B  |     |  Complete  |  |
// |  +-----------+     +-----------+     +------------+  |
// |  |           |     |   [2, 4]  |     |            |  |
// |  +-----------+     +-----------+     +------------+  |
// +------------------------------------------------------+

{ "index" : { "_id": "6" } }
{ "user": { "name": "elkbee" }, "process": { "name": "attrib" }, ...}
// Creates sequence [6] in state A for "elkbee".
//
// +-----------------------"elkbee"-----------------------+
// |  +-----------+     +-----------+     +------------+  |
// |  |  State A  |     |  State B  |     |  Complete  |  |
// |  +-----------+     +-----------+     +------------+  |
// |  |    [6]    |     |           |     |            |  |
// |  +-----------+     +-----------+     +------------+  |
// +------------------------------------------------------+

{ "index" : { "_id": "7" } }
{ "user": { "name": "root" }, "process": { "name": "attrib" }, ...}
// Creates sequence [7] in state A for "root".
// Sequence [2, 4] remains in state B for "root".
//
// +------------------------"root"------------------------+
// |  +-----------+     +-----------+     +------------+  |
// |  |  State A  |     |  State B  |     |  Complete  |  |
// |  +-----------+     +-----------+     +------------+  |
// |  |    [7]    |     |   [2, 4]  |     |            |  |
// |  +-----------+     +-----------+     +------------+  |
// +------------------------------------------------------+

{ "index" : { "_id": "8" } }
{ "user": { "name": "elkbee" }, "process": { "name": "bash" }, ...}
// Sequence [6, 8] moves to state B for "elkbee".
// State A for "elkbee" is now empty.
//
// +-----------------------"elkbee"-----------------------+
// |  +-----------+     +-----------+     +------------+  |
// |  |  State A  |     |  State B  |     |  Complete  |  |
// |  +-----------+ --> +-----------+     +------------+  |
// |  |           |     |   [6, 8]  |     |            |  |
// |  +-----------+     +-----------+     +------------+  |
// +------------------------------------------------------+

{ "index" : { "_id": "9" } }
{ "user": { "name": "root" }, "process": { "name": "cat" }, ...}
// Sequence [2, 4, 9] is complete for "root".
// State B for "root" is now empty.
// Sequence [7] remains in state A.
//
// +------------------------"root"------------------------+
// |  +-----------+     +-----------+     +------------+  |
// |  |  State A  |     |  State B  |     |  Complete  |  |
// |  +-----------+     +-----------+ --> +------------+  |
// |  |    [7]    |     |           |     |  [2, 4, 9] |
// |  +-----------+     +-----------+     +------------+  |
// +------------------------------------------------------+

{ "index" : { "_id": "10" } }
{ "user": { "name": "elkbee" }, "process": { "name": "cat" }, ...}
// Sequence [6, 8, 10] is complete for "elkbee".
// State A and B for "elkbee" are now empty.
//
// +-----------------------"elkbee"-----------------------+
// |  +-----------+     +-----------+     +------------+  |
// |  |  State A  |     |  State B  |     |  Complete  |  |
// |  +-----------+     +-----------+ --> +------------+  |
// |  |           |     |           |     | [6, 8, 10] |
// |  +-----------+     +-----------+     +------------+  |
// +------------------------------------------------------+

{ "index" : { "_id": "11" } }
{ "user": { "name": "root" }, "process": { "name": "cat" }, ...}
// Nothing happens.
// The machines for "root" and "elkbee" remain the same.
//
// +------------------------"root"------------------------+
// |  +-----------+     +-----------+     +------------+  |
// |  |  State A  |     |  State B  |     |  Complete  |  |
// |  +-----------+     +-----------+     +------------+  |
// |  |    [7]    |     |           |     |  [2, 4, 9] |
// |  +-----------+     +-----------+     +------------+  |
// +------------------------------------------------------+
//
// +-----------------------"elkbee"-----------------------+
// |  +-----------+     +-----------+     +------------+  |
// |  |  State A  |     |  State B  |     |  Complete  |  |
// |  +-----------+     +-----------+     +------------+  |
// |  |           |     |           |     | [6, 8, 10] |
// |  +-----------+     +-----------+     +------------+  |
// +------------------------------------------------------+