전통적인 로드 밸런싱 환경에서의 세션
일반적으로 웹 애플리케이션에서는 인메모리 세션(RAM에 저장된 데이터)을 사용합니다. 전용 VM이나 공유 호스팅 플랜에 애플리케이션을 배포하는 대부분의 전통적인 호스팅 환경에서는 이 방식이 문제없이 잘 작동합니다.
그러나 트래픽이 증가하면 여러 대의 웹 서버를 두고 로드 밸런서로 트래픽을 분산하는 로드 밸런싱을 도입하게 됩니다. 이런 상황에서는 하나의 세션과 관련된 요청이 여러 서버에서 처리될 수 있기 때문에 세션이 제대로 작동하지 않습니다(동일한 서버가 한 세션의 모든 요청을 처리하도록 구성하는 것도 가능하지만 권장되지 않습니다). 이에 대한 해결책은 로드 밸런싱 환경의 모든 웹 서버가 접근할 수 있는 SQL Server에 세션을 저장하는 것입니다.
Azure 오토스케일 환경에서의 세션
이론적인 논의보다는, 세션을 사용하는 간단한 프로그램을 직접 개발하면서 Azure에서 세션이 실제로 어떻게 동작하는지 살펴보겠습니다.
다음과 같은 웹 페이지들을 가진 웹사이트를 만들고(MVC 애플리케이션으로 만들어도 무방합니다), 이전에 소개한 방법대로 배포합니다.
- Azure App Service - Visual Studio에서 기존 애플리케이션 구성하기
Login.aspx
이 페이지는 사용자 이름과 비밀번호를 입력받습니다. "Login" 버튼을 클릭하면 세션을 생성하고 로그인한 사용자 이름을 세션에 저장한 뒤, 사용자를 "Default.aspx"로 리디렉션합니다.
참고로 이 페이지에는 웹사이트가 호스팅된 가상 머신의 IP 주소 "10.202.116.91"도 함께 표시됩니다.
Default.aspx
이 페이지는 세션 값에 따라 다음과 같은 메시지를 표시합니다.
세션에 값이 있으면 아래 화면처럼 "Logged in user as admin. You are in 10.202.116.91"(admin으로 로그인했습니다. 현재 10.202.116.91에 있습니다)이 표시됩니다.
세션에 값이 없으면 아래 화면처럼 "Session is NULL. You are in 10.202.116.91"(세션이 NULL입니다. 현재 10.202.116.91에 있습니다)이 표시됩니다.
Login.aspx와 Default.aspx 두 페이지 모두에서 서버의 IP 주소가 동일함을 확인할 수 있습니다. 즉, 모든 요청이 같은 서버로 전달되고 있는 것입니다.
또한 아래 화면과 같이 세션은 "ASP.NET_SessionID"라는 이름의 쿠키를 통해 유지됩니다. 대부분에게 익숙한 내용일 것입니다.

이제 웹사이트 트래픽이 증가하여 두 개의 인스턴스로 확장(scale out)하고 싶다고 가정해 보겠습니다. 인스턴스 수를 2개로 늘려 보겠습니다.
참고: 무료(Free) 및 공유(Shared) 계층에서는 인스턴스 수를 늘릴 수 없습니다. App Service가 "Basic", "Standard" 또는 "Premium" 계층이어야 합니다.

로그인 페이지에 다시 접속하여 페이지를 여러 번 새로 고침해 보겠습니다. 몇 번을 새로 고침해도 IP 주소는 변경되지 않습니다.
동일한 웹 서버가 계속해서 페이지를 서빙하고 있음을 알 수 있습니다. 두 개 인스턴스로 확장을 활성화했음에도 App Service는 여전히 한 대의 서버에서만 요청을 처리하고 있습니다.
실제 운영 환경에서는 이것이 문제가 될 수 있습니다. 로드 밸런싱이 활성화되어 있음에도 특정 서버만 요청을 처리하고 나머지 서버는 제대로 활용되지 않기 때문입니다.
모든 요청이 한 서버로 집중되는 원인은 아래와 같은 ARR 쿠키 때문입니다. 자세한 내용은 관련 문서를 참고하시기 바랍니다.

