IoT 동적 스케일 프로젝트

기존 아키텍처

기존 IoT 아키텍처는 아래와 같았습니다.
IoT BIz Request 서버는 AWS ASG로 묶여있어 동적으로 손쉽게 개수를 조절할 수 있습니다. 또한 Request 서버는 사용자로부터 HTTP 요청을 받아므로 AWS ALB와 연결되어 있습니다. AWS가 제공하는 ASG와 ALB의 기능 덕분에 Request 서버의 수가 늘어나거나 줄어들어도 이를 Target Group에 자동으로 반영해주므로 개발자가 별도의 작업을 할 필요가 없습니다.
그러나 IoT Relay 서버는 AWS ASG로 묶여있지 않습니다. 그대신 여러 대의 EC2를 생성하고, 그 위에서 수동으로 Relay 서버를 실행하는 형태를 띠고 있습니다.
킥보드는 출고가 될 때 연결될 서버의 IP를 저장하고 있습니다. 그리고 전원이 연결되면 저장된 서버 IP와 통신을 시도하게 됩니다. 즉, 서버가 킥보드를 찾아서 연결하는 방식이 아니라 킥보드가 자신이 연결될 서버를 미리 알고 있고 킥보드가 직접 서버에 연결합니다.
EC2를 재시작하다가 IP를 잃어버리게 된다면 그 IP를 알고 있던 킥보드는 더이상 서버에 연결될 수가 없습니다. 이 킥보드들을 다시 서버에 연결하기 위해서는 직접 하나하나 찾아가서 블루투스로 IP를 바꿔줘야 합니다. 이를 방지하기 위해서 Relay 서버는 모두 고정 IP를 가지고 있습니다.
예를 들어 11111 킥보드와 11112 킥보드는 IoT Relay A 서버와 연결을 시도하며 11113 킥보드는 IoT Relay B와 연결을 시도합니다. 만약 11113 킥보드를 IoT Relay A 서버에 연결하기 위해서는 킥보드에게 저장된 서버의 IPIoT Relay A 서버의 IP에서 IoT Relay B 서버의 IP로 바꿔야만 합니다.
또한 Relay 서버는 고유한 이름(A, B 등)을 가지고 있습니다. 이는 EC2 환경 변수에 설정되어 있으며, Relay 서버가 처음 시작될 때 환경 변수에서 이 값을 가져와 초기화하므로 런타임에서 이 값을 수정할 수 없습니다. Relay 서버의 이름을 변경하려면 EC2의 환경 변수를 수정하고 Relay 서버를 수동으로 다시 시작해야 합니다. 이러한 특성때문에 IoT Relay 서버는 ASG를 사용할 수 없습니다. 대신 수동으로 EC2를 생성하고 각각이 고유한 이름을 가지도록 환경 변수를 설정해야만 합니다.
Relay 서버는 고유한 이름을 가지고 있기 때문에 이 이름을 가지고 각자가 다른 Kafka Topic을 구독하는 것이 가능합니다. 위 그림에서 Relay 서버가 구독하는 Topic을 나타내면 다음과 같습니다.
즉, Relay 서버의 이름과 Kafka Topic의 이름은 일대일 대응 관계를 가집니다.

킥보드에게 메시지를 보내려면

Biz Request 서버가 킥보드에게 메시지를 보내기 위해서는 해당 킥보드가 연결된 Relay 서버를 찾고, 그 Relay 서버가 구독하고 있는 Kafka Topic을 찾아서 메시지를 보내면 됩니다.
예를 들어 사용자가 특정 킥보드(11111)에게 잠금 해제를 요청했다고 가정하면 아래와 같은 과정을 거치게 됩니다.
1.
사용자의 HTTP 요청이 ALB를 거쳐서 ASG로 묶여있는 여러 개의 IoT Biz Request 서버 중 하나에게 전달됩니다.
2.
선택된 Request 서버는 DB에서 킥보드 이름으로 request_topic을 가져옵니다. 즉, Request Topic A를 가져옵니다.
3.
Kafka에 있는 Request Topic A잠금 해제 메시지를 보냅니다.
4.
Request Topic A 토픽을 구독하고 있던 IoT Relay A 서버는 해당 메시지를 가져와 11111 킥보드에게 보냅니다.
만약에 킥보드의 수가 늘어나서 더이상 2대의 Relay 서버로는 부족한 상황이 오게되면 Relay 서버의 수를 늘려야만 합니다. 새로운 Relay 서버인 IoT Relay C 서버를 만드는 과정을 살펴보면 아래와 같습니다.

