Commit cb9450104ebdaf9a440cd763d0cd3562c3d926a0

Authored by Sergey Matvienko
Committed by Andrew Shvayka
1 parent 91e0a77a

device count performance for 100k+ devices per tenant. Added additional tests for the DeviceService

... ... @@ -98,4 +98,6 @@ public interface DeviceService {
98 98 PageData<Device> findDevicesByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, PageLink pageLink);
99 99
100 100 PageData<Device> findDevicesByTenantIdAndEdgeIdAndType(TenantId tenantId, EdgeId edgeId, String type, PageLink pageLink);
  101 +
  102 + long countByTenantId(TenantId tenantId);
101 103 }
... ...
... ... @@ -15,10 +15,16 @@
15 15 */
16 16 package org.thingsboard.server.common.data.tenant.profile;
17 17
  18 +import lombok.AllArgsConstructor;
  19 +import lombok.Builder;
18 20 import lombok.Data;
  21 +import lombok.NoArgsConstructor;
19 22 import org.thingsboard.server.common.data.ApiUsageRecordKey;
20 23 import org.thingsboard.server.common.data.TenantProfileType;
21 24
  25 +@AllArgsConstructor
  26 +@NoArgsConstructor
  27 +@Builder
22 28 @Data
23 29 public class DefaultTenantProfileConfiguration implements TenantProfileConfiguration {
24 30
... ...
... ... @@ -615,6 +615,11 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
615 615 return deviceDao.findDevicesByTenantIdAndEdgeIdAndType(tenantId.getId(), edgeId.getId(), type, pageLink);
616 616 }
617 617
  618 + @Override
  619 + public long countByTenantId(TenantId tenantId) {
  620 + return deviceDao.countByTenantId(tenantId);
  621 + }
  622 +
618 623 private DataValidator<Device> deviceValidator =
619 624 new DataValidator<Device>() {
620 625
... ...
... ... @@ -189,5 +189,16 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit
189 189 @Param("searchText") String searchText,
190 190 Pageable pageable);
191 191
192   - Long countByTenantId(UUID tenantId);
  192 + /**
  193 + * Count devices by tenantId.
  194 + * Custom query applied because default QueryDSL produces slow count(id).
  195 + * <p>
  196 + * There is two way to count devices.
  197 + * OPTIMAL: count(*)
  198 + * - returns _row_count_ and use index-only scan (super fast).
  199 + * SLOW: count(id)
  200 + * - returns _NON_NULL_id_count and performs table scan to verify isNull for each id in filtered rows.
  201 + * */
  202 + @Query("SELECT count(*) FROM DeviceEntity d WHERE d.tenantId = :tenantId")
  203 + Long countByTenantId(@Param("tenantId") UUID tenantId);
193 204 }
... ...
... ... @@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.DeviceProfileType;
32 32 import org.thingsboard.server.common.data.DeviceTransportType;
33 33 import org.thingsboard.server.common.data.EntityType;
34 34 import org.thingsboard.server.common.data.Event;
  35 +import org.thingsboard.server.common.data.Tenant;
35 36 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
36 37 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
37 38 import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
... ... @@ -70,6 +71,8 @@ import java.util.Comparator;
70 71 import java.util.HashMap;
71 72 import java.util.Map;
72 73
  74 +import static org.junit.Assert.assertNotNull;
  75 +
73 76
74 77 @RunWith(SpringRunner.class)
75 78 @ContextConfiguration(classes = AbstractServiceTest.class, loader = AnnotationConfigContextLoader.class)
... ... @@ -225,4 +228,12 @@ public abstract class AbstractServiceTest {
225 228 return deviceProfile;
226 229 }
227 230
  231 + public TenantId createTenant() {
  232 + Tenant tenant = new Tenant();
  233 + tenant.setTitle("My tenant " + Uuids.timeBased());
  234 + Tenant savedTenant = tenantService.saveTenant(tenant);
  235 + assertNotNull(savedTenant);
  236 + return savedTenant.getId();
  237 + }
  238 +
228 239 }
... ...
... ... @@ -21,13 +21,19 @@ import org.junit.After;
21 21 import org.junit.Assert;
22 22 import org.junit.Before;
23 23 import org.junit.Test;
24   -import org.thingsboard.server.common.data.*;
  24 +import org.thingsboard.server.common.data.Customer;
  25 +import org.thingsboard.server.common.data.Device;
  26 +import org.thingsboard.server.common.data.DeviceInfo;
  27 +import org.thingsboard.server.common.data.EntitySubtype;
  28 +import org.thingsboard.server.common.data.Tenant;
  29 +import org.thingsboard.server.common.data.TenantProfile;