간단히 말해, 이 쿠키에는 최초 요청을 처리한 서버의 정보가 담겨 있어, 동일한 세션의 이후 모든 요청이 같은 VM에서 처리되도록 만듭니다.
이제 App Service의 애플리케이션 설정으로 이동하여 ARR 쿠키 기능을 비활성화해 보겠습니다.

위 화면과 같이 "ARR 선호도(Affinity)"를 끄고 "저장" 버튼을 클릭해 변경 사항을 저장합니다. 기존 세션을 모두 삭제하기 위해 App Service를 재시작하세요.
여기부터는 조금 주의해서 읽어야 합니다. 다음 몇 단락은 다소 혼란스러울 수 있습니다.
중요 사항
ARR 선호도를 끄면 ARRAffinity 쿠키 생성 과정이 비활성화됩니다. 쿠키가 비활성화되면 요청이 사용 가능한 어떤 서버로든 전송될 수 있으므로, 세션이 올바르게 유지된다는 보장이 없습니다. 세션은 오직 요청이 동일한 서버에서 처리될 때만 작동합니다. 세션이 저장된 서버가 아닌 다른 서버에서 요청이 처리되면 세션은 NULL이 되어버립니다.
이제 웹 앱으로 돌아가 로그인 페이지로 이동하면 새로운 IP 주소 "10.202.174.84"가 표시됩니다. (처음에는 이전과 동일한 IP 주소가 보일 수 있습니다. 페이지를 새로 고침하면 IP 주소가 바뀌는데, 필자의 경우 새 IP 주소가 나타날 때까지 두 번 새로 고침했습니다.)

"Login" 버튼을 클릭한다고 해서 폼이 반드시 10.202.174.84로 전송되는 것은 아닙니다. 데이터가 다른 서버, 필자의 경우 "10.202.116.91"로 전송될 수도 있습니다.
실제로 위 화면에서 "Login" 버튼을 클릭했을 때 Default 페이지로 이동했으며, 다음과 같은 결과가 나타났습니다.
- IP 주소는 10.202.174.84 (로그인 페이지와 동일)
- 세션은 NULL — Login 버튼을 클릭했을 때 요청이 세션이 저장된 다른 인스턴스("10.202.116.91")로 전송되지 않았기 때문
- 페이지를 몇 번 새로 고침
그러면 다음과 같은 결과를 관찰할 수 있습니다.
- 세션에 "admin"이라는 값이 존재
- IP 주소는 10.202.116.91
결론적으로, 자동 확장 기능으로 로드 밸런서를 구성하면 Azure App Service에서 "세션"은 예상대로 작동하지 않습니다.
여기서 구원자가 등장합니다. 바로 Redis Cache 공급자입니다. 아래는 Azure 공식 웹사이트의 정의입니다.
"Azure Redis Cache는 널리 사용되는 오픈 소스 Redis 캐시를 기반으로 합니다. Microsoft가 관리하고 Azure 내의 모든 애플리케이션에서 접근할 수 있는 안전한 전용 Redis 캐시를 제공합니다."
세션이 예상대로 작동하도록 만들기 위해 필요한 단계는 다음과 같습니다.
- Azure 관리 포털에서 Redis Cache 생성
- Azure Redis Cache를 사용하도록 애플리케이션 구성
- 세션 사용
Azure 관리 포털에서 Redis Cache 생성하기
Azure 관리 포털을 통해 Redis Cache를 만들어 보겠습니다.
아래와 같이 Redis Cache의 세부 정보를 입력합니다.
위 화면에서 "만들기(Create)" 버튼을 클릭합니다. Redis Cache 생성에는 몇 분 정도 소요됩니다.
Azure Redis Cache를 사용하도록 애플리케이션 구성하기
방금 생성한 Redis Cache를 사용하도록 애플리케이션을 구성해 보겠습니다.
위 화면과 같이 패키지 관리자 콘솔(Package Manager Console)로 이동하여 "Install-Package StackExchange.Redis" 명령을 입력하고 Enter 키를 누릅니다.
이제 필요한 패키지가 성공적으로 설치되었습니다. (.NET Framework가 4 이상이어야 합니다.)
어셈블리를 사용하려면 먼저 Login.aspx 페이지와 Default.aspx 페이지 양쪽에 다음 네임스페이스를 추가해야 합니다.
using StackExchange.Redis;
프로젝트에 "RedisConnection"이라는 새 클래스를 추가합니다. 첨부된 프로젝트를 참고하세요.
Redis Cache에 연결하려면 ConnectionMultiplexer.Connect 함수에 다음 정보를 전달해야 합니다. 해당 정보는 포털에서 확인할 수 있습니다.
- Redis Cache URL
- 액세스 키(Keys)