새로운 Relay를 추가하기 위해서는?

먼저 개발자가 수동으로 고정 IP를 가지는 EC2 인스턴스를 생성합니다. 이 서버의 이름은 IoT Relay C이므로 EC2 환경 변수에 해당 값을 설정합니다.
그 후에 EC2에 IoT Relay 서버를 시작하면 IoT Relay C라는 이름을 가지는 새로운 Relay 서버가 생기게 됩니다.
하지만 11114 킥보드는 새로 만든 Relay C 서버의 IP를 가지고 있지 않아 Relay C 서버에 연결할 수 없습니다. 새로 만든 Relay 서버를 사용하려면, 이 IP 주소를 킥보드에 설정해야 합니다. 그렇게 해야 킥보드들이 새로운 Relay 서버에 연결할 수 있습니다.
11114 킥보드에 Relay C 서버의 IP를 설정하면 11114 킥보드는 Relay C 서버에 연결될 수 있습니다. 킥보드는 주기적으로 Relay C 서버에 자신의 GPS 등을 보고할 것이며 Relay C 서버도 잠금 해제 등의 요청을 킥보드에게 보낼 수 있습니다.
그러나 Relay C 서버는 아직 Kafka Topic을 구독하고 있지 않아 Biz Request 서버는 11114 킥보드에게 요청을 보내는 것이 불가능합니다.
KafkaRequest Topic C를 생성했고 Relay C 서버는 이제 Request Topic C를 구독합니다. 이제 Kafka 토픽에 메시지를 보내기만 한다면 11114 킥보드에게 요청을 보낼 수 있습니다.
하지만 DB에는 아직 11114 킥보드에 요청을 보내기 위해 어떤 Kafka 토픽에 메시지를 보내야 하는지가 저장되어 있지 않습니다. 그러므로 사용자가 11114 킥보드의 잠금을 해제하려고 해도 Request 서버는 여전히 11114 킥보드가 어떤 Relay에 연결되어 있는지 모르기 때문에 11114 킥보드에게 요청을 보낼 수 없습니다.
그래서 DB11114 킥보드에게 요청을 보내기 위해서는 Request Topic C를 사용하면 된다는 사실을 저장했고 이제 Request 서버는 11114 킥보드에게 요청을 보낼 수 있습니다.

문제점

새로운 Relay를 추가하는 과정은 위에서 보인 것처럼 그리 간단하지 않습니다. 이 글에서 다루지는 않지만 요청을 주고 받기 위해서는 부가적인 정보가 더 필요하기 때문에 사실은 더 많은 과정을 필요로 합니다. 물론 킥보드의 수는 사용자와 달리 유동적이지도 않고 킥보드의 수가 늘어나는 일 또한 흔치 않습니다. 그래서 이정도 불편함은 감수할만 하다고 생각하고 1년이 넘는 시간 동안 문제를 해결하지 않고 방치해왔습니다. 그러나 시간이 흐르면서 결국 아래와 같은 불편을 느끼게 되었습니다.
Relay 서버를 추가하는 일이 자주 있는 것은 아니지만 생길 떄마다 너무 복잡한 과정을 거쳐야만 했습니다. 여러 번 반복하다 보면 손에 익어 점점 빠르게 추가할 수 있었지만 그렇다고 해도 추가하는 과정에서 받는 스트레스가 작지 않았습니다.
과정이 복잡해 실수할 여지가 너무나도 많았습니다. 값이 하나라도 잘못 들어가게 된다면 킥보드와 정상적인 통신을 할 수 없기에 매번 확인해야만 했고 문제가 생기면 확인해야 할 곳이 많아 해결하기 쉽지 않았습니다.
킥보드의 수가 늘어나지 않아도 Relay 서버를 추가해야 하는 경우가 종종 있었습니다. EC2에 문제가 발생하면 급하게 고정 IP를 가져와 새로운 Relay 서버를 띄워야만 했습니다. 새로운 Relay를 만들어야 할 때 위 과정을 빠르고 정확하게 수행하는 것은 쉬운 일이 아니었습니다.
따라서 이를 해결하려는 동적 스케일 프로젝트를 시작하게 되었습니다.