25 30 import org.thingsboard.server.common.data.id.CustomerId;
26 31 import org.thingsboard.server.common.data.id.TenantId;
27 32 import org.thingsboard.server.common.data.page.PageData;
28 33 import org.thingsboard.server.common.data.page.PageLink;
29 34 import org.thingsboard.server.common.data.security.DeviceCredentials;
30 35 import org.thingsboard.server.common.data.security.DeviceCredentialsType;
  36 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
31 37 import org.thingsboard.server.dao.exception.DataValidationException;
32 38
33 39 import java.util.ArrayList;
... ... @@ -37,33 +43,93 @@ import java.util.List;
37 43 import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
38 44
39 45 public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
40   -
  46 +
41 47 private IdComparator<Device> idComparator = new IdComparator<>();
42   -
  48 +
43 49 private TenantId tenantId;
  50 + private TenantId anotherTenantId;
44 51
45 52 @Before
46 53 public void before() {
47   - Tenant tenant = new Tenant();
48   - tenant.setTitle("My tenant");
49   - Tenant savedTenant = tenantService.saveTenant(tenant);
50   - Assert.assertNotNull(savedTenant);
51   - tenantId = savedTenant.getId();
  54 + tenantId = createTenant();
  55 + anotherTenantId = createTenant();
52 56 }
53 57
54 58 @After
55 59 public void after() {
56 60 tenantService.deleteTenant(tenantId);
  61 + tenantService.deleteTenant(anotherTenantId);
  62 +
  63 + tenantProfileService.deleteTenantProfiles(tenantId);
  64 + tenantProfileService.deleteTenantProfiles(anotherTenantId);
  65 + }
  66 +
  67 + @Test
  68 + public void testSaveDevicesWithoutMaxDeviceLimit() {
  69 + Device device = this.saveDevice(tenantId, "My device");
  70 + deleteDevice(tenantId, device);
57 71 }
58 72
59 73 @Test
60   - public void testSaveDevice() {
  74 + public void testSaveDevicesWithInfiniteMaxDeviceLimit() {
  75 + TenantProfile defaultTenantProfile = tenantProfileService.findDefaultTenantProfile(tenantId);
  76 + defaultTenantProfile.getProfileData().setConfiguration(DefaultTenantProfileConfiguration.builder().maxDevices(Long.MAX_VALUE).build());
  77 + tenantProfileService.saveTenantProfile(tenantId, defaultTenantProfile);
  78 +
  79 + Device device = this.saveDevice(tenantId, "My device");
  80 + deleteDevice(tenantId, device);
  81 + }
  82 +
  83 + @Test(expected = DataValidationException.class)
  84 + public void testSaveDevicesWithMaxDeviceOutOfLimit() {
  85 + TenantProfile defaultTenantProfile = tenantProfileService.findDefaultTenantProfile(tenantId);
  86 + defaultTenantProfile.getProfileData().setConfiguration(DefaultTenantProfileConfiguration.builder().maxDevices(1).build());
  87 + tenantProfileService.saveTenantProfile(tenantId, defaultTenantProfile);
  88 +
  89 + Assert.assertEquals(0, deviceService.countByTenantId(tenantId));
  90 +
  91 + this.saveDevice(tenantId, "My first device");
  92 + Assert.assertEquals(1, deviceService.countByTenantId(tenantId));
  93 +
  94 + this.saveDevice(tenantId, "My second device that out of maxDeviceCount limit");
  95 + }
  96 +
  97 + @Test
  98 + public void testCountByTenantId() {
  99 + Assert.assertEquals(0, deviceService.countByTenantId(tenantId));
  100 + Assert.assertEquals(0, deviceService.countByTenantId(anotherTenantId));
  101 + Assert.assertEquals(0, deviceService.countByTenantId(TenantId.SYS_TENANT_ID));
  102 +
  103 + Device anotherDevice = this.saveDevice(anotherTenantId, "My device 1");
  104 + Assert.assertEquals(1, deviceService.countByTenantId(anotherTenantId));
  105 +
  106 + int maxDevices = 8;
  107 + List<Device> devices = new ArrayList<>(maxDevices);
  108 +
  109 + for (int i = 1; i <= maxDevices; i++) {
  110 + devices.add(this.saveDevice(tenantId, "My device " + i));
  111 + Assert.assertEquals(i, deviceService.countByTenantId(tenantId));
  112 + }
  113 +
  114 + Assert.assertEquals(maxDevices, deviceService.countByTenantId(tenantId));
  115 + Assert.assertEquals(1, deviceService.countByTenantId(anotherTenantId));
  116 + Assert.assertEquals(0, deviceService.countByTenantId(TenantId.SYS_TENANT_ID));
  117 +
  118 + devices.forEach(device -> deleteDevice(tenantId, device));
  119 + deleteDevice(anotherTenantId, anotherDevice);
  120 + }
  121 +
  122 + void deleteDevice(TenantId tenantId, Device device) {
  123 + deviceService.deleteDevice(tenantId, device.getId());
  124 + }
  125 +
  126 + Device saveDevice(TenantId tenantId, final String name) {
61 127 Device device = new Device();
62 128 device.setTenantId(tenantId);
63   - device.setName("My device");
  129 + device.setName(name);
64 130 device.setType("default");
65 131 Device savedDevice = deviceService.saveDevice(device);
66   -
  132 +
67 133 Assert.assertNotNull(savedDevice);
68 134 Assert.assertNotNull(savedDevice.getId());
69 135 Assert.assertTrue(savedDevice.getCreatedTime() > 0);
... ... @@ -71,7 +137,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
71 137 Assert.assertNotNull(savedDevice.getCustomerId());
72 138 Assert.assertEquals(NULL_UUID, savedDevice.getCustomerId().getId());
73 139 Assert.assertEquals(device.getName(), savedDevice.getName());
74   -
  140 +
75 141 DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, savedDevice.getId());
76 142 Assert.assertNotNull(deviceCredentials);
77 143 Assert.assertNotNull(deviceCredentials.getId());
... ... @@ -79,16 +145,15 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
79 145 Assert.assertEquals(DeviceCredentialsType.ACCESS_TOKEN, deviceCredentials.getCredentialsType());
80 146 Assert.assertNotNull(deviceCredentials.getCredentialsId());
81 147 Assert.assertEquals(20, deviceCredentials.getCredentialsId().length());
82   -
83   - savedDevice.setName("My new device");
84   -
  148 +
  149 + savedDevice.setName("New " + savedDevice.getName());
  150 +
85 151 deviceService.saveDevice(savedDevice);
86 152 Device foundDevice = deviceService.findDeviceById(tenantId, savedDevice.getId());
87 153 Assert.assertEquals(foundDevice.getName(), savedDevice.getName());
88   -
89   - deviceService.deleteDevice(tenantId, savedDevice.getId());
  154 + return foundDevice;
90 155 }
91   -
  156 +
