首页 > 解决方案 > 使用 Rails 5,为每条新记录生成和使用 Basecamp 风格的“hash_id”(而不是顺序行 id)的最有效方法是什么?

问题描述

我想要的是 URL 与Basecamp的非常相似:

https://3.basecamp.com/4000818/buckets/7452203/message_boards/1039416768

我已经按照本指南实现了这个功能,但是我对需要运行可能数百万个 .exists 的过程不满意?查找开放号码并担心这会很快影响我的应用程序的性能。

def set_hash_id
    hash_id = nil
    loop do
      hash_id = SecureRandom.urlsafe_base64(9).gsub(/-|_/,('a'..'z').to_a[rand(26)])
      break unless self.class.name.constantize.where(:hash_id => hash_id).exists?
    end
    self.hash_id = hash_id
  end

我发现很难相信 Basecamp 在每次记录保存时都依赖于如此低效的东西,我正在寻找他们是如何做到的,或者找到一个看起来相同但没有链接教程开销的设置。

对于生成非顺序记录 ID 的方法,我将不胜感激。我对 UUID 不感兴趣,因为我无法忍受它们生成的不讨人喜欢的 URL。此外,它们必须是整数。基本上,与 Basecamp URL 完全一样,但没有存在的开销?检查。他们是否有可能将数字与编码时间戳或其他东西组合在一起以确保没有冲突?我已经探索了hashids.org方法,但这不会生成仅整数哈希。

我使用 Postgres 作为我的数据库,以防万一这有帮助。

标签: ruby-on-railsrubydatabaseurlactiverecord

解决方案


效率方面我认为你应该没问题。GitLab 也使用类似的东西来生成独特的令牌。

还有另一个值得考虑的问题:

您的方法不能保证生成唯一密钥,因为该操作不是原子的(GitLab 也不是)。在检查唯一性和将记录写入数据库之间,可能已经生成了相同的密钥。

您至少有 2 个选项来处理此问题。两种解决方案也应该更有效(这是您主要关心的问题)。

在保存时捕获数据库的唯一键约束违规

def save
  begin
    self.hash_id = generate_hash_id
    super
  rescue ActiveRecord::RecordNotUnique => e
    # 1. you may need to check the message for the specific constraint
    # 2. you may need to implement an abort condition to prevent infinite retries
    retry
  end
end

您也可以在 ActiveRecord 回调中执行此操作。

让数据库生成密钥

另一种解决方案是让您的数据库在创建时生成唯一键。像这篇博文A Better ID Generator For PostgreSQL中描述的函数可能会更好地满足您的目的。

此解决方案的优点是您的应用程序代码无需担心生成或捕获冲突。缺点是这个解决方案是特定于数据库的。


推荐阅读