스냅샷은 앞장 이벤트소싱과 함께 사용이되었습니다. 그 컨셉은 간단합니다. 무수히 발생하는 이벤트로 인해 우리가 설계한 어떠한 객체의 상태는

지속적으로 변경된다는 점이며 필요한 순간의 청사진만 찍어서 그것을 적절하게 활용할수 있다란 것입니다.



상태를 기록하는 범위주기에따라 아래와 같은 차이가 있으며, 그 차이를통해 스냅샷을 이해할수가 있습니다.

  • 발생 이벤트 저장 : 상태복원은 연속된 이벤트 재생을 통해 가능할수있으며 리플레이와같은 구현을 위해 이벤트 자체를 모두 기록 -이벤트소싱에활용
  • 상태변경 히스토리 저장 : 모든 이벤트를 기록할필요없이 특정시점의 상태를 복원을 하면되기 때문에, 원하는 만큼 상태를 기록함 -스냅샷
  • 마지막 상태만 저장 : 기존값을 Update하고 마지막 저장 상태만 유지함 -보편적인 Update방식


최근 이벤트 100개를 유지하는것과 ( 순수 Persist기능), 1000번째마다 스냅샷을 찍어서 100개를 유지하는것은

각각 요구하는 목적이 다르며 필요로하는 성능이 다를수 있으나 함께 작동을 하여 상호보완하는 컨셉이며

VMWare를 포함하여 데이터베이스 복구등에서 보편적으로 사용되는 장치입니다.


스냅샷 구현

@Component
@Scope("prototype")
public class SnapShotActor extends AbstractPersistentActor {
	
	private final LoggingAdapter log = Logging.getLogger(getContext().system(), "AbstractPersistentActor");	  
		
	private Object state;
	private int snapShotInterval = 5;
	
	private int msgCnt = 0;
	
    @Override
    public String persistenceId() { return "ExamplePersistentActor-id-1"; }
    
    @Override public Receive createReceiveRecover() {
	  return receiveBuilder().
	    match(SnapshotOffer.class, s -> {
	      state = s.snapshot();	//상태복원
	      log.info("상태복원");
	      // ...
	    }).
	    match(String.class, s -> {/* ...*/}).build();
	}
    
    @Override	//복구전략(스냅샷을 무시할수도 있음)
    public Recovery recovery() {
      //return Recovery.create(SnapshotSelectionCriteria.none());    	
      return Recovery.create(
        SnapshotSelectionCriteria
          .create(457L, System.currentTimeMillis()));
    }
	 
	// 스냅샷을 지원하는 메시지 정의
	@Override public Receive createReceive() {
	  return receiveBuilder().
	    match(SaveSnapshotSuccess.class, ss -> {
	      SnapshotMetadata metadata = ss.metadata();	      
	      // ...
	    }).
	    match(SaveSnapshotFailure.class, sf -> {
	      SnapshotMetadata metadata = sf.metadata();
	      // ...
	    }).	      
	    match(String.class, cmd -> {
	    	log.info("EventFired:"+cmd);
	    	if(cmd.indexOf("print") ==0 ) {
	    		//로그를 통해 확인하거나?
	    		log.info("상태확인:"+state);
	    		// 전송자에게 메시지를 통해 상태를 알려줌
	    		sender().tell(state, ActorRef.noSender());	    		
	    	}else {
	    		msgCnt++;	    		
	    		state = cmd + "을 먹은 상태";		//커멘드에따른 상태변화
	    		log.info("ChangeStated" + state);
	    		
	    		if (msgCnt % snapShotInterval == 0 ) {
		    		//이벤트가 ?회 발생할때만,상태를 변경하고 스냅샷을 찍음 ( 순수스냅샷 테스트 )	        		        	  		    				    		
		    		log.info("SaveSnapShot:" + state);
		    		saveSnapshot(state);	    		
		    	}	    		
	    	}
	    })	    
	    .build();
	}
}

이벤트를 받게되면, 상태가 변경되게되며 자신이 원하는 타이밍에

카메라 셔터(saveSnapshot) 를 누르기만 하면됩니다.

여기서는 수많은 이벤트를 모두 저장하는것은 비효율적이니, 매 5번째 상태의 스냅샷정보를 유지하기로 하였다고 가정하였습니다.

실제, 앞장 샘플에서는 persist기능과 연동되어 스냅샷과 상관없이 최근 X개 유지기능을 통해 , 마지막 상태복구가가능합니다.


스냅샷 복원전략

@Override
public Recovery recovery() {
  return Recovery.create(
    SnapshotSelectionCriteria
      .create(457L, System.currentTimeMillis()));
}

마지막 상태를 복원(SnapshotOffer - 추천복원 )할수도 있고,

스냡샷은 히스토리 관리가 되기때문에

특정 시간의 스냅샷(SnapshotSelectionCriteria)을 복원할수도 있습니다.


스냅샷 테스트

