Ceph蹚坑筆記 - (3)
- Ceph蹚坑筆記 - 3
- 分片上傳中的資料丢失問題
- 分片上傳中的存儲空間洩漏問題
- Bucket删除操作的存儲空間洩漏問題
分片上傳中的資料丢失問題
該問題的發生過程如下
1. InitMultipart發起一次分片上傳,獲得相應的Upload ID
2. 逐片上傳資料
3. 發出CompleteMultipart請求,但請求逾時(用戶端設定30秒逾時)
4. 再次用相同Upload ID重試CompleteMultipart請求,傳回成功
5. 下載下傳對象以确認上傳成功
6. 兩小時後再次下載下傳該對象,收到HTTP 404錯誤
該問題的原因是:步驟3和步驟4的操作由于線程排程的原因幾乎同時被RGW執行,結果步驟4把步驟2上傳的分片進行邏輯删除,即放入GC清單(發生在
RGWRados::Object::complete_atomic_modification()
),兩個小時後GC線程對這些分片進行實體删除。
一個RGW對象所對應的分片存放在其head對象的manifest的結構中。為了處理資料覆寫的情況,
RGWRados::Object::complete_atomic_modification()
會把舊的manifest取出來,把裡面列出的分片全部邏輯删掉,然後在把manifest指向新的分片。當步驟3調用完
RGWRados::Object::complete_atomic_modification()
之後,步驟4立即調用
RGWRados::Object::complete_atomic_modification()
。是以,在步驟4看來老的manifest和新的manifest都指向相同的分片。但按照固有的邏輯,步驟4的
RGWRados::Object::complete_atomic_modification()
卻把老的manifest中所記錄的分片邏輯删除了,實際上就是把自己的分片邏輯删除了。
當然步驟4不是每次都能重制的,因為如果步驟4執行得太晚的話,Upload ID已經被步驟3标記為失效,那麼RGW會直接拒絕掉步驟4的請求,進而不會觸發競争條件。
從用戶端角度,最好能為CompleteMultipart設定較長的逾時時間,以免短促的重試會破壞資料。
從RGW這一側,可做的改進是在
RGWRados::Object::complete_atomic_modification()
檢查新舊manifest是否指向相同的分片。如果發現這種情況,就應當跳過删除分片的步驟。
分片上傳中的存儲空間洩漏問題
在使用某Upload ID上傳了某個對象的若幹分片之後,但在執行CompleteMultipart之前,使用者可以使用同一Upload ID再次上傳已經上傳過的的任何一個分片。這樣的操作會有可能導緻存儲空間的洩漏。
這裡,以8MB為一個分片大小,上傳10MB檔案的第一個分片。RGW将其切成兩個4MB的對象,存入RGW所配置的Pool裡。2~4KTKIzu7uYPV2xapDruHK8t_mXf0Kh0是Upload ID,緊跟其後的.1表示第一個分片
[[email protected] ~]# rados -p .za.rgw.buckets ls 2>/dev/null
za.196717.1__shadow_10MB.bin.a.2~4KTKIzu7uYPV2xapDruHK8t_mXf0Kh0.1_1
za.196717.1__multipart_10MB.bin.a.2~4KTKIzu7uYPV2xapDruHK8t_mXf0Kh0.1
再次上傳第一個分片。此時,可以看到
za.196717.1__multipart_10MB.bin.a.2~4KTKIzu7uYPV2xapDruHK8t_mXf0Kh0.1
消失,出現了包含
jcnZC7Qg-Bb1j-xQpB_gviDe63x7uay
的對象名。這是因為
RGWPutObj::execute()
會判斷
put_data_and_throttle()
的傳回值,如果是
-EEXIST
,就會做出上述動作。就是說,如果一個分片被第二次上傳時,
put_data_and_throttle()
會發現第一次上傳的
za.196717.1__multipart_10MB.bin.a.2~4KTKIzu7uYPV2xapDruHK8t_mXf0Kh0.1
已經存在,傳回
-EEXIST
錯誤。這時,
RGWPutObj::execute()
會把
za.196717.1__multipart_10MB.bin.a.2~4KTKIzu7uYPV2xapDruHK8t_mXf0Kh0.1
删掉,再生成一個随機字元串
jcnZC7Qg-Bb1j-xQpB_gviDe63x7uay
,然後用這個字元串代替Upload ID,以
za.196717.1__multipart_10MB.bin.a.jcnZC7Qg-Bb1j-xQpB_gviDe63x7uay.1
和
za.196717.1__shadow_10MB.bin.a.jcnZC7Qg-Bb1j-xQpB_gviDe63x7uay.1_1
為對象名向Pool裡寫入資料。但顯然,這裡忘記删除
za.196717.1__shadow_10MB.bin.a.2~4KTKIzu7uYPV2xapDruHK8t_mXf0Kh0.1_1
了。
[[email protected] ~]# rados -p .za.rgw.buckets ls 2>/dev/null
za.196717.1__shadow_10MB.bin.a.2~4KTKIzu7uYPV2xapDruHK8t_mXf0Kh0.1_1
za.196717.1__shadow_10MB.bin.a.jcnZC7Qg-Bb1j-xQpB_gviDe63x7uay.1_1
za.196717.1__multipart_10MB.bin.a.jcnZC7Qg-Bb1j-xQpB_gviDe63x7uay.1
第3次上傳第一個分片。此時,由于
za.196717.1__multipart_10MB.bin.a.2~4KTKIzu7uYPV2xapDruHK8t_mXf0Kh0.1
不存在了,是以
put_data_and_throttle()
成功寫入,那麼它的行為和第1次上傳時是一樣的。但是,第二次上傳的
za.196717.1__shadow_10MB.bin.a.jcnZC7Qg-Bb1j-xQpB_gviDe63x7uay.1_1
和
za.196717.1__multipart_10MB.bin.a.jcnZC7Qg-Bb1j-xQpB_gviDe63x7uay.1
殘留下來了。
[[email protected] ~]# rados -p .za.rgw.buckets ls 2>/dev/null
za.196717.1__shadow_10MB.bin.a.2~4KTKIzu7uYPV2xapDruHK8t_mXf0Kh0.1_1
za.196717.1__shadow_10MB.bin.a.jcnZC7Qg-Bb1j-xQpB_gviDe63x7uay.1_1
za.196717.1__multipart_10MB.bin.a.2~4KTKIzu7uYPV2xapDruHK8t_mXf0Kh0.1
za.196717.1__multipart_10MB.bin.a.jcnZC7Qg-Bb1j-xQpB_gviDe63x7uay.1
第4次上傳第一個分片。這時,行為與第二次上傳一樣,
za.196717.1__multipart_10MB.bin.a.2~4KTKIzu7uYPV2xapDruHK8t_mXf0Kh0.1
被清理掉,但
za.196717.1__shadow_10MB.bin.a.2~4KTKIzu7uYPV2xapDruHK8t_mXf0Kh0.1_1
殘留下來。
[[email protected] ~]# rados -p .za.rgw.buckets ls 2>/dev/null
za.196717.1__multipart_10MB.bin.a.LMfvR4p8TF1JNdiNOkvUwFprauLadFG.1
za.196717.1__shadow_10MB.bin.a.2~4KTKIzu7uYPV2xapDruHK8t_mXf0Kh0.1_1
za.196717.1__shadow_10MB.bin.a.LMfvR4p8TF1JNdiNOkvUwFprauLadFG.1_1
za.196717.1__shadow_10MB.bin.a.jcnZC7Qg-Bb1j-xQpB_gviDe63x7uay.1_1
za.196717.1__multipart_10MB.bin.a.jcnZC7Qg-Bb1j-xQpB_gviDe63x7uay.1
最後,把
2~4KTKIzu7uYPV2xapDruHK8t_mXf0Kh0
這個Upload ID對應的分片上傳Abort掉,但以下檔案并沒有被清理掉,因而造成了存儲洩漏。
[[email protected] ~]# rados -p .za.rgw.buckets ls 2>/dev/null
za.196717.1__shadow_10MB.bin.a.2~4KTKIzu7uYPV2xapDruHK8t_mXf0Kh0.1_1
za.196717.1__shadow_10MB.bin.a.jcnZC7Qg-Bb1j-xQpB_gviDe63x7uay.1_1
za.196717.1__multipart_10MB.bin.a.jcnZC7Qg-Bb1j-xQpB_gviDe63x7uay.1
在新的radosgw-admin中提供了新指令
orphans find
可以找到這些洩漏的對象。但
orphans find
的必須掃描所有的Bucket Index對象裡的記錄,并列出
.za.rgw.buckets
中的所有對象,通過兩個清單的比較找到沒有被Bucket Index中記錄的對象,這些對象就是
orphans
。當存量資料很大時,其時間複雜度和空間複雜度可想而知。
RGW
-EEXIST
這種情況做這樣的特殊處理,可能是為了確定多用戶端使用同一Upload ID上傳同一分片時,不至于在分片的内部出現資料交叉(比如同一分片的前100位元組來自用戶端1,接下來的100位元組來自用戶端2)。但感覺這種功能的用途不太大,有點多餘。
Bucket删除操作的存儲空間洩漏問題
RGW會拒絕對非空Bucket的删除。但這裡的“空”僅是說,對Bucket做List操作時,裡面沒有對象,并不考慮還未完成的分片上傳(還沒執行CompleteMultipart)。
建立一個Bucket時,會有一個RGWBucketInfo(對象名是.bucket.meta.字首拼上Bucket名和ID)和一個RGWBucketEntryPoint(對象名就是Bucket名)建立在
domain_root
Pool中,一個(不分shard)或多個(分shard)Bucket Index對象建立在
index_pool
Pool中(對象名是字首是dir拼上Bucket ID再拼上shard編号)。當删除一個Bucket時,RGW僅是删除那個Bucket的RGWBucketEntryPoint對象(這是0.94的行為,新的版本中也删除RGWBucketInfo對象),至于Bucket Index以及未完成的分片上傳的分片資料是不做處理的。這些孤兒分片所占用的存儲空間就洩漏掉了。
如上節所述,
orphans find
可以用很高的時間複雜度和空間複雜度吧這些洩漏的孤兒分片清理掉。
鑒于Bucket Index并未被删除,一種更高效的方法就是把已經被删除的Bucket的Bucket Index對象讀取出來(至少目前的RGW實作沒有删除Bucket Index對象),從中擷取未完成的分片上傳的Upload ID清單,然後模拟AbortMultipart功能,将這些孤兒分片清理掉,最後在把這些無用的Bucket Index對象也删除掉。