온보딩 마지막 과제는 DynamoDB, Redis, SQS 등을 사용한 시스템 구조에서
사용자가 많아졌을 경우, 발생할 수 있는 여러 가지 문제들을 해결하는 것이었다.
DynamoDB와 Redis는 이전에 다루어본 경험이 있어서 어렵지 않았지만,
SQS의 경우, 처음 사용해보는 기술이어서 새롭게 배울 수 있는 것이 많았다.
그리고 앞으로 담당할 서비스와도 밀접한 관련이 있어 유익한 시간이었다.
SQS는 AWS에서 제공하는 완전 관리형 메시지 큐 서비스이다.
메시지 큐란 프로세스 또는 프로그램 간에 데이터를 비동기적으로 교환함으로써
메세지 지향 미들웨어(Message Oriented Middleware)를 구현한 시스템을 의미한다.
다양한 서비스에서 다양한 이유로 메시지 큐를 사용하겠지만,
회사 제품에서는 특정 작업이 지금 당장은 아니더라도 반드시 수행될 필요가 있을 때 사용한다.
예를 들어, 여러 사용자들이 동시에 어떤 상품을 구매한다고 가정해보자.
병목이 발생하여 지연이 있더라도, 각 사용자들의 구매는 반드시 정상적으로 이루어져야한다.
이때, 메시지 큐를 사용하여 구매 작업의 수행이 보장되도록 구현할 수 있다.
아래는 C#을 이용하여 SQS를 사용한 코드이다.
동시에 여러 메시지를 전송하고 수신하는 것이 안정적으로 보장되는지를 확인할 수 있다.
public class SendMessageToQueue
{
public static async Task Main()
{
var client = new AmazonSQSClient();
var queue = "SQSPractice";
var queueUrl = await GetQueueUrl(client, queue);
while (true)
{
var actions = new List<Action>();
for (var i = 0; i < 16; i++)
{
actions.Add(async () =>
{
var messageBody = "This is a sample message to send to SQSPractice.";
var request = new SendMessageRequest
{
MessageBody = messageBody,
QueueUrl = queueUrl,
};
var sendResponse = await client.SendMessageAsync(request);
if (sendResponse.HttpStatusCode == System.Net.HttpStatusCode.OK)
{
Console.WriteLine($"Successfully sent message. Message ID: {sendResponse.MessageId}");
}
else
{
Console.WriteLine("Could not send message.");
}
});
actions.Add(async () =>
{
var receiveResponse = await ReceiveAndDeleteMessage(client, queueUrl);
Console.WriteLine($"Message: {receiveResponse.Messages[0].Body}");
});
}
Parallel.Invoke(actions.ToArray());
Thread.Sleep(10000);
};
Console.ReadLine();
}
public static async Task<string> GetQueueUrl(IAmazonSQS client, string queueName)
{
var request = new GetQueueUrlRequest
{
QueueName = queueName,
};
GetQueueUrlResponse response = await client.GetQueueUrlAsync(request);
return response.QueueUrl;
}
public static async Task<ReceiveMessageResponse> ReceiveAndDeleteMessage(IAmazonSQS client, string queueUrl)
{
var receiveMessageRequest = new ReceiveMessageRequest
{
AttributeNames = { "SentTimestamp" },
MaxNumberOfMessages = 1,
MessageAttributeNames = { "All" },
QueueUrl = queueUrl,
VisibilityTimeout = 0,
WaitTimeSeconds = 0,
};
var receiveMessageResponse = await client.ReceiveMessageAsync(receiveMessageRequest);
var deleteMessageRequest = new DeleteMessageRequest
{
QueueUrl = queueUrl,
ReceiptHandle = receiveMessageResponse.Messages[0].ReceiptHandle,
};
await client.DeleteMessageAsync(deleteMessageRequest);
return receiveMessageResponse;
}
}
흥미로운 점은 메시지를 전송하였으나 수신하는 측에서 특정 기간동안 삭제하지 않으면
SQS에서는 해당 메시지를 DLQ(Dead Letter Queue)로 옮긴다는 것이었다.
앞서 이야기하였듯이 반드시 수행되어야 하는 작업이 DLQ에 많이 쌓인다면,
프로그래머 입장에서는 어딘가 이슈가 발생한 것이라고 생각할 수 있다.
앞으로 메시지 큐는 프로그래밍을 하면서 여러 가지 방식으로 사용할 것 같은데
실제 업무를 하면서 동작하는 방식에 대한 필요와 추가적인 이해를 가질 수 있었으면 한다.