我正在开发一个Web应用程序,它显示了一个让我们说'#34;线程"的列表。列表可以按线程所具有的数量进行排序。一个列表中可以有数千个线程。
应用程序需要在线程类似的内容可以在一秒钟内更改超过10倍的情况下工作。此外,该应用程序分布在多个服务器上。
我无法找到为这种列表启用分页的有效方法。并且我无法立即通过喜欢向用户传输整个排序列表。
无法解决的解决方案:
(如果重要的是我使用MongoDB + c#)
你会如何解决这类问题?
答案 0 :(得分:6)
有趣的问题。除非我误解你,并且无论如何让我知道,如果我是,那么最好的解决方案就是实现一个系统,而不是页面数字,使用时间戳。它类似于许多主要API已经做的事情。我知道Tumblr甚至会在仪表板上执行此操作,当然这不是一个不合理的情况:在高峰时段可能会在少量时间内添加大量帖子,具体取决于用户遵循的人数。
基本上,你的"下一页"按钮可以链接到/threads/threadindex/1407051000
,这可以转换为"在2014-08-02 17:30之前创建的所有线程。这使您的查询非常容易实现。然后,当您下拉所有下一个元素时,您只需查找页面上最后一个元素之前发生的任何内容。
当然,这种情况的失败是,很难知道自用户开始浏览以来添加了多少个新元素,但您始终可以记录开始时间和从那时起就知道什么是新的。用户输入他们自己的页面也很困难,但在大多数应用程序中这不是问题。您还需要为线程中的每条记录存储时间戳,但这可能已经完成,如果不是,那么它肯定不难实现。您将为每条记录额外支付八个字节的费用,但这比必须存储任何关于"""讯息。
它也很好,因为这可能不适用于你,但是用户可以为列表中的页面添加书签,并且它将永远保持不变,因为它与其他任何内容都不相关。
答案 1 :(得分:1)
通常使用OLAP cube处理。这里的想法是您添加自然时间维度。对于此应用程序,它们可能太重了,但是这里是一个摘要,以防其他人需要它。
OLAP多维数据集从时间的基本概念开始。您必须知道什么时候才能理解数据。
从“时间”表开始:
Time {
timestamp long (PK)
created datetime
last_queried datetime
}
这基本上跟踪您的数据快照。我包括了一个last_queried
字段。每当用户根据此特定时间戳要求提供数据时,都应使用当前时间对此进行更新。
现在我们可以开始讨论“线程”了:
Threads {
id long (PK)
identifier long
last_modified datetime
title string
body string
score int
}
id
字段是一个自动递增的键;这是永远不会暴露的。 identifier
是您线程的“唯一” ID。我说“唯一”是因为没有唯一性约束,就数据库而言,它是 不是 唯一的。其中的所有其他内容都是相当标准的... 除了... ,当您编写时,不会更新此条目。在OLAP多维数据集中,您几乎 从不 修改数据。最后将说明更新和插入。
现在,我们如何查询呢?您不能只直接查询Threads
。您需要包括一个星表:
ThreadStar {
timestamp long (FK -> Time.timestamp)
thread_id long (FK -> Threads.id)
thread_identifier long (matches Threads[thread_id].identifier)
(timestamp, thread_identifier should be unique)
}
此表为您提供了从什么时候到所有线程的状态的映射。给定特定的时间戳,您可以通过执行以下操作获取线程的状态:
SELECT Thread.*
FROM Thread
JOIN ThreadStar ON Thread.id = ThreadStar.thread_id
WHERE ThreadStar.timestamp = {timestamp}
AND Thread.identifier = {thread_identifier}
那还不错。我们如何获得线程流?首先,我们需要知道现在几点了。基本上,您想从timestamp
获取最大的Time
并将Time.last_queried
更新到当前时间。您可以在其前面放置一个仅每隔几秒钟更新一次的缓存,或者您想要的任何内容。一旦有了,就可以获取所有线程:
SELECT Thread.*
FROM Thread
JOIN ThreadStar ON Thread.id = ThreadStar.thread_id
WHERE ThreadStar.timestamp = {timestamp}
ORDER BY Thread.score DESC
好。我们有一个线程列表,随着实际分数的变化,排序是稳定的。您可以在闲暇时翻页...一种。最终,数据将被清理,并且您将丢失快照。
所以这很好,但现在您需要创建或更新线程。创建和修改几乎相同。两者都由INSERT
处理,唯一的区别是您使用现有的identifier
还是创建新的{1>}。
所以现在您插入了一个新线程。您需要更新ThreadStar。这是疯狂的昂贵部分。基本上,您会使用最新的timestamp
来复制所有ThreadStar条目,只是为刚修改的线程更新thread_id
。这真是疯狂的重复。幸运的是,它几乎只不过是外键。
您也不会执行DELETE
;将行标记为已删除或在更新ThreadStar时将其排除。
现在您正在嗡嗡作响,但是您正在疯狂增长大量数据。除非您有大量的存储花销,否则您可能要清理它,但是即使那样,事情也会开始放慢速度(顺便说一句:即使有大量数据,这实际上也会令人震惊地执行)。
清理非常简单。这只是一些级联删除和清理孤立数据的问题。随时从“时间”中删除条目(例如,它不是最新条目,并且last_queried为null或早于任何截止日期)。将这些删除级联到ThreadStar。然后找到ThreadStar中没有id
的所有线程,并清理它们。
如果您有更多的嵌套数据,但是这种查询也会变得困难。
最后的注释:由于大量的数据,您会发现插入速度真的很慢。大多数地方在开发和测试环境中都以适当的约束来构建它,但是随后 在生产中禁用约束!
是的。确保测试可靠。
但是至少您对分页过程中重新排序的数据不敏感。
答案 2 :(得分:0)
对于不断变化的数据,比如喜欢,我会使用两个阶段的appraoch。对于频繁变化的数据,我会使用内存数据库来跟上变化率,并将其频繁地刷新到"真实的" D b。 一旦你有了这个查询,不断查询数据很容易。
如果很多人查看相同的数据,可能有助于缓存3的结果本身,以进一步减少真实数据库的负载。
您当前的体系结构没有缓存层(网站越大,缓存的内容就越多)。如果事情变得太大,你将无法使用简单的数据库和针对数据库的有效查询。
答案 3 :(得分:-1)
当用户第一次访问数据库时,我会将所有“线程”结果缓存在服务器上。然后将数据的第一页返回给用户,对于随后的下一个页面调用,我将返回缓存的结果。
为了最大限度地减少内存使用,您可以仅缓存记录ID,并在用户请求时获取整个数据。
每次用户退出当前页面时都可以清除缓存。如果不是大量数据,我会坚持使用此解决方案,因为用户不会对不断变化的数据感到烦恼。