【问题标题】:How to search billions of items by tags (looking for optimal architecture)?如何通过标签搜索数十亿个项目(寻找最佳架构)?
【发布时间】:2017-01-22 12:05:56
【问题描述】:

我有一个庞大的项目数据库,数十亿条条目:

t_item

itemId ...

每个item都标有多个标签

t_tag

tagId     tagName

t_item_tag

itemTagId [AI]     itemId     tagId

最终用户的用例场景很简单:

选择所有标有特定标签的项目并按某些属性对它们进行排序。例如:

  • 选择所有带有Core i7 处理器(另一个标签)的笔记本电脑(标签),并按评级(t_item 表)对其进行排序;李>
  • 选择所有黄色(标签) 包(标签)皮革(标签)制成,并按价格(t_item表)

目前我在 MySQL 之上运行该系统,但它开始达到它的极限。主要关注的是最终排序是使用临时的;使用文件排序,这有点慢。

我可以采用哪些其他架构来处理我的用例和卷?

【问题讨论】:

    标签: mysql database performance optimization architecture


    【解决方案1】:

    “使用临时;使用文件排序”不是小人,架构和索引是。

    这是tags 的最佳选择:

    CREATE TABLE Tags (
        item_id ...,
        tag ... -- either a string or a tag_id, it does not matter much,
        PRIMARY KEY(tag, item_id),  -- for clustering and searching
        INDEX(item_id)  -- for maintenance (eg deleting an item_id)
        -- no FOREIGN KEYs; they slow things down
        -- no AUTO_INCREMENT; it is a total waste here
    ) ENGINE=InnoDB; -- so PK will be clustered.
    

    因此,即使有数十亿行,这样的表也能非常有效地访问——所有“黄色”项目都将被“聚集”起来,并且只需要很少的磁盘命中。

    (你建议tag_id 和另一张桌子。我认为这是否更好是一个折腾。)

    计算磁盘命中 - 它们是大型表的主要性能指标。

    对于select all laptops (tag) with Core i7 processor (another tag) and sort them by rating (t_item table); --

    SELECT i.id
        FROM Items AS i
        JOIN Tags  AS t1  ON t1.item_id = i.id
        JOIN Tags  AS t2  ON t2.item_id = i.id
        WHERE t1.tag = 'laptops'
          AND t2.tag = 'Core i7 processor'
        ORDER BY i.rating DESC
        LIMIT 10;
    

    如果有 2000 台“笔记本电脑”,则可能是 20 个磁盘块(计为 20 个磁盘命中)。 150 'Core i7 处理器' 可能还有 2 次磁盘命中。如果这些导致 70 个“项目”,那么您将获得 70 个更多的磁盘命中,因为它们可能是随机定位的。是的,会有一个临时表(在 RAM 中)和一个文件排序(但没有物理“文件”)来对 70 个 id 进行排序并提供 10 个。

    是的,您必须动态构造这个SELECT

    出于几个原因,我停止了(仅获取 i.id)...

    Items 中还有什么?一些大的TEXT 列(description 等)?他们很讨厌;它们使工作变得混乱。

    希望是说SELECT i.* 而不是SELECT i.id。让我们看看我们是否可以妥协。

    假设你有INDEX(id, rating)。但是有充分的理由用 idrating 制作一张桌子——你经常改变它,并且我们需要一张“小”桌子用于按评级查找和排序 70 个 id。

    所以,让我们有两个表:Items -- id、描述等,以及 Ratings 仅:

    CREATE TABLE Ratings (
        item_id -- 1:1 with `Items.id`
        rating ...,
        PRIMARY KEY(id)
    ) ENGINE=InnoDB;
    

    现在,将我的第一个查询更改为 FROM Ratings(而不是 FROM Items)。现在,我们仍然需要从该表中获取 70 行,但由于它更小,缓存的可能性更大,因此它可能涉及少于 70 次磁盘命中。

    然后,我们需要Items 中最终提取 10 个大件物品。

    总计:20+2+70+10 = 102 次磁盘命中,或者在普通的旧旋转驱动器上大约 1 秒;在 SSD 上更快。 (另外希望最大的数字 70 会更低。)

    回到文件排序... 通过只有“小”列(id、评级)和只有 70 个“行”要排序,文件排序将是一个内存结构,几乎不需要任何时间来执行。这就是我说忽略文件排序的原因。并且通过安排获取庞大的description稍后,我避免将其拖过 filsort,这几乎肯定会强制 tmp 表成为 MyISAM,而不是 MEMORY。

    既然您提到了按评级或价格排序,也许这很好:

    CREATE TABLE RatingsPrices (
        item_id ..., -- 1:1 with `Items.id`
        rating ...,
        price ...,
        INDEX(item_id, rating),  -- covering index for the main query
        INDEX(item_id, price),  -- covering index for the main query
    ) ENGINE=InnoDB;
    

    可能还有更多的细节和微妙之处,但我希望这些提示能让你朝着好的方向前进。

    请记住:如果数据集太大以至于即使工作集也无法放入 RAM,请“计算磁盘命中数”。

    【讨论】:

    • 非常感谢您的精彩回答。我真的很喜欢阅读它,并且一定会尝试所有这些想法!
    • 再次感谢您。我已经实现了你的想法,它就像一个魅力。给系统带来了火箭般的提升,用户对页面服务时间很满意。
    猜你喜欢
    • 2020-01-20
    • 2020-04-18
    • 1970-01-01
    • 2022-07-19
    • 1970-01-01
    • 2018-06-25
    • 2010-09-19
    • 2014-10-02
    • 2021-09-25
    相关资源
    最近更新 更多