【问题标题】:Log Data Analytics : Choice of Database [closed]日志数据分析:数据库的选择 [关闭]
【发布时间】:2014-05-08 14:23:30
【问题描述】:

我正在从各种 Web 应用程序获取以下格式的日志数据:

Session    Timestamp    Event                Parameters
1          1            Started Session      
1          2            Logged In            Username:"user1"
2          3            Started Session
1          3            Started Challenge    title:"Challenge 1", level:"2"
2          4            Logged In            Username:"user2"

现在,有人想要对此日志数据进行分析(并希望在经过适当的转换后将其作为 JSON blob 接收)。例如,他可能希望接收一个 JSON blob,其中日志数据按Session 分组,并在发送数据之前添加TimeFromSessionStartCountOfEvents,以便他可以进行有意义的分析。在这里我应该返回:

[
  {
    "session":1,"CountOfEvents":3,"Actions":[{"TimeFromSessionStart":0,"Event":"Session Started"}, {"TimeFromSessionStart":1, "Event":"Logged In", "Username":"user1"}, {"TimeFromSessionStart":2, "Event":"Startd Challenge", "title":"Challenge 1", "level":"2" }]
  },
  { 
    "session":2, "CountOfEvents":2,"Actions":[{"TimeFromSessionStart":0,"Event":"Session     Started"}, {"TimeFromSessionStart":2, "Event":"Logged In", "Username":"user2"}] 
  }
]

在这里,TimeFromSessionStartCountOfEvents 等 [我们称之为合成附加数据] 不会被硬编码,我将制作一个 Web 界面以允许人们决定他需要 JSON 中的合成数据类型斑点。我想为人们提供很大的灵活性来决定他想要 JSON blob 中的合成数据类型。

我预计数据库将存储大约 100 万行并在合理的时间内执行转换。

我的问题是关于数据库的选择。使用 PostgreSQL 等 SQL 数据库与使用 MongoDB 等 NoSQL 数据库的相对优缺点是什么。从我到现在所读到的任何内容来看,我认为 NoSQL 可能无法提供足够的灵活性来添加额外的合成数据。另一方面,如果我使用 SQL 数据库,我可能会面临数据表示的灵活性问题。

我认为 MongoDB 和 PostgreSQL 的存储要求相当,因为我必须在两种情况下构建相似的索引(可能!)以加快查询速度。

如果我使用 PostgreSQL,我可以通过以下方式存储数据: SessionEvent 可以是stringTimestamp 可以是dateParameters 可以是hstore(PostgreSQL 中可用的键值对)。之后,我可以使用 SQL 查询来计算合成(或附加)数据,将其临时存储在 Rails 应用程序的变量中(它将与 PostgreSQL 数据库交互并充当需要 JSON blob 的人的接口)并创建 JSON从它那里得到一滴。

另一种可能的方法是使用 MongoDB 来存储日志数据,并使用 Mongoid 作为 Rails 应用程序的接口,如果我能够获得足够的灵活性来添加额外的合成数据进行分析并在 PostgreSQL 上进行一些性能/存储改进。但是,在这种情况下,我不清楚在 MongoDB 中存储日志数据的最佳方式是什么。另外,我读到 MongoDB 会比 PostgreSQL 慢一些,主要是为了在后台运行。

编辑: 从我过去几天的阅读来看,Apache Hadoop 似乎也是一个不错的选择,因为它比 MongoDB(多线程)速度更快。

编辑: 我不是在征求意见,而是想知道使用特定方法的具体优点或缺点。因此,我不认为这个问题是基于意见的。

【问题讨论】:

  • 并非您阅读的所有内容都是准确的。 MongoDB 既不是单线程的,也不是要在“后台”运行。
  • @AsyaKamsky 我读到MongoDB: The Definitive GuideThe price of using MapReduce is speed: group is not particularly speedy, but MapReduce is slower and is not supposed to be used in “real time.” You run MapReduce as a background job, it creates a collection of results, and then you can query that collection in real time.
  • MongoDB 远不止 MapReduce,事实上,MR 是它的一个边缘特性,你甚至不应该使用 map-reduce - 你应该使用在服务器上运行的聚合框架(不是在 JS 中)并且比 map-reduce 快一个数量级。
  • 您说您从各种应用程序中获取日志数据,但您没有在示例中的任何地方显示应用程序 ID。您希望查询跨越所有应用程序,还是仅针对特定应用程序或???
  • 是的,查询将跨越所有应用程序(此外,如果需要,我可以添加一个应用程序 ID,没有太大问题)。如果我使用 MongoDB,我不知道应该如何存储日志数据。我的意思是,应该如何表示Parameters,以便以后可以对它们进行部分索引,以提高某些类型查询的查询速度[例如。那些关心用户名的人]?对于我的用例,与 PostgreSQL 相比,MongoDB 有什么缺点吗?

