【问题标题】:Profile Millions of Text Files In Parallel Using An Sqlite Counter?使用 Sqlite 计数器并行分析数百万个文本文件?
【发布时间】:2017-03-10 17:31:27
【问题描述】:

一大堆文本文件(A、B 和 C 类型)放在我的胸口,缓慢而冷酷地拒绝我急需的空气。多年来,每个类型规范都有增强功能,因此昨天的 typeA 文件比去年的 typeA 具有更多的属性。为了构建一个能够处理这些文件类型十年来的长期演变的解析器,迭代地、冷静地检查所有 1400 万个文件是有意义的,但在它们被压死之前。

我构建了一个运行计数器,这样每次我看到属性(熟悉与否)我都会在其计数中加 1。 sqlite 计数板如下所示:

在特殊事件中,我看到一个不熟悉的属性,我将它们添加到计数中。在一个看起来像这样的 typeA 文件上:

我把这个系统搞砸了!但是在一个过程中@ 3M 个文件/36 小时很慢。最初我使用this tricksqlite 传递需要递增的属性列表。

placeholder= '?' # For SQLite. See DBAPI paramstyle.
placeholders= ', '.join(placeholder for dummy_var in properties)
sql = """UPDATE tally_board
SET %s = %s + 1
WHERE property IN (%s)""" %(type_name, type_name, placeholders)
cursor.execute(sql, properties)

我知道这是个坏主意,因为

  1. sqlite 字符串搜索比索引搜索慢很多
  2. 数百个属性(大约 160 个字符长)构成真正长 sql 查询
  3. 使用 %s 而不是 ? 是不好的安全做法...(不是问题 ATM)

“修复”是维护脚本端 property-rowid 此循环中使用的计数的哈希:

  1. new_properties 读取文件
  2. 阅读tally_board 获取rowidproperty
  3. 从 2 的读取中生成脚本端 client_hash
  4. 为每个new_property 而不是property 写入行到tally_board(还没有增加)。使用新属性更新 client_hash
  5. 使用client_hash 查找rowidnew_properties 中的每一行
  6. 将增量写入每个rowid(现在是property 的代理)到tally_board

第 6 步。看起来像

sql = """UPDATE tally_board
SET %s = %s + 1
WHERE rowid IN %s""" %(type_name, type_name, tuple(target_rows))
cur.execute

这个问题是

  • 还是很慢!
  • 它在并行处理中表现出一种竞争条件,只要线程 A 在线程 B 完成第 6 步之前开始第 2 步,就会在 property 列中引入重复项。

竞争条件的解决方案是在 db 上为步骤 2-6 提供排他锁虽然看起来读取无法获得这些 Lock A Read

另一次尝试uses a genuineUPSERT 一举增加预先存在的property 行并插入(和增加)新的property 行。

something like this 可能有运气,但我不确定如何重写它以增加计数。

【问题讨论】:

    标签: python python-3.x sqlite multiprocessing


    【解决方案1】:

    改变表模式怎么样?不是每个类型都有一个列,而是有一个类型列。然后,您将拥有由属性和类型标识的唯一行,如下所示:

    |rowid|prop    |type |count|
    ============================
    |1    |prop_foo|typeA|215  |
    |2    |prop_foo|typeB|456  |
    

    这意味着您可以为每个文件的每个属性分别输入一个事务,让sqlite 担心比赛。因此,对于您遇到的每个属性,立即发出一个完整的事务,计算下一个总数并更新由属性名称和文件类型标识的记录。

    【讨论】:

    • 这是否与保留我当前的架构但按照您的建议单独(而不是批量)更新表具有相同的效果?
    • 好吧,使用您当前的架构,您会在由不相关文件类型引起的更新之间固有地创建竞争条件。由于文件(可能)只有一种文件类型,这意味着您可以通过根据文件类型键入记录来避免一整套竞争条件。但是,您可以定义一个定期重新计算的视图(可能在批量导入新文件集后通过显式命令触发),为方便起见以旧格式汇总结果。
    • 反过来,您避免的每个竞争条件都意味着另一种情况,即事务在快乐/快速路径中执行,而不是招致可能代价高昂的锁定/回滚。
    • 此外,拥有单个 type 列比拥有 N 类型特定列更容易扩展。毕竟,您没有预见到的每种新文件类型现在都只是另一个值,而不是昂贵的架构更新。
    • 事实证明,更频繁地写入数据库会使事情变得更慢。配方:使用 sqlite + 多处理时,尽可能不频繁地写入 sqlite。将我在 sqlite 中尝试做的大部分工作卸载到内存上,时间减少了 60%。
    【解决方案2】:

    以下内容大大加快了速度:

    • 不常写信给SQLite。将我的大部分中间结果保存在内存中,然后每 50k 文件用它们更新数据库导致大约三分之一的执行时间(35 小时到 11.5 小时)
    • 将数据传输到我的 PC 上(由于某种原因,我的 USB3.0 端口传输的数据远低于 USB2.0 速率)。这导致大约五分之一的执行时间(11.5 小时到 2.5 小时)。

    【讨论】:

      猜你喜欢
      • 2016-08-22
      • 1970-01-01
      • 1970-01-01
      • 2015-06-02
      • 2020-03-16
      • 2014-02-24
      • 1970-01-01
      • 2023-02-24
      • 2016-06-12
      相关资源
      最近更新 更多