注意事项
基准测试非常困难,这里的目的是查看响应时间中的模式
,并特别查看可以针对Postgres
和ElasticSearch
执行的 ORM 查询,以识别某些响应的模式和条件。
您应始终尝试针对自己的数据运行自己的基准测试。有许多因素(卷、基数、关系、数据倾斜等)是随机生成的数据无法满足的。
不要关注绝对数字
...默认情况下,ElasticSearch 有效地为每个属性获取一个索引。我们通常无法在我们的 OLTP 数据库上执行此操作(在表中的每一列上都有一个索引)。
设置
填充客户、产品、订单、订单详细信息(使用示例应用程序的修改:example-kotlin-web)
- 订单:500,000
- 客户:100,000
- 产品:30,000
- 订单行:5,263,023
订单使用orderDate
生成,该日期随机分布在 2015 年的 12 个月内,并且orderStatus
随机分布。
这加载到 Postgres 数据库中,并另外索引到 ElasticSearch 中。这是一个相对较小的数据库,但足以处理查询的各个方面(订单日期的选择性和非规范化)。
对于 Postgres,我确实在订单日期和状态上添加了索引,但其他方面保持 Postgres 和 ElasticSearch 的默认设置。这不是为了获得最佳数字,而是为了查看我们可以在改变谓词的选择性和非规范化时看到的变化。
同样值得注意的是,对于 ElasticSearch,这是一个非评分查询(仅筛选查询),因此很高兴看到它如何与 Postgres 对此简单查询进行比较。
查询
针对 Postgres 和 ElasticSearch 运行一个简单的 ORM 查询...
在给定的 orderDate
之后查找 前 100
个 NEW
订单。我们可以传入一个布尔值,在针对 Postgres 数据库或 ElasticSearch 运行查询之间进行切换。
// top 100 new orders after (given date)
// ... run against Postgres or ElasticSearch
// ... based on boolean - asDocStore
return Order.find.where()
.status.in(Order.Status.NEW)
.orderDate.after(useDate)
.order()
.orderDate.desc()
.setMaxRows(100)
.setUseDocStore(asDocStore)
.findList()
SQL 查询
SELECT
t0.id c0,
t0.status c1,
t0.order_date c2,
t0.ship_date c3,
t0.version c4,
t0.when_created c5,
t0.when_modified c6,
t0.customer_id c7,
t0.shipping_address_id c8
FROM orders t0
WHERE t0.status IN (?) AND t0.order_date > ?
ORDER BY t0.order_date DESC, t0.id
LIMIT 100; --bind(NEW,2015-12-08)
Elastic 查询
{
"size": 100,
"sort": [ { "orderDate": { "order": "desc" } } ],
"query": {
"filtered": {
"filter": {
"bool": {
"must": [
{ "terms": { "status": [ "NEW" ] } },
{ "range": { "orderDate": { "gt": 1450263600000 } } }
]
}
}
}
}
}
运行模式 1
在此第一个运行模式中,我将订单日期限制为 12 月的日期
,这使得查询相当具有选择性。这意味着对前 100 名所需的排序和筛选的成本不会太高。
DB mean: 10.2 std dev: 2.34 min:6 max:18
ES mean: 8.77 std dev: 1.08 min:7 max:14
ElasticSearch 和 Postgres 在这里处于同一水平 - 非常接近,这很棒。
我们能否让 ElasticSearch 在我们的应用程序中运行此查询?可以。
运行模式 2 - 反规范化效果
反规范化优势正在发挥作用(ElasticSearch 作为 DB 物化视图的替代方案)
ElasticSearch 表现得非常好,但实际上它返回的对象包含一些额外的反规范化信息,而这些信息在 Postgres 查询中没有。
具体来说,ElasticSearch 订单索引包含 customer (id,name)
和 order details (*,product(id,sku,name))
的其他详细信息。如果我们更改 ORM 查询以匹配索引返回的数据,我们需要获取客户名称和订单详细信息(并进行一些其他联接)。
// change the ORM query to additionally fetch
// customer and order details to match the index
// which has been denormalised
query
.fetch("customer", "id,name")
.fetch("details", "*")
.fetch("details.product","id,sku,name")
因此,这将使针对 DB 的 ORM 查询变得更加昂贵,因为现在我们联接到客户、订单详细信息和产品。
在 ElasticSearch 索引中,这些“联接”已反规范化到索引中,以便像以前一样运行。在 DB 中,我们可以创建一个物化视图来获得类似的效果。
DB mean: 56.4 std dev: 5.95 min:48 max:95
ES mean: 8.75 std dev: 1.01 min:7 max:13
因此,在需要时,ElasticSearch 索引中的反规范化是好的(这几乎是在陈述显而易见的事情,但总是检查一下比较好)。
数据库物化视图将为我们提供类似的效果,但我们注意到使用 ElasticSearch 还具有减轻数据库负载的优势。
运行模式 3 - 选择性较低
在此运行模式中,我们放弃获取客户和订单详细信息等(因此回到运行模式 1),但更改所使用的 order date
,使其现在从 year 的前 100 天
中随机选择。这意味着查询的选择性要低得多,因此要获得 前 100
,需要对更多行进行排序和筛选才能从结果中筛选出来。
DB mean: 51.3 std dev: 3.71 min:44 max:60
ES mean: 14.0 std dev: 2.00 min:10 max:22
因此,正如我们预期的那样,这两个查询都比运行模式 1 慢(因为它们必须对更多结果进行排序和筛选才能获得前 100 名)。ElasticSearch 在这个前 100 名相对不具选择性的查询中做得相当好,因为它并没有真正变得慢很多。
ElasticSearch 旨在对分页查询结果进行排序,但这是一个非常令人印象深刻的结果,因为它是一个仅限于非评分过滤器的查询。
附录 - Bean 映射
订单实体 bean 上的映射是
// this is Kotlin
// .. the @DocEmbedded is what you should look at
@DocStore
@Entity
@Table(name = "orders")
class Order : BaseModel() {
...
var status: Status = Status.NEW;
@DocEmbedded(doc = "id,name")
@ManyToOne @NotNull
var customer: Customer? = null;
@DocEmbedded(doc = "*,product(id,sku,name)")
@OneToMany(mappedBy = "order", cascade = arrayOf(CascadeType.PERSIST))
@OrderBy("id asc")
var details: MutableList<OrderDetail> = ArrayList();
附录:索引映射
ElasticSearch 订单索引的映射。请注意,枚举会自动视为代码(因此状态未经分析)。客户和详细信息映射来自各个 @DocEmbedded
注释。
{
"mappings" : {
"order" : {
"properties" : {
"status": { "type": "string", "index": "not_analyzed" },
"orderDate": { "type": "date" },
"shipDate": { "type": "date" },
"customer" : {
"properties" : {
"id": { "type": "long" },
"name": { "type": "string" }
}
},
"details" : {
"type" : "nested",
"properties" : {
"id": { "type": "long" },
"orderQty": { "type": "integer" },
"shipQty": { "type": "integer" },
"unitPrice": { "type": "double" },
"product" : {
"properties" : {
"id": { "type": "long" },
"sku": { "type": "string" },
"name": { "type": "string" }
}
},
"version": { "type": "long" },
"whenCreated": { "type": "date" },
"whenModified": { "type": "date" }
}
},
"version": { "type": "long" },
"whenCreated": { "type": "date" },
"whenModified": { "type": "date" }
}
}
}
}
附录:示例订单
ElasticSearch 索引中的示例。
{
"_index": "order_v1",
"_type": "order",
"_id": "72033",
"_version": 1,
"found": true,
"_source": {
"status": "NEW",
"orderDate": 1446462000000,
"shipDate": 1446721200000,
"customer": {
"id": 31772,
"name": "big 31775"
},
"details": [
{
"id": 759300,
"orderQty": 7,
"unitPrice": 78,
"product": {
"id": 9697,
"sku": "A1672",
"name": "A1672"
},
"version": 1,
"whenCreated": 1460670857630,
"whenModified": 1460670857630
},
{
"id": 759301,
"orderQty": 8,
"unitPrice": 94,
"product": {
"id": 18351,
"sku": "E2322",
"name": "E2322"
},
"version": 1,
"whenCreated": 1460670857630,
"whenModified": 1460670857630
},
{
"id": 759302,
"orderQty": 11,
"unitPrice": 73,
"product": {
"id": 12358,
"sku": "B2332",
"name": "B2332"
},
"version": 1,
"whenCreated": 1460670857630,
"whenModified": 1460670857630
},
{
"id": 759303,
"orderQty": 14,
"unitPrice": 33,
"product": {
"id": 11847,
"sku": "B1821",
"name": "B1821"
},
"version": 1,
"whenCreated": 1460670857630,
"whenModified": 1460670857630
},
{
"id": 759304,
"orderQty": 11,
"unitPrice": 34,
"product": {
"id": 14230,
"sku": "C2203",
"name": "C2203"
},
"version": 1,
"whenCreated": 1460670857630,
"whenModified": 1460670857630
},
{
"id": 759305,
"orderQty": 17,
"unitPrice": 95,
"product": {
"id": 20625,
"sku": "F2595",
"name": "F2595"
},
"version": 1,
"whenCreated": 1460670857630,
"whenModified": 1460670857630
},
{
"id": 759306,
"orderQty": 4,
"unitPrice": 89,
"product": {
"id": 11653,
"sku": "B1627",
"name": "B1627"
},
"version": 1,
"whenCreated": 1460670857630,
"whenModified": 1460670857630
},
{
"id": 759307,
"orderQty": 2,
"unitPrice": 42,
"product": {
"id": 2237,
"sku": "C450",
"name": "C450"
},
"version": 1,
"whenCreated": 1460670857630,
"whenModified": 1460670857630
},
{
"id": 759308,
"orderQty": 1,
"unitPrice": 60,
"product": {
"id": 3404,
"sku": "D726",
"name": "D726"
},
"version": 1,
"whenCreated": 1460670857630,
"whenModified": 1460670857630
},
{
"id": 759309,
"orderQty": 18,
"unitPrice": 80,
"product": {
"id": 19433,
"sku": "F1403",
"name": "F1403"
},
"version": 1,
"whenCreated": 1460670857630,
"whenModified": 1460670857630
},
{
"id": 759310,
"orderQty": 9,
"unitPrice": 53,
"product": {
"id": 22608,
"sku": "G2577",
"name": "G2577"
},
"version": 1,
"whenCreated": 1460670857630,
"whenModified": 1460670857630
}
],
"version": 1,
"whenCreated": 1460670857630,
"whenModified": 1460670857630
}
}