아키텍처

Aspectran Session Manager: Deep Dive

1. 소개

Aspectran은 단순한 프레임워크를 넘어, 다양한 실행 환경에서 일관된 개발 경험을 제공하는 것을 목표로 합니다. 이러한 철학의 핵심에 Aspectran Session Manager가 있습니다. 이 컴포넌트는 웹 애플리케이션의 전유물로 여겨졌던 ‘세션’의 개념을 모든 실행 환경(Web, Shell, Daemon 등)에서 사용 가능하도록 재정의한, 강력하고 유연한 상태 관리(State Management) 솔루션입니다.

이 문서는 Aspectran Session Manager의 내부 아키텍처, 핵심 기능, 설정 방법을 심도 있게 분석하여 개발자가 그 잠재력을 최대한 활용할 수 있도록 돕는 것을 목표로 합니다.


2. 핵심 아키텍처와 컴포넌트

Aspectran 세션 매니저는 역할별로 명확하게 분리된 컴포넌트들의 유기적인 조합으로 구성됩니다. 각 컴포넌트의 역할과 상호작용을 이해하는 것이 중요합니다.

2.1. 주요 컴포넌트 상세

  • SessionManager (구현체: DefaultSessionManager)
    • 역할: 세션 관리의 모든 흐름을 제어하는 중앙 컨트롤러이자 외부로 노출되는 진입점입니다. 세션의 생성, 조회, 무효화 등 전체 생명주기를 총괄합니다.
    • 내부 동작: SessionCache, SessionStore, HouseKeeper 등 다른 핵심 컴포넌트들을 조율하여 동작합니다. 세션 조회 요청이 오면, 먼저 SessionCache를 확인하고, 없으면 SessionStore에서 로드하는 전략을 사용합니다.
  • Session (구현체: ManagedSession)
    • 역할: 하나의 세션을 나타내는 데이터 객체입니다. 세션 ID, 생성 시각, 마지막 접근 시각과 같은 메타데이터와 사용자가 저장하는 속성(attribute)들을 내부에 저장합니다.
  • SessionCache (구현체: DefaultSessionCache)
    • 역할: 활성화된 세션 객체들을 메모리에 보관하는 캐시 계층입니다. SessionStore(파일, Redis 등)에 대한 물리적 접근을 최소화하여 세션 조회 성능을 극대화합니다.
  • SessionStore (인터페이스)
    • 역할: 세션 데이터를 영속적으로 저장하고 관리하는 저장소 추상화 계층입니다. 이 인터페이스 덕분에 세션 저장 방식을 자유롭게 교체(Pluggable)할 수 있습니다.
    • 주요 구현체: FileSessionStore, LettuceSessionStore
  • HouseKeeper
    • 역할: 주기적으로 실행되어 만료된 세션을 정리하는 백그라운드 스레드, 즉 세션 청소부(Scavenger)입니다. HouseKeeper가 없다면 만료된 세션이 시스템에 계속 남아 리소스 누수를 유발하게 됩니다.
  • SessionIdGenerator
    • 역할: 모든 세션에 대해 전역적으로 고유한(globally unique) ID를 생성합니다.

2.2. 컴포넌트 상호작용 흐름

  • 세션 생성 흐름
    1. SessionManager.createSession() 호출
    2. SessionIdGenerator가 새 ID 생성
    3. ManagedSession 객체 생성
    4. SessionCache에 세션 추가
    5. SessionStore.save()를 통해 세션을 영구 저장소에 기록
  • 세션 조회 흐름
    1. SessionManager.getSession(id) 호출
    2. SessionCache.get(id)로 메모리 캐시에서 우선 조회
    3. (캐시 miss 시) SessionStore.load(id)로 영구 저장소에서 로드
    4. 로드한 세션을 SessionCache에 추가 후 반환
  • 세션 정리 흐름
    1. SessionScheduler가 정해진 시간마다 HouseKeeper 실행
    2. HouseKeeperSessionStore에서 모든 세션 ID를 가져와 만료 여부 체크
    3. 만료된 세션 발견 시 session.invalidate() 호출
    4. SessionCache에서 해당 세션 제거
    5. SessionStore.delete()를 통해 영구 저장소에서 최종 삭제

3. 교체 가능한 저장소와 클러스터링

SessionStore 인터페이스는 Aspectran 세션 관리의 가장 강력한 특징 중 하나로, 애플리케이션의 확장성을 좌우합니다.