标签: ruby-on-rails mongodb postgresql mapreduce relational-database


【解决方案1】:

您应该从 elasticsearch 中查看 logstash / kibana。该堆栈的主要用例是收集、存储、分析日志数据。

http://www.elasticsearch.org/overview/logstash/
http://www.elasticsearch.org/videos/kibana-logstash/

如果您想自己构建它,Mongo 也是一个不错的选择,但我认为您会发现来自 elasticsearch 的产品可以很好地解决您的需求并允许您进行所需的集成。

【讨论】:

【解决方案2】:

MongoDB 非常适合您的任务,它的文档存储比死板的 SQL 表结构更灵活。 下面,请找到一个使用 Mongoid 的工作测试 这表明您对日志数据输入的理解, 轻松存储为 MongoDB 集合中的文档, 使用 MongoDB 的聚合框架进行分析。 我选择将参数放在子文档中。 这更紧密地匹配您的示例输入表并简化了管道。 生成的 JSON 稍作修改, 但所有指定的计算、数据和分组都存在。 我为参数用户名上的索引添加了一个测试,该测试演示了子文档字段上的索引。 这对于您要索引的特定字段来说已经足够了, 但是无法对键进行完全通用的索引,您必须将其重组为值。

我希望这会有所帮助并且你喜欢它。

test/unit/log_data_test.rb

require 'test_helper'
require 'json'
require 'pp'

class LogDataTest < ActiveSupport::TestCase
  def setup
    LogData.delete_all
    @log_data_analysis_pipeline = [
        {'$group' => {
            '_id' => '$session',
            'session' => {'$first' => '$session'},
            'CountOfEvents' => {'$sum' => 1},
            'timestamp0' => {'$first' => '$timestamp'},
            'Actions' => {
                '$push' => {
                    'timestamp' => '$timestamp',
                    'event' => '$event',
                    'parameters' => '$parameters'}}}},
        {'$project' => {
            '_id' => 0,
            'session' => '$session',
            'CountOfEvents' => '$CountOfEvents',
            'Actions' => {
                '$map' => { 'input' => "$Actions", 'as' => 'action',
                            'in' => {
                                'TimeFromSessionStart' => {
                                    '$subtract' => ['$$action.timestamp', '$timestamp0']},
                                'event' => '$$action.event',
                                'parameters' => '$$action.parameters'
                            }}}}
        }
    ]
    @key_names = %w(session timestamp event parameters)
    @log_data = <<-EOT.gsub(/^\s+/, '').split(/\n/)
    1          1            Started Session
    1          2            Logged In            Username:"user1"
    2          3            Started Session
    1          3            Started Challenge    title:"Challenge 1", level:"2"
    2          4            Logged In            Username:"user2"
    EOT
    docs = @log_data.collect{|line| line_to_doc(line)}
    LogData.create(docs)
    assert_equal(docs.size, LogData.count)
    puts
  end
  def line_to_doc(line)
    doc = Hash[*@key_names.zip(line.split(/  +/)).flatten]
    doc['session'] = doc['session'].to_i
    doc['timestamp'] = doc['timestamp'].to_i
    doc['parameters'] = eval("{#{doc['parameters']}}") if doc['parameters']
    doc
  end
  test "versions" do
    puts "Mongoid version: #{Mongoid::VERSION}\nMoped version: #{Moped::VERSION}"
    puts "MongoDB version: #{LogData.collection.database.command({:buildinfo => 1})['version']}"
  end
  test "log data analytics" do
    pp LogData.all.to_a
    result = LogData.collection.aggregate(@log_data_analysis_pipeline)
    json = <<-EOT
        [
            {
                "session":1,"CountOfEvents":3,"Actions":[{"TimeFromSessionStart":0,"Event":"Session Started"}, {"TimeFromSessionStart":1, "Event":"Logged In", "Username":"user1"}, {"TimeFromSessionStart":2, "Event":"Started Challenge", "title":"Challenge 1", "level":"2" }]
            },
            {
                "session":2, "CountOfEvents":2,"Actions":[{"TimeFromSessionStart":0,"Event":"Session Started"}, {"TimeFromSessionStart":2, "Event":"Logged In", "Username":"user2"}]
            }
        ]
    EOT
    puts JSON.pretty_generate(result)
  end
  test "explain" do
    LogData.collection.indexes.create('parameters.Username' => 1)
    pp LogData.collection.find({'parameters.Username' => 'user2'}).to_a
    pp LogData.collection.find({'parameters.Username' => 'user2'}).explain['cursor']
  end
