Salesforce的成功无法离开其底层平台Salesforce Platform的支持。而Salesforce Platform的核心是元数据驱动的多租户数据模型。
Salesforce Platform使用元数据来管理其内部使用的每一个逻辑数据库对象。UDD(Universal Data Dictionary, 全局数据字典)会针对对象(即传统关系型数据库中的表)、字段、存储过程和触发器构建相应的元数据进行管理。当你定义一个新的应用数据或编写存储过程时,Salesforce平台并不会直接在数据库中创建相应的表或编译存储过程代码。相反,平台仅仅存储数据库表或存储过程的元数据,以便系统引擎用来在运行时生成虚拟应用组件。当你创建应用的数据库schema时,UDD会记录对象(表)、表的字段、它们的关系及其它对象相关属性的元数据。然后,系统会创建许多数据库的大表,已存储元数据中定义的虚拟表的结构化数据与非结构化数据。同时,UDD会使用非规范化数据的透视表来实现索引。
(该图引用自Salesforce官方资料)
多租户元数据
Salesforce平台有两个核心的系统内部表:MT_objects 和 MT_fields。它们被用来存储和管理对应租户数据对象的元数据。
多租户数据
MT_data系统表保存具体应用访问的数据,这些数据根据MT_objects和MT_fields的定义被映射到特定租户或组织的表及相应字段。MT_data的每一行都包括标识字段,如全局唯一ID(GUID)、拥有该行数据的组织(OrgID)、包含该行数据的对象(ObjID)。同时,MT_data的每一行数据都包含一个名称字段,用来保存相关记录的名称,如 关于账户的记录可能用AccountName,关于事件的记录可能用EventName, 等等。
Value0..value500 共501个flex列,也叫slots,承载MT_objects和MT_fileds里声明的表与字段的应用数据。所有flex列使用varchar(可变长度的字符型)数据类型,从而可以保存结构化数据的任意类型,如字符串、数字、日期等。同一对象的两个不同字段不能被映射成MT_data里的同一slot;但是,同一slot可以管理多个字段的数据,只要每个字段来自不同的object。
MT_fields可以使用任一标准的结构化数据类型,如text、number、date、date/time,它也可以使用特殊用途的富结构(rich-structured)的数据类型,如picklist(枚举型)、自增数字(系统自动产生、按行自动增加的数字类型)、派生数据(formula, 只读的派生值)、引用关系(外键)、checkbox(布尔值)、email、URL等。MT_fileds也可以被设置为不可为空(not null),或者定义验证规则(如一个字段必须大于另一个字段)。
当一个租户声明或修改一个对象时,Salesforce平台修改或创建MT_objects表中的定义该对象的一行元数据。同理,针对每个字段,Salesforce平台修改或增加MT_fields中的一行元数据,该行元数据映射该字段到MT_data中特定的flex列以便保存相应字段的数据。由于Salesforce平台通过元数据来管理应用数据的表和字段,而不是通过直接修改数据库结构,系统可以允许在线的多租户数据schema的维护活动,而不影响正在进行业务活动的其它租户或用户。如果对数据库表进行在线修改或重新定义,则需要复杂的数据处理及小心的规划应用停止服务时间。
如MT_data的示意图所示,flex列是通用数据类型(可变长度的字符串),即同一flex列可以承载不同数据类型的应用数据。
Salesforce平台使用可变长度的字符串类型存储flex列的数据,当应用从flex列中读取数据或者写入数据到flex列时,Salesforce平台会在必要的情况下调用内置的系统类型转换函数(如TO_NUMBER, TO_DATE, TO_CHAR)。
同时,MT_data还包含其它列。例如,MT_data中有4列承载审计数据,包括哪个用户创建该记录,何时创建该记录,哪个用户最后修改该记录,何时最后修改该记录。MT_data也包含一个IsDeleted列,Salesforce平台用该列来标识某条记录是否被删除。
Salesforce平台也支持对大对象类型(CLOB,Character Large Object),该数据类型支持长达32000字符的文本数据。针对MT_data中的包含CLOB数据的记录,Salesforce平台在MT_data表以外将CLOB数据保存在MT_clobs中,同时系统会把MT_clobs中的记录与MT_data中的记录连接起来。
多租户索引
Salesforce平台会自动为各种类型的字段创建索引,以支持更快的数据访问。
传统数据库系统依赖原生的数据库索引实现根据指定条件快速定位相关表记录。但是,由于Salesforce平台使用单个flex列承载多个字段的不同数据类型的数据,在MT_data中为flex列创建原生数据库索引变得不太现实。Salesforce平台通过把相关字段数据同步复制到名为MT_indexes的透视表中的方式创建索引。
MT_indexes包含强类型、已索引的列,如StringValue、NumValue、DateValue,Salesforce平台可以用来定位相关数据类型的数据记录。例如,Salesforce平台会从MT_data的flex列中复制一个字符串类型的数据到MT_indexes的StringValue类型的字段中,会把date类型的数据复制到DateValue类型的字段中。MT_indexes中的内置索引是标准的、非唯一数据库索引。当一条内部系统查询的一个参数引用一个对象的某一结构化数据的字段时,Salesforce平台的自定义查询优化器会使用MT_index来帮助优化相关的数据访问操作。
注:由于Salesforce平台使用case-folding算法把字符串值转换为一种通用的、大小写敏感的格式,它可以处理跨多语言的查询。MT_Indexes表中的StringValue列采用该格式来保存字符串。在运行时,查询优化器自动创建数据访问操作,以便被优化的SQL语句通过这种通用格式的StringValue值来过滤数据。
Salesforce平台允许租户或组织指定何时对象中的字段包含唯一值(大小写敏感或不敏感)。考虑到MT_data的这种安排和字段数据的Value列的共享使用,创建数据库的唯一索引并不现实。
为了支持某些字段的唯一性,Salesforce平台引入了MT_unique_indexes透视表。MT_unique_indexes透视表中的内置数据库索引是唯一索引,除此以外,MT_unique_indexes透视表与MT_indexes类似。当应用试图插入一条重复数据到具有唯一性的字段时,或当管理员对已存在的字段使用唯一索引时,Salesforce平台会向应用发送一条错误消息。
极少情况下,Salesforce平台的外部搜索引擎会出现过载或不可用,或对查询情况不能及时响应。与其返回一条错误消息给提交查询请求的用户,Salesforce平台采用次优的查询机制以返回合理的查询结果。
Fall-back查询通过查阅目标记录的Name字段来直接进行数据库查询的方式来实现。为了优化全局对象查询(跨表搜索)而不执行昂贵的联合查询,Salesforce平台维护MT_fallback_indexes透视表,该表记录所有记录的Name字段。每当事务更新记录时,MT_fallback_indexes的更新同步进行,以便fall_back总是能够访问最新的数据信息。
MT_name_denorm表是一个精巧的数据表,只保存MT_data中的ObjID和该对象相关的每条记录的名字。当应用需要提供父子关系的记录时,Salesforce平台使用MT_Name_denorm表来执行相对简单的查询以获取相关数据记录。
多租户关系
Salesforce平台提供“关系”数据类型,租户用来声明数据库表之间的关系。当一租户声明某对象的字段为关系类型时,Salesforce平台把该字段映射到MT_data的Value字段,然后用该字段来保存该对象的关联对象的ObjID。
为了优化连接(join)操作,Salesforce平台维护MT_relationships透视表。该系统表默认使用两个内置唯一复合索引,以便允许对关联对象进行正向或反向遍历。
多租户字段历史
通过鼠标操作,Salesforce平台可以提供任一字段的历史轨迹。当租户对某字段使能审计功能时,系统使用一个内部透视表以异步的方式记录对该字段的变更(旧值、新值、变更日期等)。
元数据、数据和索引数据的分区
所有Salesforce平台的数据、元数据和透视表数据,包括内置的数据库索引数据,都通过OrgID(租户)使用原生的数据库分区机制进行物理分区。数据分区是一种成熟的数据库系统功能,用来把巨大的逻辑数据结构分割为较小的、可管理的片段。分区也用来提供大型数据库系统的性能、扩展性和可用性。根据定义,每个Salesforce平台的查询都首先指向一个租户的数据,所以查询优化器只需要考虑该租户内的数据片,而不需要考虑整个数据库或索引。