| | |
| | | static final String HISTORY_KEYS_KEY = "dev:bts-simulator:collector-history-keys:v1"; |
| | | static final String HISTORY_KEY_PREFIX = "dev:bts-simulator:collector-history:v1:"; |
| | | private static final long RECENT_REQUESTS_TTL_HOURS = 1; |
| | | private static final int HISTORY_READ_BATCH_SIZE = 1000; |
| | | |
| | | private final RedisTemplate<Object, Object> redisTemplate; |
| | | private List<CollectorHistoryAppend> pendingEmbeddedHistory = List.of(); |
| | |
| | | } |
| | | |
| | | @Override |
| | | public List<CollectorMeasurementRecord> loadCollectorHistory(String collectorCode) |
| | | public CollectorHistoryBatch loadCollectorHistoryPage(String collectorCode, long offset, int limit, |
| | | boolean reverse) |
| | | { |
| | | if (offset < 0) throw new IllegalArgumentException("offset must not be negative"); |
| | | if (limit < 1 || limit > MAX_HISTORY_BATCH_SIZE) |
| | | throw new IllegalArgumentException("limit must be between 1 and " + MAX_HISTORY_BATCH_SIZE); |
| | | String key = historyKey(collectorCode); |
| | | Long storedSize = redisTemplate.opsForList().size(key); |
| | | List<CollectorMeasurementRecord> records = new ArrayList<>(); |
| | | if (storedSize != null) |
| | | long redisCount = storedSize == null ? 0 : storedSize; |
| | | List<CollectorMeasurementRecord> embedded = pendingEmbeddedHistory.stream() |
| | | .filter(append -> append.collectorCode().equals(collectorCode)) |
| | | .findFirst().map(CollectorHistoryAppend::records).orElse(List.of()); |
| | | long total = redisCount + embedded.size(); |
| | | long from = Math.min(offset, total); |
| | | int count = (int) Math.min(limit, total - from); |
| | | List<CollectorMeasurementRecord> records = new ArrayList<>(count); |
| | | if (reverse) |
| | | { |
| | | for (long start = 0; start < storedSize; start += HISTORY_READ_BATCH_SIZE) |
| | | long lower = total - from - count; |
| | | long upper = total - from; |
| | | long embeddedLower = Math.max(lower, redisCount); |
| | | for (long index = upper - 1; index >= embeddedLower; index--) |
| | | records.add(embedded.get((int) (index - redisCount))); |
| | | long redisUpper = Math.min(upper, redisCount); |
| | | if (lower < redisUpper) |
| | | { |
| | | long end = Math.min(start + HISTORY_READ_BATCH_SIZE, storedSize) - 1; |
| | | List<Object> stored = redisTemplate.opsForList().range(key, start, end); |
| | | List<Object> stored = redisTemplate.opsForList().range(key, lower, redisUpper - 1); |
| | | if (stored != null) |
| | | stored.forEach(item -> records.add(collectorMeasurementRecord(item))); |
| | | for (int index = stored.size() - 1; index >= 0; index--) |
| | | records.add(collectorMeasurementRecord(stored.get(index))); |
| | | } |
| | | } |
| | | pendingEmbeddedHistory.stream() |
| | | .filter(append -> append.collectorCode().equals(collectorCode)) |
| | | .forEach(append -> records.addAll(append.records())); |
| | | return List.copyOf(records); |
| | | else |
| | | { |
| | | long upper = from + count; |
| | | long redisUpper = Math.min(upper, redisCount); |
| | | if (from < redisUpper) |
| | | { |
| | | List<Object> stored = redisTemplate.opsForList().range(key, from, redisUpper - 1); |
| | | if (stored != null) stored.forEach(item -> records.add(collectorMeasurementRecord(item))); |
| | | } |
| | | long embeddedFrom = Math.max(from, redisCount); |
| | | if (embeddedFrom < upper) |
| | | records.addAll(embedded.subList((int) (embeddedFrom - redisCount), |
| | | (int) (upper - redisCount))); |
| | | } |
| | | if (records.size() != count) |
| | | throw new IllegalStateException("Collector history page changed while reading Redis"); |
| | | long nextOffset = from + records.size(); |
| | | return new CollectorHistoryBatch(offset, nextOffset, nextOffset < total, total, records); |
| | | } |
| | | |
| | | @Override |