Bahmni Connect 동기화 - Pull과 Push
Pull: Bahmni 이벤트 로그 서비스
배경
Android와 Chrome 앱 사용자가 오프라인에서 데이터를 조회·편집할 수 있도록 Bahmni/OpenMRS 데이터를 앱에 동기화합니다. 데이터는 세 범주로 나눕니다.
- 트랜잭션 데이터: 환자와 진료 사건. 기기에 저장
- 소규모 참조 데이터: 요청 URL과 응답 맵으로 로컬 저장해 기기에서 제공(보통 20개 미만)
- 스키마가 있는 참조 데이터: 기기에 저장
- 예를 들어 EncounterTypes는 개수가 적고 고정 HTTP Endpoint에서 가져오므로 URL과 응답을 기억해 localDB에서 제공할 수 있습니다. 반면 AddressHierarchy는 최대 5,000개이며 검색에 따라 응답이 달라집니다. 질환·약물 같은 이런 데이터는 기기에 구조적으로 저장해 제공합니다.
기술 제약
원격 기기, 특히 태블릿에서는 다음 제약이 있습니다.
- 네트워크: 간헐적인 2G가 최선인 환경
- 디스크 공간: 매우 제한적
- 데이터 동기화 상호작용 설계
- 배터리
이를 고려한 설계는 다음과 같습니다.

위 그림의 흐름:
- 첫 서버 상호작용에서 앱에서 재생할 이벤트 목록을 가져옵니다.
- 이후 관련 OpenMRS/Bahmni Endpoint에서 개별 엔터티를 가져오되 느리고 불안정한 네트워크를 고려해 응답 크기를 작게 유지합니다.
엔터티마다 Marker를 두는 복잡성을 피하고 모든 서버 데이터 동기화에 마지막 성공 동기화 ID 하나만 사용합니다. 참조 데이터가 먼저 만들어지지 않으면 이를 참조하는 트랜잭션 데이터를 서버에서 만들 수 없다는 전제에 기반합니다.
필터
오프라인 저장 공간이 제한되므로 Pull해 기기에 저장할 데이터를 필터링합니다. 예를 들어 위치 필터를 사용하면 CHW가 로그인 시 선택한 위치를 기준으로 환자와 관련 데이터만 로컬에 저장해 저사양 기기의 공간 부족을 방지합니다.
이벤트 로그
Bahmni 서버가 트랜잭션·참조 엔터티 생성/수정 이벤트를 응답하도록 event_log라는 새 표에 저장합니다. 현재 event_records가 트랜잭션과 일부 참조 엔터티를 기록하지만 다음 이유로 직접 사용하지 않습니다.
- 이 표에 쓰는 Advice는 범용 Atom Feed용 bahmni-atomfeed 저장소에 있습니다.
- 위치 필터링을 위한 속성을 표와 Advice에 추가해야 합니다.
- 기존 Atom Feed에 없던 참조 데이터 이벤트를 이전 시점까지 기록해야 합니다.
event_records를 이벤트 원본으로 사용하되, 트랜잭션 이벤트를 쓰는 같은 Advice에 주소 계층 같은 참조 엔터티를 더 추가해야 합니다.
백그라운드 서비스가 event_records에서 데이터를 읽고 필터 속성을 판별해 시간순으로 event_log에 넣습니다. 그림과 같은 새 서비스 Endpoint도 작성합니다.
event_log 표의 속성:
- 이벤트 ID
- category(event_records와 동일)
- GET 호출용 엔터티 링크
- 생성/수정 이벤트 시각(event_records 값)
- filter(BD에서는 트랜잭션 데이터가 속한 위치)
Push: 기기에서 서버로 Bahmni Connect 동기화
기기가 오프라인이 되면 이벤트 로그 서비스 Pull을 통해 필요한 환자·임상 데이터가 localDB(Lovefield 또는 SQLite)에 있습니다. Chromium/Android 앱은 읽기와 쓰기를 먼저 localDB에서 수행하고 나중에 서버로 동기화하는 Offline First 방식으로 동작합니다. 동기화 주기는 구성할 수 있습니다.
Bahmni Connect에는 두 방향 동기화가 필요합니다.
- 서버→기기: event-log-service가 정의하며 CHW 위치로 필터링한 기존 환자 등을 동기화합니다.
- 기기→서버: 기기에서 생성·편집한 환자 등을 서버로 다시 동기화합니다.
세션 쿠키 재설정
기기가 서버 Timeout보다 오래 오프라인이면 모든 POST가 실패할 수 있습니다. 403(Session timeout)이 발생하면 기기 로그인 페이지로 이동하고 Push/Pull을 중지합니다. 사용자가 다시 로그인해야 재개합니다.
로컬 데이터베이스와 EventQueue
localDB는 기기 데이터를 문서로 저장합니다. 문서에 e1 뒤 e2~e5 편집이 일어나면 이벤트는 event_queue에 쌓입니다. e1 동기화 시 localDB의 최신 문서를 가져와 서버에 Push하므로 e2~e5도 같은 최신 내용을 보냅니다. 대부분 이벤트가 멱등이라 실패하지 않으며 현재는 이를 최적화하지 않습니다.
- e1이 환자 1 편집이면 localDB 환자와 큐에 e1을 저장합니다. e2 편집은 환자 데이터를 e2로 교체하고 큐에 e2를 추가하며 e5까지 반복합니다.
- 결과적으로 localDB에는 <edit 5>, 큐에는 <e1,e2,e3,e4,e5>가 있습니다.
- 서버 동기화가 e1을 꺼내 환자 ID로 localDB의 <edit 5>를 전송합니다. 성공하면 e1을 제거하고, 지정 오류이면 error_queue로 이동합니다.
- e2~e5도 같은 절차를 수행하며 요청은 멱등이므로 보통 실패하지 않습니다.
현재 두 개의 큐를 사용합니다.
- event_queue: 서버에 동기화할 모든 이벤트 목록. 예: 환자 p1 편집 p1e1~p1e3, 환자 p2 생성 p2e1, 환자 p1 추가 편집 p1e4이면 큐는 p1e1,p1e2,p1e3,p2e1,p1e4입니다.
- error_queue: event_queue Push 중 5xx·4xx 오류가 난 이벤트를 이동합니다. 최대 오류 수를 구성하며 초과하면 Push/Pull을 중지하고 수동 수정을 기다립니다.
이벤트를 error_queue로 보내는 오류
event_queue에서 서버로 Push할 때 다음 오류가 발생하면 error_queue로 이동합니다.
- 5xx: 서버가 정상적으로 보이는 요청을 처리하지 못함
- 서버 중단
데이터 상태와 Push
환자 데이터는 기기에서 세 가지 상태가 될 수 있습니다.
- 기기에서 만든 신규 환자: 서버에 없으므로 충돌 없이 Push합니다.
- 서버에서 받은 뒤 기기에서 편집했지만 아직 서버로 보내지 않은 환자: 서버에 Push합니다. 서버에서도 드물게 변경되었을 수 있으며 현재는 기기 변경으로 덮어씁니다.
- Push 실패: 재시도할 수 없는 5xx가 발생할 수 있습니다.
1. 기기에서 생성한 신규 환자

