-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Events listing, deletion and archival optimizations #11443
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f52a619
f4f2eac
568b329
e803bdb
ecf7fcb
d11afd7
7012147
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -16,15 +16,20 @@ | |||||||||||||||||||||||||||||||||||||||||||
| // under the License. | ||||||||||||||||||||||||||||||||||||||||||||
| package com.cloud.event.dao; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import java.sql.PreparedStatement; | ||||||||||||||||||||||||||||||||||||||||||||
| import java.sql.SQLException; | ||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Date; | ||||||||||||||||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||||||||||||||||
| import java.util.stream.Collectors; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import com.cloud.utils.db.DB; | ||||||||||||||||||||||||||||||||||||||||||||
| import com.cloud.utils.db.Filter; | ||||||||||||||||||||||||||||||||||||||||||||
| import com.cloud.utils.exception.CloudRuntimeException; | ||||||||||||||||||||||||||||||||||||||||||||
| import org.apache.commons.collections4.CollectionUtils; | ||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Component; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import com.cloud.event.Event.State; | ||||||||||||||||||||||||||||||||||||||||||||
| import com.cloud.event.EventVO; | ||||||||||||||||||||||||||||||||||||||||||||
| import com.cloud.utils.db.Filter; | ||||||||||||||||||||||||||||||||||||||||||||
| import com.cloud.utils.db.GenericDaoBase; | ||||||||||||||||||||||||||||||||||||||||||||
| import com.cloud.utils.db.SearchBuilder; | ||||||||||||||||||||||||||||||||||||||||||||
| import com.cloud.utils.db.SearchCriteria; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -33,83 +38,90 @@ | |||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @Component | ||||||||||||||||||||||||||||||||||||||||||||
| public class EventDaoImpl extends GenericDaoBase<EventVO, Long> implements EventDao { | ||||||||||||||||||||||||||||||||||||||||||||
| protected final SearchBuilder<EventVO> CompletedEventSearch; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| protected final SearchBuilder<EventVO> ToArchiveOrDeleteEventSearch; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| public EventDaoImpl() { | ||||||||||||||||||||||||||||||||||||||||||||
| CompletedEventSearch = createSearchBuilder(); | ||||||||||||||||||||||||||||||||||||||||||||
| CompletedEventSearch.and("state", CompletedEventSearch.entity().getState(), SearchCriteria.Op.EQ); | ||||||||||||||||||||||||||||||||||||||||||||
| CompletedEventSearch.and("startId", CompletedEventSearch.entity().getStartId(), SearchCriteria.Op.EQ); | ||||||||||||||||||||||||||||||||||||||||||||
| CompletedEventSearch.and("archived", CompletedEventSearch.entity().getArchived(), Op.EQ); | ||||||||||||||||||||||||||||||||||||||||||||
| CompletedEventSearch.done(); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| ToArchiveOrDeleteEventSearch = createSearchBuilder(); | ||||||||||||||||||||||||||||||||||||||||||||
| ToArchiveOrDeleteEventSearch.select("id", SearchCriteria.Func.NATIVE, ToArchiveOrDeleteEventSearch.entity().getId()); | ||||||||||||||||||||||||||||||||||||||||||||
| ToArchiveOrDeleteEventSearch.and("id", ToArchiveOrDeleteEventSearch.entity().getId(), Op.IN); | ||||||||||||||||||||||||||||||||||||||||||||
| ToArchiveOrDeleteEventSearch.and("type", ToArchiveOrDeleteEventSearch.entity().getType(), Op.EQ); | ||||||||||||||||||||||||||||||||||||||||||||
| ToArchiveOrDeleteEventSearch.and("accountIds", ToArchiveOrDeleteEventSearch.entity().getAccountId(), Op.IN); | ||||||||||||||||||||||||||||||||||||||||||||
| ToArchiveOrDeleteEventSearch.and("accountId", ToArchiveOrDeleteEventSearch.entity().getAccountId(), Op.EQ); | ||||||||||||||||||||||||||||||||||||||||||||
| ToArchiveOrDeleteEventSearch.and("domainIds", ToArchiveOrDeleteEventSearch.entity().getDomainId(), Op.IN); | ||||||||||||||||||||||||||||||||||||||||||||
| ToArchiveOrDeleteEventSearch.and("createdDateB", ToArchiveOrDeleteEventSearch.entity().getCreateDate(), Op.BETWEEN); | ||||||||||||||||||||||||||||||||||||||||||||
| ToArchiveOrDeleteEventSearch.and("createdDateL", ToArchiveOrDeleteEventSearch.entity().getCreateDate(), Op.LTEQ); | ||||||||||||||||||||||||||||||||||||||||||||
| ToArchiveOrDeleteEventSearch.and("createdDateLT", ToArchiveOrDeleteEventSearch.entity().getCreateDate(), Op.LT); | ||||||||||||||||||||||||||||||||||||||||||||
| ToArchiveOrDeleteEventSearch.and("archived", ToArchiveOrDeleteEventSearch.entity().getArchived(), Op.EQ); | ||||||||||||||||||||||||||||||||||||||||||||
| ToArchiveOrDeleteEventSearch.done(); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||
| public List<EventVO> searchAllEvents(SearchCriteria<EventVO> sc, Filter filter) { | ||||||||||||||||||||||||||||||||||||||||||||
| return listIncludingRemovedBy(sc, filter); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||
| public List<EventVO> listOlderEvents(Date oldTime) { | ||||||||||||||||||||||||||||||||||||||||||||
| if (oldTime == null) | ||||||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||||||
| SearchCriteria<EventVO> sc = createSearchCriteria(); | ||||||||||||||||||||||||||||||||||||||||||||
| sc.addAnd("createDate", SearchCriteria.Op.LT, oldTime); | ||||||||||||||||||||||||||||||||||||||||||||
| sc.addAnd("archived", SearchCriteria.Op.EQ, false); | ||||||||||||||||||||||||||||||||||||||||||||
| return listIncludingRemovedBy(sc, null); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||
| public EventVO findCompletedEvent(long startId) { | ||||||||||||||||||||||||||||||||||||||||||||
| SearchCriteria<EventVO> sc = CompletedEventSearch.create(); | ||||||||||||||||||||||||||||||||||||||||||||
| sc.setParameters("state", State.Completed); | ||||||||||||||||||||||||||||||||||||||||||||
| sc.setParameters("startId", startId); | ||||||||||||||||||||||||||||||||||||||||||||
| sc.setParameters("archived", false); | ||||||||||||||||||||||||||||||||||||||||||||
| return findOneIncludingRemovedBy(sc); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||
| public List<EventVO> listToArchiveOrDeleteEvents(List<Long> ids, String type, Date startDate, Date endDate, List<Long> accountIds) { | ||||||||||||||||||||||||||||||||||||||||||||
| private SearchCriteria<EventVO> createEventSearchCriteria(List<Long> ids, String type, Date startDate, Date endDate, | ||||||||||||||||||||||||||||||||||||||||||||
| Date limitDate, Long accountId, List<Long> domainIds) { | ||||||||||||||||||||||||||||||||||||||||||||
| SearchCriteria<EventVO> sc = ToArchiveOrDeleteEventSearch.create(); | ||||||||||||||||||||||||||||||||||||||||||||
| if (ids != null) { | ||||||||||||||||||||||||||||||||||||||||||||
| sc.setParameters("id", ids.toArray(new Object[ids.size()])); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| if (CollectionUtils.isNotEmpty(ids)) { | ||||||||||||||||||||||||||||||||||||||||||||
| sc.setParameters("id", ids.toArray(new Object[0])); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| if (type != null) { | ||||||||||||||||||||||||||||||||||||||||||||
| sc.setParameters("type", type); | ||||||||||||||||||||||||||||||||||||||||||||
| if (CollectionUtils.isNotEmpty(domainIds)) { | ||||||||||||||||||||||||||||||||||||||||||||
| sc.setParameters("domainIds", domainIds.toArray(new Object[0])); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| if (startDate != null && endDate != null) { | ||||||||||||||||||||||||||||||||||||||||||||
| sc.setParameters("createdDateB", startDate, endDate); | ||||||||||||||||||||||||||||||||||||||||||||
| } else if (endDate != null) { | ||||||||||||||||||||||||||||||||||||||||||||
| sc.setParameters("createdDateL", endDate); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| if (accountIds != null && !accountIds.isEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||
| sc.setParameters("accountIds", accountIds.toArray(new Object[accountIds.size()])); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| sc.setParametersIfNotNull("accountId", accountId); | ||||||||||||||||||||||||||||||||||||||||||||
| sc.setParametersIfNotNull("createdDateLT", limitDate); | ||||||||||||||||||||||||||||||||||||||||||||
| sc.setParametersIfNotNull("type", type); | ||||||||||||||||||||||||||||||||||||||||||||
| sc.setParameters("archived", false); | ||||||||||||||||||||||||||||||||||||||||||||
| return search(sc, null); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| return sc; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||
| public void archiveEvents(List<EventVO> events) { | ||||||||||||||||||||||||||||||||||||||||||||
| if (events != null && !events.isEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||
| TransactionLegacy txn = TransactionLegacy.currentTxn(); | ||||||||||||||||||||||||||||||||||||||||||||
| txn.start(); | ||||||||||||||||||||||||||||||||||||||||||||
| for (EventVO event : events) { | ||||||||||||||||||||||||||||||||||||||||||||
| event = lockRow(event.getId(), true); | ||||||||||||||||||||||||||||||||||||||||||||
| event.setArchived(true); | ||||||||||||||||||||||||||||||||||||||||||||
| update(event.getId(), event); | ||||||||||||||||||||||||||||||||||||||||||||
| txn.commit(); | ||||||||||||||||||||||||||||||||||||||||||||
| public long archiveEvents(List<Long> ids, String type, Date startDate, Date endDate, Long accountId, List<Long> domainIds, | ||||||||||||||||||||||||||||||||||||||||||||
| long limitPerQuery) { | ||||||||||||||||||||||||||||||||||||||||||||
| SearchCriteria<EventVO> sc = createEventSearchCriteria(ids, type, startDate, endDate, null, accountId, domainIds); | ||||||||||||||||||||||||||||||||||||||||||||
| Filter filter = null; | ||||||||||||||||||||||||||||||||||||||||||||
| if (limitPerQuery > 0) { | ||||||||||||||||||||||||||||||||||||||||||||
| filter = new Filter(limitPerQuery); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| long archived; | ||||||||||||||||||||||||||||||||||||||||||||
| long totalArchived = 0L; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| do { | ||||||||||||||||||||||||||||||||||||||||||||
| List<EventVO> events = search(sc, filter); | ||||||||||||||||||||||||||||||||||||||||||||
| if (events.isEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| txn.close(); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| archived = archiveEventsInternal(events); | ||||||||||||||||||||||||||||||||||||||||||||
| totalArchived += archived; | ||||||||||||||||||||||||||||||||||||||||||||
| } while (limitPerQuery > 0 && archived >= limitPerQuery); | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
81
to
+101
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| return totalArchived; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @DB | ||||||||||||||||||||||||||||||||||||||||||||
| private long archiveEventsInternal(List<EventVO> events) { | ||||||||||||||||||||||||||||||||||||||||||||
| final String idsAsString = events.stream() | ||||||||||||||||||||||||||||||||||||||||||||
| .map(e -> Long.toString(e.getId())) | ||||||||||||||||||||||||||||||||||||||||||||
| .collect(Collectors.joining(",")); | ||||||||||||||||||||||||||||||||||||||||||||
| final String query = String.format("UPDATE event SET archived=true WHERE id IN (%s)", idsAsString); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| try (TransactionLegacy txn = TransactionLegacy.currentTxn(); | ||||||||||||||||||||||||||||||||||||||||||||
| PreparedStatement pstmt = txn.prepareStatement(query)) { | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+108
to
+114
|
||||||||||||||||||||||||||||||||||||||||||||
| final String idsAsString = events.stream() | |
| .map(e -> Long.toString(e.getId())) | |
| .collect(Collectors.joining(",")); | |
| final String query = String.format("UPDATE event SET archived=true WHERE id IN (%s)", idsAsString); | |
| try (TransactionLegacy txn = TransactionLegacy.currentTxn(); | |
| PreparedStatement pstmt = txn.prepareStatement(query)) { | |
| if (CollectionUtils.isEmpty(events)) { | |
| return 0L; | |
| } | |
| final String placeholders = events.stream() | |
| .map(event -> "?") | |
| .collect(Collectors.joining(",")); | |
| final String query = String.format("UPDATE event SET archived=true WHERE id IN (%s)", placeholders); | |
| try (TransactionLegacy txn = TransactionLegacy.currentTxn(); | |
| PreparedStatement pstmt = txn.prepareStatement(query)) { | |
| for (int i = 0; i < events.size(); i++) { | |
| pstmt.setLong(i + 1, events.get(i).getId()); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This suggestion does not make sense to me
Copilot
AI
Apr 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New batching semantics in archiveEvents(...)/purgeAll(...) are non-trivial (limit handling, iteration/termination, criteria composition) but there are currently no unit tests covering these paths (unlike other DAO batch-expunge usages in engine/schema/src/test). Consider adding a focused DAO unit test validating that multiple batches are processed correctly and that limitPerQuery <= 0 behaves as intended.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,6 +51,7 @@ public InputStream[] getPrepareScripts() { | |
| @Override | ||
| public void performDataMigration(Connection conn) { | ||
| unhideJsInterpretationEnabled(conn); | ||
| addIndexes(conn); | ||
| } | ||
|
|
||
| protected void unhideJsInterpretationEnabled(Connection conn) { | ||
|
|
@@ -89,4 +90,10 @@ protected void updateJsInterpretationEnabledFields(Connection conn, String encry | |
| logger.warn("Error while decrypting configuration 'js.interpretation.enabled'. The configuration may already be decrypted."); | ||
| } | ||
| } | ||
|
|
||
| private void addIndexes(Connection conn) { | ||
| DbUpgradeUtils.addIndexWithNameIfNeeded(conn, "event", "i_event__multiple_columns_for_generic_search", | ||
| "account_id", "domain_id", "archived", "display", "resource_type", "resource_id", "start_id", "type", "level", | ||
| "created", "id"); | ||
|
Comment on lines
+95
to
+97
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure that the order of the indexes here is the best. Shouldn't every record with a given account_id have the same domain_id? If so, I think we should flip the order of those. Also, I think that the archived column might be the best one to be the first in the index, as it is probably the most selective. Also, do we need the index to cover all these columns? Did you test the data recovery with fewer columns? How does this index compare to an index with fewer columns in terms of performance of insertions and deletions? |
||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.