探讨使用 Salesforce Big Objects 进行数据归档的最佳实践

Posted by Peter Dong on February 1, 2023

归档是一种将不常使用的数据从主数据库中移除的技术。通过归档,我们可以减少数据库存储空间,提高系统性能,降低存储成本,增加可用空间,提高数据安全性,提高数据准确性和可靠性。

随着企业数据量的不断增加,数据管理和存储已经成为一个重要的问题。而对于使用 Salesforce 服务更是如此,Salesforce Big Objects 是一种特殊数据对象,旨在解决企业面临的存储和管理数据的挑战。可以帮助企业存储和管理大量的历史数据。与传统的 Salesforce 对象不同,Big Objects 可以存储超过亿级的数据记录,并且可以快速读取和查询这些数据。本文将探讨如何使用 Salesforce Big Objects 进行数据归档,以获得最佳的数据管理效果。

那么使用 Salesforce Big Objects 归档数据到底可以解决什么问题呢?

  • 提高系统性能:通过归档不常使用的数据,可以减小数据库的负荷,提升系统性能。
  • 降低存储成本:通过归档数据,可以减小数据库存储空间,降低存储成本。
  • 提高数据安全:通过归档数据,可以提高数据的安全性,防止数据泄露。
  • 提升数据治理:通过归档数据,可以更好地管理数据,提高数据的准确性和可靠性。

数据归档/备份在 Salesforce 中的几个方案?

为什么数据归档这么重要?

大数据量会导致查询性能下降并影响用户体验。通过清理不需要的数据,您可以减少数据混乱并提高数据采用率。归档使您的组织能够更好地控制您的数据信息。还可以通过归档 Salesforce 数据来降低存储成本。最后但并非最不重要的一点是,归档可确保您的数据安全。

除了 Big Object, Salesforce 还有哪些数据归档/备份的方案?

  • Shadow Objects: 它不是一个官方或正式使用的术语,是一个自定义对象,用于复制主对象的记录。它将拥有与主对象相同的结构。意味着有同样的字段,同样的 CRUD,OWD, 字段级安全。我们可以根据标准将记录转移到这个新对象。通过这种方式,我们可以减少主对象的数量。比如:可以使用 Case 对象作为主对象。任何被创建或更新的 Case 记录都会被复制到 Shadow_Case__c (Shadow) 对象。但是如果比较在意数据 storage 空间的话,这种备份方式不推荐。
  • Heroku: 基于云的服务,将数据存入或转出 Salesforce 到 Heroku(Postgres). 使用 Salesforce 和 Heroku Postgres 之间的双向同步,通过 Heroku Connect 将您 Postgres 数据库中的数据与 Salesforce 数据库中的 Contact, Account 等标准对象和其他自定义对象统一起来。
  • App exchange 产品: 最受欢迎的备份数据的 Appexchange 产品是:Backupify, Ownbackup for Salesforce, Spanning Backup, Odaseva.
  • External Objects: 类似于 Salesforce 中的自定义对象,但外部对象的记录数据是存储在您的 Salesforce 组织之外。每个外部对象都与您的 Salesforce 组织中的一个外部数据源定义相关。外部数据源规定了如何访问外部系统。比如:可以使用 Azure 作为外部数据源将数据同步至 Salesforce.

创建 Big Object

这篇文章的 Demo 会以 Case 对象为例,支持通过 Big Object 对 Case 数据进行归档。您可以在 Salesforce 的 Setup 中创建一个自定义的 Big Object. 你也可以用 Metadata API 创建一个自定义的 Big Object,但在通过 Setup 中创建要比 API 要简单得多。

在 Salesforce Setup 中,在查找框中输入 Big Object,然后选择点击 Big Object.

img

创建一个名为 Case__b 的 Big Object.

img

创建自定义字段

一旦你定义了你的 Big Object,可以继续添加自定义字段。自定义字段为您的 Big Object 存储独特的数据。Big Object 支持这些字段类型:

  • Lookup Relationship
  • Date/Time
  • Email
  • Number
  • Phone
  • Text
  • Text Area (Long)
  • URL

Case__b 对象字段创建如下,其中 Account 和 Contact 对象和 Case 有关联,我们通过存储对象的 ID 作为唯一标识,这里面会将其作为 Index 来提高查询效率。

img

创建自定义的 Big Object 索引

这个很重要!Big Object 的索引中定义的字段决定了 Big Object 被查询的能力。在你的索引中定义的字段应该是与你的查询最相关的字段,所以你在配置索引的时候要有远见,你定义字段的顺序也是一个重要的考虑因素。如果你使用 SOQL 来查询 Big Object, 你只能按照你定义的顺序来查询组成你的索引的字段。将你在 query filter 中最常使用的字段分配到你的索引中的第一个位置。

定义索引时的注意事项:

  • 一个索引必须包括至少一个自定义字段,最多可以有五个自定义字段。
  • 包含在索引中的自定义字段必须被标记为必填。
  • 长文本区字段不能包括在索引中。
  • 索引中所有文本字段的总字符数不能超过 100.
  • 一旦你创建了一个索引,就不能编辑或删除它。要改变索引,请新创建另一个 Big Object 再添加索引。

img

实现操作界面