동적 스케일 프로젝트

이 아키텍처가 문제를 가지는 이유를 생각해보면 다음과 같이 정리될 수 있습니다.
킥보드는 미리 정해진 Relay에 연결된다.
따라서 킥보드에게 요청을 보내기 위해서는 킥보드가 연결된 Relay에게 요청을 보내야 한다.
따라서 킥보드가 연결된 Relay에게만 요청을 보낼 수 이는 수단이 필요하고 각각의 Kafka 토픽이 이 역할을 한다.
킥보드가 연결된 Relay가 어떤 Relay인지 알 수 있어야 하므로 이 정보를 미리 저장해 놓아야 하며, 요청을 보낼 때마다 조회해야 한다.
그런데 위에서 나열한 문제점들은 하나만 해결하면 대부분 해결됩니다. 바로 킥보드에게 요청을 보내기 위해서 킥보드가 연결된 Relay에게 요청을 보내야만 한다는 사실입니다. 그렇게 되면 각각의 Kafka 토픽이 필요가 없어지므로 Relay 고유의 이름도 필요없고, 킥보드가 연결된 Relay도 저장할 필요도, 조회할 필요도 없습니다.
따라서 아래와 같은 간단한 아키텍처를 고안했습니다.
Relay 마다 Request 토픽이 존재하는 것이 아니라 하나의 Request 토픽이 존재하고 이 토픽을 모든 Relay가 구독합니다. 이때 Consumer Group은 고유하게 지정해 모두가 동일한 메시지를 읽을 수 있도록 합니다.
이렇게 되면 Request 서버는 킥보드에게 요청을 보내기 위해서 DB를 조회할 필요가 없습니다. 그저 지정된 Request 토픽에 메시지를 보내면 메시지를 읽은 Relay 중 하나는 그 킥보드와 연결되어 있을 것이고 그 Relay가 킥보드에게 요청을 보내면 됩니다. 다른 Relay는 그저 해당 메시지를 버리기만 하면 됩니다.
예를 들어 사용자가 특정 킥보드(11111)에게 잠금 해제를 요청했다고 가정하면 아래와 같은 과정을 거치게 됩니다.
1.
사용자의 HTTP 요청이 ALB를 거쳐서 ASG로 묶여있는 여러 개의 IoT Biz Request 서버 중 하나에게 전달됩니다.
2.
선택된 Request 서버는 Request Topic잠금 해제 메시지를 보냅니다.
3.
Relay A, Relay B, Relay C 서버는 잠금 해제 메시지를 읽습니다.
4.
Relay B, Relay C는 11111 킥보드가 연결되어 있지 않으므로 메시지를 읽고 버립ㄴ다.
5.
Relay A 서버는 해당 메시지를 가져와 11111 킥보드에게 보냅니다.
이와 같은 방법을 사용하면 위에서 언급한 문제가 모두 해결됩니다.
킥보드는 미리 정해진 Relay에 연결된다. → 여전히 존재하는 문제입니다
따라서 킥보드에게 요청을 보내기 위해서는 킥보드가 연결된 Relay에게 요청을 보내야 한다 → 모든 Relay에게 요청
따라서 킥보드가 연결된 Relay에게만 요청을 보낼 수 이는 수단이 필요하고 각각의 Kafka 토픽이 이 역할을 한다 → 하나의 Kafka 토픽만 필요하므로 Relay를 새로 생성하도 토픽을 추가 할 필요가 없습니다
킥보드가 연결된 Relay가 어떤 Relay인지 알 수 있어야 하므로 이 정보를 미리 저장해 놓아야 하며, 요청을 보낼 때마다 조회해야 한다 → 정해진 Request 토픽에 메시지를 보내면 되므로 관련된 정보를 저장할 필요가 없습니다
처음에 이러한 아키텍처가 불편했던 이유도 다시 한번 살펴보면 다음과 같이 모두 해결됩니다.
Relay 서버를 추가하는 일이 자주 있는 것은 아니지만 생길 떄마다 너무 복잡한 과정을 거쳐야만 했습니다. 여러 번 반복하다 보면 손에 익어 점점 빠르게 추가할 수 있었지만 그렇다고 해도 추가하는 과정에서 받는 스트레스가 작지 않았습니다 → 이제 Kafka 토픽도 필요 없고, DB 정보도 필요 없으므로 과정이 매우 간단해졌습니다
과정이 복잡해 실수할 여지가 너무나도 많았습니다. 값이 하나라도 잘못 들어가게 된다면 킥보드와 정상적인 통신을 할 수 없기에 매번 확인해야만 했고 문제가 생기면 확인해야 할 곳이 많아 해결하기 쉽지 않았습니다 → 이제 Kafka 토픽도 필요 없고, DB 정보도 필요 없으므로 과정이 매우 간단해졌습니다. 일반적인 서버를 구동하는 것과 큰 차이가 없습니다.
킥보드의 수가 늘어나지 않아도 Relay 서버를 추가해야 하는 경우가 종종 있었습니다. EC2에 문제가 발생하면 급하게 고정 IP를 가져와 새로운 Relay 서버를 띄워야만 했습니다. 새로운 Relay를 만들어야 할 때 위 과정을 빠르고 정확하게 수행하는 것은 쉬운 일이 아니었습니다 → 과정이 간단해져 전보다 더 빠르게 띄울 수 있고 실수를 할 여지도 적습니다
하지만 잘 생각해보면 여전히 Relay를 ASG로 묶어서 동적으로 개수를 유동적으로 조정하는 것은 불가능합니다. 여전히 킥보드는 미리 정해진 Relay에 연결되기 때문입니다. 하지만 이는 동적 스케일 프로젝트가 아닌 동적 할당 프로젝트에서 해결하는 문제이며 동적 스케일 프로젝트 자체는 인프라 관리의 안정성과 편리함을 높이고 동적 할당 프로젝트의 기틀을 마련했다고 생각할 수 있습니다.

