1. 개요
2. 문제 상황
3. 가장 쉬운 접근
4. 캐싱
5. 결론
본 글에서는 로그 파일을 파싱하고 구조화 할때 사용한 메모리 캐싱에 대해 다룹니다. 파일을 순차적으로 탐색하며 진행할 때 이미 지나친 파일에 대한 정보가 필요했습니다. 이를 해결하는 방식으로 한 차례 더 순차 탐색을 진행할 수도 있었지만 내부적으로 캐싱 방식을 구현하여 해결한 케이스입니다. 스프링 캐시나 별도의 캐시 라이브러리를 사용하지 않고 직접 구현한 경험을 소개합니다.
아래와 같은 SimpleLogEventReader
는 LogData
를 String
으로 받아 이를 읽고 LogEvent
로 변환하는 작업을 수행합니다. 이때 LogEvent
는 내부에 Message
를 비롯한 복잡한 메시지 타입의 객체를 갖고 있어 이들은 LogEvent
를 Root
로 하는 LogAggregate
로 분류됩니다.
@Component
@RequiredArgsConstructor
public class SimpleLogEventReader implements LogEventReader {
private static final String LOG_EVENT_PATTERN = "정규식 패턴";
private final EventActionFactory eventActionFactory;
private final MessageFactory messageFactory;
@Override
public List<LogEvent> read(String logData) {
return Arrays.stream(logData.split("\\n"))
// ...
.collect(Collectors.toList());
}
public LogEvent createLogEvent(Matcher matcher) {
EventAction eventAction = eventActionFactory.fromString(matcher.group(5));
Message<?> Message = messageFactory.createMessage(matcher.group(7), recentLogEvents.get(extractUUIDFromMessage(matcher.group(7))));
LogEvent logEvent = LogEvent.builder()
.파라미터1(LocalTime.parse(matcher.group(1)))
.파라미터2(matcher.group(2))
.파라미터3(matcher.group(3))
.파라미터4(matcher.group(4))
.파라미터5(eventAction)
//...
.build();
return logEvent;
}
}
LogEvent
는 그 속성에 따라 Request
와 Response
로 분류될 수 있습니다. 즉, 어떤 LogEvent
는 Request
타입이며, 어떤 LogEvent
는 Response
타입입니다. 즉, LogEvent
각자는 고유한 개별 객체이지만 도메인 백그라운드가 적용된 논리적인 존재로서 pair
형태로 존재함을 의미합니다.
여기서 Request
와 Response
는 UUID
를 공유하며 이를 통해 연결됩니다. 따라서 Request
속성의 LogEvent
는 스스로의 pair
로서 response
를 인지할 필요가 있으며, 반대도 마찬가지입니다.
이때, Request
와 Response
타입은 Message
를 통해 판단되어지기 때문에 LogEventReader
는 Message
생성을 다음과 같은 MessageFactory
에 위임합니다.
@Component
@RequiredArgsConstructor
public class MessageFactoryImpl implements MessageFactory {
private final PayloadActionFactory payloadActionFactory;
@Override
public <P> Message<P> createChannelMessage(String rawData, LogEvent pairLogEvent) {
JsonArray jsonArray = new JsonParser().parse(rawData).getAsJsonArray();
String uniqueId = jsonArray.get(1).getAsString();
return Arrays.stream(MessageFormat.values())
.filter(format -> format.getMessageCode() == jsonArray.get(0).getAsInt())
.findFirst()
.map(format -> (Message<P>) format.createCMessage(uniqueId, payloadActionFactory, jsonArray, pairLogEvent))
.orElse(Message.nullObject());
}
}
MessageFactory
는 MessageFormat
의 타입을 판단해주고, 각각의 타입 포맷에게 다시 스스로의 생성 책임을 맡깁니다. 이때 과거의 LogEvent
에 대한 정보를 알아야 하는 필요성이 생기는데 앞서 살펴보았듯 LogEvent
는 UUID
를 통해 Request
와 Response
가 연결되어 있는 상황입니다. Response
타입은 스스로 Payload
를 인지하지 못하며 Request
에 대응되는 Payload
여야 하기 때문에 Request
를 알아야 자기 자신의 Payload
를 규정할 수 있기 때문입니다.
다음과 같이 Response Format
은 UUID
가 일치하여 pair
로 판명난 LogEvent
를 받아와 해당 LogEvent
의 Request Payload
가 무엇인지를 이해해야 해당 Payload
에 맞는 Response
로 스스로를 생성할 수 있습니다.
public class SomethingResultFormat implements MessageFormatter {
@Override
public <P> Message<P> create(int messageTypeId, String uniqueId, PayloadActionFactory payloadActionFactory, JsonArray jsonArray, LogEvent pairLogEvent) {
if (pairLogEvent == null) {
// 처리 후 리턴 로직
}
// 처리 후 리턴 로직
}
}
이러한 플로우가 진행된다고 할 때, Response
타입에 해당하는 LogEvent
는 pair
로서 Request
타입의 LogEvent
를 인지해야 합니다.