그냥 사는 이야기

React Native에서 AWS Appsync의 subscription reconnect 시키기 본문

Development/App

React Native에서 AWS Appsync의 subscription reconnect 시키기

없다캐라 2022. 6. 10. 16:04
반응형

근래에 GraphQL을 사용하는데 AWS AppSync로 작업을 많이 하고 있습니다. REST API보다 편하지만 비슷한 성격인 Query, Mutation과 달리 Subscription은 웹소켓통신으로 연결해서 소켓링크를 생성해서 작업하는 것입니다.

Building a real-time WebSocket client

이 subscription을 사용하는 것은 어렵지 않아서 무엇인가 넘어오면 이벤트 처리하듯 사용하면 되는데 모바일 환경에서는 이 세션이 끊어 질 경우를 대비해야 합니다. 이런 예외가 모바일에서는 꽤 빈번히 발생하기에 reconnect 시켜주는 것은 필수에 가깝지 않을까 생각합니다. 그래서 사실은 적절한 처리법이 있을 거라 생각했는데 ...  막상 검색하면 방법을 못찾았습니다. 비슷한 문제를 공유한 사람은 많은데 딱히 Ctrl-C, Ctrl-P 로 해결하기 쉽게 나와있는게 없어서 이렇게 글을 작성하게 되었습니다.

기본 적인 Subscription 구현

import Amplify, { API, graphqlOperation } from "aws-amplify";

const subscription = API.graphql(
        graphqlOperation(onSearchResultSubscription, { queryId: search.id })
    ).subscribe({
        next: (result) => {
            // 구독으로부터 데이터 업데이트 수신 중지
            subscription.unsubscribe();
            console.log(result);
        }
    });

subscription 구현은 대략 위의 코드 형태대로 구현합니다. 여기서 next: 부분을 이벤트 처리부를 넣어주면 되고 unsubscribe()로 자원 해제 시켜주면 됩니다. 

이렇게 생성한 웹소켓 링크가 언제 쉽게 broken 되냐면 저 같은 경우는 iOS에서 화면을 껐을 때입니다. 이 링크로 실시간 정보를 받아 처리하는 것을 구현하였고 안드로이드에서 테스트 할 때도 이런 예외상황이 발생하지 않았습니다.

그런데 애플 아이폰에서 테스트 하다가 화면이 잠시 꺼졌고.... 뭐 꺼질수도 있죠. 다시 켜서 보니 실시간 데이터를 받던 것이 멈춰있었습니다. 이런 현상은 안드로이드에선 발생하지 않았습니다.

이렇게 아이폰과 안드로이드에서 다르게 발생하는 것은 App의 state를 다루는 것이 조금 다른 면이 있는데 아이폰에서는 화면이 꺼졌을 때는 Inactive 상태에서 suspend 상태가 추가로 있어서 바로 빠져 버리게 됩니다. suspend 상태에서는 코드 실행하는 부분이 짤없이 하나도 없습니다. 그렇기에 suspend 상태에 대한 방어코드를 넣어봐야 동작하지도 않습니다. 안드로이드에서도 다른 형식으로라도 있을 수 있는 부분이긴 하지만.... 보통의 앱을 사용하다 슬립처럼 화면이 꺼지고 다시 홈버튼으로 켜서 이어서 보는 상황은 충분히 자주 있기에 아이폰을 위한 경우를 위해서라도 이에 대한 예외처리가 되어야 합니.

가령, 티맵 같은 네이게이션 앱은 위치에 대한 실시간 데이터를 받고 앱 사용중에는 슬립이 되지 않게끔 처리하면 막는 것 같지만 실수라도 전원버튼을 짧게 누르면 화면은 일단 꺼짐으로 들어가버립니다. 그 뒤 다시 화면을 켰을 때 멈춰있다면?

뭐 욕심을 부리자면 이렇게 화면을 다시 켜서 앱실행상태를 만들었을 때 자동으로 subscription 링크가 reconnect 까지 된다면 제일 좋지만 아직 그렇게는 안되더라구요.

Reconnect하기 위한 state 추가

이 문제를 수정하기 위한 힌트는 그래도 검색으로 나마 얻기는 했습니다.

위의 이슈 타래를 봤지만 해법에 대한 직접적인 코드는 안나오고 reconnect를 직접 구현해서 처리했다는 멘트를 보고 저도 어쩔 수 없이 고민해서 작성을 해야만 했습니다.

먼저, broken 예외가 발생하면 subcription의 error: 부분으로 처리요청을 하게 됩니다. 그렇기에 이 부분에 다시금 subscribe 를 해주도록 구현하면 됩니다...... 와... 이건 또 어떻게 하지? 재귀호출인가? 가능은 한가? 등등 이부분도 고민이었는데 전 이 부분을 useStateuseEffect로 처리해줬습니다. 그래서 setState로 값이 변경되는 것을 체크한 후 useEffect 내로 subscribe 처리부를 옮겼습니다.  아래의 코드 처럼요.

const [eventSubscription, setEventSubscription] = useState(null);

useEffect(() => {
    const subscription = API.graphql(
        graphqlOperation(onSearchResultSubscription, { queryId: search.id })
    ).subscribe({
        next: (result) => {
            // 구독으로부터 데이터 업데이트 수신 중지
            console.log(result);
        },
        error: err => {
            setEventSubscription(err);
        },
    });
    
    return () => {
    	subscription.unscribe();
    };
}, [eventSubscription]);

여기까지 구현해서 동작하면 링크가 끊어지더라도 다시 링크를 생성하려고 무지하게 애를 쓰는 상황을 만들 수 있습니다. 한마디고 동작은 하는데 링크가 맺어지지 않는 경우가 많더라구요. 왜 그런지는 정확하게 알수 없지만 unscribe() 부분은 일종의 자원회수 부분인데 이 부분에서 약간의 시간이 필요한 것 같습니다. 

한마디로 완전히 링크정리가 되지 않았는데 또 맺으려고 시도하고. 실패하니 또 다시 정리하면서 맺으려고 시도하고 가 무한 루프가 발생했습니다. 따라서 이를 해결하기 위해 약간의 delay를 줬습니다.

setTimeout(() => {
    setEventSubscription(err);
}, 2000);

결론

이런식으로 AppSync subscription을 감싸줬고 한번에 6~7개의 subscribe를 사용한 화면에서도 무난히 테스트 해보았습니다. (아이폰 Xs)

 

Comments