Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Info

스냅샷은 앞장 이벤트소싱에서도 부분적으로 이벤트소싱과 함께 사용이되었습니다. 스냅샷은 성능상의 목적으로 다양한곳에서 혼합되어 사용될수 있으며 그 컨셉은 간단합니다. 무수히 발생하는 이벤트로 인해 우리가 설계한 어떠한 객체의 상태는

지속적으로 변경된다는 점이며 모든 상태변화를 기록하는것은 불필요할수도 있으며, 필요한 순간의 청사진만 찍어서 그것을 적절하게 활용할수 있다란 것입니다.



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

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

AKKA에서의 스냅샷은 실시간 이벤트 모두를 저장해야 요구와, 중요한 최근 몇건은 꼭 저장해야 하는 지속력에 있어서

중간쯤에 위치하여 조율을하는 장치로 생각할수가 있으며, 메시지 혹은 상태변경 히스토리 저장 기능을 도메인 모델별로

각각 별도로 만들지 않아도 일괄적인 방법으로 활용을 할수가 있습니다.

  • 저장 상태만 유지함 -보편적인 Update방식


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

각각 요구하는 성능이 다르며 다른 목적으로 활용이 될수가 있으나 함께 작동 한다란것입니다.

연관 키워드 : difference between redo and snapshot

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

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


스냅샷 구현

Code Block
languagejava
themeEmacs
@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 ) {
		    		//이벤트가 ?회 발생할때만,상태를 변경하고 스냅샷을 찍음 ( 순수스냅샷 테스트를 위해 persist기능을 뺏습니다.) 테스트 )	        		        	  		    				    		
		    		log.info("SaveSnapShot:" + state);
		    		saveSnapshot(state);	    		
		    	}	    		
	    	}
	    })	    
	    .build();
	}
}

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

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

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

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


스냅샷

...

복원전략

Code Block
languagejava
themeEmacs
private Object state;

@Override public Receive createReceiveRecover() {
  return receiveBuilder().
    match(SnapshotOffer.class, s -> {
      state = s.snapshot();
      // ...
    }).
    match(String.class, s -> {/* ...*/}).build();
}


또는


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

...

Code Block
languagejava
themeEmacs
	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===== 마지막 상태snapShotActor 확인재생성");
			ActorRef snapShotActo2 = system.actorOf(snapShotActorProp, "eventActor");
			
			System.out.println("=====  상태 복원확인 복원확인을 위해 probe를 액터 참조자로 지정");
			snapShotActo2.tell( "print" , probe ActorRef.noSender());
		
		// === 기대결과 1초이내에 '라면을 먹은 상태'임을 확인 : 비동기 메시지를 검사하는방법중하나  
			expectNoMessageexpectMsgEquals(java.time.Duration.ofSeconds(1));				       
, "라면을 먹은 상태" );			
	    }};
	}
Expand
title수행결과

===== snapShotActor 액터생성
===== event 생성
===== 상태확인
[INFO] [06/1720/2018 1509:4322:1034.283811] [AkkaTestApp-akka.actor.default-dispatcher-52] [AbstractPersistentActor] 상태복원
[INFO] [06/1720/2018 1509:4322:1034.398830] [AkkaTestApp-akka.actor.default-dispatcher-43] [AbstractPersistentActor] EventFired:커피
[INFO] [06/20/2018 09:22:34.830] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] ChangeStated커피을 먹은 상태
[INFO] [06/1720/2018 1509:4322:1034.400830] [AkkaTestApp-akka.actor.default-dispatcher-43] [AbstractPersistentActor] EventFired:사탕
[INFO] [06/20/2018 09:22:34.830] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] ChangeStated사탕을 먹은 상태
[INFO] [06/1720/2018 1509:4322:1034.400831] [AkkaTestApp-akka.actor.default-dispatcher-43] [AbstractPersistentActor] EventFired:커피
[INFO] [06/17/2018 15:43:10.400/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-43] [AbstractPersistentActor] EventFired:스테이크
[INFO] [06/20/2018 09:22:34.831] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] ChangeStated스테이크을 먹은 상태
[INFO] [06/1720/2018 1509:4322:1034.403831] [AkkaTestApp-akka.actor.default-dispatcher-53] [AbstractPersistentActor] EventFired:라면
[INFO] [06/17/2018 15:43:10.40320/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-53] [AbstractPersistentActor] SaveSnapShot:라면을 먹은 상태
[INFO] [06/1720/2018 1509:4322:1034.405831] [AkkaTestApp-akka.actor.default-dispatcher-53] [AbstractPersistentActor] EventFired:사탕
[INFO] [06/20/2018 09:22:34.831] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] ChangeStated사탕을 먹은 상태
[INFO] [06/1720/2018 1509:4322:1034.405831] [AkkaTestApp-akka.actor.default-dispatcher-53] [AbstractPersistentActor] EventFired:커피
[INFO] [06/20/2018 09:22:34.831] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] ChangeStated커피을 먹은 상태
[INFO] [06/1720/2018 1509:4322:1034.406832] [AkkaTestApp-akka.actor.default-dispatcher-53] [AbstractPersistentActor] EventFired:print
[INFO] [06/1720/2018 1509:4322:1034.406832] [AkkaTestApp-akka.actor.default-dispatcher-53] [AbstractPersistentActor] 상태확인:라면을 커피을 먹은 상태
===== snapShotActor 종료또는 비정상종료
snapShotActor 마지막 상태 확인
상태 복원확인===== snapShotActor 재생성
===== 상태 복원확인을 위해 probe를 액터 참조자로 지정
[INFO] [06/1720/2018 1509:4322:1136.887754] [AkkaTestApp-akka.actor.default-dispatcher-23] [AbstractPersistentActor] 상태복원
[INFO] [06/1720/2018 1509:4322:1136.889755] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] EventFired:print
[INFO] [06/1720/2018 1509:4322:1136.889755] [AkkaTestApp-akka.actor.default-dispatcher-3] [AbstractPersistentActor] 상태확인:라면을 먹은 상태

...

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

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

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


참고Link :

...