需求:希望可以在 Case 详情页面提供 Archive 功能,通过触发 Archive 操作数据自动删除并归档,并且能够在 Account 和 Contact 界面查看到已归档的 Case 数据。本次 Demo 演示的是如何归档单条 Case 记录,暂不支持批量归档 Case 记录,不过实现逻辑与此大同小异,读者可自行实现。

部分 Apex 代码:

CaseArchiveController.cls

将主数据 Case 记录转为 Big Object, 并删除主数据的记录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//查询主数据
Case caseS = [
    SELECT 
    Id,
    ContactId,
    AccountId,
    CaseNumber,
    CreatedDate,
    ...
    FROM Case WHERE Id = :recordId
];

// 将 Case 数据转换为 BigObject
Case__b caseB = new Case__b();
caseB.Record_Id__c = caseS.Id;
caseB.Case_Number__c = caseS.CaseNumber;
caseB.Account_Id__c = caseS.AccountId;
caseB.Contact_Id__c = caseS.ContactId;
...

Database.SaveResult result = Database.insertImmediate(caseB);

delete caseS;

部分 lwc 实现逻辑:

caseArchive.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
    <div class="slds-card slds-p-around_medium" style="text-align:center;">
        <template if:true={showArchive}>
            <lightning-button variant="brand" label="Archive Case" onclick={archiveClick} class="slds-p-around_medium"></lightning-button>
        </template>

        <template if:true={showSuccess}>
            <div class="slds-text-heading_small slds-p-bottom_small">Case archived and deleted!</div>
            <div class="slds-text-body_regular slds-p-bottom_medium">To retrieve this case, navigate to the related Account or Contact record.</div>
            <div>
                <lightning-button label="View the Account" onclick={navigateToAccount} class="slds-p-around_xx-small"></lightning-button>
                <lightning-button label="View the Contact" onclick={navigateToContact} class="slds-p-around_xx-small"></lightning-button>
            </div>
        </template>
    </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import { LightningElement, api, wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
import { NavigationMixin } from 'lightning/navigation';
import callArchiveCase from '@salesforce/apex/CaseArchiveController.archiveCase';

export default class CaseArchive extends NavigationMixin(LightningElement) {
    @api recordId;
    showArchive = true;
    showSuccess = false;

    @wire(getRecord, { recordId: '$recordId', fields: ['Case.AccountId', 'Case.ContactId'] })
    record;

    archiveClick(event) {
        callArchiveCase({
            'recordId': this.recordId
        })
        .then(result => {
            this.showArchive = false;
            this.showSuccess = true;
        })
        .catch(error => {
            console.log('ERROR: ' + JSON.stringify(error));
        });        
    }

    navigateToAccount(event) {
        this[NavigationMixin.Navigate]({
            type: 'standard__recordPage',
            attributes: {
                recordId: this.record.data.fields.AccountId.value,
                actionName: 'view'
            }
        });
    }

    navigateToContact(event) {
        this[NavigationMixin.Navigate]({
            type: 'standard__recordPage',
            attributes: {
                recordId: this.record.data.fields.ContactId.value,
                actionName: 'view'
            }
        });
    }
}

操作界面如下:Case Detail -> 点击 Archive Case 按钮

img img

在 Account 上查看归档数据

由于 Case 归档后,原始数据已经删除,那么我们可以通过其他对象来查看已归档数据,还记得我们在创建 Big object 的时候,创建了 Account 和 Contact Id 字段,并创建了索引,我们可以通过这些字段查看归档的 Case 数据。

获取归档的 Case 记录相关逻辑代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 如果有一个联系人 ID,但没有 account ID,可以通过查询获取它。
if ( String.isBlank(accountId) && !String.isBlank(contactId) ) {
    Contact contact = [SELECT AccountId FROM Contact WHERE Id = :contactId];
    accountId = contact.AccountId;
}

List<Case__b> cases;
List<String> columns = new List<String>{'Record_Id__c', 'Account_Id__c', 'Contact_Id__c', 'Subject__c', ... };

String query = 'SELECT ';
for (Integer i = 0; i < columns.size(); i++) {
    query = query + columns[i];
    if (i < (columns.size() - 1)) {
        query = query + ', ';
    }
}

query = query + ' FROM Case__b';

// Filter by Account ID
if (!String.isBlank(accountId)) {
    query = query + ' WHERE Account_Id__c = :accountId';
}

// Filter by Contact ID
if (!String.isBlank(contactId)) {
    query = query + ' AND Contact_Id__c = :contactId';
}

query = query + ' LIMIT 30';

cases = Database.query(query);

Account 记录中已归档的 Cases 相关列表界面:

img img

使用 Big Object 要考虑的一些因素:

  • 每个组织只能创建 100 个 Big Objects. 字段的限制与自定义对象的限制类似,并取决于你的组织的许可类型。
  • Big Objects 只支持对象和字段级别的安全。
  • Big Objects 可以在 Einstein analytics 中使用,但不能用于搜索和 report builder.
  • 在向一个 Big Objects 写入数据时,最好的做法是有一个重试机制,直到你从 API 或 Apex 方法中得到一个成功的结果。
  • 你可以使用 deleteImmediate() 方法删除大对象中的数据。
  • Big Objects 不支持事务。如果试图使用 sObject 上的 trigger,flow 等来读取或写入 Big Objects 数据,请使用 Asynchronous Apex (Future Methods, Batch Apex, Queueable Apex, Scheduled Apex), 通过异步写入,你可以更好地处理数据库生命周期事件。

Buy Me a Coffee