【问题标题】:Mongo $sum slowMongo $sum 慢
【发布时间】:2015-10-09 12:19:48
【问题描述】:

我需要了解如何使用 mongo 来获得更好的性能。我有 2 个使用 mongo 的项目,其中一个有 1.4 亿行,每个查询几乎立即运行,数据以小块显示,因此通过一些索引 mongo 能够过滤 99% 的数据并快速返回选定的数据. Mongo 在这类项目上表现出色。

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

第一次尝试

我使用传统的数据模式,逐行,类似于:

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

有 200+ 百万行(即使点击按小时打包),我有每个字段的索引和查询最多的组的一些复合索引。如果结果的行块足够大,尝试通过某个 $match 聚合和 $sum 点击真的很慢,更糟糕的是总行数,索引吃掉了服务器中 32gb 的内存。

第二次尝试

利用 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
                            }
                        }
                    }
                }
            }
        }
    }
    }
}

由于这种策略,200+ 百万行减少了 10 倍,并且索引适合内存,因此插入速度变慢了,因为在插入新“行”之前,您必须检查是否有找到相同的特征并将其应用于日期数组的点击合并(如果之前存在)。

当我需要计算行数时,速度已经比传统模式有所提高,但我需要做这样的模糊聚合操作来计算数据:

['$sum'=>'$date.years.'.$year.'.months.'.$month.'.days.'.$day.'.total']

这比一般mysql的平均速度要低一些,但是差距太小了,即使在某些条件下mysql也赢了太多,考虑到mysql是2亿行,mongo是2000万行,这是不能接受的因为很多时候 mysql 在 16 秒内进行查询,而 mongo 在 120 秒内解决它。我想击败 mysql (myIsam) 来使用 mongo 作为替代品。我尝试了很多东西,从日期树上的稀疏索引到保存一些预处理结果并将它们混合的二级缓存。它不可能在某一天缓存所有数据排列,因为 [...] 字段很多。

分片可能是一种解决方案,但我认为不会神奇地将速度提高 2 倍。

请给我一些提示

更新

让我们搜索某个国家/地区的某些日子:

Mongo 压缩架构

Mongodb count rows where 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 传统模式

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 传统架构

项目数:

聚合:

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

【问题讨论】:

  • 分片可以极大地提高速度。但是,如果您真的想要/可以允许,问题仍然存在。从模式中,您可以尝试第三种方法。创建更多的集合,其中包含更少的 [...] 字段,从而对数据进行更多分组。如果大多数查询使用 2 个字段,则仅使用这两个字段再创建一个集合,并在需要超过这两个字段时使用大集合等。
  • @Endijs 实际上这是一个好主意,但很难找到最常用的字段组,因为查询是所有内容的混合,没有明确的赢家。真正的问题是,mysql 似乎比 mongo 快(不可扩展,不那么容易分发,但对于简单的扫描来说更快),即使您使用 mongo 可以提高 mysql 速度的功能,mysql 也会继续获胜 :/ 。是否有一些指南可以添加数据以在 mongo 中表现更好?

标签: php performance mongodb


【解决方案1】:

我推迟回答,因为我确信一些 MongoDB 专家会回答。但是,由于没有人给出答案,我将给出一些提示。也许其中的一些东西可以提供帮助。但话又说回来 - 我不是 MongoDB 专家。把所有的东西都小心翼翼。