성능 테스트

새로운 아키텍처를 도입하면 지금까지 고민했던 대부분의 문제를 해결할 수 있을 것으로 보입니다. 그러나 새로운 아키텍처가 기존 아키텍처보다 안 좋을 것으로 예상되는 부분은 성능입니다. 이전에는 Relay가 각자만의 큐를 관리해 많은 요청이 몰려도 적절하게 분배될 수 있었으나 지금은 모든 Relay가 함께 부하를 감당하는 구조입니다. 즉, 자신과 연결되어 있지 않은 킥보드의 요청에 대한 메시지를 매번 읽고 버리는 과정이 필요합니다.
사실, 처음에 Relay마다 고유한 큐를 사용한 이유는 성능에 대한 우려 때문이었습니다. 그러나 저는 이를 통해 얻는 성능적인 이점보다 잃는 것이 더 많다고 생각했으며, 서비스 특성 상 요청이 많지 않아 충분히 처리 가능하다고 생각했습니다.
따라서 성능에 대한 우려가 실제로 문제가 되는지를 파악하기 위해 성능 테스트를 진행했습니다.
성능 테스트에는 간편하게 여러 시나리오를 테스트하기 위해서 k6를 사용했습니다.
k6는 기본적으로는 Kafka를 지원하지 않지만 xk6-kafka라는 k6 extension를 사용하면 Kafka도 k6에 사용할 수 있습니다.
Kafka 토픽을 1개로 줄였을 때 엄밀하게 성능 차이가 얼마나 발생하는지를 알기 위해서는 위 빨간색 사각형 영역을 측정해야 합니다. Relay가 Kafka로부터 메시지를 읽고 보내기 전까지의 시간 차이가 얼마나 발생하는지를 알아야 토픽 개수에 따른 성능 차이를 알 수 있습니다.
그러나 Kafka는 Relay 코드 내에 존재하지 않고 외부 인프라에 존재하기 때문에 이를 측정하려면 많은 노력이 필요합니다. 먼저, Relay에서 Kafka 메시지를 처리한 시점을 어떻게 파악할지 고민해야 합니다.
그러나 앞에서 말했듯이 킥보드에 대한 요청 수 자체가 그리 많지 않기 때문에 엄밀하게 성능을 측정할 필요가 없고, Relay에서 Kafka로부터 메시지를 읽고 수행하는 동작이 TCP 소켓에 write하는 작업이기 떄문에 유의미한 차이가 발생할 것 같지도 않았습니다. 따라서 빠르게 성능 테스트를 하기 위해서 아래와 같은 방법을 택했습니다.
테스트 서버는 K6를 실행하는 서버입니다. 테스트 서버는 킥보드처럼 소켓 서버를 하나 열고 Relay에 연결합니다. 그리고 Kafka에 메시지를 하나 보내고 Relay로부터 TCP 응답을 받을 때까지의 시간을 측정합니다.
이 사이에는 Relay가 Kafka로부터 메시지를 읽는 것 말고도 다른 관련없는 많은 작업이 들어있습니다. 그러나 아래와 같은 이유로 위와 같은 방법으로 성능 테스트를 해도 괜찮다고 생각했습니다.
Relay가 결국 수행하는 동작은 Kafka로부터 메시지를 읽어서 TCP로 킥보드에게 메시지를 보내는 것입니다. 이는 정확히 변화를 준 부분은 아니지만 실질적으로 Kafka에 메시지를 썼을 때 킥보드까지 도착하는 시간을 측정할 수 있으므로 의미있는 성능 측정이라 생각했습니다.
중간에 존재하는 수많은 변수로 측정 떄마다 시간이 달라질 수는 있겠지만 그러한 동작이 매우 간단하며, k6를 사용해 많은 테스트를 수행하면 평균적인 값을 얻을 수 있을 것이라 생각했습니다.
따라서 k6를 이용한 테스트 코드는 대략적으로 아래와 같게 됩니다.
import { check } from 'k6' import tcp from 'k6/x/tcp' import { Counter, Trend } from 'k6/metrics' import { produce, writer } from 'k6/x/kafka' import exec from 'k6/execution' const kafkaSendCounter = new Counter('kafka_send_counter') const relayDuration = new Trend('relay_duration', true) const randomNumber = getRandomArbitrary(0, 10000000) const iotIdString = `${ randomNumber }000000000000` const iotId = iotIdString.slice(0, 15) function getRandomArbitrary(min, max) { return Math.floor(Math.random() * (max - min) + min) } const topicList = [ 'request-a', 'request-b', 'request-c', ] const ipMap = { 'request-a': 'xxx.xxx.xxx.xxx', 'request-b': 'xxx.xxx.xxx.xxx', 'request-c': 'xxx.xxx.xxx.xxx', } const counterMap = { 'request-a': new Counter('request-a'), 'request-b': new Counter('request-b'), 'request-c': new Counter('request-c'), } const tcpMap = { 'request-a': createConnection('request-a'), 'request-b': createConnection('request-b'), 'request-c': createConnection('request-c'), } /* 여러 개의 토픽을 사용하는 경우 const writerMap = { 'request-a': createProducer('request-a'), 'request-b': createProducer('request-b'), 'request-c': createProducer('request-c'), } */ // 1개의 토픽을 사용하는 경우 const writerMap = { 'request-a': createProducer('request'), 'request-b': createProducer('request'), 'request-c': createProducer('trequest'), } function createConnection(kafkaTopic) { // kafkaTopic을 가지고 IP를 찾아서 TCP Send } function createProducer(kafkaTopic) { // kafka Topic에 Produce하는 Producer 생성 } function getTopicListFromIndex(index) { // index를 가지고 topic 중 하나를 가져옵니다 return topicList[index % topicList.length] } let conn export default function () { // k6의 경우 병렬적으로 실행되는 프로세스에서 고유한 id를 가져올 수 있습니다 // 이를 통해 프로세스마다 topic을 나눠가질 수 있습니다 const kafkaTopic = getTopicListFromIndex(Number(exec.vu.idInTest)) // topic을 가지고 TCP 커넥션을 가져옵니다 conn = tcpMap[kafkaTopic] // topic을 가지고 Kafka Producer를 가져옵니다 const producer = writerMap[kafkaTopic] counterMap[kafkaTopic].add(1) const message = { key: JSON.stringify(null), value: JSON.stringify({ // Kafka 메시지 }) } const startTime = Date.now() // kafka에 메시지를 보냅니다 let error = produce(producer, [message]) // Kafka에 메시지를 보냈으니 kafkaSendCounter를 1 올립니다 kafkaSendCounter.add(1, {iotId}) check(error, { 'Messages are sent': (err) => err == null, }) // TCP 연결로부터 메시지를 읽습니다 let read = tcp.read(conn) const endTime = Date.now() // Kafka에 메시지를 보낸 시점부터 TCP로 응답을 받은 시점까지의 시간을 측정합니다 relayDuration.add(endTime - startTime, {iotId}) // TCP 응답이 제대로 왔는지 확인합니다 let response = String.fromCharCode(...(read.slice(2))).slice(0, 15) check(response, { 'Received Data is correct': (r) => r == iotId }) } export function teardown() { if (conn) { tcp.close(conn) } }
JavaScript
k6를 이용하면 다음과 같이 빠르게 원하는 시나리오를 작성하고 성능 테스트를 돌려볼 수 있습니다.
성능 테스트는 다음과 같은 경우를 나누어서 진행했습니다.
동적 스케일 프로젝트 전
Relay 서버 1개
요청이 적을 때(vus 10)
요청이 많을 때(vus 500)
Relay 서버 10개
요청이 적을 때(vus 10)
요청이 많을 때(vus 500)
동적 스케일 프로젝트 전
Relay 서버 1개
요청이 적을 때(vus 10)
요청이 많을 때(vus 500)
Relay 서버 10개
요청이 적을 때(vus 10)
요청이 많을 때(vus 500)
테스트 환경은 아래와 같습니다.
릴레이 instance type: t3.small
부하테스트 instance type: m6i.large
test kafka: t3.small
elastic cache: t3.micro
이전 아키텍처 실험 결과는 아래와 같았습니다.
동적 스케일을 적용한 아키텍처의 실험 결과는 아래와 같았습니다.
같은 실험 조건에서 각각 비교하면 아래와 같았습니다.
릴레이가 1개일 때는 실험을 할 때마다 값이 크게 달라지는 특성을 보였습니다. 또한 릴레이가 1개인 경우는 토픽의 개수가 큰 상관이 없고 실제 운영 환경에서는 릴레이가 1대가 아니라 더욱 많아지므로 릴레이가 10대일 떄의 실험 결과에 주목했습니다.
릴레이가 10개일 때의 relay_duration 위주로 살펴보면 전체적으로 동적 스케일을 적용한 버전이 성능이 떨어지는 것을 알 수 있습니다. relay_duration min의 경우 요청이 적을 때는 큰 차이가 없지만 요청이 많아지면 큰 차이를 보이는 것을 알 수 있습니다 relay_duration max의 경우 요청이 적은 경우나 많은 경우나 큰 성능 차이가 발생하는 것을 알 수 있습니다. max 값이 크게 차이가 나다 보니까 relay_duration avg 값도 큰 차이를 보이는 것을 알 수 있습니다.
max 값의 경우 요청의 수가 증가함에 따라 밀리는 이벤트가 생겨 동적 스케일 버전이 큰 성능 저하를 보이게 됩니다. 그러나 이 실험의 경우 Kafka에 초당 메시지를 적어도 몇백개 정도를 보내고 있는데 운영 환경에서는 이런 일이 발생하지 않으므로 운영 환경에서는 동적 스케일 버전을 사용해도 무리가 없을 것이라 예상할 수 있습니다. 실제로 요청이 적은 경우 성능 저하가 63% 정도 발생하지만, 그래도 246ms 정도로 감수할만한 수준입니다.
min 값의 경우 요청이 많을 때 94%의 성능 저하를 보이지만 64ms는 여전히 매우 작은 값이기 때문에 감수할 만한 정도입니다.
avgmed, 그리고 p90을 따져보면 실질적인 성능 저하가 크다는 것을 알 수 있습니다. 그러나 이는 요청이 훨씬 많은 경우에만 크게 나타나고 요청이 적은 경우에는 성능 저하가 발생해도 감수할만한 수준이라는 점, 요청이 적은 경우에도 운영 환경에서 발생할 수 있는 트래픽보다 현저히 많다는 점을 고려해서 동적 스케일이 적용된 아키텍처를 사용해도 괜찮겠다는 결론을 내렸습니다.

