Improve vehicle reference caching during ingest

This commit is contained in:
trifonovt 2026-05-02 22:54:26 +02:00
parent 2e6e1aa5c6
commit bd3620b9af
2 changed files with 150 additions and 13 deletions

View File

@ -4,7 +4,10 @@ import at.procon.eventhub.dto.DriverCardRefDto;
import at.procon.eventhub.dto.DriverRefDto;
import at.procon.eventhub.dto.EventHubEventDto;
import at.procon.eventhub.dto.SourcePackageRefDto;
import at.procon.eventhub.dto.VehicleRefDto;
import at.procon.eventhub.dto.VehicleRegistrationRefDto;
import at.procon.eventhub.persistence.VehicleIdentityRepository.ResolvedVehicleReference;
import at.procon.eventhub.persistence.VehicleIdentityRepository.ResolvedVehicleReferenceResolution;
import at.procon.eventhub.service.EventAcquisitionRecordKeyService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
@ -51,10 +54,11 @@ public class EventRepository {
*/
public int batchInsert(UUID packageId, String tenantKey, int eventSourceId, List<EventHubEventDto> events) {
Map<String, UUID> entityIdCache = new HashMap<>();
Map<String, List<VehicleRefCacheEntry>> vehicleRefCache = new HashMap<>();
List<ResolvedEventImportRow> rows = new ArrayList<>(events.size());
for (EventHubEventDto event : events) {
ResolvedEntityRefs refs = resolveEntityRefs(tenantKey, eventSourceId, event, entityIdCache);
ResolvedEntityRefs refs = resolveEntityRefs(tenantKey, eventSourceId, event, entityIdCache, vehicleRefCache);
rows.add(resolveEventImportRow(packageId, eventSourceId, event, refs));
}
@ -350,10 +354,11 @@ public class EventRepository {
String tenantKey,
int eventSourceId,
EventHubEventDto event,
Map<String, UUID> entityIdCache
Map<String, UUID> entityIdCache,
Map<String, List<VehicleRefCacheEntry>> vehicleRefCache
) {
UUID driverEntityId = resolveDriverEntityId(tenantKey, eventSourceId, event, entityIdCache);
ResolvedVehicleReference vehicleRef = resolveVehicleReference(tenantKey, eventSourceId, event);
ResolvedVehicleReference vehicleRef = resolveVehicleReference(tenantKey, eventSourceId, event, vehicleRefCache);
UUID sourcePackageEntityId = resolveSourcePackageEntityId(tenantKey, eventSourceId, event, entityIdCache);
return new ResolvedEntityRefs(driverEntityId, vehicleRef.vehicleId(), vehicleRef.vehicleRegistrationId(), sourcePackageEntityId);
}
@ -399,14 +404,32 @@ public class EventRepository {
private ResolvedVehicleReference resolveVehicleReference(
String tenantKey,
int eventSourceId,
EventHubEventDto event
EventHubEventDto event,
Map<String, List<VehicleRefCacheEntry>> vehicleRefCache
) {
return vehicleIdentityRepository.resolveOrCreateVehicleReference(
String cacheKey = vehicleRefCacheKey(event.vehicleRef());
if (cacheKey != null) {
ResolvedVehicleReference cached = findCachedVehicleReference(
vehicleRefCache.get(cacheKey),
event.occurredAt()
);
if (cached != null) {
return cached;
}
}
ResolvedVehicleReferenceResolution resolved = vehicleIdentityRepository.resolveOrCreateVehicleReference(
tenantKey,
eventSourceId,
event.vehicleRef(),
event.occurredAt()
);
if (cacheKey != null) {
vehicleRefCache
.computeIfAbsent(cacheKey, ignored -> new ArrayList<>())
.add(buildVehicleRefCacheEntry(event.vehicleRef(), event.occurredAt(), resolved));
}
return resolved.reference();
}
private UUID resolveSourcePackageEntityId(
@ -469,7 +492,7 @@ public class EventRepository {
return null;
}
String cacheKey = entityType + "|" + normalizedSourceEntityId + "|" + normalizeNullable(sourceExternalKey);
String cacheKey = entityType + "|" + normalizedSourceEntityId;
UUID cached = entityIdCache.get(cacheKey);
if (cached != null) {
return cached;
@ -503,6 +526,70 @@ public class EventRepository {
}
}
private String vehicleRefCacheKey(VehicleRefDto vehicleRef) {
if (vehicleRef == null || !vehicleRef.hasAnyReference()) {
return null;
}
String sourceVehicleEntityId = normalizeNullable(vehicleRef.sourceVehicleEntityId());
String vin = normalizeNullable(vehicleRef.vin());
String sourceRegistrationEntityId = normalizeNullable(vehicleRef.sourceRegistrationEntityId());
VehicleRegistrationRefDto registration = vehicleRef.vehicleRegistration();
String registrationNation = registration == null ? null : normalizeNullable(registration.nation());
String registrationNumber = registration == null ? null : normalizeNullable(registration.number());
return String.join("|",
sourceVehicleEntityId == null ? "" : sourceVehicleEntityId,
vin == null ? "" : vin,
sourceRegistrationEntityId == null ? "" : sourceRegistrationEntityId,
registrationNation == null ? "" : registrationNation,
registrationNumber == null ? "" : registrationNumber
);
}
private ResolvedVehicleReference findCachedVehicleReference(
List<VehicleRefCacheEntry> cacheEntries,
OffsetDateTime occurredAt
) {
if (cacheEntries == null || cacheEntries.isEmpty()) {
return null;
}
for (VehicleRefCacheEntry cacheEntry : cacheEntries) {
if (cacheEntry.matches(occurredAt)) {
return cacheEntry.reference();
}
}
return null;
}
private VehicleRefCacheEntry buildVehicleRefCacheEntry(
VehicleRefDto vehicleRef,
OffsetDateTime occurredAt,
ResolvedVehicleReferenceResolution resolved
) {
boolean hasDirectVehicleIdentity = hasDirectVehicleIdentity(vehicleRef);
if (hasDirectVehicleIdentity) {
return new VehicleRefCacheEntry(resolved.reference(), null, null, null);
}
if (resolved.resolvedFromAssignment()) {
return new VehicleRefCacheEntry(
resolved.reference(),
resolved.assignmentValidFrom(),
resolved.assignmentValidTo(),
null
);
}
return new VehicleRefCacheEntry(resolved.reference(), null, null, occurredAt);
}
private boolean hasDirectVehicleIdentity(VehicleRefDto vehicleRef) {
if (vehicleRef == null) {
return false;
}
return normalizeNullable(vehicleRef.sourceVehicleEntityId()) != null
|| normalizeNullable(vehicleRef.vin()) != null;
}
private String toJson(JsonNode value) {
try {
return objectMapper.writeValueAsString(value == null ? objectMapper.createObjectNode() : value);
@ -560,4 +647,23 @@ public class EventRepository {
String attributesJson
) {
}
private record VehicleRefCacheEntry(
ResolvedVehicleReference reference,
OffsetDateTime validFrom,
OffsetDateTime validTo,
OffsetDateTime exactOccurredAt
) {
private boolean matches(OffsetDateTime occurredAt) {
if (exactOccurredAt != null) {
return exactOccurredAt.equals(occurredAt);
}
if (validFrom == null && validTo == null) {
return true;
}
boolean fromMatches = validFrom == null || occurredAt == null || !occurredAt.isBefore(validFrom);
boolean toMatches = validTo == null || occurredAt == null || occurredAt.isBefore(validTo);
return fromMatches && toMatches;
}
}
}

View File

@ -24,14 +24,14 @@ public class VehicleIdentityRepository {
this.objectMapper = objectMapper;
}
public ResolvedVehicleReference resolveOrCreateVehicleReference(
public ResolvedVehicleReferenceResolution resolveOrCreateVehicleReference(
String tenantKey,
int eventSourceId,
VehicleRefDto vehicleRef,
OffsetDateTime occurredAt
) {
if (vehicleRef == null || !vehicleRef.hasAnyReference()) {
return ResolvedVehicleReference.empty();
return ResolvedVehicleReferenceResolution.empty();
}
String normalizedTenantKey = normalizeRequired(tenantKey, "tenantKey");
@ -68,8 +68,10 @@ public class VehicleIdentityRepository {
sourceVehicleEntityId,
vin
);
AssignedVehicleReference assignedVehicle = null;
if (vehicleId == null && registrationId != null) {
vehicleId = resolveAssignedVehicleId(registrationId, occurredAt);
assignedVehicle = resolveAssignedVehicleReference(registrationId, occurredAt);
vehicleId = assignedVehicle == null ? null : assignedVehicle.vehicleId();
}
if (vehicleId == null && (sourceVehicleEntityId != null || vin != null)) {
vehicleId = createVehicle(
@ -86,7 +88,12 @@ public class VehicleIdentityRepository {
if (registrationId != null) {
touchRegistration(registrationId, sourceRegistrationEntityId, registrationNation, registrationNumber);
}
return new ResolvedVehicleReference(vehicleId, registrationId);
return new ResolvedVehicleReferenceResolution(
new ResolvedVehicleReference(vehicleId, registrationId),
assignedVehicle != null,
assignedVehicle == null ? null : assignedVehicle.validFrom(),
assignedVehicle == null ? null : assignedVehicle.validTo()
);
}
@Transactional
@ -431,10 +438,10 @@ public class VehicleIdentityRepository {
);
}
private UUID resolveAssignedVehicleId(UUID registrationId, OffsetDateTime occurredAt) {
private AssignedVehicleReference resolveAssignedVehicleReference(UUID registrationId, OffsetDateTime occurredAt) {
return jdbcTemplate.query(
"""
select vehicle_id
select vehicle_id, valid_from, valid_to
from eventhub.vehicle_registration_assignment
where vehicle_registration_id = ?
and (? is null or valid_from is null or valid_from <= ?)
@ -442,7 +449,13 @@ public class VehicleIdentityRepository {
order by valid_from desc nulls last, updated_at desc
limit 1
""",
rs -> rs.next() ? (UUID) rs.getObject("vehicle_id") : null,
rs -> rs.next()
? new AssignedVehicleReference(
(UUID) rs.getObject("vehicle_id"),
rs.getObject("valid_from", OffsetDateTime.class),
rs.getObject("valid_to", OffsetDateTime.class)
)
: null,
registrationId,
occurredAt,
occurredAt,
@ -676,4 +689,22 @@ public class VehicleIdentityRepository {
}
}
public record ResolvedVehicleReferenceResolution(
ResolvedVehicleReference reference,
boolean resolvedFromAssignment,
OffsetDateTime assignmentValidFrom,
OffsetDateTime assignmentValidTo
) {
public static ResolvedVehicleReferenceResolution empty() {
return new ResolvedVehicleReferenceResolution(ResolvedVehicleReference.empty(), false, null, null);
}
}
private record AssignedVehicleReference(
UUID vehicleId,
OffsetDateTime validFrom,
OffsetDateTime validTo
) {
}
}