Mongo $ sum慢

时间:2015-07-20 11:34:04

标签: php performance mongodb

我需要了解如何使用mongo来表现更好。我有两个使用mongo的项目,其中一个有1.4亿行,每个查询都在附近运行,数据以小块显示,所以有几个索引mongo能够过滤99%的数据并快速返回选定的数据。 Mongo在这类项目上运作良好。

另一方面,我有另一个项目,如谷歌分析跟踪访问,点击等。目标是根据某些标准(使用表单)计算时间范围内的点击次数。我在同一个任务中挑战mysql。

首先尝试

我逐行使用传统的数据模式,如:

{
'user':'abc',
'date':'2015-07-20',
'hour':02,
[....]
'clicks':30
}

拥有200多万行(即使按照你所看到的每小时点击次数),每个字段都有索引,而且查询最多的组有一些复合索引。试图通过某些$ match来进行agregate和$ sum点击真是非常慢,如果产生的行数足够大,甚至更糟的是总行数,索引会占用服务器中32gb的ram。

第二次尝试

使用mongo的架构优势,设计了一个分组架构以尽可能减少重复数据,这是一种架构,其中每种类型的点击属性由唯一的字段组合(具有唯一索引)确定,然后单击分组在按日期分配的树上,行示例:

{
    "user" : "asd",
    [....]
    "date" : {
    "total" : 5,
    "years" : {
        "2015" : {
            "total" : 5,
            "months" : {
                "06" : {
                    "total" : 5,
                    "days" : {
                        "30" : {
                            "total" : 2,
                            "hours" : {
                                "16" : 1,
                                "22" : 1
                            }
                        },
                        "28" : {
                            "total" : 1,
                            "hours" : {
                                "6" : 1
                            }
                        },
                        "29" : {
                            "total" : 2,
                            "hours" : {
                                "14" : 1,
                                "20" : 1
                            }
                        }
                    }
                }
            }
        }
    }
    }
}

感谢这个策略,2亿多行减少了10倍并且索引适合内存然后,插入速度减慢因为在插入新的“行”之前你必须检查是否有一个如果之前存在,则找到相同的特征并合并应用于日期数组的点击。

当我需要对行进行计数时,速度已经针对传统模式进行了验证,但我需要做一些模糊的聚合事件来计算数据:

[ '$总和'=>'。个月。'。天。 '总' '$ date.years' $每年$每月$日]

这是执行一般下来的mysql的平均速度有点下降,但差异是如此紧张,即使在某些条件下mysql赢得了太多的战斗,考虑到mysql计数2亿行和mongo 2000万,这是不可接受的因为很多时候mysql在16s内进行查询,而mongo在120s内解析它。我想击败mysql(myIsam)使用mongo作为替换。我已经尝试了很多东西,从日期树上的稀疏索引到二级缓存,保存了一些预处理结果并混合它们。它无法在某一天缓存所有数据排列,因为[...]字段很多。

碎片可能是一个解决方案但我不认为会神奇地提高速度2。

请给我一些提示

更新

让某些国家搜索某个国家:

Mongo压缩架构

Mongodb计数country ='AD'的行:11389

骨料:

Array
(
    [0] => Array
    (
        [$match] => Array
            (
                [country] => AD
            )

    )

    [1] => Array
    (
        [$group] => Array
            (
                [_id] => Array
                    (
                        [country] => $country
                    )

                [2015-07-01] => Array
                    (
                        [$sum] => $date.years.2015.months.07.days.01.total
                    )

                [2015-07-02] => Array
                    (
                        [$sum] => $date.years.2015.months.07.days.02.total
                    )

                [2015-07-03] => Array
                    (
                        [$sum] => $date.years.2015.months.07.days.03.total
                    )

                [2015-07-04] => Array
                    (
                        [$sum] => $date.years.2015.months.07.days.04.total
                    )

                [2015-07-05] => Array
                    (
                        [$sum] => $date.years.2015.months.07.days.05.total
                    )

                [2015-07-06] => Array
                    (
                        [$sum] => $date.years.2015.months.07.days.06.total
                    )

                [2015-07-07] => Array
                    (
                        [$sum] => $date.years.2015.months.07.days.07.total
                    )

                [2015-07-08] => Array
                    (
                        [$sum] => $date.years.2015.months.07.days.08.total
                    )

                [2015-07-09] => Array
                    (
                        [$sum] => $date.years.2015.months.07.days.09.total
                    )

                [2015-07-10] => Array
                    (
                        [$sum] => $date.years.2015.months.07.days.10.total
                    )

                [2015-07-11] => Array
                    (
                        [$sum] => $date.years.2015.months.07.days.11.total
                    )

                [2015-07-12] => Array
                    (
                        [$sum] => $date.years.2015.months.07.days.12.total
                    )

            )

    )

    [2] => Array
    (
        [$project] => Array
            (
                [_id] => $_id
                [dates] => Array
                    (
                        [2015-07-01] => $2015-07-01
                        [2015-07-02] => $2015-07-02
                        [2015-07-03] => $2015-07-03
                        [2015-07-04] => $2015-07-04
                        [2015-07-05] => $2015-07-05
                        [2015-07-06] => $2015-07-06
                        [2015-07-07] => $2015-07-07
                        [2015-07-08] => $2015-07-08
                        [2015-07-09] => $2015-07-09
                        [2015-07-10] => $2015-07-10
                        [2015-07-11] => $2015-07-11
                        [2015-07-12] => $2015-07-12
                    )

            )

    )

)

结果:

Array
(
    [data] => Array
    (
        [AD] => Array
            (
                [_id] => Array
                    (
                        [country] => AD
                    )

                [dates] => Array
                    (
                        [2015-07-01] => 6080
                        [2015-07-02] => 6580
                        [2015-07-03] => 6178
                        [2015-07-04] => 6084
                        [2015-07-05] => 7085
                        [2015-07-06] => 7192
                        [2015-07-07] => 5672
                        [2015-07-08] => 6769
                        [2015-07-09] => 6370
                        [2015-07-10] => 6035
                        [2015-07-11] => 5513
                        [2015-07-12] => 6941
                    )

            )

    )

    [time] => 17.0764780045
)

Mysql tradicional schema

Mysql计数行:38515

Mysql查询:

SELECT date,sum(clicks) as clicks FROM table WHERE  (  country = "AD" AND  ( date > 20150700 AND date < 20150712  )  ) GROUP BY country,date;

结果:

Array
(
    [0] => Array
    (
        [date] => 20150701
        [clicks] => 6080
    )

    [1] => Array
    (
        [date] => 20150702
        [clicks] => 6580
    )

    [2] => Array
    (
        [date] => 20150703
        [clicks] => 6178
    )

    [3] => Array
    (
        [date] => 20150704
        [clicks] => 6084
    )

    [4] => Array
    (
        [date] => 20150705
        [clicks] => 7085
    )

    [5] => Array
    (
        [date] => 20150706
        [clicks] => 7192
    )

    [6] => Array
    (
        [date] => 20150707
        [clicks] => 5672
    )

    [7] => Array
    (
        [date] => 20150708
        [clicks] => 6769
    )

    [8] => Array
    (
        [date] => 20150709
        [clicks] => 6370
    )

    [9] => Array
    (
        [date] => 20150710
        [clicks] => 6035
    )

    [10] => Array
    (
        [date] => 20150711
        [clicks] => 5513
    )

)
time: 0.25689506530762

Mongodb tradicional schema

项目数:

骨料:

Array
(
    [0] => Array
    (
        [$match] => Array
            (
                [country] => AD
                [date] => Array
                    (
                        [$in] => Array
                            (
                                [0] => 20150701
                                [1] => 20150702
                                [2] => 20150703
                                [3] => 20150704
                                [4] => 20150705
                                [5] => 20150706
                                [6] => 20150707
                                [7] => 20150708
                                [8] => 20150709
                                [9] => 20150710
                                [10] => 20150711
                                [11] => 20150712
                            )

                    )

            )

    )

    [1] => Array
    (
        [$group] => Array
            (
                [_id] => Array
                    (
                        [country] => $country
                    )

                [count] => Array
                    (
                        [$sum] => $clicks
                    )

            )

    )

)

结果:

