分页频繁变化的数据

问题描述 投票:11回答:4

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

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

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

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

解决方案不起作用:

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

(如果重要我正在使用MongoDB + c#)

你会如何解决这类问题?

database sorting paging
4个回答
6
投票

有趣的问题。除非我误解你,并且无论如何让我知道,如果我是,那么最好的解决方案就是实现一个系统,而不是页码,使用时间戳。它类似于许多主要API已经做的事情。我知道Tumblr甚至在仪表板上做到这一点,当然这不是一个不合理的案例:在高峰时段可以在少量时间内添加大量帖子,具体取决于用户跟随的人数。

基本上,你的“下一页”按钮可以链接到/threads/threadindex/1407051000,它可以转换为“2014-08-02 17:30之前创建的所有线程。这使得你的查询非常容易实现。然后,当你拉在所有下一个元素中,您只需查找页面上最后一个元素之前发生的任何事情。

当然,这种情况的缺点是,很难知道自用户开始浏览以来添加了多少新元素,但是您可以随时记录开始时间并知道任何事情,因为那时候是新的。用户也很难输入他们自己的页面,但这在大多数应用程序中都不是问题。您还需要为线程中的每条记录存储时间戳,但这可能已经完成,如果不是,那么实现起来肯定不难。你将支付每条记录额外8字节的费用,但这比必须存储任何关于“看到”的帖子更好。

这也很好,因为这可能不适用于你,但是用户可以为列表中的页面添加书签,并且它将永远保持不变,因为它与其他任何内容都不相关。


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

尼斯。我们有一个线程列表,并且随着实际分数的变化,顺序是稳定的。你可以在闲暇时浏览这个页面......有点儿。最终数据将被清理,您将丢失快照。

所以这很好,但现在你需要创建或更新一个Thread。创建和修改几乎完全相同。两者都用INSERT处理,唯一的区别是你是使用现有的identifier还是创建一个新的timestamp

所以现在你已经插入了一个新线程。您需要更新ThreadStar。这是疯狂的昂贵部分。基本上你使用最新的thread_id制作所有ThreadStar条目的副本,除了你为刚修改的线程更新DELETE。这是一个疯狂的重复。幸运的是,它几乎只是外键,但仍然如此。

你也不做ids;将行标记为已删除或仅在更新ThreadStar时将其排除。

现在你一直在哼着,但你的数据量却在疯狂增长。你可能想要清除它,除非你有很多存储预算,但即使这样,事情也会开始放缓(除此之外:即使有大量的数据,这实际上也会表现得非常好)。

清理非常简单。这只是对孤立数据进行一些级联删除和清理的问题。随时删除时间中的条目(例如,它不是最新条目,last_queried为空或者比任何截止值更旧)。将这些删除级联到ThreadStar。然后找到任何不在ThreadStar中的qazxswpoi的线程并擦除它们。

如果您有更多嵌套数据,这种通用机制也可以工作,但您的查询会变得更难。

最后注意事项:由于数据量巨大,您会发现插入的速度非常慢。大多数地方在开发和测试环境中使用适当的约束来构建它,但随后在生产中禁用约束!

是啊。确保您的测试结果稳定。

但至少你对重新排序的数据中间分页不敏感。


0
投票

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

  1. 查询数据库。
  2. 查询内存db。
  3. 将内存数据库中经常更改的数据与“慢”数据库数据合并。
  4. 请记住您已经显示的结果,因此按下下一个按钮将不会显示两次已经显示的值,因为在不同的页面上因为其排名已更改。

如果许多人查看相同的数据,可能有助于缓存3的结果本身,以进一步减少真实数据库的负载。

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


-1
投票

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

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

每次用户退出当前页面时都可以逐出缓存。如果不是大量的数据,我会坚持这个解决方案,因为用户不会厌倦不断变化的数据。

© www.soinside.com 2019 - 2024. All rights reserved.