92 157 @Test(expected = DataValidationException.class)
93 158 public void testSaveDeviceWithEmptyName() {
94 159 Device device = new Device();
... ... @@ -96,7 +161,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
96 161 device.setTenantId(tenantId);
97 162 deviceService.saveDevice(device);
98 163 }
99   -
  164 +
100 165 @Test(expected = DataValidationException.class)
101 166 public void testSaveDeviceWithEmptyTenant() {
102 167 Device device = new Device();
... ... @@ -104,7 +169,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
104 169 device.setType("default");
105 170 deviceService.saveDevice(device);
106 171 }
107   -
  172 +
108 173 @Test(expected = DataValidationException.class)
109 174 public void testSaveDeviceWithInvalidTenant() {
110 175 Device device = new Device();
... ... @@ -113,7 +178,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
113 178 device.setTenantId(new TenantId(Uuids.timeBased()));
114 179 deviceService.saveDevice(device);
115 180 }
116   -
  181 +
117 182 @Test(expected = DataValidationException.class)
118 183 public void testAssignDeviceToNonExistentCustomer() {
119 184 Device device = new Device();
... ... @@ -149,7 +214,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
149 214 tenantService.deleteTenant(tenant.getId());
150 215 }
151 216 }
152   -
  217 +
153 218 @Test
154 219 public void testFindDeviceById() {
155 220 Device device = new Device();
... ... @@ -198,7 +263,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
198 263 devices.forEach((device) -> { deviceService.deleteDevice(tenantId, device.getId()); });
199 264 }
200 265 }
201   -
  266 +
202 267 @Test
203 268 public void testDeleteDevice() {
204 269 Device device = new Device();
... ... @@ -214,15 +279,15 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
214 279 DeviceCredentials foundDeviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, savedDevice.getId());
215 280 Assert.assertNull(foundDeviceCredentials);
216 281 }
217   -
  282 +