Array
(
    [result] => Array
    (
        [0] => Array
            (
                [_id] => Array
                    (
                        [country] => AD
                    )

                [clicks] => 76499
            )

    )

    [ok] => 1
)
time: 27.8900089264

1 个答案:

答案 0 :(得分:1)

我没有回答,因为我确信一些MongoDB专家会回答。然而,由于没有人给出答案,我会给出一些提示。也许某些东西可以帮助。但话又说回来 - 我不是MongoDB专家。拿一小块盐就可以了。

1)您使用的是哪个版本?如果你仍然在2.6 - 使用WiredTiger引擎试用3.0.x(或更新版本) 2)如果你有很多数据分片可以大大帮助。这会增加设置的复杂性,但是由于您将能够以并行方式处理部分数据集,因此可以获得显着的速度提升。但是要小心选择合适的分片键 3)考虑创建几个可以作为较小视图的集合。示例:如果[...]目前有15个字段,很多查询很可能只使用1或2。像国家一样。再创建一个集合,在其中使用国家/地区数据并跳过休息。如果查询仅使用国家/地区字段而不使用其他15个字段,则使用小集合。如果查询使用更多字段,请使用大字段。这样,对国家/地区的查询会更快,因为您可以更多地对数据进行分组。然而,并非总是如此,因为它在构建这样的小集合时增加了额外的复杂性。如果您在某个队列中处理数据(以大插入),您也可以插入小。或者你可以使用一些聚合查询和$ out来每隔X分钟构建一次更小的表 4)想出第3个架构。您的第二个架构很容易放入数据,但很难获取数据。您可以更多地使用数组。这样,获取数据将更加困难,但查询数据会更容易,更快捷。请记住,在您的第二个架构和我的第三个架构文档的示例中,正在增长,并且可能需要MongoDB在磁盘上移动它们,这实际上是非常慢的操作。测试是否会影响您的设置。潜在收集模式的一个小例子:

{
    "user": "asd",
    [...],
    "date": ISODate("2015-07-01T00:00:00Z"), // first date of the month
    "total": 2222,
    "daily": [
        {"date": ISODate("2015-07-01T00:00:00Z"), "total": 22},
        {"date": ISODate("2015-07-11T00:00:00Z"), "total": 200},
        {"date": ISODate("2015-07-20T00:00:00Z"), "total": 2000},
   ]
}

插入数据时,您可以使用带标准的更新(如果您使用的是PHP):$criteria = ["user": "asd", "daily.date": new MongoDate("...."), // other fields]和更新子句$update = ['$inc': ["total: 1, 'daily.$.total': 1]]。检查已更新的行数。如果为0,则从相同数据创建插入。即取消设置$criteria['daily.date']并将更新更改为$update = ['$inc' => ['total' => 1], '$push' => ['daily' => ['date' => new MonoDate('..'), 'total': 1]]]。请记住,如果您有多个插入数据的脚本,则可能会遇到问题。最好一个一个地排队。或者你并行确保$ push不会导致添加几个具有相同日期的daily.date。所以 - 你尝试更新,如果不能更新,插入。当您使用数组和可能的运算符时,您不能使用upsert。这就是为什么需要额外插入的原因。正如我所说,获取数据更复杂。但是更容易获取数据。确保设置正确的索引。例如在'daily.date'等上。因此更新查询不需要检查大量文档。甚至更多 - 您可以创建一些哈希字段来放置[...]字段,这些字段将保存所有字段的哈希值。并在更新中使用它。这样就可以更容易地创建小索引以精确定位特定文档(您放入索引'daily.date',哈希字段等等,但不需要放置15 [..]字段)。 当你有这样的结构时,你可以用查询做很多事情。例如 - 如果您需要整整几个月,只需查询您需要的日期和字段,总计就可以了。如果您需要一些日期范围(如1月1日至10日),您可以通过[...]字段和日期进行查询,项目可以删除不必要的字段,$每天放松,再次匹配,但这次是在daily.date字段,然后投影重命名字段,然后分组和求和。它比使用$ date.years.2015.months.07.days.03.total更灵活。

请记住,所有这些只是提示。自己测试一切。也许有1到5个提示可行。但这可以产生重大影响。