用户活动记录的架构设计

How*_*ame 10 postgresql database-design

我正在设计一个模式来支持用户活动的日志记录,用户必须能够搜索:

  • 跨所有事件(任何类型的事件),具有日期时间范围,按用户名;
  • 跨一种类型的事件,与上述相同,另外还有该模块的参数。

我创建了这个架构:

在此处输入图片说明

并设计查询以进行搜索:

  • 在所有事件中:

    SELECT extract(epoch from log_time) * 1000,
      u.username,
      CASE WHEN l.event_type IN (0, 2) THEN e.template
        WHEN l.event_type = 1 THEN format(e.template, ecs.field)
        WHEN l.event_type = 3 THEN format(e.template, ess.field)
        WHEN l.event_type = 4 THEN format(e.template, ect.ip, ect.port)
      END
    FROM logs l
    JOIN users u ON u.id = l.user_id
    JOIN event_def e ON e.id = l.event_type
    LEFT JOIN event_client_search ecs ON ecs.log_id = l.id
    LEFT JOIN event_switch_search ess ON ess.log_id = l.id
    LEFT JOIN event_cable_test ect ON ect.log_id = l.id
    
    Run Code Online (Sandbox Code Playgroud)
  • 跨同类型事件:

    SELECT extract(epoch from log_time) * 1000,
        u.username,
        format(e.template, ect.ip, ect.port)
    FROM logs l
    JOIN users u ON u.id = l.user_id
    JOIN event_def e ON e.id = l.event_type
    JOIN event_cable_test ect ON ect.log_id = l.id
    
    Run Code Online (Sandbox Code Playgroud)

这是event_def表的样子:

 id |                template
----+----------------------------------------
  0 | Logged in
  2 | Logged out
  1 | Searched in clients %s
  3 | Searched in switches %s
  4 | Cable test switch %s port %s
Run Code Online (Sandbox Code Playgroud)

但是我不喜欢的是,当我有大约 100 个事件(现在我有大约 50 个,但还没有实现它们)时,这将是一个性能问题。

所以我想合并具有相同参数的事件,如下所示:

在此处输入图片说明

我认为这不会有太大帮助(它最多可以将事件数量减少一半),也许我在思考错误的方向?

Joe*_*own 6

我要求您重新考虑的假设之一是您是否真的希望为每种事件类型使用不同的表,或者这些事件类型是否是表中的行而不是不同的表。

如果您有一个EVENT_TYPE包含事件类型列表(大约 100 个)的表,您可以从当前的各种事件表中获取列,并将它们排在EVENT_PARAMETER表中。

当您添加新的事件类型时,这种类型的设计会将您的架构更改转换为数据更改。这将节省您的代码维护问题,并避免使您的查询更复杂和可能更慢。

事件的实例将类似地保存在两个表中,例如一个LOG表和一个LOG_DETAIL表。

LOG 将是用户和事件类型之间的交集。

LOG_DETAIL 将是日志和事件参数之间的交集。

您的数据模型可能如下所示:ERD

请注意,有些人可能对这种方法有一个反对意见,即您最终将日志参数值保存为字符串格式,而不是本机格式。这显然是一种权衡。您必须问问自己,这是否适合您的情况。

  • @SarvarNishonboyev 我将 Visio 与我自己设计的 ERD 模具一起使用,以使用 James Martin 乌鸦脚符号,这是我更喜欢的符号。模板使用自定义线条样式使其具有手绘外观。我有一个类似的,它是规则的直线。我发现手绘外观非常适合快速草图和初步设计,我想传达设计是初步的印象。 (2认同)

小智 1

如果您愿意接受非关系方法,您最终可能会得到一个更干净的模式:

table event_log
id | occurred_at         | event_type      | attributes
1  | 2016-01-01 00:00:00 | user.registered | {"user.id": 1, "user.role": "admin"}}
2  | 2016-01-01 00:01:00 | user.exploded   | {"user.id": 1}

table attribute_search_index
event_id | attribute_name | attribute_value
1        | user.id        | 1
1        | user.role      | admin
2        | user.id        | 1

table event_format (optional)
event_type      | format
user.registered | User {user.id} has registered with role {user.role}
user.exploded   | User {user.id} has exploded
Run Code Online (Sandbox Code Playgroud)

这种模式将允许您搜索事件,但每个参数需要一个额外的连接:

 SELECT e.* FROM event_log AS e
   INNER JOIN attribute_search_index AS p1 
     ON p1.event_id = e.id 
       AND p1.attribute_name = 'user.id' 
       AND p1.attribute_value = '1'
   INNER JOIN attribute_search_index AS p2 
     ON p2.event_id = e.id 
       AND p2.attribute_name = 'user.role' 
       AND p2.attribute_value = 'admin'
 WHERE e.type = 'user.registered'
Run Code Online (Sandbox Code Playgroud)

然而,这也带来了一些警告:

  • 目前尚不清楚 JSON 属性字段是否可以有界,如果它最终位于行之外,则可能会影响性能(但是,该字段可能不需要超过 256 个字符)
  • 虽然嵌套属性很容易被扁平化为x.y.z符号,但数组处理却不清楚。然而,您可以简单地将其展平为搜索索引的记录集,即如果user.role[developer, engineer],您可以为搜索索引创建两个记录,(<id>, 'user.role', 'developer')并且(<id>, 'user.role', 'engineer')
  • 整数、浮点数、布尔值、空值被视为字符串。在大多数情况下这不是问题。

第三个表是复制的event_def但在现实中完全没有用。有可能,如果工程师查看日志,他根本不需要格式化,并且如果将它们呈现给最终用户,他们将通过应用程序获得,并且直接编译格式化定义要容易得多进入应用程序。