将CloudSql中的数据流式传输到Dataflow

时间:2018-02-14 10:28:34

标签: jdbc google-cloud-dataflow apache-beam spotify-scio

我们目前正在探索如何使用Apache Beam / Google Dataflow在Google Cloud SQL数据库(MySQL)中处理大量数据存储。

数据库在一个表中存储大约200GB的数据。

我们使用JdbcIO成功读取了数据库中的行,但到目前为止,这只有LIMIT查询的行数才有可能。否则我们会遇到内存问题。我假设默认情况下SELECT查询尝试在内存中加载所有结果行。

这是什么惯用法?批处理SQL查询?流式传输结果?

我们尝试调整已执行语句的fetch size,但没有取得多大成功。

这就是我们的JDBC读取设置:

JdbcReadOptions(
  connectionOptions = connOpts,
  query = "SELECT data FROM raw_data",
  statementPreparator = statement => statement.setFetchSize(100),
  rowMapper = result => result.getString(1)
)

到目前为止,我还没有找到任何有关sql流的资源。

修改

我要列出我采取的观点方法,以便其他人可以学习一些东西(例如这样做)。为了获得更多的上下文,有问题的数据库表结构非常糟糕:它有一个包含JSON字符串的列,id列(主键)加上addedmodified列(两个TIMESTAMP类型)。在第一次进场时,它没有进一步的指数。该表包含25 mio行。所以这可能更像是数据库问题,而不是Apache Beam / JDBC问题。但尽管如此:

方法1(上文) - 查询所有内容

基本上它看起来像这样:

val readOptions = JdbcReadOptions(
  connectionOptions = connOpts,
  query = "SELECT data FROM raw_data",
  rowMapper = result => result.getString(1)
)

context
  .jdbcSelect(readOptions)
  .map(/*...*/)

如果我在查询中添加LIMIT,则此方法有效。但显然很慢。

方法2 - 密钥集分页

val queries = List(
  "SELECT data from raw_data LIMIT 5000 OFFSET 0",
  "SELECT data from raw_data LIMIT 5000 OFFSET 5000",
  "SELECT data from raw_data LIMIT 5000 OFFSET 10000"
  // ...
)

context
  .parallelize(queries)
  .map(query => {
      val connection = DriverManager.getConnection(/* */)
      val statement = connection.prepareStatement(query)
      val result = statement.executeQuery()

      makeIterable(result) // <-- creates a Iterator[String]
  })
  .flatten
  .map(/* processing */)

虽然我很快就知道LIMIT _ OFFSET _组合也从第一行开始扫描,但效果稍好一些。因此,每个后续查询都需要更长时间,并且会长时间收敛。

方法2.5 - 带有排序的键集分页

与上述方法类似,但我们在added列上创建了一个索引,并将查询更新为

SELECT data FROM raw_data ORDER BY added LIMIT 5000 OFFSET x

这加快了速度,但最终查询时间变长了。

方法3 - 无波束/数据流

val connection = DriverManager.getConnection(/* */)
val statement = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)
statement.setFetchSize(Integer.MIN_VALUE)

val rs = statement.executeQuery("SELECT data FROM raw_data")

while(rs.next()) {
  writer writeLine rs.getString(1)
}

这会逐行将结果集流式传输并将行写入文件。所有25个mio记录的运行时间约为2小时。最后。如果有人能指出如何使用Beam实现这个解决方案,那就太好了。

顺便说一句:现在我将原始数据作为CSV文件处理使用Beam是一件轻而易举的事。这是大约80GB的原始数据,可以在大约5分钟内通过自动缩放等转换为另一种CSV格式。

2 个答案:

答案 0 :(得分:2)

似乎MySQL JDBC驱动程序需要一些特殊措施才能使整个结果集无法加载到内存中;例如我能够找到this code在另一个项目中解决问题。 JdbcIO需要做同样的事情,或者至少可以配置足以让用户这样做。我提交了问题https://issues.apache.org/jira/browse/BEAM-3714

同时,作为一种变通方法,您可以使用JdbcIO.readAll()将查询分区为许多较小的查询,例如您可以通过一系列ID对其进行分区。请注意,它们之间不会强制执行事务一致性 - 就MySQL而言,它们将是独立的查询。

答案 1 :(得分:1)

我认为JDBCIO由于其固有的局限性(单个SELECT)而无法很好地扩展。我不知道来自MySQL和BEAM的流媒体支持。

您可以将数据库转储到更容易处理的数据处理系统(例如,csv)。它对你有用吗?