Merge pull request #9353 from thingsboard/fix/notifications-performance
Performance improvements for notification center
This commit is contained in:
commit
491724c8a8
26
application/src/main/data/upgrade/3.6.0/schema_update.sql
Normal file
26
application/src/main/data/upgrade/3.6.0/schema_update.sql
Normal file
@ -0,0 +1,26 @@
|
||||
--
|
||||
-- Copyright © 2016-2023 The Thingsboard Authors
|
||||
--
|
||||
-- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
-- you may not use this file except in compliance with the License.
|
||||
-- You may obtain a copy of the License at
|
||||
--
|
||||
-- http://www.apache.org/licenses/LICENSE-2.0
|
||||
--
|
||||
-- Unless required by applicable law or agreed to in writing, software
|
||||
-- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
-- See the License for the specific language governing permissions and
|
||||
-- limitations under the License.
|
||||
--
|
||||
|
||||
ALTER TABLE widget_type
|
||||
ADD COLUMN IF NOT EXISTS tags text[];
|
||||
|
||||
ALTER TABLE api_usage_state ADD COLUMN IF NOT EXISTS tbel_exec varchar(32);
|
||||
UPDATE api_usage_state SET tbel_exec = js_exec WHERE tbel_exec IS NULL;
|
||||
|
||||
ALTER TABLE notification DROP CONSTRAINT IF EXISTS fk_notification_request_id;
|
||||
ALTER TABLE notification DROP CONSTRAINT IF EXISTS fk_notification_recipient_id;
|
||||
CREATE INDEX IF NOT EXISTS idx_notification_notification_request_id ON notification(request_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_notification_request_tenant_id ON notification_request(tenant_id);
|
||||
@ -92,7 +92,6 @@ public class AlarmsCleanUpService {
|
||||
while (true) {
|
||||
PageData<AlarmId> toRemove = alarmDao.findAlarmsIdsByEndTsBeforeAndTenantId(expirationTime, tenantId, removalBatchRequest);
|
||||
for (AlarmId alarmId : toRemove.getData()) {
|
||||
relationService.deleteEntityRelations(tenantId, alarmId);
|
||||
Alarm alarm = alarmService.delAlarm(tenantId, alarmId, false).getAlarm();
|
||||
if (alarm != null) {
|
||||
entityActionService.pushEntityActionToRuleEngine(alarm.getOriginator(), alarm, tenantId, null, ActionType.ALARM_DELETE, null);
|
||||
|
||||
@ -63,8 +63,8 @@ public class NotificationsCleanUpService extends AbstractCleanUpService {
|
||||
if (lastRemovedNotificationTs > 0) {
|
||||
long gap = TimeUnit.MINUTES.toMillis(10);
|
||||
long requestExpTime = lastRemovedNotificationTs - TimeUnit.SECONDS.toMillis(NotificationRequestConfig.MAX_SENDING_DELAY) - gap;
|
||||
// TODO: double-check this
|
||||
notificationRequestDao.removeAllByCreatedTimeBefore(requestExpTime);
|
||||
int removed = notificationRequestDao.removeAllByCreatedTimeBefore(requestExpTime);
|
||||
log.info("Removed {} outdated notification requests older than {}", removed, requestExpTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -25,9 +25,11 @@ import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.thingsboard.rule.engine.api.NotificationCenter;
|
||||
import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.common.data.User;
|
||||
import org.thingsboard.server.common.data.audit.ActionType;
|
||||
import org.thingsboard.server.common.data.id.DeviceId;
|
||||
import org.thingsboard.server.common.data.id.NotificationRequestId;
|
||||
import org.thingsboard.server.common.data.id.NotificationRuleId;
|
||||
import org.thingsboard.server.common.data.id.NotificationTargetId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
@ -62,6 +64,7 @@ import org.thingsboard.server.common.data.notification.template.NotificationTemp
|
||||
import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate;
|
||||
import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate;
|
||||
import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate;
|
||||
import org.thingsboard.server.common.data.page.PageLink;
|
||||
import org.thingsboard.server.common.data.security.Authority;
|
||||
import org.thingsboard.server.dao.notification.DefaultNotifications;
|
||||
import org.thingsboard.server.dao.notification.NotificationDao;
|
||||
@ -74,6 +77,7 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ -279,6 +283,24 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
|
||||
assertThat(getMyNotifications(false, 10)).size().isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenTenantIsDeleted_thenDeleteNotificationRequests() throws Exception {
|
||||
createDifferentTenant();
|
||||
NotificationTarget target = createNotificationTarget(savedDifferentTenantUser.getId());
|
||||
int notificationsCount = 20;
|
||||
for (int i = 0; i < notificationsCount; i++) {
|
||||
NotificationRequest request = submitNotificationRequest(target.getId(), "Test " + i, NotificationDeliveryMethod.WEB);
|
||||
awaitNotificationRequest(request.getId());
|
||||
}
|
||||
List<NotificationRequest> requests = notificationRequestService.findNotificationRequestsByTenantIdAndOriginatorType(differentTenantId, EntityType.USER, new PageLink(100)).getData();
|
||||
assertThat(requests).size().isEqualTo(notificationsCount);
|
||||
|
||||
deleteDifferentTenant();
|
||||
|
||||
assertThat(notificationRequestService.findNotificationRequestsByTenantIdAndOriginatorType(differentTenantId, EntityType.USER, new PageLink(1)).getTotalElements())
|
||||
.isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotificationUpdatesForSeveralUsers() throws Exception {
|
||||
int usersCount = 150;
|
||||
@ -692,6 +714,11 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
|
||||
return future.get(30, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private NotificationRequestStats awaitNotificationRequest(NotificationRequestId requestId) {
|
||||
return await().atMost(30, TimeUnit.SECONDS)
|
||||
.until(() -> getStats(requestId), Objects::nonNull);
|
||||
}
|
||||
|
||||
private void checkFullNotificationsUpdate(UnreadNotificationsUpdate notificationsUpdate, String... expectedNotifications) {
|
||||
assertThat(notificationsUpdate.getNotifications()).extracting(Notification::getText).containsOnly(expectedNotifications);
|
||||
assertThat(notificationsUpdate.getNotifications()).extracting(Notification::getType).containsOnly(DEFAULT_NOTIFICATION_TYPE);
|
||||
|
||||
@ -456,7 +456,9 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
|
||||
|
||||
loginSysAdmin();
|
||||
notifications = await().atMost(30, TimeUnit.SECONDS)
|
||||
.until(() -> getMyNotifications(true, 10), list -> list.size() == 1);
|
||||
.until(() -> getMyNotifications(true, 10).stream()
|
||||
.filter(notification -> notification.getType() == NotificationType.RATE_LIMITS)
|
||||
.collect(Collectors.toList()), list -> list.size() == 1);
|
||||
assertThat(notifications).allSatisfy(notification -> {
|
||||
assertThat(notification.getSubject()).isEqualTo("Rate limits exceeded for tenant " + TEST_TENANT_NAME);
|
||||
});
|
||||
|
||||
@ -42,6 +42,7 @@ import java.util.Optional;
|
||||
public class DefaultNotificationRequestService implements NotificationRequestService, EntityDaoService {
|
||||
|
||||
private final NotificationRequestDao notificationRequestDao;
|
||||
private final NotificationDao notificationDao;
|
||||
|
||||
private final NotificationRequestValidator notificationRequestValidator = new NotificationRequestValidator();
|
||||
|
||||
@ -81,10 +82,10 @@ public class DefaultNotificationRequestService implements NotificationRequestSer
|
||||
return notificationRequestDao.findByRuleIdAndOriginatorEntityId(tenantId, ruleId, originatorEntityId);
|
||||
}
|
||||
|
||||
// ON DELETE CASCADE is used: notifications for request are deleted as well
|
||||
@Override
|
||||
public void deleteNotificationRequest(TenantId tenantId, NotificationRequestId requestId) {
|
||||
notificationRequestDao.removeById(tenantId, requestId.getId());
|
||||
notificationDao.deleteByRequestId(tenantId, requestId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -97,6 +98,7 @@ public class DefaultNotificationRequestService implements NotificationRequestSer
|
||||
notificationRequestDao.updateById(tenantId, requestId, requestStatus, stats);
|
||||
}
|
||||
|
||||
// notifications themselves are left in the database until removed by ttl
|
||||
@Override
|
||||
public void deleteNotificationRequestsByTenantId(TenantId tenantId) {
|
||||
notificationRequestDao.removeByTenantId(tenantId);
|
||||
|
||||
@ -41,4 +41,8 @@ public interface NotificationDao extends Dao<Notification> {
|
||||
|
||||
int updateStatusByRecipientId(TenantId tenantId, UserId recipientId, NotificationStatus status);
|
||||
|
||||
void deleteByRequestId(TenantId tenantId, NotificationRequestId requestId);
|
||||
|
||||
void deleteByRecipientId(TenantId tenantId, UserId recipientId);
|
||||
|
||||
}
|
||||
|
||||
@ -104,6 +104,16 @@ public class JpaNotificationDao extends JpaAbstractDao<NotificationEntity, Notif
|
||||
return notificationRepository.updateStatusByRecipientId(recipientId.getId(), status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteByRequestId(TenantId tenantId, NotificationRequestId requestId) {
|
||||
notificationRepository.deleteByRequestId(requestId.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteByRecipientId(TenantId tenantId, UserId recipientId) {
|
||||
notificationRepository.deleteByRecipientId(recipientId.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<NotificationEntity> getEntityClass() {
|
||||
return NotificationEntity.class;
|
||||
|
||||
@ -61,6 +61,12 @@ public interface NotificationRepository extends JpaRepository<NotificationEntity
|
||||
@Transactional
|
||||
int deleteByIdAndRecipientId(UUID id, UUID recipientId);
|
||||
|
||||
@Transactional
|
||||
void deleteByRequestId(UUID requestId);
|
||||
|
||||
@Transactional
|
||||
void deleteByRecipientId(UUID recipientId);
|
||||
|
||||
@Modifying
|
||||
@Transactional
|
||||
@Query("UPDATE NotificationEntity n SET n.status = :status " +
|
||||
|
||||
@ -140,7 +140,7 @@ public class SqlPartitioningRepository {
|
||||
try {
|
||||
partitions.add(Long.parseLong(partitionTsStr));
|
||||
} catch (NumberFormatException nfe) {
|
||||
log.warn("Failed to parse table name: {}", partitionTableName);
|
||||
log.debug("Failed to parse table name: {}", partitionTableName);
|
||||
}
|
||||
}
|
||||
return partitions;
|
||||
|
||||
@ -104,6 +104,8 @@ CREATE INDEX IF NOT EXISTS idx_notification_rule_tenant_id_trigger_type_created_
|
||||
CREATE INDEX IF NOT EXISTS idx_notification_request_tenant_id_user_created_time ON notification_request(tenant_id, created_time DESC)
|
||||
WHERE originator_entity_type = 'USER';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_notification_request_tenant_id ON notification_request(tenant_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_notification_request_rule_id_originator_entity_id ON notification_request(rule_id, originator_entity_id)
|
||||
WHERE originator_entity_type = 'ALARM';
|
||||
|
||||
@ -112,6 +114,8 @@ CREATE INDEX IF NOT EXISTS idx_notification_request_status ON notification_reque
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_notification_id ON notification(id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_notification_notification_request_id ON notification(request_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_created_time ON notification(recipient_id, created_time DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_unread ON notification(recipient_id) WHERE status <> 'READ';
|
||||
|
||||
@ -851,8 +851,8 @@ CREATE TABLE IF NOT EXISTS notification_request (
|
||||
CREATE TABLE IF NOT EXISTS notification (
|
||||
id UUID NOT NULL,
|
||||
created_time BIGINT NOT NULL,
|
||||
request_id UUID NULL CONSTRAINT fk_notification_request_id REFERENCES notification_request(id) ON DELETE CASCADE,
|
||||
recipient_id UUID NOT NULL CONSTRAINT fk_notification_recipient_id REFERENCES tb_user(id) ON DELETE CASCADE,
|
||||
request_id UUID,
|
||||
recipient_id UUID NOT NULL,
|
||||
type VARCHAR(50) NOT NULL,
|
||||
subject VARCHAR(255),
|
||||
body VARCHAR(1000) NOT NULL,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user