对内存中的数据运行mongo查询

时间:2018-01-29 20:52:33

标签: python mongodb

我有一个mongodb集合,我需要每小时运行许多计数操作(每个操作都有不同的查询)。当我第一次设置它时,集合很小,这些计数操作在大约一分钟内运行,这是可以接受的。现在他们需要大约55分钟,所以他们几乎不停地跑步。

与每个计数操作相关联的查询相当复杂,我认为没有办法让它们全部用索引运行(即作为COUNT_SCAN操作)。

我提出的唯一可行的解​​决方案是:

  • 每小时运行一次完整的集合扫描,将每个文档从db
  • 中拉出来
  • 每个文档都在内存中后,自行运行所有计数操作

如果没有我的解决方案,服务器每小时运行数十次和几十次完整的收集扫描。使用我的解决方案,服务器只运行一个。这导致我进入了一个奇怪的地方,我需要自己处理复杂的查询并重新实现它们,这样我每小时都可以自己计算。

所以我的问题是,在解释查询文档时是否有来自mongo驱动程序的支持(在我的情况下是pymongo,但我一般都很好奇)但是在内部针对内存中的数据运行它们,而不是针对数据在mongodb服务器上。

最初这感觉就像一个奇怪的请求,但实际上有很多地方这种方法可能会大大减轻我在特定用例中对数据库的负担。所以我想知道它是否会不时出现在其他生产部署中。

4 个答案:

答案 0 :(得分:0)

MongoDB内存中存储引擎

如果只想使用MongoDB语法在RAM中使用复杂查询来处理数据,则可以将MongoDB配置为使用In-Memory only storage engine,这完全避免了磁盘I / O。
对我来说,拥有复杂查询和最佳性能的能力是最好的选择。

Python内存数据库:

您可以使用以下之一:

  • PyDbLite-快速,纯Python,无类型的内存数据库引擎,使用Python语法代替SQL来管理数据
  • TinyDB-如果您需要一个带有干净API的简单数据库,而无需大量配置即可使用,那么TinyDB可能是您的正确选择。但不是一个快速的解决方案,并且拥有few other disadvantages

它们应该允许直接在RAM中处理数据,但是我不确定这是否比以前的选择更好。

自己的自定义解决方案(例如,用Python编写)

某些服务仅在应用程序级别上处理RAM中的数据。如果您的解决方案并不复杂且查询很简单-可以。但是由于一段时间以来查询变得更加复杂,并且代码需要某种抽象级别(对于高级CRUD),就像以前的数据库一样。

最后一个解决方案可能具有最佳性能,但需要更多时间来开发和支持它。

答案 1 :(得分:0)

这是我当前要解决此问题的代码。我已经针对它进行了足够的测试以使其符合我的用例,但可能并非100%正确。我当然不会处理所有可能的查询文档。

def check_doc_against_mongo_query(doc, query):
    """Return whether the given doc would be returned by the given query.

    Initially this might seem like work the db should be doing, but consider a use case where we
    need to run many complex queries regularly to count matches. If each query results in a full-
    collection scan, it is often faster to run a single scan fetching the entire collection into
    memory, then run all of the matches locally.

    We don't support mongo's full query syntax here, so we'll need to add support as the need
    arises."""

    # Run our check recursively
    return _match_query(doc, query)


def _match_query(doc, query):
    """Return whether the given doc matches the given query."""

    # We don't expect a null query
    assert query is not None

    # Check each top-level field for a match, we AND them together, so return on mismatch
    for k, v in query.items():
        # Check for AND/OR operators
        if k == Mongo.AND:
            if not all(_match_query(doc, x) for x in v):
                return False
        elif k == Mongo.OR:
            if not any(_match_query(doc, x) for x in v):
                return False
        elif k == Mongo.COMMENT:
            # Ignore comments
            pass
        else:
            # Now grab the doc's value and match it against the given query value
            doc_v = nested_dict_get(doc, k)
            if not _match_doc_and_query_value(doc_v, v):
                return False

    # All top-level fields matched so return match
    return True


def _match_doc_and_query_value(doc_v, query_v):
    """Return whether the given doc and query values match."""

    cmps = []  # we AND these together below, trailing bool for negation

    # Check for operators
    if isinstance(query_v, Mapping):
        # To handle 'in' we use a tuple, otherwise we use an operator and a value
        for k, v in query_v.items():
            if k == Mongo.IN:
                cmps.append((operator.eq, tuple(v), False))
            elif k == Mongo.NIN:
                cmps.append((operator.eq, tuple(v), True))
            else:
                op = {Mongo.EQ: operator.eq, Mongo.GT: operator.gt, Mongo.GTE: operator.ge,
                      Mongo.LT: operator.lt, Mongo.LTE: operator.le, Mongo.NE: operator.ne}[
                          k]
                cmps.append((op, v, False))
    else:
        # We expect a simple value here, perform an equality check
        cmps.append((operator.eq, query_v, False))

    # Now perform each comparison
    return all(_invert(_match_cmp(op, doc_v, v), invert) for op, v, invert in cmps)


def _invert(result, invert):
    """Invert the given result if necessary."""

    return not result if invert else result


def _match_cmp(op, doc_v, v):
    """Return whether the given values match with the given comparison operator.

    If v is a tuple then we require op to match with any element.

    We take care to handle comparisons with null the same way mongo does, i.e. only null ==/<=/>=
    null returns true, all other comps with null return false. See:
    https://stackoverflow.com/questions/29835829/mongodb-comparison-operators-with-null
    for details.

    As an important special case of null comparisons, ne null matches any non-null value.
    """

    if doc_v is None and v is None:
        return op in (operator.eq, operator.ge, operator.le)
    elif op is operator.ne and v is None:
        return doc_v is not None
    elif v is None:
        return False
    elif isinstance(v, tuple):
        return any(op(doc_v, x) for x in v)
    else:
        return op(doc_v, v)

答案 2 :(得分:0)

在使用python时,您是否考虑过Pandas ?,您可以基本上尝试将JSON数据转换为pandas数据帧并根据需要进行查询,您可以完成诸如count,group的全部操作,汇总等。请查看文档。在下面添加一个小示例,以帮助您建立联系。希望这会有所帮助。

例如:

import pandas as pd
from pandas.io.json import json_normalize
data = {
"data_points":[
    {"a":1,"b":3,"c":2},
    {"a":3,"b":2,"c":1},
    {"a":5,"b":4,"d":3}
   ]
}
# convert json to data frame
df = json_normalize(data["data_points"])

enter image description here

上方的熊猫数据框视图。

现在您可以尝试对它们进行求和,计数等操作。

示例:

# sum of column `a`
df['a'].sum()

output: 9

# sum of column `c` that has null values.
df['c'].sum()

output: 3.0

# count of column `c` that has null values.
df['c'].count()

output: 2

答案 3 :(得分:0)

也许您可以尝试其他方法? 我的意思是,总体而言,MongoDB在计数方面确实表现不佳。

在上一家公司中,我遇到了一个非常类似的问题,我们要做的是创建一些“计数器”对象,并在每次对数据执行的更新中对其进行更新。 这样,您就可以完全避免计数。

该文档将类似于:

{
query1count: 12,
query2count: 512312,
query3count: 6
}

如果query1count与查询相关:“ userId = 13的所有文档”,则可以在python层中检查是否在userId = 13之前创建/更新文档,如果是,则增加所需的计数器。

这确实会增加代码的额外复杂性,但计数器的读取将在O(1)中进行。

当然,并不是所有的查询都那么容易,但是您可以通过这种方法减少执行时间。