高性能多层标签过滤
Posted
技术标签:
【中文标题】高性能多层标签过滤【英文标题】:High-performance multi-tier tag filtering 【发布时间】:2011-10-21 02:23:50 【问题描述】:我有一个包含艺术家、专辑和曲目的大型数据库。这些项目中的每一个都可以通过粘合表(track_attributes、album_attributes、artist_attributes)分配一个或多个标签。每种物品类型都有几千个(甚至十万个)标签。
我正在尝试完成两项任务,但我很难让查询以可接受的方式执行。
任务 1) 获取具有任何给定标签(如果提供)的艺术家在具有任何给定标签(如果提供)的专辑中具有任何给定标签(如果提供)的所有曲目。可能不存在任何一组标签(即只有一个曲目标签处于活动状态,没有艺术家或专辑标签)
变化:结果也可以按艺术家或专辑而不是按曲目显示
任务 2) 获取应用于上一个过滤器结果的标签列表,以及每个给定标签的轨道数。
我所追求的是一些通用的方法指导。我已经尝试过临时表、内部连接、IN(),到目前为止我所做的所有努力都导致响应缓慢。我所追求的结果的一个很好的例子可以在这里看到:http://www.yachtworld.com/core/listing/advancedSearch.jsp, except 他们只有一层标签,我正在处理三层。
表结构:
Table: attribute_tag_groups
Column | Type |
------------+-----------------------------+
id | integer |
name | character varying(255) |
type | enum (track, album, artist) |
Table: attribute_tags
Column | Type |
--------------------------------+-----------------------------+
id | integer |
attribute_tag_group_id | integer |
name | character varying(255) |
Table: track_attribute_tags
Column | Type |
------------+-----------------------------+
track_id | integer |
tag_id | integer |
Table: artist_attribute_tags
Column | Type |
------------+-----------------------------+
artist_id | integer |
tag_id | integer |
Table: album_attribute_tags
Column | Type |
------------+-----------------------------+
album_id | integer |
tag_id | integer |
Table: artists
Column | Type |
------------+-----------------------------+
id | integer |
name | varchar(350) |
Table: albums
Column | Type |
------------+-----------------------------+
id | integer |
artist_id | integer |
name | varchar(300) |
Table: tracks
Column | Type |
-------------+-----------------------------+
id | integer |
artist_id | integer |
album_id | integer |
compilation | boolean |
name | varchar(300) |
编辑我正在使用 php,我不反对在脚本中进行任何排序或其他 hijinx,我的第一个问题是返回速度。
【问题讨论】:
为什么不包括完整的 show create table 和您当前的查询供我们处理? mysql 还是 PostgreSQL?如果是前者,如果存在使用后者的良好解决方案,是否可以切换到后者? @Denis - MySQL。目前还不能切换到 DBMS。 @guido - 完整的表格无关紧要,是吗?它只是像曲目持续时间或专辑发行年份这样的字段,与手头的问题无关。至于现有的查询,我当然可以在这个问题上抛出一堆长长的查询,但我希望得到一个新的视角。 我只是想看看你的索引在哪里和是什么;无论如何,我假设每首曲目“继承”父专辑和作者的标签。我的建议是使这些标签变得多余,这样对于添加到专辑中的每个标签,您实际上都将标签插入到 track-tags 粘合表中;换句话说,您在插入时花费了更多的空间/时间,以便在选择作为权衡时获得一些时间。或者,作为替代方案,创建一个包含曲目、专辑和作者的唯一 ID 的超级表,并从那里加入标签。 【参考方案1】:如果你想要速度,我建议你研究一下 Solr/Lucene。您可以存储数据,并通过调用 Solr 并从 PHP 解析结果进行非常快速的查找。作为一个额外的好处,您还可以获得分面搜索(如果我解释正确,这是您问题的任务 2)。缺点当然是您可能有冗余信息(一次存储在 DB 中,一次存储在 Solr 文档存储中)。而且设置确实需要一些时间(嗯,您可以从 Drupal Solr 集成中学到很多东西)。
只需查看Solr 的 PHP 参考文档即可。
这是关于如何在 PHP 中使用 Solr 的文章,以防万一:http://www.ibm.com/developerworks/opensource/library/os-php-apachesolr/。
【讨论】:
没听说过这个,不知道是什么。我会在自己的时间进行研究,但我知道,从我略读的内容来看,我的组织没有足够的财务或时间预算来完成这个切线——我们已经接近这个项目的尾声,我们已经解决了数据库复制和新的 Web 服务器的问题!感谢您的意见! 我刚刚添加了一篇有用的文章,它只是展示了基础知识。 Solr 非常强大且速度极快...不过,为您提供的信息编制索引可能需要一段时间。 我认为 Lucene 现在也是 SO 用于标签的东西。-1
是谁的?? Solr 绝对是最好的选择。实施起来并不难,而且是目前最快、最模块化/可配置的搜索/索引工具。
我没有放-1,但 Solr 是最好的选择至少值得商榷。【参考方案2】:
您可能应该尝试对数据进行非规范化。您的结构针对插入/更新负载进行了优化,但不适用于查询。据我所知,您将拥有比插入/更新查询更多的选择查询。
例如,您可以这样做:
以标准化结构存储您的数据。
像这样创建聚合表
track_id, artist_tags, album_tags, track_tags
1 , jazz/pop/, jazz/rock, /heavy-metal/
or
track_id, artist_tags, album_tags, track_tags
1 , 1/2/, 1/3, 4/
为了加快搜索速度,您可能应该在 *_tags 列上创建 FULLTEXT 索引
用类似sql查询这张表
select * from aggregate where album_tags MATCH (track_tags) AGAINST ('rock')
每天以增量方式重建此表一次。
【讨论】:
我现在正在使用它来看看性能如何。感谢您的想法! 仍在构建表格和测试。我不会消失 :D FULLTEXT 索引只帮助 MATCH() AGAINST(),而不是 LIKE 查询或者我错过了什么? LIKE 查询是 MySql 中最长的查询——通常它们几乎完成了表扫描(如果它以 '%' 开头),不使用索引。 你说得对。作者应该使用 MATCH AGAINST,而不是“like”。并且 FULLTEXT 索引仅在 MyISAM 引擎上支持,所以聚合表应该使用 myisam 引擎。 我认为你应该改变你的帖子。并添加一些关于 FULLTEXT 分隔符、最小索引字长等的评论,我认为您的想法可能有效(我不确定 FULLTEXT 索引速度),但您的主要帖子似乎具有误导性。还有一件事:我从未在高性能应用程序上使用过 MyIasm,但我认为它在读取时会锁定全表(没有行级锁定)?这将是非常糟糕的。【参考方案3】:你可以尝试一下:
使用Query Analyzer 来探索查询的瓶颈。 (在大多数情况下,底层 DBS 在优化方面做得非常出色)
您的表结构已经很好地规范化,但个人经验告诉我,您可以使用能够避免连接和子查询的结构归档更高的性能级别。对于您的情况,我建议将标签信息存储在一个字段中。 (这需要底层 DBS 的支持)
到目前为止。
【讨论】:
【参考方案4】:我认为答案很大程度上取决于您希望在项目上花多少钱——在严格的条件下,有些任务在理论上甚至是不可能完成的(例如,您必须只使用一个弱服务器)。我将假设您已准备好升级您的系统。
首先 - 你的表结构强制加入 - 我认为在编写高性能应用程序时应该尽可能避免它们。我不知道“attribute_tag_groups”是什么,所以我提出一个表结构:tag(varchar 255), id(int), id_type(enum (track, album, artist))。根据 id_type,ID 可以是艺术家 ID、轨道 ID 或专辑 ID。这样您就可以在一个表中查找所有数据,但当然会占用更多内存。
下一步 - 您应该考虑使用多个数据库。如果每个数据库只包含您的部分数据(每次查找会更快),它会更有帮助。决定如何在数据库之间传播数据通常是一项相当艰巨的任务:我建议您对标签长度进行一些统计,找到能够获得相似 trac/artists 结果计数的长度范围,并将其硬编码到您的查找代码中。
当然,您应该考虑调整 MySql(我相信您已经这样做了,但以防万一)-您的所有表都应该驻留在 RAM 中-如果不可能,请尝试获取 SSD 磁盘、raid 等。正确的索引和数据库类型/设置也很重要(MySql 甚至可能在内部统计中显示一些瓶颈)。
这个建议听起来很疯狂——但有时让 PHP 做一些 MySql 可以自己做的计算是件好事。 MySql 数据库更难扩展,而用于 PHP 处理的服务器可以在几分钟内添加。并且不同的 PHP 线程可以在不同的 CPU 内核上运行——MySql 有问题。您可以通过使用一些高级模块来提高您的 PHP 性能(您甚至可以自己编写它们 - 分析您的 PHP 脚本和快速 C 代码中的硬代码瓶颈)。
最后但我认为最重要的是 - 你必须使用某种类型的缓存。我知道这真的很难,但我认为没有一个非常好的缓存系统没有任何大项目。在您的情况下,某些标签肯定会比其他标签更受欢迎,因此它应该会大大提高性能。缓存是一种艺术形式 - 取决于您可以在其上花费多少时间以及有多少可用资源,您可以使 99% 的所有请求都使用缓存。
使用其他数据库/索引工具可能会对您有所帮助,但您应该始终考虑理论查询速度比较(O(n),O(nlog(n))...)以了解它们是否真的可以帮助您 - 使用这个工具有时会给您带来较低的性能增益(例如恒定的 20%),但它们可能会使您的应用程序设计复杂化,而且大多数情况下并不值得。
【讨论】:
【参考方案5】:根据我的经验,最“慢”的 MySQL 数据库没有正确的索引和/或查询。所以我会先检查这些:
-
确保所有数据表的 id 字段都是主索引。以防万一。
对于所有数据表,在外部 id 字段上创建索引,然后在 id 上创建索引,以便 MySQL 可以在搜索中使用它。
对于粘合表,在两个字段上设置主键,首先是主题,然后是标签。这是为了正常浏览。然后在标签 id 上创建一个普通索引。这是用于搜索的。
还是很慢?你在为你的桌子使用 MyISAM 吗?它专为快速查询而设计。
如果仍然很慢,请对慢速查询运行 EXPLAIN 并将查询和结果发布到问题中。最好使用完整数据库结构的可导入 sql 转储。
【讨论】:
【参考方案6】:检查您的索引,以及它们是否正确使用。也许 MySQL 不能胜任这项任务。 PostgreSQL 使用起来应该类似,但在复杂 情况下具有更好的性能。
在完全不同的轨道上,谷歌 map-reduce 并使用这些新奇的非 SQL 数据库之一来处理非常大的数据集。这可以在多台服务器上并行进行分布式搜索。
【讨论】:
以上是关于高性能多层标签过滤的主要内容,如果未能解决你的问题,请参考以下文章