小紅書MySQL數(shù)據(jù)一致性校驗(yàn)?zāi)芰μ剿髋c實(shí)踐
01 背景
1.1 什么是數(shù)據(jù)一致性校驗(yàn)
在數(shù)據(jù)遷移、數(shù)據(jù)同步以及多數(shù)據(jù)中心部署等場(chǎng)景中,數(shù)據(jù)的一致性要求極為嚴(yán)格。然而冗長(zhǎng)的同步計(jì)算鏈路產(chǎn)生的誤寫或丟失、主從復(fù)制延遲產(chǎn)生的臟讀,業(yè)務(wù)雙寫、人為誤操作產(chǎn)生的臟數(shù)據(jù)等眾多因素,都可能導(dǎo)致數(shù)據(jù)不一致。
通過(guò)建設(shè)數(shù)據(jù)一致性校驗(yàn)?zāi)芰?,能夠及時(shí)、準(zhǔn)確的發(fā)現(xiàn)并解決數(shù)據(jù)不一致問(wèn)題,有效降低對(duì)業(yè)務(wù)的影響。
1.2 數(shù)據(jù)一致性校驗(yàn)應(yīng)具備的能力
在小紅書內(nèi)部,數(shù)據(jù)傳輸服務(wù)每天服務(wù)著眾多的業(yè)務(wù),保障著眾多的數(shù)據(jù)同步任務(wù),在數(shù)據(jù)同步過(guò)程中,源端和目標(biāo)端的數(shù)據(jù)一致性需要嚴(yán)格保證,否則將會(huì)產(chǎn)生業(yè)務(wù)損傷。同時(shí),在面對(duì)不同的業(yè)務(wù)場(chǎng)景下,數(shù)據(jù)一致性校驗(yàn)工具能夠進(jìn)行無(wú)損且快速的數(shù)據(jù)校驗(yàn)。因此,針對(duì)數(shù)據(jù)一致性校驗(yàn)工具我們需要面對(duì)以下難點(diǎn):
- 數(shù)據(jù)量和內(nèi)容不斷地變化:在數(shù)據(jù)校驗(yàn)過(guò)程中,源端和目標(biāo)端數(shù)據(jù)的總量和內(nèi)容可能都在變化,是一個(gè)動(dòng)態(tài)的過(guò)程,數(shù)據(jù)校驗(yàn)?zāi)芰π枰獞?yīng)對(duì)不斷變化的數(shù)據(jù),防止產(chǎn)生誤報(bào);
- 無(wú)鎖且不停服的數(shù)據(jù)校驗(yàn):數(shù)據(jù)一致性校驗(yàn)不能影響現(xiàn)有的業(yè)務(wù),需要在業(yè)務(wù)不停服的情況下進(jìn)行,且應(yīng)該在無(wú)鎖狀態(tài)進(jìn)行,避免影響業(yè)務(wù)讀寫操作;
- 對(duì)數(shù)據(jù)庫(kù)的性能影響要可控:校驗(yàn)工具一般都會(huì)高并發(fā)讀取數(shù)據(jù)庫(kù)的數(shù)據(jù),然而,數(shù)據(jù)庫(kù)同時(shí)承擔(dān)著線上的業(yè)務(wù)應(yīng)用。如何有效平衡數(shù)據(jù)校驗(yàn)速度以及數(shù)據(jù)庫(kù)穩(wěn)定性是校驗(yàn)工具應(yīng)該解決的重要命題。
- 適配不同數(shù)據(jù)源:由于校驗(yàn)數(shù)據(jù)源具有多樣性,數(shù)據(jù)校驗(yàn)需要考慮到不同數(shù)據(jù)源之間的差異,能夠在不同數(shù)據(jù)源之間實(shí)現(xiàn)數(shù)據(jù)校驗(yàn)工作;
- 數(shù)據(jù)分布不均勻:在源端和目標(biāo)端數(shù)據(jù)分布不均勻的場(chǎng)景下(如源端和目標(biāo)端分表數(shù)量不一致),數(shù)據(jù)校驗(yàn)程序能夠?qū)⒃炊藬?shù)據(jù)正確映射到目標(biāo)端,并進(jìn)行數(shù)據(jù)校驗(yàn)工作。
- 快速定位不一致內(nèi)容:數(shù)據(jù)校驗(yàn)的目的是為了驗(yàn)證數(shù)據(jù)一致性,并針對(duì)不一致數(shù)據(jù)進(jìn)行快速補(bǔ)救。所以,能夠定位并提供具體不一致的內(nèi)容也是數(shù)據(jù)校驗(yàn)工具的重要特質(zhì)。
- 快速訂正數(shù)據(jù)的能力:當(dāng)數(shù)據(jù)校驗(yàn)定位到不一致內(nèi)容后,是否能夠提供數(shù)據(jù)訂正腳本幫助用戶快速修復(fù)不一致數(shù)據(jù)也是校驗(yàn)工具必不可少的基礎(chǔ)能力。
02 現(xiàn)狀分析
隨著小紅書公司業(yè)務(wù)的不斷發(fā)展,原先的MySQL集群容量已經(jīng)無(wú)法滿足業(yè)務(wù)的發(fā)展,因此需要對(duì)原先的MySQL集群進(jìn)行擴(kuò)容等操作,以滿足業(yè)務(wù)不斷增長(zhǎng)的需求。數(shù)據(jù)遷移一般需要將全部數(shù)據(jù)從源端遷移到目標(biāo)數(shù)據(jù)源上,遷移通常是一次性的,遷移之后鏈路即停止。常見的場(chǎng)景有:
- 集群擴(kuò)縮容,當(dāng)預(yù)期流量增長(zhǎng)或者資源達(dá)到瓶頸,對(duì)整個(gè)數(shù)據(jù)庫(kù)集群分片擴(kuò)容,以減輕各個(gè)分片的壓力,同理預(yù)期流量下降或者資源利用率偏低時(shí),要對(duì)集群縮容,避免資源浪費(fèi)。
- 庫(kù)表遷移,由于容量或者業(yè)務(wù)邏輯變動(dòng),將數(shù)據(jù)庫(kù)集群上的部分庫(kù)或表遷出,到新的集群中,或者分表規(guī)則變更(分表鍵,分表數(shù),分表規(guī)則變更)都需要將原有數(shù)據(jù)同步一份至新表中。
- 異構(gòu)數(shù)據(jù)源遷移,比如技術(shù)升級(jí)或者存儲(chǔ)介質(zhì)變更,需要將歷史數(shù)據(jù)同步至新的數(shù)據(jù)源。
在數(shù)據(jù)遷移過(guò)程中,我們利用自研的數(shù)據(jù)傳輸服務(wù)將數(shù)據(jù)從源集群同步到新集群。然而,網(wǎng)絡(luò)抖動(dòng)、臟寫污染等問(wèn)題可能導(dǎo)致源端與目標(biāo)端數(shù)據(jù)出現(xiàn)不一致。然而,現(xiàn)在的一些業(yè)界解決方案可能無(wú)法滿足現(xiàn)在小紅書內(nèi)部的復(fù)雜業(yè)務(wù)場(chǎng)景,我們需要一款適配上述各種場(chǎng)景的數(shù)據(jù)一致性校驗(yàn)工具,來(lái)確保在數(shù)據(jù)傳輸過(guò)程中的數(shù)據(jù)質(zhì)量,保障數(shù)據(jù)的一致性。因此,我們決定為小紅書打造一套全新的數(shù)據(jù)校驗(yàn)系統(tǒng),以應(yīng)對(duì)內(nèi)部業(yè)務(wù)的多樣化和現(xiàn)有基礎(chǔ)架構(gòu)的挑戰(zhàn),它融合了業(yè)界成熟的校驗(yàn)技術(shù)。其主要具有以下特性:
- 允許源端和目標(biāo)端數(shù)據(jù)分布不一致:數(shù)據(jù)一致性校驗(yàn)工具能夠適應(yīng)數(shù)據(jù)分布不一致的情況,針對(duì)單表、分庫(kù)分表等場(chǎng)景都能夠很好的進(jìn)行校驗(yàn)工作。
- 自動(dòng)選擇最佳校驗(yàn)方式:根據(jù)源端和目標(biāo)端的表結(jié)構(gòu)以及數(shù)據(jù)分布等信息,數(shù)據(jù)一致性校驗(yàn)工具會(huì)自動(dòng)選擇最合適的校驗(yàn)方法,從而快速進(jìn)行數(shù)據(jù)校驗(yàn)。
- 適應(yīng)動(dòng)態(tài)數(shù)據(jù)變化:數(shù)據(jù)一致性校驗(yàn)工具支持在數(shù)據(jù)量和內(nèi)容不斷變化的情況下進(jìn)行校驗(yàn)。
- 無(wú)中斷&無(wú)鎖校驗(yàn):數(shù)據(jù)一致性校驗(yàn)工具能在不中斷服務(wù)且無(wú)需鎖定的狀態(tài)下進(jìn)行數(shù)據(jù)校驗(yàn),確保業(yè)務(wù)和同步鏈路的順暢運(yùn)行。
- 校驗(yàn)參數(shù)動(dòng)態(tài)可配置:數(shù)據(jù)一致性校驗(yàn)工具提供了一系列可配置參數(shù),允許用戶根據(jù)需要?jiǎng)討B(tài)調(diào)整校驗(yàn)速度和批次大小。
- 快速定位不一致內(nèi)容:數(shù)據(jù)一致性校驗(yàn)工具在發(fā)現(xiàn)數(shù)據(jù)不一致時(shí),能夠快速發(fā)現(xiàn)具體不一致的內(nèi)容。
- 自定義列校驗(yàn)和規(guī)則轉(zhuǎn)化:數(shù)據(jù)一致性校驗(yàn)工具支持自定義列校驗(yàn),即使源端和目標(biāo)端列名不同,也能通過(guò)配置列映射實(shí)現(xiàn)校驗(yàn)。同時(shí)支持用戶自定義列轉(zhuǎn)化規(guī)則。
03 數(shù)據(jù)一致性校驗(yàn)在小紅書內(nèi)部得實(shí)現(xiàn)
3.1 校驗(yàn)類型
按照數(shù)據(jù)校驗(yàn)的方式,可以劃分為全量數(shù)據(jù)校驗(yàn)和增量數(shù)據(jù)校驗(yàn):
- 全量校驗(yàn):這種方法涉及對(duì)數(shù)據(jù)庫(kù)中所有數(shù)據(jù)的一次性檢查,確保高準(zhǔn)確度。然而,它反映的僅是校驗(yàn)時(shí)刻的數(shù)據(jù)狀態(tài)。全量數(shù)據(jù)校驗(yàn)又被分為同構(gòu)全量數(shù)據(jù)校驗(yàn)和異構(gòu)全量數(shù)據(jù)校驗(yàn),通常情況下,全量數(shù)據(jù)校驗(yàn)建議多次周期性運(yùn)行,以確保數(shù)據(jù)一致性。
- 增量校驗(yàn):此方法基于數(shù)據(jù)變更事件,僅校驗(yàn)新變更的數(shù)據(jù),減少了校驗(yàn)量并提高了實(shí)時(shí)性。但它無(wú)法覆蓋歷史數(shù)據(jù)的一致性校驗(yàn)。
在實(shí)際應(yīng)用中,我們會(huì)根據(jù)具體情況靈活結(jié)合這兩種校驗(yàn)方式,以更有效地確保數(shù)據(jù)的準(zhǔn)確性和時(shí)效性。
在實(shí)際實(shí)踐中,我們將全量數(shù)據(jù)校驗(yàn)根據(jù)源端和目標(biāo)端校驗(yàn)表結(jié)構(gòu)的差異又細(xì)分為同構(gòu)校驗(yàn)和異構(gòu)校驗(yàn),它們的主要區(qū)別如下:
在實(shí)際進(jìn)行全量數(shù)據(jù)校驗(yàn)時(shí),數(shù)據(jù)一致性校驗(yàn)工具會(huì)自動(dòng)根據(jù)源端和和目標(biāo)端的數(shù)據(jù)源類型、校驗(yàn)表的數(shù)據(jù)分布以及數(shù)據(jù)校驗(yàn)任務(wù)配置等多種因素為校驗(yàn)任務(wù)選擇合適的校驗(yàn)方式,此過(guò)程中用戶不感知。
3.2 方案實(shí)現(xiàn)
基于實(shí)時(shí)數(shù)據(jù)流傳輸服務(wù)的特點(diǎn),我們抽象了讀取端(Reader)、寫入端(Writer)和處理端(Processor)組件,實(shí)現(xiàn)了業(yè)務(wù)邏輯解耦。
- 讀取端(Reader):主要負(fù)責(zé)從源端獲取數(shù)據(jù),分為Selector和Replicator。Selector主要負(fù)責(zé)在全量數(shù)據(jù)校驗(yàn)時(shí)對(duì)全量數(shù)據(jù)的抽取,而Replicator主要負(fù)責(zé)在增量數(shù)據(jù)校驗(yàn)時(shí)對(duì)Binlog的解析。
- 處理端(Processor):主要負(fù)責(zé)數(shù)據(jù)的處理、過(guò)程和加工。在數(shù)據(jù)校驗(yàn)時(shí),如果用戶自定義了列映射或者數(shù)據(jù)轉(zhuǎn)化規(guī)則,此時(shí)通過(guò)Processror可以對(duì)數(shù)據(jù)進(jìn)行一次二次轉(zhuǎn)化,然后和目標(biāo)端數(shù)據(jù)進(jìn)行對(duì)比。
- 寫入端(Writer):主要負(fù)責(zé)將數(shù)據(jù)寫入下游,主要包括數(shù)據(jù)校驗(yàn)實(shí)時(shí)位點(diǎn)更新、校驗(yàn)摘要更新以及校驗(yàn)不一致數(shù)據(jù)的持久化等。
3.3 全量數(shù)據(jù)校驗(yàn)
全量數(shù)據(jù)校驗(yàn)是指對(duì)數(shù)據(jù)庫(kù)中的全量數(shù)據(jù)進(jìn)行一次對(duì)比,它是一次性任務(wù)。在實(shí)際實(shí)踐中,全量數(shù)據(jù)校驗(yàn)一般會(huì)被多次運(yùn)行,從而確保數(shù)據(jù)的完整性和準(zhǔn)確性。全量數(shù)據(jù)校驗(yàn)需要在數(shù)據(jù)傳輸服務(wù)全量同步任務(wù)運(yùn)行完成后,且增量同步任務(wù)追平延遲后開啟,否則會(huì)因?yàn)閿?shù)據(jù)同步延遲導(dǎo)致大量數(shù)據(jù)不一致誤判。
全量數(shù)據(jù)校驗(yàn)分為同構(gòu)校驗(yàn)和異構(gòu)校驗(yàn),兩者均采用分塊抽樣校驗(yàn)法。數(shù)據(jù)一致性校驗(yàn)工具在執(zhí)行全量校驗(yàn)時(shí),會(huì)分批次從源端和目標(biāo)端提取數(shù)據(jù)塊,然后對(duì)比這些數(shù)據(jù)塊以驗(yàn)證一致性。分塊策略允許用戶配置數(shù)據(jù)塊的大小,每次從數(shù)據(jù)庫(kù)中提取固定數(shù)量的數(shù)據(jù)進(jìn)行校驗(yàn)。一旦完成一個(gè)數(shù)據(jù)塊的校驗(yàn),程序就會(huì)繼續(xù)下一個(gè)數(shù)據(jù)塊。當(dāng)提取的數(shù)據(jù)量小于設(shè)定的數(shù)據(jù)塊大小時(shí),校驗(yàn)結(jié)束。若在某個(gè)數(shù)據(jù)塊中發(fā)現(xiàn)不一致,將進(jìn)行復(fù)檢,僅當(dāng)多次校驗(yàn)均不一致時(shí),才會(huì)記錄為數(shù)據(jù)不一致。采用分塊校驗(yàn)的原因包括:
- 提高校驗(yàn)效率:避免一次性處理大量數(shù)據(jù),減少對(duì)比時(shí)間,加快整體校驗(yàn)速度。
- 減少業(yè)務(wù)影響:分塊校驗(yàn)減輕了對(duì)業(yè)務(wù)數(shù)據(jù)庫(kù)的讀取壓力,避免對(duì)業(yè)務(wù)操作造成顯著影響。
同構(gòu)全量數(shù)據(jù)校驗(yàn)通過(guò)校驗(yàn)和(checksum)實(shí)現(xiàn),因?yàn)樯舷掠螖?shù)據(jù)分布是均勻的,我們可以通過(guò)主鍵對(duì)數(shù)據(jù)進(jìn)行分塊的checksum校驗(yàn)。在此過(guò)程中,待校驗(yàn)表的數(shù)據(jù)被劃分為多個(gè)固定大小的校驗(yàn)塊(chunk)。在特定時(shí)刻,源端和目標(biāo)端的對(duì)應(yīng)數(shù)據(jù)塊將進(jìn)行整體Hash校驗(yàn)和(如CRC32),以判斷數(shù)據(jù)是否一致,同構(gòu)校驗(yàn)過(guò)程中,校驗(yàn)塊的大小可以動(dòng)態(tài)調(diào)整,設(shè)置過(guò)大可能會(huì)增大數(shù)據(jù)庫(kù)壓力,同時(shí)可能會(huì)因?yàn)閰^(qū)間過(guò)大導(dǎo)致區(qū)間數(shù)據(jù)不一致概率大;設(shè)置過(guò)小,可能會(huì)降低校驗(yàn)速度。在實(shí)際應(yīng)用中,業(yè)務(wù)可以根據(jù)上下游數(shù)據(jù)庫(kù)的指標(biāo)以及數(shù)據(jù)變化情況進(jìn)行動(dòng)態(tài)調(diào)整數(shù)據(jù)塊的大小。
當(dāng)校驗(yàn)過(guò)程中,發(fā)現(xiàn)某個(gè) chunk 的上下游的 checksum 不一致,通過(guò)二分法將原來(lái)的 chunk 劃分成大小接近的兩個(gè)子 chunk,對(duì)子 chunk 進(jìn)行 checksum 對(duì)比,進(jìn)一步縮小不一致行的可能范圍。通過(guò) checksum 對(duì)比不斷的縮小不一致行的可能范圍,可以減少需要進(jìn)行逐行對(duì)比的數(shù)據(jù)行,加快對(duì)比速度,減少內(nèi)存損耗,并且由于每次計(jì)算 checksum 都相當(dāng)于遍歷一次二分后的子 chunk,理論上不考慮多次額外消耗,二分檢驗(yàn)的開銷相當(dāng)于只對(duì)原 chunk 多做兩次 checksum,當(dāng)chunk大小變成1時(shí),即可找到對(duì)應(yīng)的不一致記錄。但是,在一個(gè)校驗(yàn)塊內(nèi),如果我們發(fā)現(xiàn)源端的數(shù)據(jù)比目標(biāo)端數(shù)據(jù)要少,我們會(huì)通過(guò)逐行對(duì)比的方式去尋找出不一致的數(shù)據(jù)。
異構(gòu)全量數(shù)據(jù)校驗(yàn)采用逐行對(duì)比的方法。在執(zhí)行校驗(yàn)時(shí),系統(tǒng)會(huì)分批次從源端提取數(shù)據(jù),并通過(guò)一系列預(yù)設(shè)規(guī)則進(jìn)行處理,以便與目標(biāo)端的數(shù)據(jù)進(jìn)行比較。一旦發(fā)現(xiàn)數(shù)據(jù)不一致,系統(tǒng)將自動(dòng)進(jìn)行復(fù)檢,且復(fù)檢的間隔時(shí)間會(huì)逐漸延長(zhǎng),呈指數(shù)級(jí)增長(zhǎng)。這種機(jī)制旨在減少對(duì)系統(tǒng)的不必要負(fù)擔(dān),同時(shí)確保數(shù)據(jù)的準(zhǔn)確性。
3.4 增量數(shù)據(jù)校驗(yàn)
增量數(shù)據(jù)校驗(yàn)專注于驗(yàn)證數(shù)據(jù)遷移、同步或更新過(guò)程中新增或修改的數(shù)據(jù),確保數(shù)據(jù)的完整性和準(zhǔn)確性,對(duì)保持?jǐn)?shù)據(jù)一致性和可靠性至關(guān)重要。數(shù)據(jù)一致性校驗(yàn)工具的增量校驗(yàn)功能實(shí)時(shí)監(jiān)控源端數(shù)據(jù)變更,并與目標(biāo)端數(shù)據(jù)進(jìn)行比對(duì)。它與全量數(shù)據(jù)校驗(yàn)獨(dú)立運(yùn)行,用戶可按需選擇是否啟用增量校驗(yàn)。
增量校驗(yàn)以源端數(shù)據(jù)庫(kù)為基準(zhǔn),利用其binlog來(lái)驅(qū)動(dòng)校驗(yàn)過(guò)程。通過(guò)主鍵或唯一鍵,系統(tǒng)會(huì)檢索目標(biāo)數(shù)據(jù)庫(kù)中相應(yīng)的行數(shù)據(jù),進(jìn)行一致性對(duì)比。考慮到數(shù)據(jù)同步或校驗(yàn)任務(wù)可能存在延遲以及數(shù)據(jù)頻繁變更等問(wèn)題,實(shí)際的源端與目標(biāo)端數(shù)據(jù)庫(kù)數(shù)據(jù)可能比已消費(fèi)的binlog位點(diǎn)更新。為應(yīng)對(duì)這種情況,我們?cè)O(shè)計(jì)了延遲點(diǎn)查以及復(fù)查機(jī)制來(lái)對(duì)兩邊數(shù)據(jù)庫(kù)的當(dāng)前數(shù)據(jù)執(zhí)行一致性比對(duì),確保數(shù)據(jù)的準(zhǔn)確性。
04 總結(jié)與展望
4.1 階段性總結(jié)
小紅書MySQL數(shù)據(jù)一致性校驗(yàn)工具自上線以來(lái),已經(jīng)在數(shù)據(jù)庫(kù)遷移、單元化等重要場(chǎng)景得到了很好的應(yīng)用,為小紅書內(nèi)部業(yè)務(wù)提供了數(shù)據(jù)一致性保障。
4.2 展望
后續(xù)關(guān)于數(shù)據(jù)一致性校驗(yàn)將在以下方面繼續(xù)深入建設(shè)。
- 增強(qiáng)產(chǎn)品成熟度:持續(xù)深化現(xiàn)有功能,簡(jiǎn)化操作流程,提升用戶體驗(yàn)。后續(xù)會(huì)豐富數(shù)據(jù)源,支持更多的端到端異構(gòu)數(shù)據(jù)校驗(yàn)。
- 擴(kuò)展產(chǎn)品應(yīng)用范圍:進(jìn)一步擴(kuò)展至數(shù)據(jù)入湖入倉(cāng)、數(shù)據(jù)變更訂閱、緩存更新等場(chǎng)景,以全面提升數(shù)據(jù)傳輸?shù)姆?wù)質(zhì)量,同時(shí)支持用戶定制業(yè)務(wù)邏輯,支持對(duì)賬等復(fù)雜場(chǎng)景。
- 提升數(shù)據(jù)修復(fù)效率:完善數(shù)據(jù)修復(fù)功能,減少手動(dòng)操作,降低成本,提供一鍵生成修復(fù)SQL能力,快速進(jìn)行回歸驗(yàn)證。
- 完善數(shù)據(jù)質(zhì)量大盤:提供歸因分析能力,對(duì)產(chǎn)生的不一致數(shù)據(jù)進(jìn)行原因推測(cè),建立質(zhì)量大盤。
05作者簡(jiǎn)介
初原(張勇)
小紅書關(guān)系型數(shù)據(jù)庫(kù)部研發(fā)工程師,數(shù)據(jù)庫(kù)中間件小組成員,畢業(yè)于西北工業(yè)大學(xué),現(xiàn)主要負(fù)責(zé)小紅書數(shù)據(jù)傳輸服務(wù)的日常維護(hù)與迭代。
元甲(鄭云龍)
小紅書關(guān)系型數(shù)據(jù)庫(kù)部研發(fā)工程師,數(shù)據(jù)庫(kù)中間件小組成員,畢業(yè)于浙江大學(xué),曾就職于美團(tuán)基礎(chǔ)架構(gòu)中間件中心,目前是小紅書數(shù)據(jù)傳輸服務(wù)負(fù)責(zé)人。
克邪(沈力鍇)
小紅書關(guān)系型數(shù)據(jù)庫(kù)部研發(fā)工程師,數(shù)據(jù)庫(kù)中間件小組負(fù)責(zé)人,畢業(yè)于中國(guó)科學(xué)技術(shù)大學(xué),現(xiàn)主要負(fù)責(zé)小紅書數(shù)據(jù)傳輸服務(wù)、數(shù)據(jù)庫(kù)代理和數(shù)據(jù)庫(kù)SDK等數(shù)據(jù)庫(kù)中間件產(chǎn)品的整體架構(gòu)和技術(shù)演進(jìn)。