3.1. FileSessionStore

  • 동작: SessionData 객체를 Java 직렬화 메커니즘을 통해 파일로 저장합니다. 각 세션은 별개의 파일로 관리됩니다.
  • 장점: 외부 의존성이 없어 구성이 간단하고, 단일 서버 환경이나 개발 환경에서 사용하기 편리합니다.
  • 단점: 여러 서버 인스턴스가 파일 시스템을 공유하기 어렵기 때문에 세션 클러스터링 환경에는 부적합합니다.

3.2. LettuceSessionStore (Redis 기반)

  • 동작: 고성능 비동기 Redis 클라이언트인 Lettuce를 사용하여 세션 데이터를 Redis에 저장합니다. 이를 통해 여러 애플리케이션 서버가 중앙의 Redis를 통해 세션을 공유하는 세션 클러스터링을 구현할 수 있습니다.
  • Redis 데이터 구조: 각 세션은 Redis의 String 타입으로 저장됩니다. 키는 네임스페이스:세션ID (예: aspectran:session:xxxxx) 형태이며, 값은 SessionData 객체가 직렬화된 바이트 배열입니다.

3.3. 동작 모드 비교: 단일 서버 vs. 클러스터

SessionManagerConfigclusterEnabled 플래그는 세션 데이터의 신뢰도와 동기화 전략을 결정하는 핵심 스위치입니다.

단일 서버 모드 (clusterEnabled: false)

  • 데이터 신뢰도: SessionCache(메모리)를 우선적으로 신뢰합니다.
  • 동작 방식: 세션 조회 시, 먼저 메모리 캐시를 확인하고 데이터가 있으면 즉시 반환합니다. SessionStore(영구 저장소)는 주로 서버 재시작 시 복구를 위한 백업 용도로 사용됩니다. 데이터가 여러 곳에서 동시에 변경될 우려가 없으므로, 최고의 성능을 위해 메모리 내에서만 읽고 쓰는 것을 목표로 합니다.
  • 저장 시점:
    • saveOnCreatefalse인 경우, 세션을 사용한 마지막 요청이 완료될 때 저장됩니다. 이는 단일 요청 내에서 생성되고 소멸하는 짧은 세션(예: 봇 접속)에 대해 불필요한 저장소 접근을 막는 성능 최적화입니다.
    • saveOnCreatetrue이면, 세션 생성 즉시 저장됩니다.
  • 주요 목표: 최고의 성능. 불필요한 저장소 I/O를 최소화합니다.

클러스터 모드 (clusterEnabled: true)

  • 데이터 신뢰도: SessionStore(Redis 등 중앙 저장소)를 유일한 최종 데이터 저장소(Single Source of Truth)로 신뢰합니다.
  • 동작 방식: 로드밸런서에 의해 여러 서버로 요청이 분산되는 환경을 전제로 합니다. 각 서버의 로컬 메모리 캐시는 “복사본”으로 취급되며, 데이터 일관성을 위해 중앙 저장소와 지속적으로 동기화합니다.
    • 세션 로드: 로컬 캐시에 세션이 있더라도, 다른 서버에 의해 변경되었을 가능성을 염두에 두고 중앙 저장소의 데이터와 비교하여 최신 상태를 유지합니다.
    • 세션 저장: 데이터 일관성을 위해 여러 시점에 걸쳐 적극적으로 저장 작업이 발생합니다.
      1. 생성 시점: 새로운 세션이 만들어지면 즉시 중앙 저장소에 저장되어 클러스터의 모든 서버가 인식할 수 있게 합니다.
      2. 마지막 요청 완료 시점: 요청 처리 중 세션 속성이 변경되었을 수 있으므로, 마지막 요청이 끝나면 변경 사항을 중앙 저장소에 반영합니다.
      3. 메모리에서 제거(Eviction)될 때: 특정 서버의 메모리에서 비활성 세션이 제거될 때, 그 직전의 최종 상태를 중앙 저장소에 안전하게 저장합니다.
  • 주요 목표: 데이터 일관성. 어떤 서버 노드에서 요청을 처리하든 항상 동일한 세션 상태를 보장합니다.
구분단일 서버 모드 (clusterEnabled: false)클러스터 모드 (clusterEnabled: true)
데이터 신뢰도SessionCache (메모리)를 우선 신뢰SessionStore (중앙 저장소)를 최종 신뢰
세션 로드 전략캐시에 있으면 저장소 접근 안 함캐시에 있어도 저장소와 비교하여 데이터 정합성 확인
세션 저장 전략마지막 요청 완료 시점에 주로 저장 (성능 최적화)생성, 요청 완료, 제거 등 여러 시점에 저장 (일관성 보장)
주요 목표최고의 성능데이터 일관성

