存储和检索数百万个JSON编码的事件(PHP/Database)


Store and retrieve millions of JSON-encoded events (PHP/Database)

假设我们有以下JSON事件数据示例:

{
    "eventId":"eb1363c3-6bf7-4a42-9daa-66270b922367",
    "timestamp":"2014-10-28T09:12:22.628Z",
    "ip":"1.2.3.4",
    "device":{
        "type":"mobile",
        "os":{
            "name":"iOS",
            "version":"7.1.1"
        },
        "name":"iPhone 4/4s",
        ...
    },
    "eventType":"AddedProductToCart",
    "store":"US",
    "product":{
        "sku":"ABC123",
        "name":"Yellow Socks",      
        "quantity":1,       
        "properties":{
            "foo":"bar",
            "bar":1
        }       
        ...
    },
    "user":{
        "id":123456,
        "name":"jeff",
        "type":"registered"
        ...
    }
}

虽然总是提供"eventId"answers"timestamp",但数组的结构可能会有所不同,也不相同。大约有30-40种独特的事件类型,它们都具有不同的事件属性。大多数事件数据都具有嵌套结构。

存储这些事件属性的最佳方法是什么?我研究了MongoDB、DynamoDB和一个名为EventStore的项目(http://geteventstore.com)。显然,我也考虑过MySQL,但我想知道它在我们的用例中会如何执行。

数据的存储只是第一部分。在这之后,我们应该能够用下面这样的复杂查询来查询我们的数据库/事件存储(例如,不仅仅是通过索引ID检索):

select all events where eventType is "AddedProductToCart" and timestamp > 2 weeks ago
-> should return all "AddedProductToCart" from 2 weeks ago until now
select all events where device.OS.name is "iOS" and device.OS.version is "7.1.1"
-> should return all events from iOS 7.1.1

等等。

我们预计每月约有1000万场活动。这相当于平均每秒3-4次写入,可能更像是峰值/最坏情况下每秒30-40次写入。存储应该不是一个真正的问题——每个事件的总大小可能不会超过1或2kb(相当于每100万个事件1-2GB)。

查询部分最好使用PHP。例如,DynamoDB有一个用于PHP的SDK,这肯定会促进我们的

我们对此的最佳解决方案是什么?写入速度应该很快,我们的查询也应该是可接受的。简而言之,我们正在寻找一个低成本的数据存储,以便轻松存储和检索(->不仅使用索引查询,还使用嵌套JSON中的事件属性查询)我们的数据。

感谢您的任何建议,如果需要更多信息来正确回答这个问题,我很乐意提供更多信息。

亚马逊的DynamoDB提供了一个完全管理(自动扩展)、持久和可预测的解决方案。

从您期望的流量和数据量来看,DynamoDB的25个写/读容量单元和25 GB的免费层基本上免费覆盖了您的操作。

每个写入容量单位相当于写入1KB的数据,因此,如果您希望每秒写入3-4次2KB的数据时,则需要提供8个WCU。此外,DynamoDB的性能非常可预测,具有快速的个位数毫秒延迟。有关免费层的更多信息,请查看http://aws.amazon.com/dynamodb/pricing/.

就数据集而言,对于非文档对象,使用全局二级索引进行查询相对简单。

这里有一个PHP SDK的例子

$twoWeeksAgo = date("Y-m-d H:i:s", strtotime("-14 days"));
$response = $dynamoDB->query(array(
   "TableName" => <Table Name>,
   "KeyConditions => array(
      "EventType" => array(
           "ComparisonOperator" => ComparisonOperator::EQ,
           "AttributeValueList" => array(
                array(Type::STRING => "AddedProductToCart")
            )
      ),
      "Timestamp" => array(
           "ComparisonOperator" => ComparisonOperator:GE,
           "AttributeValueList" => array(
                array(Type::STRING => $twoWeeksAgo)
               )
          )
     )
 ));

您可以通过扫描查询"Device.OS.Name"answers"Device.OS.Version",但您应该根据要进行的查询类型考虑一些优化。

如果您希望进行临时查询,可以进行并行扫描调用,然后在嵌套属性上使用ConditionalExpression应用ScanFilter。通过并行化扫描,可以优化表上读取容量单位的消耗以及操作速度。有关并行扫描的更多信息,请查看http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/QueryAndScan.html#QueryAndScanParallelScan.

或者,如果您有要查询的select属性,可以考虑将一些字段设置为顶级属性,或者将它们移动到自己的单独表中,展平必要的属性(即os.name到osname),并对原始项进行反向引用(主要适用于"设备"等文档)。通过这样做,您可以在这些属性之上添加索引,并快速高效地查询它们。此外,随着在线索引的提前发布,您应该能够在必要时添加和删除索引,以尽快满足您的需求。

如果你想更详细地讨论这个问题,或者问一些关于使用DynamoDB的问题,请随时通过私人信息联系我。

感谢

MongoDB是一个不错的选择。它可以很容易地处理写操作(mongod在我的笔记本电脑上看到了更多的操作)。

你提到的问题都是基本问题。例如:

db.collection.find({"device.OS.name":"iOS","device.OS.version":"7.1.1"})

和(为便于阅读而缩短)

db.collection.find({"eventType":"AddedProductToCart",timestamp:{$gte: ISODate(iso8601String)}})

如果指数设置正确,这些指数应该是闪电般的快。您甚至可以使用TTL索引来自动删除某个时间以前的事件。

对于数据分析,您既有map/reduce,也有MongoDB极其强大的聚合框架。

让我们来看看缺点。虽然MongoDB的扩展相对容易,但出于某种原因,人们认为具有自动分发数据的复制分片集群与MongoDB的其他集群一样容易管理。关键词是相对来说很容易(将其与MySQL或-Lord help us-Oracle的复制数据分区进行比较),但它仍然存在一些陷阱。

在不使用MMS的分片环境中进行时间点恢复是可能的,但您确实必须知道自己在做什么,因为分片的单个备份的同步非常棘手。

无论你选择哪个数据库,我强烈建议你与相应的专家联系。生产数据是基本的,不应由非专业人员规划和维护任何数据库。