여전히 존재하는 문제점

동적 스케일 프로젝트를 통해 Request 서버는 더이상 킥보드가 연결된 Relay를 찾을 필요가 없습니다. 또한 Relay의 수에 따라서 의존적인 것들이 많이 사라지면서 Relay 서버를 새로 생성하는 과정 또한 매우 간단해졌습니다.
그러나 위에서 잠시 언급했던 것처럼 동적 스케일 프로젝트를 수행했다고 해서 Relay를 바로 ASG로 묶을 수 있는 것은 아닙니다. 여전히 킥보드는 고정된 Relay에 연결되기 때문입니다.
이를 해결하기 위해서 동적 스케일 프로젝트가 끝난 후 바로 동적 할당 프로젝트를 수행했습니다. 결과적으로 동적 스케일 프로젝트와 동적 할당 프로젝트 덕분에 Relay를 ASG로 묶어서 안전하고 편하게 관리하는 것이 가능해졌습니다. 또한 다른 이점들도 얻을 수 있었는데 이는 동적 할당 프로젝트 문서에서 다룹니다.
또한 토픽을 여러 개에서 하나로 줄이면서 다음과 같은 문제가 발생했습니다.
성능 차이가 크지는 않지만 그래도 성능 차이가 존재합니다
이전에는 킥보드가 연결된 Relay가 이미 결정되어 있었기 때문에, 요청을 받은 Relay가 현재 킥보드와 통신을 끊었을 경우, 이를 즉시 Request 서버에 알릴 수 있었습니다. 그러나 모든 릴레이가 하나의 토픽을 구독하다 보니, Relay 스스로는 자신이 킥보드와 연결이 끊겨있다고 해서 실제로 킥보드가 모든 Relay와 연결이 끊긴 것인지 판단할 수 없게 되었습니다. 따라서, Request 서버는 통신이 끊긴 킥보드에 대한 요청을 보내면, 정해진 타임아웃 시간 동안 항상 기다려야 하는 문제가 생겼습니다.
이에 대한 문제는 동적 할당 프로젝트 이후에도 지속되었지만 그 뒤에 진행한 프로젝트를 통해 모두 해결되었습니다.