1) 您使用的是哪个版本?如果您仍在使用 2.6,请尝试使用 WiredTiger 引擎的 3.0.x(或更新版本)。
2)如果你有很多数据分片可以帮助很大。这将增加设置的复杂性,但由于您将能够并行处理部分数据集,因此您可以获得显着的速度提升。但要小心选择正确的分片键。
3)考虑创建几个可以充当较小视图的集合。示例:如果您当前在 [..] 中有 15 个字段,则很有可能很多查询一次只使用 1 或 2 个。像国家。再创建一个集合,在其中使用国家数据并跳过休息。如果查询仅使用国家/地区字段而不使用这 15 个字段中的其他字段,则使用小型集合。如果查询使用更多字段,请使用大字段。这样,对国家/地区的查询将更快,因为您将能够对数据进行更多分组。然而,这并不总是可行的,因为它在构建如此小的集合时增加了额外的复杂性。如果您在某个队列中处理数据(插入大),您也可以插入小。或者,您可以使用一些聚合查询和 $out 每 X 分钟构建一次较小的表。
4)提出第三个模式。您的第二个模式很容易放入数据,但很难取出数据。你可以更多地使用数组。这样一来,获取数据将更加困难,但查询起来更加容易和快捷。请记住,在您的第二个模式和我的第三个模式示例中,文档正在增长,可能需要 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,则从相同的数据创建插入。 IE。取消设置 $criteria['daily.date'] 并将更新更改为 $update = ['$inc' =&gt; ['total' =&gt; 1], '$push' =&gt; ['daily' =&gt; ['date' =&gt; new MonoDate('..'), 'total': 1]]]。请记住,如果您有多个插入数据的脚本,您可能会遇到问题。最好一个接一个地完成队列中的所有事情。或者您同时确保 $push 不会导致添加多个具有相同日期的 daily.date。所以 - 你尝试更新,如果不能更新,插入。当您使用数组和位置运算符时,您不能使用 upserts。这就是为什么需要额外插入的原因。正如我所说,获取数据更复杂。但获取数据会更容易。确保设置正确的索引。例如在“daily.date”等。因此更新查询不需要检查大量文档。甚至更多 - 您可以创建一些哈希字段来放置 [...] 字段,这些字段将保存所有 [...] 字段的哈希。并在更新中使用它。这样,创建小索引以查明特定文档将更加容易(您放入索引“daily.date”、哈希字段等,但不需要放入 15 个 [..] 字段)。 当你有这样的结构时,你可以用查询做很多事情。例如 - 如果您需要整月,只需查询您需要的日期和 [...] 字段,总和就可以了。如果您需要某个日期范围(如每月 1 日至 10 日),您可以通过 [...] 字段和日期进行查询,项目以消除不必要的字段,每天 $unwind,再次匹配,但这次在 daily.date字段,然后项目重命名字段,然后分组和求和。它比使用 $date.years.2015.months.07.days.03.total 灵活得多。

请记住,所有这些都只是提示。自己测试一切。也许 1 到 5 个提示会起作用。但这可以使一切变得不同。

【讨论】:

  • 非常感谢您的回复。 1)我使用的是最后一个版本,激活了 WT 并打开了 zlib。 2)然后会测试它,但不能吞下 mongo 需要 3 台服务器来做 mysql 用 1 做的事情:S
  • 3) 这种方法的问题是我需要对所涉及的字段进行大量排列,因为也许本周 country+os 是最流行的查询,但它的变化非常快。我制作了第二个模式,因为它减少了行数并且用途广泛,能够查询每个可能的行组合并计算结果。像您所说的那样拥有很少的视图会增加插入时间并从我的 pov 中增加太多开销。同样,mysql 只在一个主表中执行此操作,而 mongo 将需要多个:S
  • 4) 怎么样?我建议的新模式。它像您的第二个架构一​​样减少了文档数量,但更容易进行查询。
  • 4) 我认为这很有趣,也许瓶颈是 mongo 必须计算多级数组(不知道如何衡量它),这个模式很有趣,因为你可以计算几个月和他自己数年,优化了时间,但经过后来的分析,这并不像我一样有规律,即使是这样,如果数组更简单,每年计算 360 个元素可能会更快。我虽然也为每个数组天制作稀疏哈希索引,但一点也不快。你提到散列字段,如果你这样做,你就会失去查询某些字段以过滤某些数据的习惯。
  • 散列字段用于更快的更新。与散列字段 + 月份一样,您将获得非常独特的键 - 即 - 用于在给定月份为给定用户精确定位 [...] 组合的唯一键。您不会使用它来查询数据。对于数据查询,您将设置不同的索引。无论如何 - 我不是 MongoDB 专家。但是这样的想法在我思考的时候出现在我的脑海中。也许其他人有更好的想法。如果您发现效果很好 - 请分享。
猜你喜欢
  • 2016-11-08
  • 1970-01-01
  • 2021-08-28
  • 2018-09-22
  • 2019-11-17
  • 2012-04-15
  • 1970-01-01
  • 2019-07-19
  • 2018-11-08
相关资源
最近更新 更多