218 283 @Test
219 284 public void testFindDevicesByTenantId() {
220 285 Tenant tenant = new Tenant();
221 286 tenant.setTitle("Test tenant");
222 287 tenant = tenantService.saveTenant(tenant);
223   -
  288 +
224 289 TenantId tenantId = tenant.getId();
225   -
  290 +
226 291 List<Device> devices = new ArrayList<>();
227 292 for (int i=0;i<178;i++) {
228 293 Device device = new Device();
... ... @@ -231,7 +296,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
231 296 device.setType("default");
232 297 devices.add(deviceService.saveDevice(device));
233 298 }
234   -
  299 +
235 300 List<Device> loadedDevices = new ArrayList<>();
236 301 PageLink pageLink = new PageLink(23);
237 302 PageData<Device> pageData = null;
... ... @@ -242,19 +307,19 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
242 307 pageLink = pageLink.nextPageLink();
243 308 }
244 309 } while (pageData.hasNext());
245   -
  310 +
246 311 Collections.sort(devices, idComparator);
247 312 Collections.sort(loadedDevices, idComparator);
248   -
  313 +
249 314 Assert.assertEquals(devices, loadedDevices);
250   -
  315 +
251 316 deviceService.deleteDevicesByTenantId(tenantId);
252 317
253 318 pageLink = new PageLink(33);
254 319 pageData = deviceService.findDevicesByTenantId(tenantId, pageLink);
255 320 Assert.assertFalse(pageData.hasNext());
256 321 Assert.assertTrue(pageData.getData().isEmpty());
257   -
  322 +
258 323 tenantService.deleteTenant(tenantId);
259 324 }
260 325
... ... @@ -284,7 +349,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
284 349 device.setType("default");
285 350 devicesTitle2.add(new DeviceInfo(deviceService.saveDevice(device), null, false, "default"));
286 351 }
287   -
  352 +
288 353 List<DeviceInfo> loadedDevicesTitle1 = new ArrayList<>();
289 354 PageLink pageLink = new PageLink(15, 0, title1);
290 355 PageData<DeviceInfo> pageData = null;
... ... @@ -295,12 +360,12 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
295 360 pageLink = pageLink.nextPageLink();
296 361 }
297 362 } while (pageData.hasNext());
298   -
  363 +
299 364 Collections.sort(devicesTitle1, idComparator);
300 365 Collections.sort(loadedDevicesTitle1, idComparator);
301   -
  366 +
302 367 Assert.assertEquals(devicesTitle1, loadedDevicesTitle1);
303   -
  368 +
304 369 List<DeviceInfo> loadedDevicesTitle2 = new ArrayList<>();
305 370 pageLink = new PageLink(4, 0, title2);
306 371 do {
... ... @@ -313,22 +378,22 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
313 378
314 379 Collections.sort(devicesTitle2, idComparator);
315 380 Collections.sort(loadedDevicesTitle2, idComparator);
316   -
  381 +
317 382 Assert.assertEquals(devicesTitle2, loadedDevicesTitle2);
318 383
319 384 for (Device device : loadedDevicesTitle1) {
320 385 deviceService.deleteDevice(tenantId, device.getId());
321 386 }
322   -
  387 +
323 388 pageLink = new PageLink(4, 0, title1);
324 389 pageData = deviceService.findDeviceInfosByTenantId(tenantId, pageLink);
325 390 Assert.assertFalse(pageData.hasNext());
326 391 Assert.assertEquals(0, pageData.getData().size());
327   -
  392 +
328 393 for (Device device : loadedDevicesTitle2) {
329 394 deviceService.deleteDevice(tenantId, device.getId());
330 395 }
331   -
  396 +
332 397 pageLink = new PageLink(4, 0, title2);
333 398 pageData = deviceService.findDeviceInfosByTenantId(tenantId, pageLink);
334 399 Assert.assertFalse(pageData.hasNext());
... ... @@ -413,21 +478,21 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
413 478 Assert.assertFalse(pageData.hasNext());
414 479 Assert.assertEquals(0, pageData.getData().size());
415 480 }
416   -
  481 +
