4

我正在重新实现一个应用程序来支持全国工程竞赛,将其从本地服务器移动到云端。

为了告诉团队他们目前所处的位置,查询的形式为

select 1 + count(*) from team where where score < ?

球队的分数变化非常动态。可能有多达 200 万个团队,我需要每秒处理至少 10 个这样的查询。

通过使用单独的伯克利团队/得分记录数据库,原始版本获得了所需的性能(实际上它已经在 1999 年的硬件中实现了)。Berkeley DB 中有一个“记录编号”功能,它提供了完全正确的功能,而且速度非常快。

Heroku 显然没有办法支持 Berkeley DB。他们的标准数据库 PostgreSQLselect count(*)使用全表或索引扫描,这太慢了。

关于如何进行的任何想法?我不喜欢 Heroku,但必须转向某种云解决方案。

4

4 回答 4

2

创建一个排名表并尽可能频繁地更新。包括类别(公开或官方)和分数,这样您就不必在查询时将其加入团队表:

create table "rank" (
    team integer primary key, 
    category integer,
    score integer,
    rank_consolidated integer,
    rank_category integer
);

begin;
truncate table "rank"
;
insert into "rank" (team, category, score, rank_consolidated, rank_category)
select 
    team, category, score,
    rank() over(order by score desc) rank_consolidated,
    rank() over(partition by category order by score desc) rank_category
from team
;
commit
;
select * from "rank" where team = 11;

至于确切的排名行为,请查看窗口函数

于 2012-10-08T01:11:40.337 回答
2

使用redis将您的团队数据存储在sorted set. 然后该ZRANK函数将返回您需要的计数。Redis 总体上非常快,并且ZRANK函数是 O(log N) 预期的。它是用跳过列表实现的。

于 2012-10-08T14:00:52.710 回答
0

将索引放在分数上应该避免全表扫描。

于 2012-10-07T15:16:24.247 回答
0

如果它的读取量比写入量多得多,并且它始终必须是最新的,那么这对于触发器维护的汇总表(一种物化视图)来说是一个理想的工作。

team在表上有一个触发器,它AFTER EACH INSERT OR UPDATE OR DELETE FOR EACH ROW执行一个触发器函数,team_summary用新分数更新该团队的表条目。

team_summary表可以通过一个简单的、直接的等式索引查找来访问,所以它会非常快。由于 Pg 支持同时读取器和写入器,team_summary即使表被非常频繁地更新,它也会保持响应。为了获得最佳结果,您真正需要做的唯一事情是FILLFACTOR在表中设置为 50team_summary左右,以便 HOT 可以正常工作,并确保将 autovacuum 设置为经常运行以分散 Vacuum I/O churn 的负载。

编写触发器应该很简单。您只需要小心编写一个并发安全触发器,当您通过多个并发连接对同一团队进行并发更新时,该触发器不会中断。就像是:

UPDATE team_summary SET score = score + 1 WHERE team_id = NEW.team_id;

SERIALIZABLEREAD COMMITTED隔离下应该没问题。请参阅并发控制。唯一困难的一点是,您必须确保team_summary在将新团队的第一行插入之前插入新行,team这样您的触发器就不必处理表team_summary中可能尚不存在该行的异常棘手的情况team。获得正确的 upsert/merge 有点棘手。

如果写入速率也非常高,并且您可以避免每隔几秒/分钟更新一次结果,请改用 Clodoaldo 的方法。

于 2012-10-08T08:11:23.293 回答