걸음 수, 거리, 칼로리, 심박수, 수면은 건강 및 영양 앱에 의해 사용자의 플랫폼(Android의 Android Health Connect, iOS의 Apple HealthKit)에서 가져와져야 하며 — 하나의 깔끔하고 중복 제거된 일일 프로필로 제시되어야 합니다. 두 API 간에는 유사점이 거의 없습니다: 별도의 읽기 메커니즘, 다른 권한 부여 체계, 그리고 다른 데이터 구조. 저희는 이들을 하나처럼 보이게 하는 모바일 동기화 레이어를 구축했습니다.
당면 과제
- 어떤 것도 동의하지 않는 두 개의 네이티브 API. Apple HealthKit은 콜백을 통해 일별 집계를 반환하고; Health Connect는 풍부한 소스 메타데이터와 함께 원시적이고 페이지로 나뉜 샘플을 반환합니다. 동일한 개념이지만 완전히 다른 형태 — 그리고 앱은 둘 다 하나의 스키마로 정규화해야 했습니다.
- 이중 계산이 기본값입니다. 휴대폰, 스마트워치, 타사 앱 모두 동일한 8,000 걸음을 보고할 수 있습니다. 단순히 합산하면 모든 지표가 과장됩니다. 앱은 중복되는 소스를 인식하고 각 실제 활동을 한 번만 계산해야 했습니다.
- 배터리와 네트워크는 최신 상태를 유지하는 대가를 치를 수 없습니다. 건강 데이터는 하루 종일 변경되지만, 매 동기화마다 전체 기록을 읽으면 배터리가 소모되고 셀룰러 연결이 포화 상태가 됩니다. 동기화 주기는 실시간으로 느껴지면서도 가벼워야 했습니다.
권한은 혼란스러울 수 있습니다. 수십 가지 데이터 유형, 두 가지 권한 모델, 백그라운드 읽기 제한, 그리고 일부 범위는 허용하지만 다른 범위는 허용하지 않는 사용자 — 이 모든 것을 앱은 흐름을 중단시키지 않고 처리해야 합니다.
우리의 솔루션
저희는 3단계 동기화를 구축했습니다: 얇은 네이티브 레이어가 각 플랫폼의 API를 읽고, 온디바이스 정규화 레이어가 데이터를 중복 제거하고 일별 요약으로 롤업하며, 백엔드는 이를 저장하고 단일 신뢰할 수 있는 원장으로 조정합니다. 클라이언트는 원시 샘플을 전송하지 않고 — 깔끔하고 시간대 보정된 일별 롤업을 전송합니다.

아키텍처
- iOS는 react-native-health를 통해 Apple HealthKit을 읽고; Android는 react-native-health-connect를 통해 Health Connect를 읽습니다.
- 온디바이스 정규화는 두 플랫폼의 출력을 하나의 스키마로 변환하고 휴대폰을 떠나기 전에 중복을 제거합니다.
- 동기화 트리거 — 첫 실행 시 30일 기록, 이후 10분 간격 및 앱 재개 시마다 경량 1일 증분 읽기.
- 전송 — 정규화된 일일 롤업은 메인 서버의 /user-health/system-activity에 POST되며, 이는 (사용자 ID + 시간대와 함께) 헬스 마이크로서비스로 전달됩니다.
- 영속성 — 각 소스/일은 HealthSystemAggregate로 멱등적으로 저장됩니다; 그러면 MongoDB change stream이 이를 일별 HealthLedger로 조정합니다.
네이티브 HealthKit 및 Health Connect 모듈을 사용한 Expo / React Native 관리형 워크플로우.
주요 기능
- 두 API를 위한 단일 정규화 계약. 플랫폼별 리더는 단일 formatHealthData 단계를 공급하여 소스와 관계없이 동일한 형태를 내보냅니다 — 따라서 백엔드와 UI는 iOS 대 Android에 따라 분기되지 않습니다.
2. 기기 내 최대값 기반 중복 제거. 각 소스 내에서 하루의 측정값은 합산됩니다; 그런 다음 일별 값은 소스 전반에 걸친 Math.max()입니다 (칼로리는 source|deviceType으로 키 지정됨). 따라서 한 기기에서 여러 측정값이 누적되는 동안, 같은 날을 보고하는 시계와 전화는 이중으로 계산되지 않습니다:
// Sum readings within each source, then take the max ACROSS sources dateSourceMap[date][source] += entry.count; const totalSteps = Math.ceil(Math.max(...Object.values(dateSourceMap[date]))); |
3.플랫폼별 올바른 읽기 전략. Health Connect 읽기는 pageToken 루프 (페이지당 1,000개 기록)로 페이지가 매겨집니다; HealthKit의 일별 집계는 일별로 반복됩니다. 각 특이사항은 앱의 나머지 부분에는 보이지 않는 자체 리더에 포함되어 있습니다.
4. 배터리를 고려한 동기화 주기. 전체 30일 백필은 한 번만 실행되며 (첫 호출 플래그로 보호됨); 그 후에는 모든 동기화가 하루치 데이터만 가져옵니다 — 셀룰러에서 빠르고, 전력 소모가 적습니다.
5. 시간대 보정된 일일 그룹화. 샘플은 사용자의 시간대에 따라 YYYY-MM-DD 키로 분류되므로, 서버 위치와 관계없이 오후 11시 운동이 올바른 날짜에 포함됩니다.
6. 멱등적인 백엔드 쓰기. 각 롤업은 (userId, deviceId, source, date)를 기준으로 upsert됩니다 — 동일한 날짜를 다시 전송하는 것은 no-op이 되어 재시도 및 중복 동기화를 안전하게 만듭니다.
7. 정직한 성능 저하 처리. 누락된 권한, 빈 읽기, Health Connect rate-limit 오류는 깔끔하게 포착되고 표시됩니다 — 빈 날짜로 인해 동기화가 중단되지 않으며, 사용자에게는 조용한 실패 대신 명확한 프롬프트가 제공됩니다.
결과
- iOS 및 Android 전반에 걸쳐 단일하고 일관된 건강 프로필 — 플랫폼 차이가 앱의 나머지 부분에서는 보이지 않습니다.
- 중복되는 기기/앱 소스는 중복 제거되어, 걸음 수와 칼로리가 부풀려진 합계 대신 현실을 반영합니다.
- 경량 증분 동기화는 배터리를 소모하거나 모바일 데이터를 사용하지 않고 데이터를 최신 상태로 유지합니다.
시간대 보정된 멱등적 쓰기는 모든 지표가 올바른 날짜에 기록되고, 재시도가 기록을 손상시키지 않음을 의미합니다.
기술 스택
React Native (Expo) · react-native-health (HealthKit) · react-native-health-connect · NestJS · MongoDB · MongoDB Change Streams · moment-timezone

