分页频繁变化的数据

coa*_*mee 12 database sorting paging

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

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

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

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

解决方案不起作用:

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

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

你会如何解决这类问题?

Mat*_*gen 6

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

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

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

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


Hou*_*ell 6

这通常使用OLAP 多维数据集来处理。这里的想法是添加一个自然的时间维度。对于此应用程序来说,它们可能太重了,但这里有一个摘要,以防其他人需要它。

OLAP 多维数据集从时间的基本概念开始。您必须知道您关心什么时间才能理解数据。

您从“时间”表开始:

Time {
  timestamp     long      (PK)
  created       datetime
  last_queried  datetime
}
Run Code Online (Sandbox Code Playgroud)

这基本上跟踪数据的快照。我已经添加了一个last_queried字段。每当用户请求基于此特定时间戳的数据时,都应使用当前时间进行更新。

现在我们可以开始谈论“线程”:

Threads {
  id             long      (PK)
  identifier     long
  last_modified  datetime
  title          string
  body           string
  score          int
}
Run Code Online (Sandbox Code Playgroud)

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)
}
Run Code Online (Sandbox Code Playgroud)

该表为您提供了从当前时间到所有线程状态的映射。给定一个特定的时间戳,您可以通过执行以下操作来获取线程的状态:

SELECT Thread.*
FROM   Thread
JOIN   ThreadStar ON Thread.id = ThreadStar.thread_id
WHERE  ThreadStar.timestamp = {timestamp}
   AND Thread.identifier = {thread_identifier}
Run Code Online (Sandbox Code Playgroud)

那还不错。我们如何获得线程流?首先我们需要知道现在是什么时间。基本上你想要获取最大的当前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
Run Code Online (Sandbox Code Playgroud)

好的。我们有一个线程列表,并且随着实际分数的变化,顺序是稳定的。您可以在闲暇时翻阅此内容......有点。最终数据将被清理,您将丢失快照。

这一切都很棒,但现在您需要创建或更新一个线程。创建和修改几乎是相同的。两者都用 处理INSERT,唯一的区别是您是使用现有的identifier还是创建新的。

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

你也不做DELETEs ;当您更新 ThreadStar 时,将一行标记为已删除或只是将其排除。

现在您正在顺利进行,但数据量却在疯狂增长。你可能想要清理它,除非你有很多存储预算,但即使这样,事情也会开始变慢(旁白:这实际上会表现得非常好,即使有大量的数据)。

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

如果您有更多嵌套数据,则这种通用机制也适用,但查询会变得更加困难。

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

是的。确保你的测试是可靠的。

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