From b7fc7179fca83f12f2b6ef4d215b02f79f04bcba Mon Sep 17 00:00:00 2001 From: Fabricio Duarte Date: Thu, 29 Jan 2026 03:14:05 -0300 Subject: [PATCH] Add batch deletion support to `removeRawUsageRecords` (#12522) * Add batch deletion support to `removeRawUsageRecords` * Remove ORDER BY from batch expunge --- .../java/com/cloud/usage/dao/UsageDao.java | 2 +- .../com/cloud/usage/dao/UsageDaoImpl.java | 37 ++++++++++--------- .../main/java/com/cloud/utils/db/Filter.java | 3 +- .../com/cloud/utils/db/GenericDaoBase.java | 4 +- .../ConfigurationManagerImpl.java | 2 +- .../com/cloud/usage/UsageServiceImpl.java | 3 +- 6 files changed, 29 insertions(+), 22 deletions(-) diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageDao.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageDao.java index ea490e60f9e..b9d8d9c0536 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageDao.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageDao.java @@ -62,7 +62,7 @@ public interface UsageDao extends GenericDao { void saveUsageRecords(List usageRecords); - void removeOldUsageRecords(int days); + void expungeAllOlderThan(int days, long limitPerQuery); UsageVO persistUsage(final UsageVO usage); diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageDaoImpl.java index 34e7843bfc4..2d99c78fad1 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageDaoImpl.java @@ -26,15 +26,16 @@ import com.cloud.utils.Pair; import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.QueryBuilder; +import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.Transaction; import com.cloud.utils.db.TransactionCallback; -import com.cloud.utils.db.TransactionCallbackNoReturn; import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.db.TransactionStatus; import com.cloud.utils.exception.CloudRuntimeException; import org.apache.cloudstack.acl.RoleType; +import org.apache.commons.lang3.time.DateUtils; import org.springframework.stereotype.Component; import java.sql.PreparedStatement; @@ -51,7 +52,6 @@ import java.util.TimeZone; public class UsageDaoImpl extends GenericDaoBase implements UsageDao { private static final String DELETE_ALL = "DELETE FROM cloud_usage"; private static final String DELETE_ALL_BY_ACCOUNTID = "DELETE FROM cloud_usage WHERE account_id = ?"; - private static final String DELETE_ALL_BY_INTERVAL = "DELETE FROM cloud_usage WHERE end_date < DATE_SUB(CURRENT_DATE(), INTERVAL ? DAY)"; private static final String INSERT_ACCOUNT = "INSERT INTO cloud_usage.account (id, account_name, uuid, type, role_id, domain_id, removed, cleanup_needed) VALUES (?,?,?,?,?,?,?,?)"; private static final String INSERT_USER_STATS = "INSERT INTO cloud_usage.user_statistics (id, data_center_id, account_id, public_ip_address, device_id, device_type, network_id, net_bytes_received," + " net_bytes_sent, current_bytes_received, current_bytes_sent, agg_bytes_received, agg_bytes_sent) VALUES (?,?,?,?,?,?,?,?,?,?, ?, ?, ?)"; @@ -88,8 +88,12 @@ public class UsageDaoImpl extends GenericDaoBase implements Usage private static final String UPDATE_BUCKET_STATS = "UPDATE cloud_usage.bucket_statistics SET size=? WHERE id=?"; + protected SearchBuilder endDateLessThanSearch; public UsageDaoImpl() { + endDateLessThanSearch = createSearchBuilder(); + endDateLessThanSearch.and("endDate", endDateLessThanSearch.entity().getEndDate(), SearchCriteria.Op.LT); + endDateLessThanSearch.done(); } @Override @@ -539,21 +543,20 @@ public class UsageDaoImpl extends GenericDaoBase implements Usage } @Override - public void removeOldUsageRecords(int days) { - Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallbackNoReturn() { - @Override - public void doInTransactionWithoutResult(TransactionStatus status) { - TransactionLegacy txn = TransactionLegacy.currentTxn(); - PreparedStatement pstmt = null; - try { - pstmt = txn.prepareAutoCloseStatement(DELETE_ALL_BY_INTERVAL); - pstmt.setLong(1, days); - pstmt.executeUpdate(); - } catch (Exception ex) { - logger.error("error removing old cloud_usage records for interval: " + days); - } - } - }); + public void expungeAllOlderThan(int days, long limitPerQuery) { + SearchCriteria sc = endDateLessThanSearch.create(); + + Date limit = DateUtils.addDays(new Date(), -days); + sc.setParameters("endDate", limit); + + TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.USAGE_DB); + try { + logger.debug("Removing all cloud_usage records older than [{}].", limit); + int totalExpunged = batchExpunge(sc, limitPerQuery); + logger.info("Removed a total of [{}] cloud_usage records older than [{}].", totalExpunged, limit); + } finally { + txn.close(); + } } public UsageVO persistUsage(final UsageVO usage) { diff --git a/framework/db/src/main/java/com/cloud/utils/db/Filter.java b/framework/db/src/main/java/com/cloud/utils/db/Filter.java index fb8c9ee37fc..90e42952a99 100644 --- a/framework/db/src/main/java/com/cloud/utils/db/Filter.java +++ b/framework/db/src/main/java/com/cloud/utils/db/Filter.java @@ -57,7 +57,8 @@ public class Filter { } public Filter(long limit) { - _orderBy = " ORDER BY RAND() LIMIT " + limit; + _orderBy = " ORDER BY RAND()"; + _limit = limit; } public Filter(Long offset, Long limit) { diff --git a/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java b/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java index c3a4d2c2487..535fa032d23 100644 --- a/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java +++ b/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java @@ -1161,6 +1161,8 @@ public abstract class GenericDaoBase extends Compone if (filter.getLimit() != null) { sql.append(", ").append(filter.getLimit()); } + } else if (filter.getLimit() != null) { + sql.append(" LIMIT ").append(filter.getLimit()); } } } @@ -1322,7 +1324,7 @@ public abstract class GenericDaoBase extends Compone Filter filter = null; final long batchSizeFinal = ObjectUtils.defaultIfNull(batchSize, 0L); if (batchSizeFinal > 0) { - filter = new Filter(batchSizeFinal); + filter = new Filter(null, batchSizeFinal); } int expunged = 0; int currentExpunged = 0; diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index 62b3c23d27e..3a4bdf8afec 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -527,7 +527,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati public static final ConfigKey DELETE_QUERY_BATCH_SIZE = new ConfigKey<>("Advanced", Long.class, "delete.query.batch.size", "0", "Indicates the limit applied while deleting entries in bulk. With this, the delete query will apply the limit as many times as necessary," + " to delete all the entries. This is advised when retaining several days of records, which can lead to slowness. <= 0 means that no limit will " + - "be applied. Default value is 0. For now, this is used for deletion of vm & volume stats only.", true); + "be applied. Default value is 0. For now, this is used for deletion of VM stats, volume stats, and usage records.", true); private static final String IOPS_READ_RATE = "IOPS Read"; private static final String IOPS_WRITE_RATE = "IOPS Write"; diff --git a/server/src/main/java/com/cloud/usage/UsageServiceImpl.java b/server/src/main/java/com/cloud/usage/UsageServiceImpl.java index c46f1f43fd0..edaa22c3bcf 100644 --- a/server/src/main/java/com/cloud/usage/UsageServiceImpl.java +++ b/server/src/main/java/com/cloud/usage/UsageServiceImpl.java @@ -26,6 +26,7 @@ import java.util.TimeZone; import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.configuration.ConfigurationManagerImpl; import org.apache.cloudstack.api.command.admin.usage.GenerateUsageRecordsCmd; import org.apache.cloudstack.api.command.admin.usage.ListUsageRecordsCmd; import org.apache.cloudstack.api.command.admin.usage.RemoveRawUsageRecordsCmd; @@ -489,7 +490,7 @@ public class UsageServiceImpl extends ManagerBase implements UsageService, Manag } } } - _usageDao.removeOldUsageRecords(interval); + _usageDao.expungeAllOlderThan(interval, ConfigurationManagerImpl.DELETE_QUERY_BATCH_SIZE.value()); } else { throw new InvalidParameterValueException("Invalid interval value. Interval to remove cloud_usage records should be greater than 0"); }