DFLAT:SQLITE FLOTBUFFERS

2021-04-04 17:40:09

如果您熟悉核心数据或境界,DFLAT在应用程序中占用与这两个相同的空间。它可以帮助您持续和从磁盘检索对象以获取应用程序需求。与这两个不同,DFLAT具有不同的特征,并进行了非常不同的权衡。这些特征和权衡基于撰写世界上一些最大的应用程序的现实世界经验。 DFLAT也是通过迅速的上下次建造的,希望您能发现与SWIFT语言相互作用是自然的。

在过去的几年里,我一直在手机上编写不同的结构化数据持久性系统。 DFLAT是在构建这些专有系统时积累的课程。在IOS上,去选择长期以来一直是核心数据。它有效,是许多系统应用程序的内部数据持久性机制。

但是,在将结构化数据持久性系统部署到数百万个移动设备时,存在某些挑战,既存在数据如何持续存在,也是在更高级别的应用程序如何与此类系统交互。

DFLAT CodeBase仍处于一个非常年轻的阶段。但是,基本原则在其他专有系统中一直在证明是成功的。 dflat以特定顺序实现以下功能:

系统返回可以传递给其他系统的不可变数据对象(例如您的视图模型生成器);

可以观察到所有查询和对象。更新将通过回调或组合框架发布;

突变只能在单独的线程上发生,呼叫者几乎没有控制,从而异步;

