From 9b4007ac42984802bd6eeb1c02e82563ad55f333 Mon Sep 17 00:00:00 2001 From: flywukong <2229306838@qq.com> Date: Wed, 6 Sep 2023 09:05:32 +0800 Subject: [PATCH] fix: refactor quota table (#1110) * fix: fix update quota * fix: fix check quota enough logic * fix: fix and refactor traffic table and update logic * fix: fix comment --- store/sqldb/traffic.go | 89 ++++++++++++++++++++++------------- store/sqldb/traffic_schema.go | 8 ++-- store/sqldb/traffic_test.go | 46 +++++++++--------- 3 files changed, 85 insertions(+), 58 deletions(-) diff --git a/store/sqldb/traffic.go b/store/sqldb/traffic.go index 0adc08273..cdb4dfae0 100644 --- a/store/sqldb/traffic.go +++ b/store/sqldb/traffic.go @@ -79,30 +79,39 @@ func (s *SpDBImpl) CheckQuotaAndAddReadRecord(record *corespdb.ReadRecord, quota return nil } -func getUpdatedConsumedQuota(record *corespdb.ReadRecord, freeQuota, freeConsumedQuota, totalConsumeQuota, chargedQuota uint64) (uint64, uint64, error) { - recordQuotaCost := record.ReadSize - needCheckChainQuota := true - freeQuotaRemain := freeQuota - freeConsumedQuota - // if remain free quota more than 0, consume free quota first - if freeQuotaRemain > 0 && recordQuotaCost < freeQuotaRemain { - // if free quota is enough, no need to check charged quota - totalConsumeQuota += recordQuotaCost - freeConsumedQuota += recordQuotaCost - needCheckChainQuota = false - } - // if free quota is not enough, check the charged quota - if needCheckChainQuota { - // the quota size of this month should be (chargedQuota + freeQuotaRemain) - if totalConsumeQuota+recordQuotaCost > chargedQuota+freeQuotaRemain { - return 0, 0, ErrCheckQuotaEnough - } - totalConsumeQuota += recordQuotaCost +// getUpdatedConsumedQuota compute the updated quota of traffic table by the incoming read cost and the newest record. +// it returns the updated consumed free quota,consumed charged quota and remained free quota +func getUpdatedConsumedQuota(recordQuotaCost, freeQuotaRemain, consumeFreeQuota, consumeChargedQuota, chargedQuota uint64) (uint64, uint64, uint64, bool, error) { + // if remain free quota enough, just consume free quota + needUpdateFreeQuota := false + if recordQuotaCost < freeQuotaRemain { + needUpdateFreeQuota = true + consumeFreeQuota += recordQuotaCost + freeQuotaRemain -= recordQuotaCost + } else { + // if free remain quota exist, consume all the free remain quota first if freeQuotaRemain > 0 { - freeConsumedQuota += freeQuotaRemain + if freeQuotaRemain+chargedQuota < recordQuotaCost { + return 0, 0, 0, false, ErrCheckQuotaEnough + } + needUpdateFreeQuota = true + consumeFreeQuota += freeQuotaRemain + // update the consumed charge quota by remained free quota + // if read cost 5G, and the remained free quota is 2G, consumed charge quota should be 3G and remained free quota should be 0 + consumeQuota := recordQuotaCost - freeQuotaRemain + consumeChargedQuota += consumeQuota + freeQuotaRemain = uint64(0) + log.CtxDebugw(context.Background(), "free quota has been exhausted", "consumed", consumeFreeQuota, "remained", freeQuotaRemain) + } else { + // free remain quota is zero, no need to consider the free quota + // the consumeChargedQuota plus record cost need to be more than total charged quota + if chargedQuota < consumeChargedQuota+recordQuotaCost { + return 0, 0, 0, false, ErrCheckQuotaEnough + } + consumeChargedQuota += recordQuotaCost } } - - return freeConsumedQuota, totalConsumeQuota, nil + return consumeFreeQuota, consumeChargedQuota, freeQuotaRemain, needUpdateFreeQuota, nil } // updateConsumedQuota update the consumed quota of BucketTraffic table in the transaction way @@ -111,7 +120,7 @@ func (s *SpDBImpl) updateConsumedQuota(record *corespdb.ReadRecord, quota *cores err := s.db.Transaction(func(tx *gorm.DB) error { var bucketTraffic BucketTrafficTable var err error - if err = tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("bucket_id = ? and month = ?", record.BucketID, yearMonth).Find(&bucketTraffic).Error; err != nil { + if err = tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("bucket_id = ? and month = ?", record.BucketID, yearMonth).First(&bucketTraffic).Error; err != nil { return fmt.Errorf("failed to query bucket traffic table: %v", err) } @@ -122,36 +131,50 @@ func (s *SpDBImpl) updateConsumedQuota(record *corespdb.ReadRecord, quota *cores ChargedQuotaSize: quota.ChargedQuotaSize, ModifiedTime: time.Now(), }) + if result.Error != nil { return fmt.Errorf("failed to update bucket traffic table: %s", result.Error) } - if result.RowsAffected != 1 { return fmt.Errorf("update traffic of %s has affected more than one rows %d, "+ "update charged quota %d", bucketTraffic.BucketName, result.RowsAffected, quota.ChargedQuotaSize) } + log.CtxDebugw(context.Background(), "updated quota", "charged quota", quota.ChargedQuotaSize) } - // compute the new consumed quota size to be updated - updatedReadConsumedSize, updatedFreeConsumedSize, err := getUpdatedConsumedQuota(record, + // compute the new consumed quota size to be updated by the newest record and the read cost size + updatedConsumedFreeQuota, updatedConsumedChargedQuota, updatedRemainedFreeQuota, needUpdateFreeQuota, err := getUpdatedConsumedQuota(record.ReadSize, bucketTraffic.FreeQuotaSize, bucketTraffic.FreeQuotaConsumedSize, - bucketTraffic.ReadConsumedSize, bucketTraffic.ChargedQuotaSize) + bucketTraffic.ReadConsumedSize, quota.ChargedQuotaSize) if err != nil { return err } - if err = tx.Model(&bucketTraffic). - Updates(BucketTrafficTable{ - ReadConsumedSize: updatedReadConsumedSize, - FreeQuotaConsumedSize: updatedFreeConsumedSize, + if needUpdateFreeQuota { + // it is needed to add select items if you need to update a value to zero in gorm db + err = tx.Model(&bucketTraffic). + Select("read_consumed_size", "free_quota_consumed_size", "free_quota_size", "modified_time").Updates(BucketTrafficTable{ + ReadConsumedSize: updatedConsumedChargedQuota, + FreeQuotaConsumedSize: updatedConsumedFreeQuota, + FreeQuotaSize: updatedRemainedFreeQuota, ModifiedTime: time.Now(), - }).Error; err != nil { + }).Error + } else { + err = tx.Model(&bucketTraffic).Updates(BucketTrafficTable{ + ReadConsumedSize: updatedConsumedChargedQuota, + ModifiedTime: time.Now(), + }).Error + } + if err != nil { return fmt.Errorf("failed to update bucket traffic table: %v", err) } return nil }) + if err != nil { + log.CtxErrorw(context.Background(), "updated quota transaction fail", "error", err) + } return err } @@ -194,14 +217,13 @@ func (s *SpDBImpl) InitBucketTraffic(record *corespdb.ReadRecord, quota *corespd BucketID: bucketID, Month: yearMonth, FreeQuotaSize: newestTraffic.FreeQuotaSize, - FreeQuotaConsumedSize: newestTraffic.FreeQuotaConsumedSize, + FreeQuotaConsumedSize: 0, BucketName: bucketName, ReadConsumedSize: 0, ChargedQuotaSize: quota.ChargedQuotaSize, ModifiedTime: time.Now(), } } - result = tx.Create(insertBucketTraffic) if result.Error != nil && MysqlErrCode(result.Error) != ErrDuplicateEntryCode { return fmt.Errorf("failed to create bucket traffic table: %s", result.Error) @@ -246,6 +268,7 @@ func (s *SpDBImpl) GetBucketTraffic(bucketID uint64, yearMonth string) (traffic err = fmt.Errorf("failed to query bucket traffic table: %s", result.Error) return nil, err } + return &corespdb.BucketTraffic{ BucketID: queryReturn.BucketID, YearMonth: queryReturn.Month, diff --git a/store/sqldb/traffic_schema.go b/store/sqldb/traffic_schema.go index a37e7cb32..469919b4f 100644 --- a/store/sqldb/traffic_schema.go +++ b/store/sqldb/traffic_schema.go @@ -9,10 +9,10 @@ type BucketTrafficTable struct { BucketID uint64 `gorm:"primary_key"` Month string `gorm:"primary_key"` BucketName string - ReadConsumedSize uint64 - FreeQuotaConsumedSize uint64 // indicates the consumed free quota size - FreeQuotaSize uint64 // the greenfield chain free quota - ChargedQuotaSize uint64 //the greenfield chain bucket charged quota + ReadConsumedSize uint64 // indicates the consumed chargedQuota of this month + FreeQuotaConsumedSize uint64 // indicates the consumed free quota size of this month + FreeQuotaSize uint64 // indicate the remained free quota + ChargedQuotaSize uint64 // indicate the greenfield chain bucket charged quota ModifiedTime time.Time } diff --git a/store/sqldb/traffic_test.go b/store/sqldb/traffic_test.go index e514cc4eb..e877d3b0a 100644 --- a/store/sqldb/traffic_test.go +++ b/store/sqldb/traffic_test.go @@ -43,7 +43,7 @@ func TestSpDBImpl_CheckQuotaAndAddReadRecordSuccess1(t *testing.T) { BucketID: 2, BucketName: "mockBucketName", ReadConsumedSize: 10, - FreeQuotaConsumedSize: 100, + FreeQuotaConsumedSize: 10, FreeQuotaSize: 25, ChargedQuotaSize: 30, ModifiedTime: time.Now(), @@ -52,7 +52,7 @@ func TestSpDBImpl_CheckQuotaAndAddReadRecordSuccess1(t *testing.T) { yearMonth := TimestampYearMonth(b.ModifiedTime.Unix()) s, mock := setupDB(t) mock.ExpectBegin() - mock.ExpectQuery("SELECT * FROM `bucket_traffic` WHERE bucket_id = ? and month = ? FOR UPDATE").WillReturnRows(sqlmock.NewRows([]string{"bucket_id", "year_month", "bucket_name", "read_consumed_size", "free_quota_consumed_size", + mock.ExpectQuery("SELECT * FROM `bucket_traffic` WHERE bucket_id = ? and month = ? ORDER BY `bucket_traffic`.`bucket_id` LIMIT 1 FOR UPDATE").WillReturnRows(sqlmock.NewRows([]string{"bucket_id", "year_month", "bucket_name", "read_consumed_size", "free_quota_consumed_size", "free_quota_size", "charged_quota_size", "modified_time"}).AddRow(b.BucketID, yearMonth, b.BucketName, b.ReadConsumedSize, b.FreeQuotaConsumedSize, b.FreeQuotaSize, b.ChargedQuotaSize, b.ModifiedTime)) @@ -60,8 +60,8 @@ func TestSpDBImpl_CheckQuotaAndAddReadRecordSuccess1(t *testing.T) { WithArgs(20, sqlmock.AnyArg(), record.BucketID). WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec("UPDATE `bucket_traffic` SET `read_consumed_size`=?,`free_quota_consumed_size`=?,`modified_time`=? WHERE `bucket_id` = ? "). - WithArgs(sqlmock.AnyArg(), 12, sqlmock.AnyArg(), record.BucketID). + mock.ExpectExec("UPDATE `bucket_traffic` SET `read_consumed_size`=?,`free_quota_consumed_size`=?,`free_quota_size`=?,`modified_time`=? WHERE `bucket_id` = ? "). + WithArgs(sqlmock.AnyArg(), 12, 23, sqlmock.AnyArg(), record.BucketID). WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit() @@ -72,7 +72,6 @@ func TestSpDBImpl_CheckQuotaAndAddReadRecordSuccess1(t *testing.T) { mock.ExpectCommit() err := s.CheckQuotaAndAddReadRecord(newRecord, quota) - assert.Nil(t, err) } @@ -104,11 +103,12 @@ func TestSpDBImpl_CheckQuotaAndAddReadRecordSuccess2(t *testing.T) { yearMonth := TimestampYearMonth(b.ModifiedTime.Unix()) s, mock := setupDB(t) mock.ExpectBegin() - mock.ExpectQuery("SELECT * FROM `bucket_traffic` WHERE bucket_id = ? and month = ? FOR UPDATE").WillReturnRows(sqlmock.NewRows([]string{"bucket_id", "year_month", "bucket_name", "read_consumed_size", "free_quota_consumed_size", + mock.ExpectQuery("SELECT * FROM `bucket_traffic` WHERE bucket_id = ? and month = ? ORDER BY `bucket_traffic`.`bucket_id` LIMIT 1 FOR UPDATE").WillReturnRows(sqlmock.NewRows([]string{"bucket_id", "year_month", "bucket_name", "read_consumed_size", "free_quota_consumed_size", "free_quota_size", "charged_quota_size", "modified_time"}).AddRow(b.BucketID, yearMonth, b.BucketName, b.ReadConsumedSize, b.FreeQuotaConsumedSize, b.FreeQuotaSize, b.ChargedQuotaSize, b.ModifiedTime)) - mock.ExpectExec("UPDATE `bucket_traffic` SET `read_consumed_size`=?,`free_quota_consumed_size`=?,`modified_time`=? WHERE `bucket_id` = ?"). + mock.ExpectExec("UPDATE `bucket_traffic` SET `read_consumed_size`=?,`free_quota_consumed_size`=?,`free_quota_size`=?,`modified_time`=? WHERE `bucket_id` = ? "). + WithArgs(sqlmock.AnyArg(), 25, 24, sqlmock.AnyArg(), record.BucketID). WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit() @@ -136,7 +136,7 @@ func TestSpDBImpl_CheckQuotaAndAddReadRecordFailure1(t *testing.T) { FreeQuotaSize: 10, } s, mock := setupDB(t) - mock.ExpectQuery("SELECT * FROM `bucket_traffic` WHERE bucket_id = ? and month = ? ORDER BY `bucket_traffic`.`bucket_id` LIMIT 1"). + mock.ExpectQuery("SELECT * FROM `bucket_traffic` WHERE bucket_id = ? and month = ? ORDER BY `bucket_traffic`.`bucket_id` LIMIT 1 FOR UPDATE"). WillReturnError(mockDBInternalError) err := s.CheckQuotaAndAddReadRecord(record, quota) assert.Contains(t, err.Error(), mockDBInternalError.Error()) @@ -159,7 +159,7 @@ func TestSpDBImpl_CheckQuotaAndAddReadRecordFailure2(t *testing.T) { } s, mock := setupDB(t) mock.ExpectBegin() - mock.ExpectQuery("SELECT * FROM `bucket_traffic` WHERE bucket_id = ? and month = ? FOR UPDATE").WillReturnError(gorm.ErrRecordNotFound) + mock.ExpectQuery("SELECT * FROM `bucket_traffic` WHERE bucket_id = ? and month = ? ORDER BY `bucket_traffic`.`bucket_id` LIMIT 1 FOR UPDATE").WillReturnError(gorm.ErrRecordNotFound) err := s.CheckQuotaAndAddReadRecord(record, quota) assert.Contains(t, err.Error(), noRecordInTrafficErr) @@ -193,7 +193,7 @@ func TestSpDBImpl_CheckQuotaAndAddReadRecordFailure3(t *testing.T) { yearMonth := TimestampYearMonth(b.ModifiedTime.Unix()) s, mock := setupDB(t) mock.ExpectBegin() - mock.ExpectQuery("SELECT * FROM `bucket_traffic` WHERE bucket_id = ? and month = ? FOR UPDATE").WillReturnRows(sqlmock.NewRows([]string{"bucket_id", "year_month", "bucket_name", "read_consumed_size", "free_quota_consumed_size", + mock.ExpectQuery("SELECT * FROM `bucket_traffic` WHERE bucket_id = ? and month = ? ORDER BY `bucket_traffic`.`bucket_id` LIMIT 1 FOR UPDATE").WillReturnRows(sqlmock.NewRows([]string{"bucket_id", "year_month", "bucket_name", "read_consumed_size", "free_quota_consumed_size", "free_quota_size", "charged_quota_size", "modified_time"}).AddRow(b.BucketID, yearMonth, b.BucketName, b.ReadConsumedSize, b.FreeQuotaConsumedSize, b.FreeQuotaSize, b.ChargedQuotaSize, b.ModifiedTime)) @@ -232,7 +232,7 @@ func TestSpDBImpl_CheckQuotaAndAddReadRecordFailure4(t *testing.T) { yearMonth := TimestampYearMonth(b.ModifiedTime.Unix()) s, mock := setupDB(t) mock.ExpectBegin() - mock.ExpectQuery("SELECT * FROM `bucket_traffic` WHERE bucket_id = ? and month = ? FOR UPDATE").WillReturnRows(sqlmock.NewRows([]string{"bucket_id", "year_month", "bucket_name", "read_consumed_size", "free_quota_consumed_size", + mock.ExpectQuery("SELECT * FROM `bucket_traffic` WHERE bucket_id = ? and month = ? ORDER BY `bucket_traffic`.`bucket_id` LIMIT 1 FOR UPDATE").WillReturnRows(sqlmock.NewRows([]string{"bucket_id", "year_month", "bucket_name", "read_consumed_size", "free_quota_consumed_size", "free_quota_size", "charged_quota_size", "modified_time"}).AddRow(b.BucketID, yearMonth, b.BucketName, b.ReadConsumedSize, b.FreeQuotaConsumedSize, b.FreeQuotaSize, b.ChargedQuotaSize, b.ModifiedTime)) @@ -240,8 +240,8 @@ func TestSpDBImpl_CheckQuotaAndAddReadRecordFailure4(t *testing.T) { WithArgs(20, sqlmock.AnyArg(), record.BucketID). WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec("UPDATE `bucket_traffic` SET `read_consumed_size`=?,`free_quota_consumed_size`=?,`modified_time`=? WHERE `bucket_id` = ?"). - WithArgs(sqlmock.AnyArg(), 11, sqlmock.AnyArg(), record.BucketID). + mock.ExpectExec("UPDATE `bucket_traffic` SET `read_consumed_size`=?,`free_quota_consumed_size`=?,`free_quota_size`=?,`modified_time`=? WHERE `bucket_id` = ? "). + WithArgs(sqlmock.AnyArg(), 101, 24, sqlmock.AnyArg(), record.BucketID). WillReturnError(mockDBInternalError) mock.ExpectRollback() @@ -277,7 +277,7 @@ func TestSpDBImpl_CheckQuotaAndAddReadRecordFailure5(t *testing.T) { yearMonth := TimestampYearMonth(b.ModifiedTime.Unix()) s, mock := setupDB(t) mock.ExpectBegin() - mock.ExpectQuery("SELECT * FROM `bucket_traffic` WHERE bucket_id = ? and month = ? FOR UPDATE").WillReturnRows(sqlmock.NewRows([]string{"bucket_id", "year_month", "bucket_name", "read_consumed_size", "free_quota_consumed_size", + mock.ExpectQuery("SELECT * FROM `bucket_traffic` WHERE bucket_id = ? and month = ? ORDER BY `bucket_traffic`.`bucket_id` LIMIT 1 FOR UPDATE").WillReturnRows(sqlmock.NewRows([]string{"bucket_id", "year_month", "bucket_name", "read_consumed_size", "free_quota_consumed_size", "free_quota_size", "charged_quota_size", "modified_time"}).AddRow(b.BucketID, yearMonth, b.BucketName, b.ReadConsumedSize, b.FreeQuotaConsumedSize, b.FreeQuotaSize, b.ChargedQuotaSize, b.ModifiedTime)) @@ -285,9 +285,9 @@ func TestSpDBImpl_CheckQuotaAndAddReadRecordFailure5(t *testing.T) { WithArgs(20, sqlmock.AnyArg(), record.BucketID). WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec("UPDATE `bucket_traffic` SET `read_consumed_size`=?,`free_quota_consumed_size`=?,`modified_time`=? WHERE `bucket_id` = ?"). - WithArgs(sqlmock.AnyArg(), 11, sqlmock.AnyArg(), record.BucketID). - WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectExec("UPDATE `bucket_traffic` SET `read_consumed_size`=?,`free_quota_consumed_size`=?,`free_quota_size`=?,`modified_time`=? WHERE `bucket_id` = ? "). + WithArgs(sqlmock.AnyArg(), 101, 24, sqlmock.AnyArg(), record.BucketID). + WillReturnError(mockDBInternalError) mock.ExpectCommit() mock.ExpectBegin() @@ -307,7 +307,7 @@ func TestSpDBImpl_CheckQuotaAndAddReadRecordFailure6(t *testing.T) { UserAddress: "mockUserAddress", BucketName: "mockBucketName", ObjectName: "mockObjectName", - ReadSize: 1, + ReadSize: 25, ReadTimestampUs: 1, } quota := &corespdb.BucketQuota{ @@ -319,14 +319,14 @@ func TestSpDBImpl_CheckQuotaAndAddReadRecordFailure6(t *testing.T) { BucketName: "mockBucketName", ReadConsumedSize: 60, FreeQuotaConsumedSize: 25, - FreeQuotaSize: 25, + FreeQuotaSize: 3, ChargedQuotaSize: 30, ModifiedTime: time.Now(), } yearMonth := TimestampYearMonth(b.ModifiedTime.Unix()) s, mock := setupDB(t) mock.ExpectBegin() - mock.ExpectQuery("SELECT * FROM `bucket_traffic` WHERE bucket_id = ? and month = ? FOR UPDATE").WillReturnRows(sqlmock.NewRows([]string{"bucket_id", "year_month", "bucket_name", "read_consumed_size", "free_quota_consumed_size", + mock.ExpectQuery("SELECT * FROM `bucket_traffic` WHERE bucket_id = ? and month = ? ORDER BY `bucket_traffic`.`bucket_id` LIMIT 1 FOR UPDATE").WillReturnRows(sqlmock.NewRows([]string{"bucket_id", "year_month", "bucket_name", "read_consumed_size", "free_quota_consumed_size", "free_quota_size", "charged_quota_size", "modified_time"}).AddRow(b.BucketID, yearMonth, b.BucketName, b.ReadConsumedSize, b.FreeQuotaConsumedSize, b.FreeQuotaSize, b.ChargedQuotaSize, b.ModifiedTime)) @@ -334,6 +334,10 @@ func TestSpDBImpl_CheckQuotaAndAddReadRecordFailure6(t *testing.T) { WithArgs(20, sqlmock.AnyArg(), record.BucketID). WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectExec("UPDATE `bucket_traffic` SET `read_consumed_size`=?,`free_quota_consumed_size`=?,`free_quota_size`=?,`modified_time`=? WHERE `bucket_id` = ? "). + WithArgs(sqlmock.AnyArg(), 101, 24, sqlmock.AnyArg(), record.BucketID). + WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectCommit() err := s.CheckQuotaAndAddReadRecord(record, quota) assert.Equal(t, ErrCheckQuotaEnough, err) @@ -366,7 +370,7 @@ func TestSpDBImpl_CheckQuotaAndAddReadRecordFailure7(t *testing.T) { yearMonth := TimestampYearMonth(b.ModifiedTime.Unix()) s, mock := setupDB(t) mock.ExpectBegin() - mock.ExpectQuery("SELECT * FROM `bucket_traffic` WHERE bucket_id = ? and month = ? FOR UPDATE").WillReturnRows(sqlmock.NewRows([]string{"bucket_id", "year_month", "bucket_name", "read_consumed_size", "free_quota_consumed_size", + mock.ExpectQuery("SELECT * FROM `bucket_traffic` WHERE bucket_id = ? and month = ? ORDER BY `bucket_traffic`.`bucket_id` LIMIT 1 FOR UPDATE").WillReturnRows(sqlmock.NewRows([]string{"bucket_id", "year_month", "bucket_name", "read_consumed_size", "free_quota_consumed_size", "free_quota_size", "charged_quota_size", "modified_time"}).AddRow(b.BucketID, yearMonth, b.BucketName, b.ReadConsumedSize, b.FreeQuotaConsumedSize, b.FreeQuotaSize, b.ChargedQuotaSize, b.ModifiedTime))