分页频繁变化的数据

时间:2014-08-02 21:52:50

标签: database sorting paging

我正在开发一个Web应用程序,它显示了一个让我们说'#34;线程"的列表。列表可以按线程所具有的数量进行排序。一个列表中可以有数千个线程。

应用程序需要在线程类似的内容可以在一秒钟内更改超过10倍的情况下工作。此外,该应用程序分布在多个服务器上。

我无法找到为这种列表启用分页的有效方法。并且我无法立即通过喜欢向用户传输整个排序列表。

  • 一旦用户转到此列表的第2页,它可能会更改,并且可能包含已从第一页列出的主题

无法解决的解决方案:

  • 在客户端存储看到的线程(在移动设备上可能太多)
  • 在服务器端存储看到的线程(用户和线程太多)
  • 快照临时数据库表中的列表(它过于频繁地更改数据并且需要实际)

(如果重要的是我使用MongoDB + c#)

你会如何解决这类问题?

4 个答案:

答案 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还是创建新的{}。

所以现在您插入了一个新线程。您需要更新ThreadStar。这是疯狂的昂贵部分。基本上,您会使用最新的timestamp来复制所有ThreadStar条目,只是为刚修改的线程更新thread_id。这真是疯狂的重复。幸运的是,它几乎只不过是外键。

您也不会执行DELETE;将行标记为已删除或在更新ThreadStar时将其排除。

现在您正在嗡嗡作响,但是您正在疯狂增长大量数据。除非您有大量的存储花销,否则您可能要清理它,但是即使那样,事情也会开始放慢速度(顺便说一句:即使有大量数据,这实际上也会令人震惊地执行)。

清理非常简单。这只是一些级联删除和清理孤立数据的问题。随时从“时间”中删除条目(例如,它不是最新条目,并且last_queried为null或早于任何截止日期)。将这些删除级联到ThreadStar。然后找到ThreadStar中没有id的所有线程,并清理它们。

如果您有更多的嵌套数据,但是这种查询也会变得困难。

最后的注释:由于大量的数据,您会发现插入速度真的很慢。大多数地方在开发和测试环境中都以适当的约束来构建它,但是随后 在生产中禁用约束!

是的。确保测试可靠。

但是至少您对分页过程中重新排序的数据不敏感。

答案 2 :(得分:0)

对于不断变化的数据,比如喜欢,我会使用两个阶段的appraoch。对于频繁变化的数据,我会使用内存数据库来跟上变化率,并将其频繁地刷新到"真实的" D b。 一旦你有了这个查询,不断查询数据很容易。

  1. 查询数据库。
  2. 查询内存数据库。
  3. 将来自内存数据库的频繁更改的数据与" slow"合并。数据库。
  4. 记住您已经显示的结果,然后按下一个按钮 由于其排名已发生变化,因此在不同的页面上不会显示已经显示过两次的值。
  5. 如果很多人查看相同的数据,可能有助于缓存3的结果本身,以进一步减少真实数据库的负载。

    您当前的体系结构没有缓存层(网站越大,缓存的内容就越多)。如果事情变得太大,你将无法使用简单的数据库和针对数据库的有效查询。

答案 3 :(得分:-1)

当用户第一次访问数据库时,我会将所有“线程”结果缓存在服务器上。然后将数据的第一页返回给用户,对于随后的下一个页面调用,我将返回缓存的结果。

为了最大限度地减少内存使用,您可以仅缓存记录ID,并在用户请求时获取整个数据。

每次用户退出当前页面时都可以清除缓存。如果不是大量数据,我会坚持使用此解决方案,因为用户不会对不断变化的数据感到烦恼。

相关问题