개발중인 서버의 특정 프로세스의 기능은 네트워크의 패킷을 감시하고 특정 프로토콜을 필터로 각 서버에서 보내는 데이터의 일관성이 유지되는지를 확인하는 것 이다.
상위서버에서 gateway 로 데이터를 요청하면 gateway 는 하위서버에 요청을 보내고 하위서버 에서는 gateway로 응답을, 그리고 응답받은 gateway 는 상위 서버에 맞는 데이터로 변환하여 응답을 보내게 된다.
이 상황에서 하위 데이터와 상위 데이터가 쌍을 이뤄 일관성을 유지하는지 실시간 패킷의 이동을 감시하여 해당 값를 분석하여 확인하는 것 이다.
해당 프로세스는 위와 같은 형태로 구성되어 있다.
감시 대상의 패킷이 발생하면 감시 프로세스가 동작하고 해당 데이터에 대한 값을 DB 와 내부 캐시로 반영을 하고 특정 조건을 만족 할 경우 웹 서버에 반영하는 형태이다.
문제는 성능이슈가 발생했다.
맨 처음 예상했던 시나리오와는 다르게 상위 서버에서 모든 데이터의 값을 하위서버로 요청해야 하는 요구사항이 추가된 것이다.
문자열로만 구성되는 상위 데이터와 하위데이터가 쌍을 이루는 값은 총 70만개, 특정 문자열을 포함한 데이터를 요청할 경우 해당 문자열을 가진 모든 데이터를 연산해야 하기 때문에 엄청나게 많은 연산을 필요로 했다. 또한 상위 서버에서 gateway 로 요청이 발생 할 경우 맨 첫번째 요청은 무조건 70만개의 모든 데이터를 다 읽어들여와야 했기 때문에 프로세스의 딜레이가 발생하는 것은 어쩌면 당연한 결과 이기도 했다.
각 단계별로 실행 시간을 파악 한 후 역시나 데이터 베이스에서 특정 문자열을 포함한 모든 문자열을 읽어 오는 과정에서 딜레이가 발생한다는 것을 확인했다. 70만개의 모든 값을 데이터베이스에서 비교 후 읽어오는 것의 트래픽을 감당할 수 없으니 다른 방법을 고안해야 했다.
다행스럽게도 해당 프로세스의 특성상 현재 데이터의 값의 일치여부가 중요하고 데이터의 과거 값은 큰 중요도를 가지지 않는다는 것을 알아냈다. 다행히도 개발중인 PC의 컴퓨팅 파워는 꾀나 훌륭해서 매번 DB 에 접근하는 기존의 방식에서 벗어나서 내부 캐시에 좀 더 집중하여 실시간 처리를 가능하게 하도록 컨셉을 변경하는 것이 옳다는 생각을 했다. 그럼 단순히 캐시를 이용해서 구조를 변경하면 되는것이 아닌가 했지만 70만개가 되는 모든 값을 개별로 유지하는 것은 큰 리소스를 낭비 할 수 있다는 생각이 들었다.
심지어 개별적으로 관리한다고 하더라도 문자열을 하나하나씩 비교해서 값을 가져오는 연산을 한다는 것에서 성능적으로 큰 우위를 점하지 못할 것이라고 생각했다. 따라서 위 상황을 해결할 만한 다른 자료구조를 생각해야 했고 나는 tree 구조가 그 해결법이 될 수 있을것이라고 생각했다.
RootNode 와 Node 는 실제로 복잡한 구조를 가지고 있지만 간단하게 정리하면 RootNode 의 경우 첫 번째 노드를, 그리고 일반 Node 의 경우 해당 문자열과 해당 문자열을 포함하고 있는 다음 노드들에 대한 정보를 가질 수 있도록 구성했다.
public class RootNode {
private Map<String, Node> nodeMap;
}
public class Node {
private String element;
private Map<String, Node> childNodeMap;
}
특정 노드의 문자열은 상위 노드의 문자열을 포함하고 있다는 특징을 반영하여 tree 구조를 설계하고 특정 문자열을 가진 노드에 빠르게 접근 할 수 있도록 트리 구조로 캐시를 구성하였다. 해당 구조를 설계 함에 있어서 가장 큰 가치를 두었던 것은 특정 파일 정보를 파싱하는 과정과 동시에 트리 구조체를 내부 캐시에 띄우는 과정이 동시에 진행 되어야 한다는 점, 그리고 반복 호출이 아닌 단 한번의 호출을 통해 tree 구조를 설계 해야 하는 것이었다. 또한 서버가 강제 종료 혹은 재 시작 될 경우 어떠한 방식으로 트리 자료구조를 관리 할 것인가에 대한 부분도 주요 이슈였다.
해당 이슈들은 구현의 문제이기 때문에 넘어가면, 결과적으로 설계 된 트리에서 특정 데이터에 속하는 모든 하위 데이터를 가져오는 과정은 다음과 같았다.
하지만 위와 같이 특정 노드에 접근한 이후 다시 하위 노드에 접근하는 것은 성능적 이슈를 발생시켰다. 따라서 Node 의 구조를 변경 해야 한다고 생각 했고 부모 노드가 아래의 모든 하위 노드 들의 고유 문자열을 미리 알 수 있도록 변경 해야 했다.
public class Node {
private String element;
private Map<String, Node> childNodeMap;
private List<String> lowerElementList;
}
다행스럽게도 이전 설명처럼 좋은 컴퓨팅 파워를 가지고 있었기에 내부 캐시에 할당 할 수 있는 메모리를 좀더 사용해도 된다고 판단이 들었고 결론적으로는 다음과 같은 형태가 되었다.
특정 값에 대한 하위 정보를 모두 얻기위해서 모든 노드에 도달하는 것이 아니고 특정 노드에 접근하기만 하면 하위 노드들이 가지고 있는 모든 문자열 정보를 바로 받아 올 수있도록 변경한 것이다. 위와 같은 방식은 첫 번째로 설계한 tree 구조보다 메모리를 많이 소비하지만 훨씬 더 빠른 속도로 원하는 값을 가져 올 수 있었다. 결과적으로 실시간 성이 중요한 해당 프로세스에서 큰 효율성을 달성 할 수 있었다.