4. 정교한 세션 생명주기 관리

Aspectran은 불필요한 세션을 신속하게 제거하여 시스템 리소스를 보호하는 정교한 타임아웃 정책을 가지고 있습니다.

4.1. 신규 세션과 일반 세션의 분리

  • 문제점: 웹 크롤러, 봇, 로드밸런서 헬스 체크 등은 세션을 생성만 하고 실제 상호작용은 하지 않아, 불필요한 “유령 세션”이 대량으로 쌓일 수 있습니다.
  • 해결책: Aspectran은 세션 생성 후 첫 번째 요청에만 ‘신규 세션’으로 간주하여 특별 타임아웃을 적용하고, 두 번째 요청부터는 ‘일반 세션’으로 승격시켜 일반 타임아웃을 적용합니다.

4.2. 상세 설정 항목 분석

다음은 데모 사이트에 적용된 세션 설정 값입니다. 데모 사이트의 특성 상 웹 크롤러 또는 웹 오토메이션 도구의 테스트 대상이 될 수 있습니다. 이런 경우 최대 세션 수가 초과되는 현상을 방지하기 위해 신규 세션의 최대 유휴 시간을 최대한 짧게 설정하는 것이 유리합니다.

<bean class="com.aspectran.core.context.config.SessionManagerConfig">
    <arguments>
        <item>
            workerName: jn0
            maxActiveSessions: 99999
            maxIdleSeconds: 489
            evictionIdleSeconds: 258
            maxIdleSecondsForNew: 60
            evictionIdleSecondsForNew: 30
            scavengingIntervalSeconds: 90
            clusterEnabled: true
        </item>
    </arguments>
</bean>
  • workerName (jn0): 세션 ID에 포함될 워커의 고유 이름입니다. 하나의 애플리케이션에서 여러 세션 매니저를 사용할 경우, 세션 ID 충돌을 방지하기 위해 이 값을 중복되지 않게 설정해야 합니다.
  • maxActiveSessions (99999개): 동시에 활성화될 수 있는 최대 세션 수를 99999개로 지정합니다.
  • maxIdleSeconds (489초): 일반 세션의 최대 유휴 시간. 세션이 이 시간 동안 사용되지 않으면 ‘만료’ 상태가 됩니다.
  • maxIdleSecondsForNew (60초): 신규 세션(첫 번째 요청 중인 세션)에만 적용되는 특별 최대 유휴 시간입니다. 두 번째 요청부터 세션은 일반 세션으로 승격되어 이 설정의 영향을 받지 않습니다.
  • scavengingIntervalSeconds (90초): HouseKeeper(세션 청소부)가 만료된 세션을 영구 삭제하기 위해 실행되는 주기(초)입니다.
  • evictionIdleSeconds (258초): 세션이 비활성 상태일 때 메모리 캐시에서 제거(Evict)되기까지의 유휴 시간(초)입니다. 이 설정은 메모리 관리를 위한 캐시 정책의 핵심입니다. (참고: 내부적으로 이 값은 HouseKeeper가 만료된 세션을 영구 삭제할 때의 유예 시간으로도 활용됩니다.)
  • evictionIdleSecondsForNew (30초): 신규 세션에 대한 위의 캐시 제거 정책 시간(초)입니다.
  • clusterEnabled (true): 클러스터링 환경임을 명시하여 세션 데이터의 일관성을 보장하는 모드로 동작시킵니다. (상세 내용은 3.3절 참조)
  • saveOnCreate (불리언): true이면 세션 생성 즉시 영구 저장소에 저장합니다. false이면, 세션을 사용한 마지막 요청이 완료될 때 저장되어 불필요한 I/O를 줄이는 성능 최적화를 제공합니다. (단, clusterEnabledtrue이면 이 값과 상관없이 항상 생성 시점에 저장됩니다.)
  • saveOnInactiveEviction (불리언): true이면, 비활성 상태가 되어 메모리 캐시에서 제거(evict)될 때, 제거 직전에 세션 데이터를 영구 저장소에 저장합니다. 이는 세션 데이터의 유실을 방지합니다. (단, clusterEnabledtrue이면 이 값과 상관없이 항상 제거 시점에 저장됩니다.)
  • removeUnloadableSessions (불리언): true이면, 영구 저장소에서 세션 데이터를 읽어올 수 없을 때(예: 클래스 변경으로 인한 역직렬화 실패), 해당 세션 데이터를 저장소에서 삭제합니다.