protected void persistenceSnapShot()  {
    new TestKit(system) {{
    	ActorRef probe = getRef();	    	
    	
		Props snapShotActorProp = ext.props("snapShotActor");			
		System.out.println("===== snapShotActor 액터생성");
		ActorRef snapShotActor = system.actorOf(snapShotActorProp, "snapShotActor");
		
		System.out.println("=====  event 생성");
		snapShotActor.tell("커피", ActorRef.noSender());
		snapShotActor.tell("사탕", ActorRef.noSender());
		snapShotActor.tell("커피", ActorRef.noSender());
		snapShotActor.tell("스테이크", ActorRef.noSender());
		snapShotActor.tell("라면", ActorRef.noSender()); // <-- 복구기대 상태
		snapShotActor.tell("사탕", ActorRef.noSender());  
		snapShotActor.tell("커피", ActorRef.noSender());

		System.out.println("===== 상태확인");
		snapShotActor.tell( "print" , ActorRef.noSender());			
		expectNoMessage(java.time.Duration.ofSeconds(1));
		
		System.out.println("=====  snapShotActor 종료또는 비정상종료");
		snapShotActor.tell( akka.actor.PoisonPill.getInstance() , ActorRef.noSender());			
		expectNoMessage(java.time.Duration.ofSeconds(1));
		
		
		System.out.println("=====  snapShotActor 재생성");
		ActorRef snapShotActo2 = system.actorOf(snapShotActorProp, "eventActor");
		
		System.out.println("=====  상태 복원확인을 위해 probe를 액터 참조자로 지정");
		snapShotActo2.tell( "print" , probe );
		
		// === 기대결과 1초이내에 '라면을 먹은 상태'임을 확인 : 비동기 메시지를 검사하는방법중하나  
		expectMsgEquals(java.time.Duration.ofSeconds(1), "라면을 먹은 상태" );			
    }};
}

===== snapShotActor 액터생성
===== event 생성
===== 상태확인
[INFO] [06/20/2018 09:22:34.811] [AkkaTestApp-akka.actor.default-dispatcher-2] [AbstractPersistentActor] 상태복원
[INFO] [06/20/2018 09:22:34.830] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] EventFired:커피
[INFO] [06/20/2018 09:22:34.830] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] ChangeStated커피을 먹은 상태
[INFO] [06/20/2018 09:22:34.830] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] EventFired:사탕
[INFO] [06/20/2018 09:22:34.830] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] ChangeStated사탕을 먹은 상태
[INFO] [06/20/2018 09:22:34.831] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] EventFired:커피
[INFO] [06/20/2018 09:22:34.831] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] ChangeStated커피을 먹은 상태
[INFO] [06/20/2018 09:22:34.831] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] EventFired:스테이크
[INFO] [06/20/2018 09:22:34.831] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] ChangeStated스테이크을 먹은 상태
[INFO] [06/20/2018 09:22:34.831] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] EventFired:라면
[INFO] [06/20/2018 09:22:34.831] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] ChangeStated라면을 먹은 상태
[INFO] [06/20/2018 09:22:34.831] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] SaveSnapShot:라면을 먹은 상태
[INFO] [06/20/2018 09:22:34.831] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] EventFired:사탕
[INFO] [06/20/2018 09:22:34.831] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] ChangeStated사탕을 먹은 상태
[INFO] [06/20/2018 09:22:34.831] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] EventFired:커피
[INFO] [06/20/2018 09:22:34.831] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] ChangeStated커피을 먹은 상태
[INFO] [06/20/2018 09:22:34.832] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] EventFired:print
[INFO] [06/20/2018 09:22:34.832] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] 상태확인:커피을 먹은 상태
===== snapShotActor 종료또는 비정상종료
===== snapShotActor 재생성
===== 상태 복원확인을 위해 probe를 액터 참조자로 지정
[INFO] [06/20/2018 09:22:36.754] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] 상태복원
[INFO] [06/20/2018 09:22:36.755] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] EventFired:print
[INFO] [06/20/2018 09:22:36.755] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] 상태확인:라면을 먹은 상태

샘플은 모든 이용자가 먹을때마다 이벤트가 발생하고,그 시식 결과에의해 어떠한 상태로 변경이 됩니다.

이벤트드리븐 의 예를 간단하게 설명하는것으로 대단한 기능이 있는것은 아닙니다. 

단지 이벤트는 무수히 발생하며 모든것을 기록하고 이용하기 어려우니, 스냅샷은 특정한 이벤트 조건에따라(여기서는 단순하게 5번째)

사진을 찍게되며 , 모든 사용자의 청사진을 최근 몇개까지 운영할것인가를 성능과 복원을 고려하여 설계에 반영할수 있다란것입니다..

최근 메시지를 유지하는것과 변경된 상태의 청사진을 오랫동안 유지하는것은 각각 다른 성능비용이 발생합니다.

스냅샷 시도에따른 반응 메시지

MethodSuccessFailure message
saveSnapshot(Any)SaveSnapshotSuccessSaveSnapshotFailure
deleteSnapshot(Long)DeleteSnapshotSuccessDeleteSnapshotFailure
deleteSnapshots(SnapshotSelectionCriteria)DeleteSnapshotsSuccessDeleteSnapshotsFailure

스냅샷은 다양한 이유로(디스크풀및 메모리풀등) 실패가 있을수 있으며 그에 대응하는 코드작성도 가능합니다.

AKKA의 에러처리는 Exception을 발생하여 하위 컴포넌트에게 전가하기보다

주로 상위 컴포넌트에게 메시지를통해 알려주는 방식을 선택합니다.


참고Link :



  • No labels
Write a comment…