在Google App Engine中实现高效的记录变更审计跟踪 - 设计模式

6 投票
1 回答
2104 浏览
提问于 2025-04-16 08:23

我遇到了一个比较常见的设计问题:我需要在Google App Engine上实现一个历史记录(审计日志),用于记录的变更。这个历史记录必须是结构化的,也就是说,我不能把所有的变更合并成一段自由格式的文本,然后存储在字符串字段里。

我考虑了几种历史记录的模型,发现第一种方案在性能上有问题,所以我选择了第三种方案。但我还是有些疑虑,这个解决方案是否高效且可扩展。例如:随着动态属性数量的增加,第三种方案的性能会不会显著下降?

你是否对每种方案的优缺点有更深入的了解,或者能否建议一些适用于Google App Engine数据库特性的其他审计日志设计模式?

  1. 使用经典的SQL“主从”关系
    • 优点
      • 对于有SQL背景的数据库开发者来说,容易理解
      • 结构清晰:历史记录及其属性直接定义
      • 搜索性能好:可以通过历史记录轻松搜索(可以使用索引)
      • 故障排查方便:可以通过管理工具轻松访问 (_ah/admin)
    • 缺点
      • 在GAE数据库中,通常不推荐以这种方式实现一对多关系
      • 读取性能差:为了显示长的审计日志,读取操作过多,例如在大型记录列表的详细信息面板中。
  2. 将历史记录存储在BLOB字段中(序列化的Python结构)
    • 优点
      • 实现简单且灵活
      • 读取性能好:非常高效
    • 缺点
      • 查询性能差:无法使用索引进行搜索
      • 故障排查不方便:无法通过管理数据库查看器检查数据 (_ah/admin)
      • 结构不清晰:对于SQL开发者来说,不太容易理解(他们觉得这很丑)
  3. 将历史记录存储在Expando的动态属性中。例如,对于每个字段 fieldName 创建 history_fieldName_n 字段(其中 n=<0..N> 是历史记录的数量)
    • 优点:
      • 简单:实现和理解都很简单
      • 故障排查方便:可以通过管理界面读取所有历史属性
      • 读取性能好:一次读取操作即可获取记录
    • 缺点:
      • 搜索性能差:无法简单地搜索历史记录(它们有不同的名称)
      • 结构不太清晰:属性数量在初看时可能会让人困惑
  4. 将历史记录存储在主记录的一组列表字段中。例如,对于每个 fieldName 创建一个 fieldName_history 列表字段
    • 优点:
      • 结构清晰:历史属性直接定义
      • 简单:对于SQL开发者来说容易理解
      • 读取性能好:一次读取操作即可获取记录
    • 缺点:
      • 搜索性能差:只能对曾经有值的记录使用索引进行搜索,无法搜索在特定时间具有某些值组合的记录;
      • 故障排查困难:在管理数据库查看器中检查列表比较困难

1 个回答

3

如果让我选择的话,我会选择选项1。读取数据的速度和其他选项一样快(甚至更快)。而且其他选项只有在特定情况下(比如变更的数量很少或非常多)才会有速度上的优势。选择这个选项还会给你带来很多灵活性,比如可以在一定天数后清除历史记录,或者跨不同模型类型查询历史记录。确保在同一个操作中,把历史记录作为被更改实体的子项创建,这样可以保证数据的一致性。你可能会得到这样的结果:

class HistoryEventFieldLevel(db.Model):
    # parent, you don't have to define this
    date = db.DateTime()
    model = db.StringProperty()
    property = db.StringProperty() # Name of changed property
    action = db.EnumProperty(['insert', 'update', 'delete'])
    old = db.PickleProperty() # Old value for field, empty on insert
    new = db.PickleProperty() # New value for field, empty on delete

class HistoryEventModelLevel(db.Model):
    # parent, you don't have to define this
    date = db.DateTime()
    model = db.StringProperty()
    action = db.EnumProperty(['insert', 'update', 'delete'])
    change = db.PickleProperty() # Dictionary with changed fields as keys and tuples (old value, new value) as values

撰写回答