Versions Compared

Key

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

스냅샷은 액터의 복구속도를 높일수가 있습니다.

SaveSnapShot을 호출하여 내부상태의 스냅샷을 저장할수가 있습니다.

다음과 같은 메시지액터를 설계한다고 합시다.

  • 단순하게 문자열 메시지만 전송받습니다.
  • 액터가 종료되더라도 최근 50개의 메시지를 복구합니다.
  • 테스트코드에서는 100개의 메시지를 보내고 , 액터를 죽인후 다시 뛰워 복구가되는지 살펴봅니다.


나의 마지막상태, 예를 들면 내가 마지막에 본 페이지를 서비스가 기록을 하고 다른기기를 옮겨가도 

그 상태를 복구하여 그대로 보여줄수 있는 서비스가 있다고 가정해봅시다. 사용자가 페이지를 이동할때마다

전통적인 개발방법은 메인 DB에 기록을 할것이며, 메인 DB에 부하를 주게될것입니다. 어쨋든 어플리케이션은

종료가 되거나 재시작이 될수 있으니까요, 액터의 스냅샷을 이용하면 자신이 원하는 타이밍에 원하는

로컬 스토리지에 저장을하고 마지막 상태를 완벽하게 복원할수가 있습니다. 여기서 원하는 타이밍은

페이지 이동에따른 비용보다 훨씬 큰 비용이 사용되게될시,예를들어 초당 100건이상의 메시지가 발생한다라고 가정해봅시다.

이 모든타이밍에 스냅샷에 기록할수도 있겠지만 낭비적입니다. 그렇게 저장할만큼 IO를 희생해야할 중요한 기능은 아니며

이러한 경우 스냅샷 시기를 느슨하게 하여 어쨋든 장애로인한 재시작 이후 마지막에 저장한 스냅샷을 복구해줄것입니다.

마지막 상태의 자료형을 String으로 가져갈것인지? EventList로 가져갈것인지에따라 이벤트 소싱을 복원할수도 있고

마지막 상태만 복원할수도 있습니다. 스냅샷은 단순하게 무엇을 복원할것인가에 대해 포커싱을 맞춥니다.

액터설계

Code Block
languagec#
themeEmacs
linenumberstrue
 public class MySnapShotActor : UntypedPersistentActor
{
    private ILoggingAdapter log = Context.GetLogger();
    public override string PersistenceId => "my-stable-persistence-id";
    private const int SnapShotInterval = 52;  //스냅샷은2의 5의배수에 배수에스냅샷을 저장합니다.
    private object state = new object();

    //실시간 복구수 전략을 선택합니다. (최근 50개최근5개)
    public override Recovery Recovery => new Recovery(fromSnapshot: new SnapshotSelectionCriteria(minSequenceNr:0, maxSequenceNr: 505, maxTimeStamp: DateTime.UtcNow));
    //public override Recovery Recovery => new Recovery(fromSnapshot: SnapshotSelectionCriteria.NoneLatest);

    protected override void OnRecover(object message)
    {
        // handle recovery here
        if (message is SnapshotOffer offeredSnapshot)
        {
            log.Debug("이전마지막 스냅샷 복구");
            state = offeredSnapshot.Snapshot;
            log.Debug("스냅샷복구완료:" + state);
        }
        else if (message is RecoveryCompleted)
        {
            log.Debug("스냅샷복구완료:");
        }
        else
        {
            // event
            log.Debug("스냅샷e:" + message);
        }
    }

    protected override void OnCommand(object message)
    {
        if (message is SaveSnapshotSuccess s)
        {
            log.Debug("스냅샷 성공");
        }
        else if (message is SaveSnapshotFailure f)
        {
            log.Debug("스냅샷 실패");
        }
        else if (message is string cmd)
        {
            log.Debug("cmd:" + cmd);
            Persist($"evt-{cmd}", e =>
            {
                UpdateState(e);
                if (LastSequenceNr % SnapShotInterval == 0 && LastSequenceNr != 0)
                {
                    log.Debug("스냅샷 시도");
                    SaveSnapshot(state);
                }
            });
        }
    }

    private void UpdateState(string evt)
    {
        state = evt;
    }
}


Test 시나리오

  • 단순하게 문자열 메시지만 전송받습니다.
  • 액터가 종료되더라도 최근 50개의 메시지를 복구합니다.
  • 테스트코드에서는 100개의 메시지를 보내고 , 액터를 죽인후 다시 뛰워 마지막 상태가 복구가되는지 살펴봅니다.