end

app/models/log_data.rb

class LogData
  include Mongoid::Document
  field :session, type: Integer
  field :timestamp, type: Integer
  field :event, type: String
  field :parameters, type: Hash
end

$ rake 测试

Run options: 

# Running tests:

[1/3] LogDataTest#test_explain
[{"_id"=>"537258257f11ba8f03000005",
  "session"=>2,
  "timestamp"=>4,
  "event"=>"Logged In",
  "parameters"=>{"Username"=>"user2"}}]
"BtreeCursor parameters.Username_1"
[2/3] LogDataTest#test_log_data_analytics      
[#<LogData _id: 537258257f11ba8f03000006, session: 1, timestamp: 1, event: "Started Session", parameters: nil>,
 #<LogData _id: 537258257f11ba8f03000007, session: 1, timestamp: 2, event: "Logged In", parameters: {"Username"=>"user1"}>,
 #<LogData _id: 537258257f11ba8f03000008, session: 2, timestamp: 3, event: "Started Session", parameters: nil>,
 #<LogData _id: 537258257f11ba8f03000009, session: 1, timestamp: 3, event: "Started Challenge", parameters: {"title"=>"Challenge 1", "level"=>"2"}>,
 #<LogData _id: 537258257f11ba8f0300000a, session: 2, timestamp: 4, event: "Logged In", parameters: {"Username"=>"user2"}>]
[
  {
    "session": 2,
    "CountOfEvents": 2,
    "Actions": [
      {
        "TimeFromSessionStart": 0,
        "event": "Started Session",
        "parameters": null
      },
      {
        "TimeFromSessionStart": 1,
        "event": "Logged In",
        "parameters": {
          "Username": "user2"
        }
      }
    ]
  },
  {
    "session": 1,
    "CountOfEvents": 3,
    "Actions": [
      {
        "TimeFromSessionStart": 0,
        "event": "Started Session",
        "parameters": null
      },
      {
        "TimeFromSessionStart": 1,
        "event": "Logged In",
        "parameters": {
          "Username": "user1"
        }
      },
      {
        "TimeFromSessionStart": 2,
        "event": "Started Challenge",
        "parameters": {
          "title": "Challenge 1",
          "level": "2"
        }
      }
    ]
  }
]
[3/3] LogDataTest#test_versions                           
Mongoid version: 3.1.6
Moped version: 1.5.2
MongoDB version: 2.6.1
Finished tests in 0.083465s, 35.9432 tests/s, 35.9432 assertions/s.
3 tests, 3 assertions, 0 failures, 0 errors, 0 skips

【讨论】:

  • 抱歉回复晚了,非常感谢您的详细解答。我还有一个疑问。您似乎对CountOfEventsTimeFromSessionStart 进行了硬编码,但我希望在添加合成附加数据时具有更大的灵活性。例如。如果这个人想要在输出中计算特定事件怎么办。例如。 Started Challenge 的计数,或者他想要在 Started ChallengeChanged Gravity 之外执行的事件数。我正在考虑制作充当模板、接受参数、计算合成数据的函数。稍后,合成数据可用于形成 JSON blob。
  • 在这种情况下 PostgreSQL 会更好还是 MongoDB 在这种情况下会更好?使用 MongoDB,我可以使用 Mongoid 进行查询,但是我怀疑 PostgreSQL 使用此类查询可能会更快(其中大多数是基于聚合的)。对此有任何建议/意见都会很棒。无论如何,我仍然会给你赏金,因为你的答案似乎是最详细和最有帮助的,它一定需要付出很多努力!
【解决方案3】:

MongoDB 是一个理想的数据库。

  1. 为您的原始日志数据创建一个集合。

  2. 使用 Mongo 强大的聚合工具之一,并将聚合的数据输出到另一个集合(或多个输出集合,如果您想要原始数据的不同存储桶或视图)

如果您可以容忍响应中的一些延迟,您可以使用一组预先确定的用户可以从中提取的可能性离线进行聚合,也可以按需/临时进行。

http://docs.mongodb.org/manual/aggregation/

【讨论】:

  • 嗨,我应该如何将日志数据存储在 MongoDB 中。我的意思是,Parameters 应该如何表示,以便以后可以对它们进行部分索引以提高某些类型查询的查询速度?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-04-18
  • 1970-01-01
  • 1970-01-01
  • 2014-11-21
  • 1970-01-01
  • 2011-12-04
  • 2013-11-28
相关资源
最近更新 更多