在 rails 中将数据从一个(糟糕的)数据库增量复制到另一个(更好的)数据库

Incrementally copy data from one (horrible) database to another (nicer) database in rails

与我目前的所有问题一样,我正在远程机器上使用“Advantage 数据库服务器”,它速度慢且笨拙。

如果我能编写一个快速脚本将对“实时”系统所做的更改转储到一个不错的 PostgreSQL 数据库中,那就太好了。

现有数据库由大约 30 table 组成,但是其中只有大约 7 个被主动更新。

我已经将要复制的那些定义为模型。

ADS tables 都有一个伪列“ROWID”,它应该在现有数据库中保持不变(根据文档)...这也经常用作“主要” ADS tables 上的密钥”,除了它没有编入索引!

我建议在 PostgreSQL 中创建一个新的 table 并复制此数据,包括伪列 ROWID(我相信这不是 PostgreSQL 保留字),然后进行比较将实时 ADS 数据转换为 PostgreSQL 等效数据。

class Determinand << AisBase
  self.table_name = 'DETS'
  self.sequence_name = :autogenerated
  self.primary_key = 'DET'
end

class PgDeterminand << PostgresBase
  self.sequence_name = :autogenerated
  self.primary_key = 'DET'
end

livet = Determinand.select("ROWID").map(&:ROWID)
devt = PgDeterminand.select("ROWID").map(&:ROWID)

new_dets = Determinand.find_by(ROWID: livet - devt)
# or maybe
(livet - devt).map do |rid|
  Determinand.find_by(ROWID: rid)
end

然后遍历 new_dets 以创建新的 PgDeterminand 行 ...

读取速度很慢:

puts Benchmark.measure { livet=Determinand.select("ROWID").map(&:ROWID) }
  0.196957   0.098432   0.295389 ( 26.503560)

livet.count
 => 6136

这不是一个大 table ...

谁能想出一个更清晰的方法来看待这个问题?

-- 编辑--

好的,我已将所有现有模型复制到“Ads”文件夹,在 Postgres 中创建了新对象(基于现有 schema.rb 文件),从中删除了所有 belongs_to模型(AIS LIMS tables 上没有参考完整性!)我可以快速轻松地将数据复制到新的 tables,如下所示:

def force_utf8 (hsh)
  hsh.each_with_object({}) do |(i,j),a|
    a[i]= j.present? && j.is_a?(String) ? j.encode("utf-8", invalid: :replace, undef: :replace, replace: '?') : j
  end
end

Ads::Determinand.all.as_json.each do |d|
  Determinand.create(force_utf8(d))
end

这还不是增量,但使用现有 table 的 ROWID,我应该可以从那里开始工作

-- 编辑 2 --

ROWID 对于每个 table 似乎基本上是连续的...除了它使用顺序“[A-Za-z0-9+/]”...太棒了!

我希望为“实时”系统中的新数据做一个“大于上次存储的 ROWID”:

Ads::Determinand.where(Ads::Determinand.arel_table['ROWID'].gt(Determinand.maximum(:ROWID))).as_json.each do |d|
  Determinand.create(force_utf8(d))
end

但这显然不能处理以“zz”结尾的 ROWIDs:

CFTquNARAXIFAAAezz 大于 CFTquNARAXIFAAAe+D

我不熟悉 ADS,但 IMO 如果您有权通过向其添加必要的索引来修改设计,这将是一个好的开始。

此外 Determinand.pluck(:id) 总是比 Determinand.select("ROWID").map(&:ROWID)

快得多

好的,我现在已经整理好了:

模式初始化

首先,我将所有模型移至“广告”目录(向每个模型添加“模块广告”),在我的项目中设置 2 个数据库并使用 rake db:schema:dump[收集“现有”模式=20=]

然后我创建了新模型(例如):

rails g model Determinand

然后我将现有模型从 ads_schema.rb 复制到 rails 迁移,并且 rake db:migrate:postgres

初始数据转储

然后我做了一个初始数据export/import。

在较小的桌子上,我可以使用以下内容:

Ads::Client.all.as_json.each do |c|
  Client.create(c)
end

但在较大的表上,我不得不使用 ADS 的 CSV 导出,以及 pgloader 脚本来引入数据:

load CSV
    from 'RESULTS.csv'
        having fields
        (
            SAMPNUM, DET, JOB, GLTHAN, INPUT, OUTPUT, RESULT, ERROR, GENFLAG,
            SPECFLAG, STATFLAG, COMPFLAG, REPEAT, DETORDER, ANALYST, DETDATE [date format 'DD/MM/YYYY'],
            DETTIME, LOGDATE [date format 'DD/MM/YYYY'], APPROVED [date format 'DD/MM/YYYY'], APPROVEDBY, INSTRUMENT, FILENAME, LINE_NO,
            TEXTRESULT, DATATYPE, SUITE, TEST, SECTION, UKAS, MCERTS, ACCRED, DEVIATING,
            PRINT_1, PRINT_1_BY, PRINT_1_AT, PRINT_2, PRINT_2_BY, PRINT_2_AT, LABEL, LABLOCN
        )
    into postgresql://$user:$password@localhost/ads_project
        TARGET TABLE results
        TARGET COLUMNS
        (
            'SAMPNUM', 'DET', 'JOB', 'GLTHAN', 'INPUT', 'OUTPUT', 'RESULT', 'ERROR', 'GENFLAG',
            'SPECFLAG', 'STATFLAG', 'COMPFLAG', 'REPEAT', 'DETORDER', 'ANALYST', 'DETDATE',
            'DETTIME', 'LOGDATE', 'APPROVED', 'APPROVEDBY', 'INSTRUMENT', 'FILENAME', 'LINE_NO',
            'TEXTRESULT', 'DATATYPE', 'SUITE', 'TEST', 'SECTION', 'UKAS', 'MCERTS', 'ACCRED', 'DEVIATING',
            'PRINT_1', 'PRINT_1_BY', 'PRINT_1_AT', 'PRINT_2', 'PRINT_2_BY', 'PRINT_2_AT', 'LABEL', 'LABLOCN'
        )
    with csv header,
        fields optionally enclosed by '"',
        fields terminated by ',',
        drop indexes

    before load do
        $ alter table results alter column created_at drop not null, alter column updated_at drop not null; $$

    after load do
        $ update results set created_at = "DETDATE", updated_at=NOW() where created_at is null and updated_at is null; $,
        $ alter table results alter column created_at set not null, alter column updated_at set not null; $
;

增量更新

对于增量更新,我必须执行如下操作:

在较小的表上(~ <1000 行):

Ads::DetLimit.where.not(ROWID: DetLimit.pluck(:ROWID)).as_json.each do |d|
  DetLimit.create(force_utf8(d))
end

在较大的表上,我需要使用 Ruby 来限制已更改的 ID(本质上是白名单而不是黑名单):

zzz = SuiteDet.pluck(:ROWID)
yyy = Ads::SuiteDet.pluck(:ROWID)

Ads::SuiteDet.where(ROWID: yyy-zzz).as_json.each do |d|
  SuiteDet.create(force_utf8(d))
end

部署

我为 运行 创建了一个 CopyTable 脚本,这样我就可以对其进行批处理,现在只需增量,到 运行 大约需要 2 分钟,这是可以接受的