支持严格的可序列化多编写器/多读取器模式,但用户可以选择单编写器(因此,如果需要,可以选择单个编写器(因此,琐碎严格的可序列化)/多读者模式;

数据查询以SWIFT代码表示,并且将由SWIFT编译器进行键入;

架构升级要求无法对底层数据库的写入访问(具有SQLite 3.22及更高版本的严格只读)。

与核心数据不同,DFLAT由迅速为速度构建。您可以通过充分利用SWIFT语言来表达数据模型。因此,对结构(产品类型),枚举(SUM型)的本机支持,具有类型检查的查询和使用组合观察。

dflat运行时使用sqlite作为存储后端。设计本身可以在将来支持其他后端(如Libmdbx)。唯一的硬依赖性是无障碍。

要使用DFLAT,您应该首先使用DFLATC编译器从FlatBuffers模式生成数据模型,包括项目中生成的代码,然后使用DFLAT运行时与数据模型进行交互。

DFLAT目前需要挥之布尔。更精确,可以使用Swift包管理器或Bazel一起安装DFLAT运行时。但DFLATC编译器需要BAZEL构建相关零件。

如果您的项目已由Bazel管理,DFLAT将从代码生成提供完全集成的工具到库依赖管理。只需将dflat添加到您的工作空间:

git_repository(name =" dflat",remote =" https://github.com/liuliu/dflat.git",提交=" 3dc11274e8c466dd28ee35cdd04e84ddf7d420bc",shadow_since =& #34; 1604185591 -0400")负载(" @dflat //:deps.bzl"" dflat_deps")dflat_deps()

加载(" @dflat //:dflat.bzl&#34 ;," dflatc")dflatc(name =" post_schema",src =" post.fbs" post.fbs" 34;)swift_library(... srcs = [..." post_schema"],deps = [..." @ dflat //:sqlitedflat"])

您现在可以将生成的源代码添加到项目中,然后继续使用Swift包管理器添加DFLAT运行时:

枚举颜色:字节{red = 0,绿色,蓝色= 2}表TextContent {Text:String;}表ImageContent {iclies:[String];} Union Content {TextContent,ImageContent}表帖子{title:string(might); //这是主键颜色:颜色;标签:字符串;优先权:int(索引); //此属性是索引内容:内容; root_type帖子; //这很重要,它表示帖子对象将是一个DFLAT管理。

如果一切都会被检查,你应该看到在...中生成的4个文件../postexample目录:post_generated.swift,post_data_model_generated.swift,post_mutation_generated.swift,post_query_generated.swift。将它们添加到项目中。

var createdpost:帖子? = nildflat。 perfaceChanges([帖子。self],changeshandler:{(txncontext)在let creationrequest = postchangeRequest。CreationRequest()CreationRequest。Title ="第一个帖子" CreationRequest。ColorionRequest。Color =。红色创建request。Content =。TextContent( TextContent(文本:"这是我的第一篇文章!"))警卫让Let插入=尝试?TXNContent。提交(CreationRequest)else {return} //交替,您可以使用txncontent.try(提交:赢得' t返回任何结果和#34;合理的"错误处理。如果案件让。插入(post)=插入{createdpost = post}}){成功in //事务完成}

dflat。 perfaceChanges([Post。self],changeShandler:{(txncontext)在Let Post = Posts [0]中,让ChangeRequest = PostChangeRequest。ChangeRequest(Post)Changerequest。Color =。Green TxnContent。尝试(提交:changeRequest)}){成功//交易完成}

dflat。 perforcechanges([post。self],changeShandler:{(txncontext)let post = posts [0]让deletionrequest = postchangeRequest。deletionrequest(post)txncontent。尝试(提交:deletionrequest)}){成功in //交易完成}

您可以订阅对查询或对象的更改。对于一个对象,订阅在删除对象时结束。对于查询,除非取消,否则订阅' T完成。有两组API,一个是基于Vanilla回调的,另一组是基于组合。我将在这里展示一个组合。

让消除= dflat。出版商(用于:帖子。自我)。在哪里(帖子。颜色==。红色,orderby:[post。优先权。下降])。订阅(开启:Dispatchqueue。Global())。水槽{post in print(posts)}

让消除= dflat。 Pulisher(for:posts [0])。订阅(开启:Dispatchqueue。Global())。 inter {帖子在开关帖子{case。更新(Newpost):打印(NewPost)案例。已删除:打印("删除,完成。")}}

DFLAT中的架构演进随着FlatBuffers的精确。唯一的例外是您无法在选择后添加更多主键或将主键更改为不同的属性。否则,您可以自由地添加或删除索引,重命名属性。要删除的属性应标记为已弃用,新属性应附加到表的末尾,并且您永远不应更改属性的类型。

只要您遵循架构进化路径,就无需版本控制。由于架构由FlatBuffers维护,而不是SQLite,因此模式升级需要磁盘操作。由于缺乏磁盘空间或延长的架构升级时间,由于病理案件缺乏磁盘空间或延长的架构升级时间,而且是DFLAT。

DFLAT架构支持名称空间,如FlatBuffers模式。但是,由于SWIFT并在' t真正支持正确的命名空间,所以命名空间实现依赖于公共枚举和扩展。因此,如果您有命名空间:

你必须自己声明命名空间。在您的项目中,您需要有一个Swift文件包含以下内容:

它将起作用。然后,您可以通过Evolution.v1.post或typeAleias Post = Evolution.v1.post访问Post对象。

dflat运行时具有非常小的API占用空间。 2个对象总共有大约15个API。

Func工作区。 perforpanges(_事务性objecttypes:[devery],changeshandler:@escaping(_ transactionContext:transactionContext) - > void,completionHandler :((_成功:bool) - > void)?= nil)

API采用ChangEShandler Closure,您可以在其中执行其他事务,例如对象创建,更新或删除。这些突变通过Changerequest对象进行。

第一个参数指定要与之交换的相关对象。如果您读取或更新此处未指定的任何对象,则会触发断言。

完成事务后,将触发完成的Handler Closure,它会让您知道事务是否成功。

该交易将在后台线程中执行,究竟是哪一个' t是您的关注。两个不同的对象可以同时执行事务,在这种情况下遵循严格的序列化协议。

您可以在事务中与DFLAT与DFLAT交互。它通过提交来处理数据突变。请注意,错误是可能的。例如,如果您创建了两次具有相同主键的对象(如果预期,则应使用UpsertRequest)。尝试(提交:方法简化了尝试?如果您不想知道返回的值,请提交舞蹈。如果有冲突主键,则致命,否则将吞下其他类型的错误(如磁盘满)。当遇到任何其他类型的错误时,DFLAT将简单地失败整个事务。中止方法将明确地中止交易。此呼叫之前和之后的所有提交都没有效果。

Func工作区。获取(fortype:元素。类型)。其中(exportQuery,limit =。nolimit,Orderby = []) - > fetchedresult<元素> Func工作区。获取(fortype:元素。类型)。所有(限制=。nolimit,Orderby = []) - > fetchedresult<元素> Func工作区。 fetchwithinasnapshot< t>(_:() - > t) - > T.

数据获取同步发生。您可以在where子句中指定条件,例如post.title =="第一个帖子"或post.priority> 100&& post.color == .dred。返回的fetchedResult<元素>相当像一个阵列。对象本身(元素)是不可变的,因此,对象或fetchedResult<元素>在线程之间传递是安全的。

让结果= dflat。 fetchwithinasnapshot {() - > (FirstPost:fetchedResult< post>,highproposts:fetchedresult< post>)在让FirstPost = DFLAT中。获取(用于:帖子。自我)。哪里(帖子。标题=="第一个帖子")让HighProposts = DFLAT。获取(用于:帖子。自我)。在哪里(帖子。优先> 100&&帖子。颜色==。红色)返回(firstpost,highproposts)}

这是必要的,因为DFLAT可以在FIREDPOST和HIGHPRIPOST之间进行获取之间的交易。 fetchwithinasnapshot won' t停止该事务,但会确保它只观察视图获取firstpost。

Func工作区。订阅<元素:易等于>(fetchedResult:fetchedResult<元素>,changeHandler:@eScaping(_:fetchedResult<元素>) - &got;订阅Func工作区。订阅<元素:易等于(对象:元素,CompentHandler:@EScaping(_:UsumcribedObject<元素>) - &goid) - >订阅

以上是本机订阅API。它订阅更改为fetchedResult或对象。对于对象,它将在删除对象时结束。在触发事务的ComplayionHandler之前触发订阅。

Func工作区。出版商<元素:相当于>(for:元素) - > Atompublisher<元素> Func工作区。出版商<元素:相当于>(for:fetchedresult<元素>) - > fetchedresultpublisher<元素> Func工作区。出版商<元素:易等于(for:元素。类型)。其中(exportQuery,limit =。nolimit,Orderby = []) - > querypublisher<元素> Func工作区。出版商<元素:易等于(for:元素。类型)。所有(限制=。nolimit,Orderby = []) - > querypublisher<元素>

这些是组合反补贴。除了订阅对象或fetchedResult,它也可以直接订阅查询。引擎盖下会发生什么是查询将在订阅时进行(如果您确实订阅了(开启:),则在您提供的任何队列中,并从那时起订阅FetchedResult。

这将触发DFLAT关闭。此呼叫后对DFLAT的所有交易都将失败。在此之前启动的事务将正常完成。数据获取后将返回空结果。在此调用之前触发的任何数据都会触发,因此完成部分。如果提供的,则将打电完成闭合,则将调用一次在关机完成前启动的所有事务和数据获取。

结构化数据持久性系统上的基准令人难以置信。 DFLAT赢得了'声称最快。但是,它努力成为可预测的表现。这意味着什么是' t是任何病态案例,即dflat的性能意外降级。它也意味着DFLAT WON' T令人惊讶地快速地快速地快速地快速。

我主要与核心数据进行比较,并从WCDB基准(来自v1.0.8.2)的FMDB和WCDB中列出的数字,以更好地概述您希望从测试设备中所期望的内容。

免责声明:您应该为任何基准号码进行一粒盐。我这里介绍的这些数字只是为了展示涉及框架的一些病理案例。它应该脱离这种背景。在实践中,结构化数据持久性系统很少是瓶颈。了解如何使用它而不是什么样的光工作负载设备中的原始数字更重要。

应用程序代码:基准在释放模式(--compilation-mode = Opt)中编译 - 使用-Whole-Module-Optimation。 WCDB基准在释放模式下编译,无论在其项目文件中是否意味着什么。

基准本身并未对同行评审。在某些情况下,它代表了这些框架的最佳案例方案。在其他情况下,它代表了最坏的情况。它不是为了反映现实世界的工作负荷。相反,这些基准设计用于在极端情况下反映框架' S特征。

首先,我们将DFLAT与对象插入,获取,更新和删除的核心数据进行比较。生成10,000个对象,没有索引(仅在核心数据中索引标题)。

获取单独评估的10,000个对象按标题(以核心数据索引索引,并且是DFLAT中的主要键)10,000次。

这些显然不是做事的最佳方式(你应该在一个大交易中更新对象,如果可能的话,批量获取它们),但这些是我们之前讨论的有趣的病理案例。

在核心数据中进行多线程插入/删除的适当方式是更棘手的,我哈登' t到目前为止。多线程插入40,000对象和多线程删除40,000个对象仅用于DFLAT。

这些数字中的一些看起来太好了。例如,在插入时,DFLAT出现多于核心数据的两倍多。这些数字中的一些没有做出直观的感觉,为什么多线程插入慢?把它放在透视中很重要。

该图表与从WCDB基准(v1.0.8.2)提取的数字进行比较,而无需任何修改。它比较每秒的OPS,而不是花费拾取33,334个对象的时间。请注意,在WCDB基准测试中,基线​​读取确实取自所有,这是SQLite中最佳的情况。它还比较一个只有两列的简单表,一个键和BLOB有效载荷(100字节)。

在我们的理想情况下,多线程写入确实较慢,因为SQLite本身无法同时执行写入。因此,我们的多作战模式真的只意味着可以同时执行这些交易闭合。写入仍然在SQLite层串行发生。它仍然有益,因为在真实的案例中,我们在交易闭合中花费了很多时间进行数据转换,而不是SQLite写入。

写入的天花板远高于达到的DFLAT。同样,WCDB表示一个理想的情况,您只有两列。现实世界中的DFLAT数字也将低于我们在此处的数字,因为我们将拥有更多的索引和对象具有许多字段,甚至数据阵列。

由于DFLAT并不导致批量操作的任何优化,它应该是一个惊喜DFLAT性能尺度线性W.R.T. DataSet大小,如下图所示。

每个框架都有略有不同的设计,以如何改变订阅工作。核心数据以两种方式实现:nsfetchedResultScontroller委派回调,以及nsmanagedObjectContextObjectSdidChange。从开发人员'透视图,NSFetchedResultScontroller可以解释为DFLAT侧的FetchedResult订阅的反映部分。两者都支持制作SQL的查询并为结果集发送更新。您可以基于NSManagedObjectContextObjectsDidChange通知构建核心数据中的DFLAT对象订阅机制。出于目标的目的,我将在比较这两个时,我将简单地观察NSManagedObjectContextObjectsDidChange通知的延迟,假设底层传递给单个对象订阅是一个No-Op。

订阅变更为1,000个获取的结果,每个结果都恰好观察一个对象(由主键获取)。后续事务将更新10,000个对象,包括这些订阅的1,000个对象。测量从已保存时的延迟,到交付更新时的时间。对于核心数据,已设置ViewContext的子上下文,并在保存子上下文之前测量延迟,以便它提供的时间。这应该是在数据持久后(viewContext.save()在保存子上下文后调用)。在DFLAT方面,这种情况发生在数据持久后。

订阅更改为1,000个获取的对象。后续事务将更新10,000个对象,包括这些订阅的1,000个对象。测量从已保存时的延迟,到交付更新时的时间。对于核心数据,NSManagedObjectContextObjectSDidChange已订阅ViewContext对象。它可以从节省子上下文之前测量延迟,以提供时间通知。

订阅变更为1,000个获取的结果,每个结果都观察大约1,000个对象(由range查询获取)。后续事务将更新10,000个对象,旋转每个获取结果中的所有对象,同时每个结果保持1,000个对象。核心数据上的测量设置与1相同。

取得的结果观察的数量,特别是在案例1上,代表了它们的全部病理情况。对于DFLAT特别麻烦,因为单独从磁盘获取1,000个对象将需要大约20毫秒。因此,如果我们拍摄SQLite.SWIFT方法,可以识别WHCIH表更改,并且只需重新确定该表上的每个查询,我们最终可能更加表现。虽然对于案例3,从磁盘上重新取出肯定会更慢(接近6秒,每个疑问,每个疑问都有1,000个对象)。

从基准开始,核心数据遭受了类似的问题,同时更糟。同样,这是一个极端的案例。对于移动应用程序,您只能少量查询订阅,每个查询的数千个对象,以及您导航到其他页面时取消订阅更改。这些极端案例几乎没有逼真,你不会从核心数据中看到35秒的口吃只是因为有10,000个对象更新,你碰巧需要更新1000个表视图。实际上,通过主键订阅个人查询似乎是一个大的否。如果要观察单个对象,则应仅为单个对象作为案例2显示。

但是,它确实揭示了我们的消息排序 -

......