2. 서버에서 동기화한 뒤 기기에서 편집한 환자

서버에서 동기화한 환자를 오프라인에서 편집하는 상황:
- 오프라인에서 기존 환자를 편집할 때마다 localDB를 갱신하고 event_queue에 이벤트를 추가합니다.
- 오프라인에서 같은 환자를 여러 번 편집할 수 있습니다.
Pull과 Push: 스케줄러
이벤트 Push/Pull은 공통 구성 가능 백그라운드 스케줄러를 사용합니다. 여러 Worker가 단계 순서대로 실행됩니다. Worker 작업은 비동기지만 이전 단계가 성공해야 다음 단계가 실행됩니다. 앱 동기화 구성을 하나로 유지하기 위해 작업도 하나입니다.

Worker 상태는 두 가지입니다.
- started: Worker 실행 메서드가 동작 중
- stopped: 실행 완료 또는 오류로 중지
허용 가능한 오류가 발생하면 Worker가 Sleep할 수 있습니다. 스케줄러가 깨어나면 처음 단계부터 다시 실행하므로 Worker 작업은 멱등이어야 합니다. 기기가 오프라인이면 다음 시점에 재시도합니다.
- 구성된 대기 시간 후
- 앱에서 동기화 버튼을 클릭할 때
현재는 Push First 설계로 기기의 모든 변경을 먼저 서버에 보낸 뒤 서버 변경을 Pull합니다. 따라서 Push 스케줄러는 병합 충돌을 고려하지 않습니다. Push에서는 error_queue를 한 번 시도한 뒤 event_queue를 처리합니다.
Pull과 Push 스케줄러 순서
초기 설정:
- 구성 Pull
- 전역 속성, REST 호출 데이터 등 소규모 참조 데이터 Pull(예: 로그인 위치, 성별)
- 로그인 성공 후 이벤트 로그 서비스에서 트랜잭션 데이터 Pull(예: 환자)
백그라운드 절차:
- 구성 Pull
- 소규모 참조 데이터 Pull
- error_queue Push
- event_queue Push
- 로그인 성공 후 이벤트 로그 서비스에서 트랜잭션 데이터 Pull
Pull과 Push 오류
Pull 오류 상황:
- 패킷 손실: TCP가 자동 처리
- 기기 메모리 가득 참: 수동 수정 필요
- 이벤트 로그 서비스 중단/접근 불가: 수동 재시작
- localDB 저장 오류: 트랜잭션 Rollback
Push 오류:
- 5xx: error_queue가 가득 차지 않았다면 이벤트를 이동
- 기타 HTTPS 오류: 구성 시간 후 또는 동기화 버튼 클릭 때까지 스케줄러 Sleep
동기화 버튼을 통한 Pull과 Push:
동기화 작업은 설정한 간격으로 주기적으로 실행됩니다. 백그라운드 문제나 긴 간격 때문에 필요하면 모든 모듈의 동기화 버튼을 눌러 수동 시작할 수 있습니다. Chrome Extension과 Android 앱에서 지원합니다. 실행 중에는 로딩 아이콘을 표시하고 완료되면 버튼이 다시 활성화됩니다.
기기 오류
가능한 기기 오류:
- 메모리 부족: Android에서 저장·동기화가 안 될 수 있으므로 불필요한 파일을 지우고 재동기화
- 서버에서 사용자 암호 변경: 온라인에서 다시 로그인
- 동기화 문제가 있는 잘못된 빌드: 앱 업데이트, 드물게 재설치 필요. 재설치 시 기기 데이터 손실
- SD 카드 문제: SQLite 저장소인 Android에서 카드 장애 시 수동 복구, 실패하면 데이터 손실
- 앱 충돌: Chrome·Android 앱이 중단될 수 있지만 데이터는 손실되지 않음
서버에서 Bahmni Connect 개념 Pull
Offline Concepts 개념 집합에 개념을 추가하거나 구성원 개념을 수정하면 OpenMRS event_records에 이벤트가 생기고 event_log로 복사됩니다. 동기화가 시작되면 이벤트를 오프라인 DB로 복사합니다.
구현자 구성:
- OpenMRS 모듈에 Bahmni Offline Sync Omod 추가
- Bahmni Connect에 필요한 개념을 "Offline Concepts" 개념 집합에 추가
Bahmni Wiki · CC BY-SA 4.0