Skip to content

Commit 27614b8

Browse files
committed
SEBSP-210 improve screenshot data queries and session update
1 parent da9cf81 commit 27614b8

File tree

5 files changed

+94
-123
lines changed

5 files changed

+94
-123
lines changed

src/main/java/ch/ethz/seb/sps/server/datalayer/dao/SessionDAO.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import java.sql.Date;
1212
import java.util.Collection;
1313
import java.util.List;
14-
import java.util.Set;
14+
import java.util.Map;
1515

1616
import ch.ethz.seb.sps.domain.model.EntityKey;
1717
import ch.ethz.seb.sps.domain.model.FilterMap;
@@ -121,7 +121,7 @@ Result<Session> createNew(
121121
* @param groupId The group id (PK)
122122
* @param updateTimes List of update_time timestamps of all cached sessions.
123123
* @return Result refer to the list of session UUIDs that needs update, ot to an error when happened*/
124-
Result<List<String>> allTokensThatNeedsUpdate(Long groupId, Set<Long> updateTimes);
124+
Result<List<String>> allTokensThatNeedsUpdate(Long groupId, Map<String, Long> updateTimes);
125125

126126
/** Get the encryption key of the session used to encrypt/decrypt client side stored screenshot data.
127127
*

src/main/java/ch/ethz/seb/sps/server/datalayer/dao/impl/ScreenshotDataDAOBatis.java

Lines changed: 70 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import ch.ethz.seb.sps.server.datalayer.batis.custommappers.ScreenshotDataMapper;
2929
import ch.ethz.seb.sps.server.datalayer.batis.custommappers.SearchApplicationMapper;
3030
import ch.ethz.seb.sps.domain.model.service.UserListForApplicationSearch;
31+
import ch.ethz.seb.sps.utils.Utils;
3132
import org.apache.commons.lang3.StringUtils;
3233
import org.mybatis.dynamic.sql.SqlBuilder;
3334
import org.mybatis.dynamic.sql.select.MyBatis3SelectModelAdapter;
@@ -126,88 +127,22 @@ public Result<Collection<ScreenshotData>> allOfSession(final String sessionUUID)
126127
@Override
127128
@Transactional(readOnly = true)
128129
public Result<ScreenshotDataRecord> getAt(final String sessionUUID, final Long at) {
129-
// TODO: this seems to produce performance issues when table is huge. Try to optimize query by reducing the time frame here
130130
return Result.tryCatch(() -> {
131-
ScreenshotDataRecord record = SelectDSL
132-
.selectWithMapper(this.screenshotDataRecordMapper::selectOne,
133-
id,
134-
sessionUuid,
135-
timestamp,
136-
imageFormat,
137-
metaData,
138-
timestamp)
139-
.from(screenshotDataRecord)
140-
.where(ScreenshotDataRecordDynamicSqlSupport.sessionUuid, SqlBuilder.isEqualTo(sessionUUID))
141-
.and(ScreenshotDataRecordDynamicSqlSupport.timestamp, SqlBuilder.isLessThanOrEqualTo(at))
142-
.orderBy(timestamp.descending())
143-
.limit(1)
144-
.build()
145-
.execute();
146-
147-
if (record != null) {
148-
return record;
149-
}
150-
151-
// there is no screenshot at the time of given timestamp. Try to get first image for the session
152-
record = SelectDSL
153-
.selectWithMapper(this.screenshotDataRecordMapper::selectOne,
154-
id,
155-
sessionUuid,
156-
timestamp,
157-
imageFormat,
158-
metaData,
159-
timestamp)
160-
.from(screenshotDataRecord)
161-
.where(ScreenshotDataRecordDynamicSqlSupport.sessionUuid, SqlBuilder.isEqualTo(sessionUUID))
162-
.orderBy(timestamp)
163-
.limit(1)
164-
.build()
165-
.execute();
166-
167-
// still no screenshot... seems that there are none at this time
168-
if (record == null) {
169-
throw new NoResourceFoundException(EntityType.SCREENSHOT_DATA, sessionUUID);
170-
}
171-
172-
return record;
131+
return getScreenshotDataAt(sessionUUID, at);
173132
});
174133
}
175134

176135
@Override
177136
@Transactional(readOnly = true)
178137
public Result<Long> getIdAt(final String sessionUUID, final Long at) {
179138
return Result.tryCatch(() -> {
180-
181-
List<Long> result = SelectDSL
182-
.selectWithMapper(this.screenshotDataRecordMapper::selectIds, id, timestamp)
183-
.from(screenshotDataRecord)
184-
.where(ScreenshotDataRecordDynamicSqlSupport.sessionUuid, SqlBuilder.isEqualTo(sessionUUID))
185-
.and(ScreenshotDataRecordDynamicSqlSupport.timestamp, SqlBuilder.isLessThanOrEqualTo(at))
186-
.orderBy(timestamp.descending())
187-
.limit(1)
188-
.build()
189-
.execute();
190-
191-
if (result != null && !result.isEmpty()) {
192-
return result.get(0);
193-
}
194-
195-
// there is no screenshot at the time of given timestamp. Try to get first image for the session
196-
result = SelectDSL
197-
.selectWithMapper(this.screenshotDataRecordMapper::selectIds, id, timestamp)
198-
.from(screenshotDataRecord)
199-
.where(ScreenshotDataRecordDynamicSqlSupport.sessionUuid, SqlBuilder.isEqualTo(sessionUUID))
200-
.orderBy(timestamp)
201-
.limit(1)
202-
.build()
203-
.execute();
204-
205-
// still no screenshot... seems that there are none at this time
206-
if (result == null || result.isEmpty()) {
207-
throw new NoResourceFoundException(EntityType.SCREENSHOT_DATA, sessionUUID);
139+
ScreenshotDataRecord screenshotDataAt = getScreenshotDataAt(sessionUUID, at);
140+
141+
if (screenshotDataAt != null) {
142+
return screenshotDataAt.getId();
208143
}
209144

210-
return result.get(0);
145+
throw new NoResourceFoundException(EntityType.SCREENSHOT_DATA, sessionUUID);
211146
});
212147
}
213148

@@ -216,24 +151,21 @@ public Result<Long> getIdAt(final String sessionUUID, final Long at) {
216151
public Result<Long> getLatestImageId(final String sessionUUID) {
217152
return Result.tryCatch(() -> {
218153

219-
final List<Long> execute = SelectDSL
220-
.selectDistinctWithMapper(this.screenshotDataRecordMapper::selectIds, id, timestamp)
221-
.from(screenshotDataRecord)
222-
.where(ScreenshotDataRecordDynamicSqlSupport.sessionUuid, SqlBuilder.isEqualTo(sessionUUID))
223-
.orderBy(timestamp.descending())
224-
.limit(1)
225-
.build()
226-
.execute();
154+
ScreenshotDataRecord screenshotDataAt = getScreenshotDataAt(sessionUUID, null);
155+
if (screenshotDataAt != null) {
156+
return screenshotDataAt.getId();
157+
}
227158

228-
return execute.get(0);
159+
throw new NoResourceFoundException(EntityType.SCREENSHOT_DATA, sessionUUID);
229160
});
230161
}
231162

232163
@Override
233164
@Transactional(readOnly = true)
234165
public Result<ScreenshotDataRecord> getLatest(final String sessionUUID) {
235166
return Result.tryCatch(() -> {
236-
final ScreenshotDataRecord latestScreenshotDataRec = getLatestScreenshotDataRec(sessionUUID);
167+
168+
final ScreenshotDataRecord latestScreenshotDataRec = getScreenshotDataAt(sessionUUID, null);
237169
if (latestScreenshotDataRec == null) {
238170
throw new NoResourceFoundException(EntityType.SCREENSHOT_DATA, sessionUUID);
239171
}
@@ -249,28 +181,10 @@ public Result<Map<String, ScreenshotDataRecord>> allLatestIn(final List<String>
249181
return Collections.emptyMap();
250182
}
251183

252-
// NOTE: This was not working as expected since limit does not work with group (groupBy)
253-
// return SelectDSL
254-
// .selectWithMapper(this.screenshotDataRecordMapper::selectMany,
255-
// id,
256-
// sessionUuid,
257-
// timestamp,
258-
// imageFormat,
259-
// metaData)
260-
// .from(screenshotDataRecord)
261-
// .where(ScreenshotDataRecordDynamicSqlSupport.sessionUuid, SqlBuilder.isIn(sessionUUIDs))
262-
// .groupBy(ScreenshotDataRecordDynamicSqlSupport.sessionUuid)
263-
// .orderBy(timestamp.descending())
264-
// .limit(1)
265-
// .build()
266-
// .execute()
267-
// .stream()
268-
// .collect(Collectors.toMap(r -> r.getSessionUuid(), Function.identity()));
269-
270184
// NOTE: For now we use a less efficient version that uses getLatest(final String sessionUUID) for
271185
// all requested sessions but in the future we should solve this problem on DB layer
272186
return sessionUUIDs.stream()
273-
.map(this::getLatestScreenshotDataRec)
187+
.map(sessionUUID -> getScreenshotDataAt(sessionUUID, null))
274188
.filter(Objects::nonNull)
275189
.collect(Collectors.toMap(
276190
ScreenshotDataRecord::getSessionUuid,
@@ -746,4 +660,59 @@ private String createMetadataSearchString(String metadataKey, String metadataVal
746660
Constants.PERCENTAGE_STRING;
747661
}
748662

663+
private ScreenshotDataRecord getScreenshotDataAt(final String sessionUUID, final Long at) {
664+
final Long ts = at != null ? at : Utils.getMillisecondsNow();
665+
666+
ScreenshotDataRecord record = getLastScreenshotDataRecordInRange(sessionUUID, ts, ts - 30 * Constants.SECOND_IN_MILLIS);
667+
if (record != null) {
668+
return record;
669+
}
670+
671+
if (log.isDebugEnabled()) {
672+
log.debug("Did not find screenshot within 30s interval, try 1 hour interval");
673+
}
674+
675+
record = getLastScreenshotDataRecordInRange(sessionUUID, ts, ts - Constants.HOUR_IN_MILLIS);
676+
if (record != null) {
677+
return record;
678+
}
679+
680+
if (log.isDebugEnabled()) {
681+
log.debug("Did not find screenshot within 1 hour interval, get first image: {}", sessionUUID);
682+
}
683+
684+
return SelectDSL
685+
.selectWithMapper(this.screenshotDataRecordMapper::selectOne,
686+
id,
687+
sessionUuid,
688+
timestamp,
689+
imageFormat,
690+
metaData,
691+
timestamp)
692+
.from(screenshotDataRecord)
693+
.where(ScreenshotDataRecordDynamicSqlSupport.sessionUuid, SqlBuilder.isEqualTo(sessionUUID))
694+
.limit(1)
695+
.build()
696+
.execute();
697+
}
698+
699+
private ScreenshotDataRecord getLastScreenshotDataRecordInRange(final String sessionUUID, final Long upper, final Long lower) {
700+
return SelectDSL
701+
.selectWithMapper(this.screenshotDataRecordMapper::selectOne,
702+
id,
703+
sessionUuid,
704+
timestamp,
705+
imageFormat,
706+
metaData,
707+
timestamp)
708+
.from(screenshotDataRecord)
709+
.where(ScreenshotDataRecordDynamicSqlSupport.sessionUuid, SqlBuilder.isEqualTo(sessionUUID))
710+
.and(ScreenshotDataRecordDynamicSqlSupport.timestamp, SqlBuilder.isLessThan(upper))
711+
.and(ScreenshotDataRecordDynamicSqlSupport.timestamp, SqlBuilder.isGreaterThanOrEqualToWhenPresent(lower))
712+
.orderBy(timestamp.descending())
713+
.limit(1)
714+
.build()
715+
.execute();
716+
}
717+
749718
}

src/main/java/ch/ethz/seb/sps/server/datalayer/dao/impl/SessionDAOBatis.java

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,7 @@
1616
import static org.mybatis.dynamic.sql.SqlBuilder.*;
1717

1818
import java.sql.Date;
19-
import java.util.ArrayList;
20-
import java.util.Arrays;
21-
import java.util.Collection;
22-
import java.util.Collections;
23-
import java.util.List;
24-
import java.util.Set;
25-
import java.util.UUID;
19+
import java.util.*;
2620
import java.util.stream.Collectors;
2721

2822
import ch.ethz.seb.sps.server.datalayer.batis.custommappers.SearchSessionMapper;
@@ -251,16 +245,21 @@ public Result<List<Date>> queryMatchingDaysForSessionSearch(final FilterMap filt
251245
.execute();
252246
});
253247
}
254-
255-
// TODO this seems to not work as expected. Seems to get always all ids of the group!?
248+
256249
@Override
257-
public Result<List<String>> allTokensThatNeedsUpdate(Long groupId, Set<Long> updateTimes) {
250+
public Result<List<String>> allTokensThatNeedsUpdate(Long groupId, Map<String, Long> updateTimes) {
258251
return Result.tryCatch(() -> {
252+
253+
254+
if (updateTimes == null || updateTimes.isEmpty()) {
255+
return Collections.emptyList();
256+
}
259257

260258
List<Long> idsForUpdate = this.sessionRecordMapper
261259
.selectIdsByExample()
262260
.where(SessionRecordDynamicSqlSupport.groupId, isEqualTo(groupId))
263-
.and(SessionRecordDynamicSqlSupport.lastUpdateTime, isNotIn(updateTimes))
261+
.and(uuid, isIn(updateTimes.keySet()))
262+
.and(SessionRecordDynamicSqlSupport.lastUpdateTime, isNotIn(updateTimes.values()))
264263
.build()
265264
.execute();
266265

src/main/java/ch/ethz/seb/sps/server/servicelayer/impl/ProctoringServiceImpl.java

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -596,19 +596,21 @@ private void updateSessionCache(String groupUUID) {
596596
if (this.groupDAO.needsUpdate(groupUUID, activeGroup.lastUpdateTime)) {
597597
this.clearGroupCache(groupUUID, true);
598598
} else {
599-
Set<Long> updateTimes = this.proctoringCacheService
599+
600+
// TODO use CacheManager to get all actual cached sessions to update
601+
Map<String, Long> updateTimes = this.proctoringCacheService
600602
.getLiveSessionTokens(activeGroup.uuid)
601603
.stream()
602604
.map(this.proctoringCacheService::getSession)
603605
.filter(Objects::nonNull)
604-
.map(s -> s.lastUpdateTime)
605-
.collect(Collectors.toSet());
606-
607-
this.sessionDAO
608-
.allTokensThatNeedsUpdate(activeGroup.id, updateTimes)
609-
.getOr(Collections.emptyList())
610-
.forEach(this.proctoringCacheService::evictSession);
611-
606+
.collect(Collectors.toMap( s -> s.uuid, s -> s.lastUpdateTime));
607+
608+
if (!updateTimes.isEmpty()) {
609+
this.sessionDAO
610+
.allTokensThatNeedsUpdate(activeGroup.id, updateTimes)
611+
.getOr(Collections.emptyList())
612+
.forEach(this.proctoringCacheService::evictSession);
613+
}
612614
}
613615
proctoringCacheService.evictSessionTokens(groupUUID);
614616
GROUP_UUID_UPDATE_REG.add(groupUUID);

src/main/resources/config/application-dev.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ spring.flyway.locations=classpath:config/sql/base,classpath:config/sql/demo
44

55
logging.level.ch.ethz.seb.sps.server.weblayer=DEBUG
66
logging.level.ch.ethz.seb.sps.server.servicelayer=DEBUG
7+
logging.level.ch.ethz.seb.sps.server.datalayer.dao=DEBUG
78
#logging.level.ch.ethz.seb.sps.server.datalayer.batis=DEBUG
89
#logging.level.ch.ethz.seb.sps.server.datalayer.batis.mapper=DEBUG
910

0 commit comments

Comments
 (0)