안녕하세요!
오늘은 앱 개발을 하며 마주한 오류의 원인과 해결 방법을 소개하려구 포스트를 작성했습니다.
# Database multi threaded access
# Serial Queue? or Concurrent Queue?
# sqlite3 multi threaded access.
illegal multi-threaded access to database connection 발생 이유
이 코드는 사용자가 읽은 챕터를 저장하는 유즈케이스 입니다. tracker, gardenLogRepository 변수는 내부적으로 각각 serialQueue에 async하게 작업을 추가해 데이터를 순차적으로 처리합니다.
tracker, gardenLogRepository 각각은 내부적으로 sqlite3 데이터베이스에 접근하는 디비는 외부에서 호출되는 스레드에 의해 실행이 됩니다. ( 아무런 queue 추가 x )
이 시점에서 보면, DB에 접근해서 작업하는 DAO는 queue 처리를 하지 않아서, CRUD actions를 호출하는 외부 스레드에 의해서 실행을 하지만, tracker는 내부적으로 backgroundQueue를 통해서 비동기적으로 작업을 추가해 queue에서 순차적으로 꺼내어 db에 접근하는 작업을 담당합니다.
gardenLogRepository또한 위의 내용과 마찬가지로, serial하게 작업합니다. 이때 장점은 tracker를 사용하는 객체가 여러개 이고, 동시에 호출을 해도 async하게 task가 추가되어 결국에 queue에서 하나씩 꺼내어 순차적으로 작업하게 됩니다. 그래서 동시성 문제가 발생되지 않습니다.
2, 3, 4 도 차례대로 큐에서 꺼내져서 DAO변수를 통해 디비에 접근해 CRUD를 수행합니다. 외부에서 serial하게 제어를 하니. 동시성 문제가 발생되지 않습니다.
tracker, gardenLogRepository 각각의 입장에선 서로 다른 스레드가 동시에 tracker, gardenLogRepository 디비에 접근할 때 serial하게 수행을 해서 동시성 문제가 발생되지 않습니다.
문제 발생 이유 : 정말 동시에 디비의 테이블에 read, write를 동시에 접근하는 경우가 있음
그럼에도 이 로직에서 50, 51라인에선 동시성 문제가 발생됩니다... tracker, gardenLogRepository내부에서 각각 아무리 serialQueue로 작업을 제어하더라도 이 두 변수는 "같은 디비 테이블"을 접근하고, useCase입장에서 50, 51을 순차적으로 실행해도 내부적으로 tracker의 qeue에서 CRUD작업을 수행할때, gardenLogRepository의 queue에서도 DB접근을 할 수 있게 됩니다.
해결 방법:
1. 가장 근접으로 DB의 접근을 담당하는 DAO애서 내부적으로 serial Queue를 통해 처리한다.
2. useCase에서 서로 다른 repository를 연이어 호출할 때 해당 레포지토리들이 어느 db를 load 하는지, 테이블을 접근하는지 판단하고 serial하게 작업을 수행하도록 제어한다.
느낀점:
- 간혹 발생하는 에러 사유 + 컬스택 유심히 살펴보아아야 함.
- 원래 하던대로 할걸..