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

发布于 2024-10-07 03:35:24 字数 1513 浏览 5 评论 0原文

我有一个非常常见的设计问题:我需要为 Google App Engine 中的记录实现历史日志(审核跟踪)。历史日志必须结构化,即我无法将所有更改加入到一些自由格式文本中并存储在字符串字段中。

我考虑了历史模型的以下选项,在注意到选项#1 中的性能问题后,我选择实现选项#3。但对于该解决方案是否高效且可扩展仍存在一些疑问。例如:随着选项#3 中动态属性数量的增加,是否存在性能显着下降的风险?

您是否对每个选项的优缺点有更深入的了解,或者可以建议适用于 Google App Engine DB 特征的其他审核跟踪设计模式?

  1. 使用经典 SQL“主从”关系
    • 优点
      • 对于具有 SQL 背景的数据库开发人员来说易于理解
      • clean:直接定义历史记录及其属性
      • 搜索性能:轻松搜索历史记录(可以使用索引)
      • 问题排查:通过管理工具 (_ah/admin) 轻松访问
    • 缺点
      • 通常不建议在 GAE DB 中以这种方式实现一对多关系
      • 读取性能:记录读取操作数量过多,无法显示较长的审计跟踪,例如在大记录列表的详细信息窗格中。
  2. 将历史记录存储在 BLOB 字段中(pickled python 结构)
    • 优点
      • 实施简单且灵活
      • 读取性能:非常高效
    • 缺点
      • 查询性能:无法使用索引进行搜索
      • 问题排查:无法通过管理数据库查看器 (_ah/admin) 检查数据
      • 不干净:SQL 开发人员不太容易理解/接受(他们认为这很难看)
  3. 将历史记录存储在 Expando 的动态属性中。例如,为每个字段 fieldName 创建 history_fieldName_n 字段(其中 n=<0..N> 是历史记录的数量)
    • 优点:
      • 简单:易于实现和理解
      • 问题排查:可以通过管理界面读取所有历史属性
      • 读取性能:一次读取操作即可获取记录
    • 缺点:
      • 搜索性能:不能简单地搜索历史记录(它们有不同的名称)
      • 不太干净:属性的数量乍一看可能会令人困惑
  4. 将历史记录存储在主记录中的某些列表字段中。例如。为每个 fieldName 创建一个 fieldName_history 列表字段
    • 优点:
      • clean:直接定义历史属性
      • 简单:SQL 开发人员易于理解
      • 读取性能:一次读取操作即可获取记录
    • 缺点:
      • 搜索性能:只能使用索引搜索任何时候具有某个值的记录,而无法搜索在某个特定时间具有值组合的记录;
      • 问题排查:在管理数据库查看器中检查列表很困难

I have a quite common design problem: I need to implement a history log (audit trail) for records in Google App Engine. The history log has to be structured, i.e I cannot join all changes into some free-form text and store in string field.

I've considered the following options for the history model and, after noticing performance issues in option #1, I've chosen to implement option #3. But have stil some doubts if this solution is efficient and scalable. For instance: is there a risk that performance will degrade significantly with increased number of dynamic properties in option #3?

Do you have some deeper knowledge on the pros/cons for each option or could suggest other audit trail design patterns applicable for Google App Engine DB characteristics?

  1. Use classic SQL "master-detail" relation
    • Pros
      • simple to understand for database developers with SQL background
      • clean: direct definition for history record and its properties
      • search performance: easy searching through history (can use indices)
      • troubleshooting: easy access by administration tools (_ah/admin)
    • Cons
      • one-to-many relations are often not recommended to be implemented this way in GAE DB
      • read performance: excessive number of record read operations to show long audit trail e.g. in details pane of a big records list.
  2. Store history in a BLOB field (pickled python structures)
    • Pros
      • simple to implement and flexible
      • read performance: very efficient
    • Cons
      • query performance: cannot search using indices
      • troubleshooting: cannot inspect data by admin db viewer (_ah/admin)
      • unclean: not so easy to understand/accept for SQL developers (they consider this ugly)
  3. Store history in Expando's dynamic properties. E.g. for each field fieldName create history_fieldName_n fields (where n=<0..N> is a number of history record)
    • Pros:
      • simple: simple to implement and understand
      • troubleshooting: can read all the history properties through admin interface
      • read performance: one read operation to get the record
    • Cons:
      • search performance: cannot simply search through history records (they have different name)
      • not too clean: number of properties may be confusing at first look
  4. Store history in some set of list fields in the main record. Eg. for each fieldName create a fieldName_history list field
    • Pros:
      • clean: direct definition of history properties
      • simple: easy to understand for SQL developers
      • read performance: one read operation to get the record
    • Cons:
      • search performance: can search using indices only for records which whenever had some value and cannot search for records having combination of values at some particular time;
      • troubleshooting: inspecting lists is difficult in admin db viewer

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

鸢与 2024-10-14 03:35:24

如果我必须选择,我会选择选项 1。读取的性能与其他选项一样(如果不是更好)。所有其他选项仅在特定情况下(小或非常大的更改集)才具有速度优势。它还将为您提供很大的灵活性(更轻松),例如 x 天后清除历史记录或跨不同模型类型的查询历史记录。确保在同一事务中将历史实体创建为已更改实体的子实体,以保证一致性。你最终可能会得到以下之一:

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

If I would have to choose I would go for option 1. The reads are as (if not more) performant for the other options. And all other options only have speed advantages under specific circumstances (small or very large sets of changes). It will also get you lots of flexibility (with more ease) like purging history after x days or query history across different model types. Make sure you create the history entities as a child of the changed entity in the same transaction to guarantee consistency. You could end up with one of these:

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文