주의: 액세스 키를 코드에 그대로 저장하지 마세요. 설명의 편의를 위해 여기서는 소스 코드에 키를 넣었지만, 실제 개발 시에는 자격 증명을 안전하게 보관하는 방법을 반드시 참고하시기 바랍니다.
이제 Redis Cache에 연결하는 데 사용할 클래스가 준비되었습니다. 이 클래스를 활용해 Redis Cache에 세션을 생성해 보겠습니다.
Login.aspx.cs 파일을 열고 다음 코드를 찾아 교체합니다.
Session["login"] = this.txtUsername.Text.Trim();
아래 코드로 바꿉니다.
IDatabase cache = RedisConnection.Connection.GetDatabase();
cache.StringSet("login", this.txtUsername.Text.Trim());
이어서 Default.aspx.cs 파일을 열고 다음 코드를 교체합니다.
protected void Page_Load(object sender, EventArgs e)
{
if (Session["login"] == null)
{
Response.Write("Session is NULL. You are in " + Request.ServerVariables["LOCAL_ADDR"]);
}
else
{
Response.Write("Logged in user is " + Session["login"] + ". You are in " + Request.ServerVariables["LOCAL_ADDR"]);
}
}
아래 코드로 바꿉니다.
protected void Page_Load(object sender, EventArgs e)
{
IDatabase cache = RedisConnection.Connection.GetDatabase();
string strLoginValue = cache.StringGet("login");
if (strLoginValue == null)
{
Response.Write("Session is NULL. You are in " + Request.ServerVariables["LOCAL_ADDR"]);
}
else
{
Response.Write("Logged in user is " + strLoginValue + ". You are in " + Request.ServerVariables["LOCAL_ADDR"]);
}
}
코드를 Azure App Service에 배포하고 변경 사항을 확인한 후 로그인 페이지로 이동합니다.
IP 주소를 확인해 보세요. 10.202.174.84입니다. 이제 "Login" 버튼을 클릭하면 Default.aspx 페이지로 이동합니다.
여기서 주목할 점은, IP 주소가 다름에도 불구하고 여전히 사용자 이름 "admin"이 표시된다는 것입니다. 이제 세션이 두 인스턴스 모두에서 접근 가능한 분산 저장소인 "Redis Cache"에 저장되기 때문입니다.
이제 페이지를 여러 번 계속 새로 고침해 보세요. IP 주소는 계속 바뀌지만 사용자 이름 값은 변하지 않는 것을 확인할 수 있습니다.
이것으로 로드 밸런싱 환경에서 세션을 저장하는 방법을 배웠습니다. Redis Cache에는 이 외에도 모든 종류의 데이터를 자유롭게 저장할 수 있습니다.
긴 글을 읽어주셔서 감사합니다. 피드백은 언제나 환영입니다.