5. 환경 독립성과 API 일관성

  • SessionManager: 환경에 구애받지 않는 독립적인 세션 기능이 필요할 때 사용합니다. (예: 데몬 애플리케이션)
  • SessionAdapter: 웹 환경의 HttpSession처럼, 특정 환경이 제공하는 네이티브 세션을 Aspectran 표준 API로 사용할 수 있게 해주는 어댑터입니다. 개발자는 activity.getSessionAdapter()를 통해 환경에 맞는 세션을 일관된 방식으로 사용할 수 있어 코드의 재사용성이 극대화됩니다.

6. 영속성 제어: @NonPersistent

@NonPersistent 어노테이션을 세션에 저장할 객체의 클래스에 붙이면, 해당 객체는 SessionStore에 저장되지 않습니다. SessionData가 직렬화될 때, 속성 객체의 클래스에 이 어노테이션이 있는지 검사하여 저장 과정에서 제외합니다.

  • 사용 사례:
    • 직렬화 불가능 객체: Socket, DB Connection
    • 보안 데이터: 비밀번호, 개인키 등 민감 정보
    • 성능 최적화: 불필요한 임시 데이터를 제외하여 직렬화 오버헤드 감소
import com.aspectran.core.component.session.NonPersistent;

@NonPersistent
public class MyTemporaryData implements java.io.Serializable {
    // 이 객체는 파일이나 Redis에 저장되지 않습니다.
}

7. 설정 예제

예제 1: 쉘 또는 데몬 환경을 위한 세션 설정

aspectran-config.apon 파일에서 쉘 또는 데몬 서비스의 세션을 직접 설정합니다. 웹 환경이 아니기 때문에 정교한 세션 생명주기 관리를 필요로 하지 않습니다.

shell: {
    session: {
        workerName: shell
        maxActiveSessions: 1
        maxIdleSeconds: 1800
        scavengingIntervalSeconds: 600
        fileStore: {
            storeDir: /work/_sessions/shell
        }
        enabled: true
    }
}

예제 2: 프로필 기반의 XML 빈 설정 (웹 환경)

프로필을 사용하여 운영 환경에 따라 세션 저장소를 동적으로 전환합니다.

<bean id="tow.context.jpetstore.sessionManager"
      class="com.aspectran.undertow.server.session.TowSessionManager">
    <properties>
        <item name="sessionManagerConfig">
            <bean class="com.aspectran.core.context.config.SessionManagerConfig">
                <arguments>
                    <item>
                        workerName: jn0
                        maxActiveSessions: 99999
                        maxIdleSeconds: 489
                        maxIdleSecondsForNew: 60
                        scavengingIntervalSeconds: 90
                        clusterEnabled: true
                    </item>
                </arguments>
            </bean>
        </item>
    </properties>
    <!-- 기본 프로필: FileStore 사용 -->
    <properties profile="!prod">
        <item name="sessionStore">
            <bean class="com.aspectran.core.component.session.FileSessionStoreFactoryBean">
                <properties>
                    <item name="storeDir">/work/_sessions/jpetstore</item>
                </properties>
            </bean>
        </item>
    </properties>
    <!-- 'prod' 프로필 활성화 시: RedisStore 사용 -->
    <properties profile="prod">
        <item name="sessionStore">
            <bean class="com.aspectran.core.component.session.redis.lettuce.DefaultLettuceSessionStoreFactoryBean">
                <properties>
                    <item name="poolConfig">
                        <bean class="com.aspectran.core.component.session.redis.lettuce.RedisConnectionPoolConfig">
                            <properties>
                                <item name="uri">%{system:redis.uri}/11</item>
                            </properties>
                        </bean>
                    </item>
                </properties>
            </bean>
        </item>
    </properties>
</bean>

8. 결론

Aspectran Session Manager는 단순한 상태 저장소가 아닌, 현대적인 애플리케이션 아키텍처의 요구사항을 충족하는 엔터프라이즈급 상태 관리 프레임워크입니다. 교체 가능한 저장소, 정교한 생명주기 관리, 환경 독립적 API 제공을 통해, 개발자는 단일 노드 기반의 간단한 애플리케이션부터 대규모 분산 서비스에 이르기까지 모든 환경에서 안정적이고 효율적으로 사용자 상태를 관리할 수 있습니다.