417 482 @Test
418 483 public void testFindDevicesByTenantIdAndCustomerId() {
419 484 Tenant tenant = new Tenant();
420 485 tenant.setTitle("Test tenant");
421 486 tenant = tenantService.saveTenant(tenant);
422   -
  487 +
423 488 TenantId tenantId = tenant.getId();
424   -
  489 +
425 490 Customer customer = new Customer();
426 491 customer.setTitle("Test customer");
427 492 customer.setTenantId(tenantId);
428 493 customer = customerService.saveCustomer(customer);
429 494 CustomerId customerId = customer.getId();
430   -
  495 +
431 496 List<DeviceInfo> devices = new ArrayList<>();
432 497 for (int i=0;i<278;i++) {
433 498 Device device = new Device();
... ... @@ -437,7 +502,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
437 502 device = deviceService.saveDevice(device);
438 503 devices.add(new DeviceInfo(deviceService.assignDeviceToCustomer(tenantId, device.getId(), customerId), customer.getTitle(), customer.isPublic(), "default"));
439 504 }
440   -
  505 +
441 506 List<DeviceInfo> loadedDevices = new ArrayList<>();
442 507 PageLink pageLink = new PageLink(23);
443 508 PageData<DeviceInfo> pageData = null;
... ... @@ -448,31 +513,31 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
448 513 pageLink = pageLink.nextPageLink();
449 514 }
450 515 } while (pageData.hasNext());
451   -
  516 +
452 517 Collections.sort(devices, idComparator);
453 518 Collections.sort(loadedDevices, idComparator);
454   -
  519 +
455 520 Assert.assertEquals(devices, loadedDevices);
456   -
  521 +
457 522 deviceService.unassignCustomerDevices(tenantId, customerId);
458 523
459 524 pageLink = new PageLink(33);
460 525 pageData = deviceService.findDeviceInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink);
461 526 Assert.assertFalse(pageData.hasNext());
462 527 Assert.assertTrue(pageData.getData().isEmpty());
463   -
  528 +
464 529 tenantService.deleteTenant(tenantId);
465 530 }
466   -
  531 +
467 532 @Test
468 533 public void testFindDevicesByTenantIdCustomerIdAndName() {
469   -
  534 +
470 535 Customer customer = new Customer();
471 536 customer.setTitle("Test customer");
472 537 customer.setTenantId(tenantId);
473 538 customer = customerService.saveCustomer(customer);
474 539 CustomerId customerId = customer.getId();
475   -
  540 +
476 541 String title1 = "Device title 1";
477 542 List<Device> devicesTitle1 = new ArrayList<>();
478 543 for (int i=0;i<175;i++) {
... ... @@ -499,7 +564,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
499 564 device = deviceService.saveDevice(device);
500 565 devicesTitle2.add(deviceService.assignDeviceToCustomer(tenantId, device.getId(), customerId));
501 566 }
502   -
  567 +
503 568 List<Device> loadedDevicesTitle1 = new ArrayList<>();
504 569 PageLink pageLink = new PageLink(15, 0, title1);
505 570 PageData<Device> pageData = null;
... ... @@ -510,12 +575,12 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
510 575 pageLink = pageLink.nextPageLink();
511 576 }
512 577 } while (pageData.hasNext());
513   -
  578 +
514 579 Collections.sort(devicesTitle1, idComparator);
515 580 Collections.sort(loadedDevicesTitle1, idComparator);
516   -
  581 +
517 582 Assert.assertEquals(devicesTitle1, loadedDevicesTitle1);
518   -
  583 +
519 584 List<Device> loadedDevicesTitle2 = new ArrayList<>();
520 585 pageLink = new PageLink(4, 0, title2);
521 586 do {
... ... @@ -528,22 +593,22 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
528 593
529 594 Collections.sort(devicesTitle2, idComparator);
530 595 Collections.sort(loadedDevicesTitle2, idComparator);
531   -
  596 +
532 597 Assert.assertEquals(devicesTitle2, loadedDevicesTitle2);
533 598
534 599 for (Device device : loadedDevicesTitle1) {
535 600 deviceService.deleteDevice(tenantId, device.getId());
536 601 }
537   -
  602 +
538 603 pageLink = new PageLink(4, 0, title1);
539 604 pageData = deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink);
540 605 Assert.assertFalse(pageData.hasNext());
541 606 Assert.assertEquals(0, pageData.getData().size());
542   -
  607 +
543 608 for (Device device : loadedDevicesTitle2) {
544 609 deviceService.deleteDevice(tenantId, device.getId());
545 610 }
546   -
  611 +
547 612 pageLink = new PageLink(4, 0, title2);
548 613 pageData = deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink);
549 614 Assert.assertFalse(pageData.hasNext());
... ...