Code Block
languagec#
themeEmacs
linenumberstrue
var actorInfo = Props.Create<MySnapShotActor>();
var myactor = actorSystem.ActorOf(actorInfo, "myActor");
for(int i = 0; i < 1005; i++)
{
    string message = "t" + i;
    myactor.Tell(message);
}

waitForTest(1000);
myactor.Tell(Akka.Actor.Kill.Instance, ActorRefs.NoSender);
waitForTest(1000);
//액터가 복구되는지 확인합니다.
myactor = actorSystem.ActorOf(actorInfo, "myActor");
Expand
titleresult

......중간생략( 0-99까지 5까지 메시지 전송하고, 액터를 죽이고 다시살림)

...... 복구과장 ( 최근 50개의 메시지가 복구가됩니다복구과정 ( 마지막 상태가 복구가됩니다. , 2의배수에서만 스냅샷 저장했으니, 3+1에해당하는 t3이 복구됩니다. )

[DEBUG][2017-09-25 오후 121:5953:2125][Thread 00130025][[akka://ServiceA/user/myActor#160404237myActor#1899122464]] 스냅샷e:evt-t87이전 스냅샷 복구
[DEBUG][2017-09-25 오후 121:5953:2125][Thread 00130025][[akka://ServiceA/user/myActor#160404237myActor#1899122464]] 스냅샷e스냅샷복구완료:evt-t88
[DEBUG][2017-09-25 오후 12:59:21][Thread 0013][[akka://ServiceA/user/myActor#160404237]] 스냅샷e:evt-t89
[DEBUG][2017-09-25 오후 12:59:21][Thread 0013][[akka://ServiceA/user/myActor#160404237]] 스냅샷e:evt-t90
[DEBUG][2017-09-25 오후 12:59:21][Thread 0013][[akka://ServiceA/user/myActor#160404237]] 스냅샷e:evt-t91
[DEBUG][2017-09-25 오후 12:59:21][Thread 0013][[akka://ServiceA/user/myActor#160404237]] 스냅샷e:evt-t92
[DEBUG][2017-09-25 오후 12:59:21][Thread 0013][[akka://ServiceA/user/myActor#160404237]] 스냅샷e:evt-t93
[DEBUG][2017-09-25 오후 12:59:21][Thread 0013][[akka://ServiceA/user/myActor#160404237]] 스냅샷e:evt-t94
[DEBUG][2017-09-25 오후 12:59:21][Thread 0013][[akka://ServiceA/user/myActor#160404237]] 스냅샷e:evt-t95
[DEBUG][2017-09-25 오후 12:59:21][Thread 0013][[akka://ServiceA/user/myActor#160404237]] 스냅샷e:evt-t96
[DEBUG][2017-09-25 오후 12:59:21][Thread 0013][[akka://ServiceA/user/myActor#160404237]] 스냅샷e:evt-t97
[DEBUG][2017-09-25 오후 12:59:21][Thread 0013][[akka://ServiceA/user/myActor#160404237]] 스냅샷e:evt-t98
[DEBUG][2017-09-25 오후 12:59:21][Thread 0013][[akka://ServiceA/user/myActor#160404237]] 스냅샷e:evt-t99
[DEBUG][2017-09-25 오후 12:59:21][Thread 0013][[akka://ServiceA/user/myActor#160404237]] 스냅샷복구완료:

...

t3

위 테스트는 어플리케이션을 껏다가 켜도, 액터의 마지막 상태가 복원이 됩니다.

Store Plugin

Image Added

스냅샷은 다양한 저장소에 저장이 가능하며,  akka.config에서 지정가능합니다.

여기서 주로 설명하는 방법이, 메인DB를 혹사시키지 않는 방법에 대해서이지만

저장 플러그인 설정에따라 로컬 DB로할지? 메인 DB로 할지? 그냥 DISK로할지?

데이터의 중요도에따라 전략화가 가능합니다.


Panel
title설정예

akka.persistence.journal.inmem {
class = "Akka.Persistence.Journal.MemoryJournal, Akka.Persistence"
# Dispatcher for the plugin actor.
plugin-dispatcher = "akka.actor.default-dispatcher"
}

akka.persistence.snapshot-store.inmem {
# Class name of the plugin.
class = "Akka.Persistence.Snapshot.MemorySnapshotStore, Akka.Persistence"
# Dispatcher for the plugin actor.
plugin-dispatcher = "akka.actor.default-dispatcher"
}

akka.persistence.snapshot-store.local {
# Class name of the plugin.
}


별다른설정이없으면, 파일에 저장이 됩니다. 

Image Added


참고 url : https://petabridge.com/blog/intro-to-persistent-actors/

...