XQzip算法分析
作者: James Cheng and Wilfred Ng
目 录
第一章 概述
XML的数据冗余
一、不可查询压缩:XMill
二、可查询压缩:XGrind、XPRESS
三、XQzip
第二章 XQzip结构
XQzip的结构分四部分:
压缩过程
查询过程
第三章 XML结构索引树(Structure Index Trees)
3.1 XML结构的基本概念
定义1 分支和分支排序
定义2 SIT等价(Sit-Equivalence)
定义3 合并运算(Merge Operator)
3.2 构造SIT
第四章 已压缩XML数据的可查询存储模型
第五章 用SIT查询已压缩XML
5.1 查询覆盖度
5.2 实施查询
解析查询
节点选取
获取数据与解压缩
查询结果输出
第一章 概述
XML的数据冗余:
原因:使用重复的标记描述数据。
后果:阻碍(hinder)了XML数据交换(data exehange)和数据存档(data archiving)上的应用。
近几年解决xml数据冗余问题的方案可归为两类:
1、可查询压缩
2、非可查询压缩
一、不可查询压缩:XMill
整块数据必须先被整体解压后再进行查询。
二、可查询压缩:XGrind、XPRESS
1、独立编码各数据项,使已压缩数据项可以直接存取,而不需把文件全部解压缩。
2、与全块压缩的方案相比,压缩率有所降低。
A、XGrind、XPRESS采用的是同型变换(homomorphic transformation),保留了XML数据的结构信息,因此可在此结构上进行查询。
缺点:所保留的结构通常太大(与xml文档的大小呈线性关系),搜索这么大的结构空间效率太低,即使是简单的路径查询。
例如:在图1的文档中搜索初始价格<initial>项低于$10的拍卖项<bid>。XGrind解析整个xml压缩文档,对解析的每一个元素/属性,将其路径与输入的查询的路径进行匹配,。XPRESS作了改进,它将逐元素的匹配改为逐路径的匹配(具体方法还不清楚)。逐路径的匹配仍然是低效的,因为xml文档中大部分路径是重复的。
|
图1 A Sample Auction XML Extract
三、XQzip
1. 支持对压缩文件的查询(采用结构索引树SIT)
2. 针对目前XML压缩算法中存在的压缩和查询性能的问题
3. 广泛支持XPath查询:如多重、深度内嵌的谓词及其集合
第二章 XQzip结构
XQzip的结构分四部分:
1、压缩模块the Compressor
2、索引构造模块the Index Constructor
3、查询处理模块 the Query Processor
4、存储区the Repository
压缩过程
这里提到的“数据”和“结构”的区别:这里提到的数据包括XML文档中的元素内容和属性值;结构包括XML文档中的标记名和属性名。
输入的XML文档被SAX解析器解析(SAX解析器的功能是把XML数据项(包括元素内容和属性值)分发给压缩模块,把XML结构信息(包括标记名和属性名)分发给索引构建模块)。压缩模块将数据压缩成块,这些块能通过哈希表高效存取,其中哈希表中存储着元素名/属性名。
索引构建模块为xml结构创建SIT。
图4 XQzip结构
查询过程
对于查询过程,查询解析器(Query Parser)解析一个输入的查询,接着查询执行模块(Query Executor)利用索引(index)进行查询。Executor与缓存管理(Buffer Manager)协调工作,其中缓存管理应用LRU算法(最近最少使用算法)管理保存已解压数据块的缓冲池。若数据已经在缓冲池中,Executor直接将其取出而不用从压缩文件中解压;否则,Executor与哈希表通信,从压缩文件中取出数据。
第三章 XML结构索引树(Structure Index Trees)
结构索引树(STI)是一种高效的索引结构,这里先定义描述SIT用到的一些术语,然后介绍一个生成SIT的算法。
3.1 XML结构的基本概念
一个XML文档的结构可以模拟为一棵树,称为结构树。结构树包含一个根节点和若干元素节点。元素节点既可表示元素也可表示属性。这里在属性名前加前缀“@”来和元素名进行区分。
算法中为每个特定的 标记/属性 名指派一个Hash ID,存入哈希表,例如图4所示的哈希表。数据项则从结构中分离出来压缩成不同的块,这些块可以通过哈希表存取。因此这个模型中没有要考虑的文本节点。同时也不考虑名字空间,PI和注释,尽管包含这些只需要简单的扩展。
XML文档的结构树表示为 的形式。其中
表示节点集, 表示边集, :唯一根节点;
定义树的一个节点: , ;其中
v.eid :v中元素/属性的Hash ID;v.nid :指派给v的唯一节点标识,是根据读取文档的顺序指派的;
初始化v.ext = {v.nid};
我们用(v.eid, v.nid)对来描述每一个节点;
(ROOT.eid,ROOT.nid)唯一指派为(0,0)。
若节点v有n个有序的子节点 ,它们在T中的次序指定为: ,若 ,则 。节点排序加速了T中的节点匹配,因为可通过它们的eids匹配两个节点,且平均只需要搜索给定节点的子节点的一半。定义1 分支和分支排序
T的一个分支(表示为b)定义为 ,这里 是T中的一个叶子节点, 是 的父节点( )。
设B为一树或子树。B上的分支排序 定义为:
,其中 且 ,
表示存在i使得 ,且 或
(1) 或(2) 且 。
例:图2中,
有
我们可以把一个树描述成其所有分支用 排序的序列。例如,图2中以节点(17,27)为根的子树可表示为:
图2 图1的结构树
而图3中的树表示为
,其中 。
图3 图2结构树的SIT 定义2 SIT等价(Sit-Equivalence)
1、分支等价
对于 ,p=q,若有 ,则两个分支 和 是SIT等价的:
如图2中,分支 和分支 是分支等价的。
2、子树等价
两个子树 和 ,对于 且m=n,若 和 为兄弟,且 和 是SIT等价的,则 和 是SIT等价的:
如图2中,以节点(7,14)和(17,27)为根的子树是SIT等价的,因为两个子树中每一对相应的分支是SIT等价的。
在XML数据中,SIT等价的子树是重复的结构,因此要通过下面定义的合并运算(Merge Operator)消除这种冗余。定义3 合并运算(Merge Operator)
合并运算 定义为: 。
这里 和 是SIT等价的,且 ,其中 , ,且 , 。对 , 令 ,然后删除 。
可见,ext中存储的是合并运算中合并到一起的节点的nid集合。
因此,合并运算将 和 合并生成t,t和 和 是SIT等效的。合并运算的结果是完全相同的SIT等价结构被删除了。通过消除子树结构的这种冗余得到一个更简练的结构表示——结构索引树(SIT)。通过对结构树反复应用 ,直到无任何两个SIT等价的子树剩下。例如,图3中的树是图2中结构树的SIT,注意到图2中所有SIT等价的子树都合并到图3的SIT中一个相应的SIT等价的子树中。
一个结构树和它的SIT是等价的,因为被删除的SIT等价的子树的结构保留在SIT中。另外,删除的节点由保存在节点exts中的它们的节点标识nid来表示,而删除的边可随着节点次序重建。由于SIT通常比其结构树更小,因此能进行比其结构树更高效的节点选定。 3.2 构造SIT
本节介绍一种构建XML文档SIT的高效算法。首先为每个树节点定义四个节点指针:parent,previousSibling,nextSibling,firstChild。指针很大程度上加速了SIT构造和查询操作中节点的定位(navigation)。指针占用的空间并不重要,因为一个SIT通常很小。
SIT构造过程:
SAX仅线性扫描输入的XML文档一次,建立其SIT,同时压缩文本数据。
对解析的每个SAX开始/结束标记事件,调用SIT构造程序,如图5所示。其主要思想是对“基(base)树”和“构造中的树”的操作。“构造中的树”是指正在为每个被解析的开始标记构造的树,它是“基”树的子树。当解析到一个结束标记时,一个“构造中的树”就完成了。若该完成的子树和“基”树中的任一子树是SIT等价的,它就被合并到该SIT等价的子树中;否则,它就成为“基”树的一部分。我们用一个堆栈来指示先前和当前XML元素的父子或兄弟关系,建立树结构。程序伪代码的11-20行保持了结构信息的连贯性并跳过冗余信息。因此,栈的大小通常小于SIT高度的两倍。
|
图5 SIT构建程序伪代码第四章 已压缩XML数据的可查询存储模型
本节讨论已压缩XML数据的一个存储模型。该模型在整块存储和细粒度存储模型之间寻求均衡,使压缩算法能够利用XML数据的共性以改善压缩(如整块压缩方法),同时支持对已压缩XML数据的高效查询(如细粒度压缩方法)。
我们把具有相同 标记/属性 名的XML数据项群组到同一数据流中。之后每一数据流分别压缩,形成块的序列。这些已压缩的块能够独立的解压缩,因此避免了查询时全部解压缩。
问题是,若块的大小太小,就不能很好利用数据的共性来实现更好的压缩;若块很大则解压一个块的开销会很大。因此选择一个合适的块大小,同时获得好的压缩率和从压缩文件中高效获取匹配数据很关键。
将存储1000个数据项的块大小定为XQzip的缺省块大小,这对压缩和查询都是合适的。另外设置2M的限制来防止内存溢出,这是因为有些数据项可能很长。当1000个数据项已解析到数据流中或数据流的大小达到2M时,QXzip用gzip压缩数据流,并为压缩的数据块指派一个id,存在磁盘上,然后重新开始这一过程。
压缩文件中一个块的开始位置存储在元素哈希表中。(注意到,给出一个块的起始地址和任意的数据长度,gzip就能够进行解压。)同时为每个块指派一个id,表明压入该块的所有节点的最大节点标识(通常也就是最后压入的节点的节点标识)。为了获取存储着一个节点的压缩数据的块,我们利用节点的节点标识的包含关系和节点的数据流压缩形成的连续的块的id来获得块的位置。节点的数据的位置保存在一个数组中,可通过对节点标识的折半查找(
a binary search)获得(这仅占用log1000的时间,因为每个块最多有1000个数据项),数据长度是两个连续地址的唯一区别。第五章 用SIT查询已压缩XML
本节介绍XQzip支持的查询操作,并说明对已压缩XML数据进行查询的实现过程。 5.1 查询覆盖度
XQzip支持XPath 1.0的大部分核心功能。这里扩展XPath,使其能够通过单一的查询来选取任意一组特定的(
distinct)元素;也提出一个映射来降低XPath语法的冗余性。
XPath查询 一个查询用定位路径(
location path)指明匹配的节点。定位路径由一个或多个定位步骤顺序构成,每个定位步骤有一个轴(axis)、一个节点校验(node test)和0到多个谓词(predicate)。轴详细说明了上下文节点和被定位步骤选定的节点之间的关系。XQzip支持8个XPath轴:ancestor、ancestor-of-self、attribute、child、descendant、descendant-or-self、parent、self。XQzip通过仅比较节点的
eids简化节点校验。谓词使用任意的表达式,表达式可以轮流使用包含更多谓词的定位路径及递归等等,来进一步精简被定位路径选定的的节点集。
除了比较运算(=,!=,>,=,<=)和字符串运算,XQzip支持一整套标准累积运算(count,sum,average,minimum,maximum)。XQzip还支持结合基于结构、基于数值、或两者集合的谓词的逻辑运算。
XPath群组查询 XPath查询一次只能指定选取一个特定的元素。通过对XPath的语法作细微的修改使其一次查询能够选取任意一组特定的元素,称之为XPath群组查询。我们用“(”和“)”指明群组,用“+”表示群组中的元素并集。
例如,XPath中的群组查询
(//Orderitem[discount[. >= 20% and . <= 50%]](@id + quantity + price))
该查询从“Orderitem”中选取了三个“discount”为“20-50%”的元素。
进行XPath群组查询比进行一组XPath查询更高效,因为一个群组里所有的定位路径共享同一由该群组之前的定位路径定位的上下文节点。例如,给定 ,对要查询的任一 仅执行
l一次。
表1 简化的语法
Full Form | Abbr. | Full Form | Abbr. | Full Form | Abbr. | Full Form | Abbr. | Full Form | Abbr. |
self | . | descendant | // | logical-not | ! | sum() | $S | text() | $T |
child | / | ancestor-or-self | ./ | logical-or | | | count() | $C | wildcard | * |
parent | / | descendant-or-self | /. | logical-and | & | max() | $U | contains | ?= |
ancestor | // | root | .. | average() | $A | min() | $L | starts-with | $= |
简化的语法 尽管XPath的语法很简单,但做为查询语言仍相当罗嗦。我们把XPath轴连同函数和运算映射为更简明的句法缩写(more concise syntactic abbreviations)。映射如表1所示。附录[14]中给出了轴
child,
attribute,
self,
descendant-or-self,
parent的缩写,但我们为最后两个轴给出的缩写与之不同,这是为了得到对XQzip所涵盖的查询而言完整而简单的映射。同时,为使解析更容易,查询解析器要求查询中的谓词全部加上括号。
例5.1:
完整形式:
/descendant::site[child::open auctions[descendant-or-self::bidder]]/descendant::closed auction[child::buyer and child::seller]/child::description
简化形式:
//site[open auctions [/.bidder]]//closed auction[[buyer] & [seller]]/description 5.2 实施查询
XQzip实施查询分四个主要阶段:(1)解析查询;(2)节点选取;(3)获取数据;(4)查询结果输出。解析查询
查询解析器将一个输入的查询译为用整数表示的事件流,其中正值表示XML元素(如它们的 Hash ID),负值表示其它表达式。节点选取
节点选取在查询中很关键。测试表明当前的XPath查询引擎进行查询的时间为多项式时间(exponential time)。产生多项式时间的原因是,对于每一个定位步骤,一组大小与文档大小呈线性的节点会被选取,且该组中的每个节点可能会依次为下一定位步骤选定一系列节点。因此时间复杂度为 ,其中|D|为文档大小,|Q|为查询的大小。我们提出一个平均产生多项式时间复杂度的简单算法。
该算法把一个轴关闭(axis closure)划分为两个不相交的域(disjoint areas)。我们为SIT中的每个节点关联一个访问标记(
a visited flag)。若一个子树的根的访问标签已置位,则该子树是访问过的。一个上下文节点相关的一个轴关闭中的所有已访问子树的集合构成已访问关闭(
visited_closure),未访问关闭(
unvisited_closure)就是轴关闭和已访问关闭的区别。
|
图6 查询算法核心
图6给出了查询算法的核心。其思想如下:
对于查询 , 为
descendant或
descendant-or-self轴(若 为ancestor或
ancestor-or-self轴时是相似的)。例如上下文节点
u,若查询过程完成了 w.r.t.
u,则以
u为根的子树被设置为访问过(不管查询的结果如何),因为对同一上下文节点
u, 的结果经常是一样的。而且,一个
ancestor通常包括其
descendants,因此当一个节点包含在结果集中时,我们设置其为已访问。从而,随着查询过程的进行,越来越多的子树将会被访问,而未访问关闭(unvisited_closure)的节点回也来越小甚至消失。这表明每个定位步骤中选取的节点在查询的后面的阶段中不再是线性的。Hence, we have the average-case polynomial query evaluation time in the size of the SIT。获取数据与解压缩
我们已经在第4章介绍了压缩块的获取和压缩块中数据的获取。尽管数据获取的开销不是很大,但一个元素可能在一个查询或连续请求的一组查询中的很多地方出现,导致一个压缩块被调用和解压缩很多次。由于采用gzip作为压缩工具,我们无法在改善一个块的解压缩时间上做得很多。然而我们通过引入一个缓冲池,避免了一个块被反复解压缩。
XQzip采用LRU(最近最少使用算法)来管理保存最近解压缩的MXL数据的缓冲池。该缓冲池被定义为有一个head指针和一个tail指针的双链表。缓冲区没有固定的大小,而是根据解压缩数据的大小动态的分配。当一个新的块被解压缩,缓冲管理将其挂到链的尾部;当一个块再次被存取时,缓冲管理将其从链中取出挂在链的尾部。缓冲池的总大小设置了一个内存上限(默认16MB)。达到内存上限时,缓冲管理从链的头部清除缓冲区,直到有足够的内存分配给新的缓冲。
缓冲池中的每个缓冲能够从哈希表中即时存取,并且得到一个和压缩块id相同的id,因此,当有同一id的缓冲已经在缓冲池中时,就避免了再次解压缩该压缩块。
根据“局部性”原理(
the principle of locality),某一特定时间里产生的查询请求的数据存取模式通常类似。因此,在进行了一些查询、缓冲区已初始化之后,新块应该仅仅偶尔需要解压缩。实验结果表明,缓冲池很大程度上减少了查询时间:暖缓冲池(已初始化的)时测得的平均查询时间是冷缓冲池时的1/6.14。而且,暖缓冲池时从压缩文件中恢复原始XML文档也更快。查询结果输出
查询处理模块生成查询结果,由输出表达式指明。XQzip支持的输出表达式有:(1)未指定(
not specified) :返回查询结果集中所有的元素;(2)定位路径/文本(location path/text()):仅返回查询结果元素的文本内容;(3)定位路径/操作(location path/op):五个累积运算中的一个;(4)[Q]:若
Q查询为真返回
true,否则返回
false。
后记:
认清分枝branch和子树subtree的关系:一个子树可能含有很多个分枝
元素ID是怎样产生的?
树结构如何进行结构恢复的??
堆栈在构造SIT中的应用!!
XQzip的压缩模块和gzip有什么不同??
文档要做的要点明确、思路更清晰;
说明过程更系统,理解更深入清楚
不仅清楚细节,更要清楚大的方向上的问题。