2

我正在使用 Ruby 客户端处理 PostgreSQL,我想使用 SHA-1 哈希 id 对表进行分区,例如

                    id                    
------------------------------------------
 fe935b318f6976afdec83fa7339ff2069b0bc0c3
 d67948e38a645fd5ffdde6dab4dc627b2b19d1b1
 0d304f5134b0a46c2248a34c3e9c50ad2b547fdf

进程分区是将数据集划分为 N 个部分,并将每个部分分配给 N 个进程之一。如果您在 RDBMS 中有整数键,这很简单:

select * from items_to_be_processed where MOD(id, N) = ASSIGNED_PARTITION

Ryan Smith建议,如果你有字符串键,你可以在它们上使用 CRC32 来获取整数,然后是模数 - 但是,假设你的键大部分是均匀分布的(就像我认为它会使用 SHA-1 哈希),这会不会更容易?对于N = 4,例如:

select * from items_to_be_processed where id < ASSIGNED_PARTITION_1
select * from items_to_be_processed where id < ASSIGNED_PARTITION_2 and id >= ASSIGNED_PARTITION_1
select * from items_to_be_processed where id < ASSIGNED_PARTITION_3 and id >= ASSIGNED_PARTITION_2
select * from items_to_be_processed where id >= ASSIGNED_PARTITION_4

所以也许如果N = 2,那么

select * from items_to_be_processed where id < '8888888888888888888888888888888888888888'   <- process 1
select * from items_to_be_processed where id >= '8888888888888888888888888888888888888888'  <- process 2

给定 N,我如何计算分区点(8888888888888888888888888888888888888888ffffffffffffffffffffffffffffffffffffffff成两半,可能我什至没有正确计算)?我应该在 SQL (Postgres) 中还是在 Ruby 客户端中进行调用?

PS。灵感来自 MongoDB 食谱中的随机属性思想。

更新

888...上面的计算不正确 - 这是一种在 Ruby 中执行此操作的方法,感谢 Carl Norum 的回答让我更接近:

>> 'f'*40
=> "ffffffffffffffffffffffffffffffffffffffff"
>> a = 0xffffffffffffffffffffffffffffffffffffffff
=> 1461501637330902918203684832716283019655932542975
>> b = a / 2
=> 730750818665451459101842416358141509827966271487
>> '%x' % b
=> "7fffffffffffffffffffffffffffffffffffffff"
>> '%x' % (b + 1)
=> "8000000000000000000000000000000000000000"
4

3 回答 3

2

那计算不正确。您的示例类似于以 10 为底的数字9999并说将其除以2yield 5555。相当:

0xffffffffffffffffffffffffffffffffffffffff

小于一:

0x10000000000000000000000000000000000000000

将该数字划分为范围很容易。对于您的 N=2 示例,一半的键小于:

0x8000000000000000000000000000000000000000

一半大于或等于。对于 N=4,类似:

ASSIGNED_PARTITION_1 = 0x4000000000000000000000000000000000000000
ASSIGNED_PARTITION_2 = 0x8000000000000000000000000000000000000000
ASSIGNED_PARTITION_3 = 0xc000000000000000000000000000000000000000

如果您尝试使用较小的数字进行分区(例如您可以轻松地以 10 为基数编写的分区),您会看到发生了什么。

我不确定这些比较对你会有什么影响——这些都是很大的数字。恐怕我不是红宝石或 SQL 专家。

于 2013-01-08T15:53:48.210 回答
2

您只需要散列的前n 个字符,具体取决于分区数。如果最多 16 则只有第一个字符:

select *
from items_to_be_processed
where left(id, 1) < '4'

select *
from items_to_be_processed
where left(id, 1) between '4' and '7'

无需转换为整数。

然后你可以在n左边的字符上建立一个索引,让它变得小而快:

create index index_name on items_to_be_processed (left(id, 1))

有必要left()在子句中使用表达式,where以使计划者使用与此答案的评论相反的建议的小索引。这就是我在 9.2 中测试的方式:

create table itbp (id char(32));

insert into itbp
select md5(a::text)
from generate_series(1, 100000) s(a)
;

我使用 md5 而不是 sha1 只是为了创建一个更简单的测试,因为 postgresql 的默认安装中没有 sha1 函数。

create index itbp_left_1_id_index on itbp (left(id, 1));

analyze itbp;

我没有忘记在测试之前进行分析。现在两个解释:

explain select *
from itbp
where left(id, 1) between '4' and '7'
;
                                             QUERY PLAN                                              
-----------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on itbp  (cost=529.17..1979.74 rows=24663 width=33)
   Recheck Cond: (("left"((id)::text, 1) >= '4'::text) AND ("left"((id)::text, 1) <= '7'::text))
   ->  Bitmap Index Scan on itbp_left_1_id_index  (cost=0.00..523.00 rows=24663 width=0)
         Index Cond: (("left"((id)::text, 1) >= '4'::text) AND ("left"((id)::text, 1) <= '7'::text))

explain select *
from itbp
where id >= '4' and id < '8'
;
                         QUERY PLAN                         
------------------------------------------------------------
 Seq Scan on itbp  (cost=0.00..2334.00 rows=24784 width=33)
   Filter: ((id >= '4'::bpchar) AND (id < '8'::bpchar))
于 2013-01-08T16:18:09.940 回答
1

您想要做的是将 id 转换为整数以进行分区。这是一种简单的方法,假设 id 值是均匀分布的,您可以使用前两位数来获取 0 到 255 之间的值:

select substring(t.id, 1, 2)::bit(8)::int as IntHash,
       t.*
from t

然后,您可以使用模算术定义范围,例如:

select (substring(t.id, 1, 2)::bit(8)::int)%8 as WhichOfEightPartitions
from t

这是假设哈希 id 存储为字符串。

对此的基本想法来自this post,在“tom Lane”的回复中。这显然是未记录的行为,但它确实适用于 SQLFiddle。

于 2013-01-08T16:12:43.027 回答