본문으로 건너뛰기

클라이언트 앱 업데이트 전략 가이드

작성일: 2026-04-06 Last Updated: 2026-04-06 대상: Browser Extension, Tauri Desktop, Flutter Mobile


목차

  1. 자동 업데이트 전략 개요
  2. 플랫폼별 업데이트 메커니즘
  3. 강제 업데이트 (최소 버전 체크)
  4. 점진적 롤아웃
  5. 롤백 전략
  6. 업데이트 알림 UX
  7. 데이터 마이그레이션

1. 자동 업데이트 전략 개요

업데이트 유형 분류

유형설명사용자 개입예시
Silent백그라운드 자동 설치없음보안 패치, 버그 수정
Passive알림 표시 후 자동 적용최소 (재시작)마이너 업데이트
Active사용자 확인 필요필수 (확인 버튼)메이저 업데이트
Forced사용 차단 + 업데이트 강제필수보안 취약점, API 호환

업데이트 판단 플로우

앱 시작 / 주기적 체크


[서버에 최신 버전 확인] ──(동일)──→ 종료
│ (새 버전 있음)

[현재 버전 < 최소 요구 버전?]
│ │
(Yes) (No)
│ │
▼ ▼
[강제 업데이트 UI] [업데이트 유형 판단]

┌──────────┼──────────┐
▼ ▼ ▼
[Silent] [Passive] [Active]

업데이트 체크 주기

상황체크 주기비고
앱 시작 시항상Cold Start
포어그라운드 복귀4시간 이상 경과 시Warm Start
백그라운드24시간모바일 배터리 절약
수동 체크설정 화면에서즉시

2. 플랫폼별 업데이트 메커니즘

2.1 Browser Extension

스토어 자동 업데이트

항목ChromeFirefox
업데이트 주기5시간마다 체크 (브라우저 관리)24시간마다 체크
업데이트 방식자동 다운로드 + 설치자동 다운로드 + 설치
사용자 제어브라우저 설정에서 비활성화 가능마찬가지
개발자 제어CWS 배포 속도만 조절 가능AMO 배포 속도만 조절
적용 시점확장 재시작 시 (브라우저 재시작 불필요)확장 재시작 시

자체 업데이트 감지 (보조)

// background.ts - 스토어 업데이트 후 감지
chrome.runtime.onInstalled.addListener((details) => {
if (details.reason === 'update') {
const previousVersion = details.previousVersion;
const currentVersion = chrome.runtime.getManifest().version;
console.log(`Updated: ${previousVersion}${currentVersion}`);

// 데이터 마이그레이션 실행
migrateData(previousVersion, currentVersion);

// 업데이트 완료 알림 (선택)
showUpdateNotification(currentVersion);
}
});

앱 내 버전 체크 (추가 알림용)

// 서버에서 최신 버전 확인 (강제 업데이트 체크)
async function checkForUpdate(): Promise<UpdateInfo | null> {
const response = await fetch(`${API_URL}/client/version-check`, {
method: 'POST',
body: JSON.stringify({
platform: 'browser_ext',
currentVersion: chrome.runtime.getManifest().version,
browser: detectBrowser(), // chrome, firefox
}),
});
return response.json();
}

2.2 Tauri Desktop

tauri-plugin-updater 기반 자체 업데이트

항목설명
프로토콜HTTPS JSON manifest + 바이너리 다운로드
서명Ed25519 서명 검증 (필수)
서버 옵션GitHub Releases, S3/R2, 자체 서버
업데이트 단위전체 바이너리 교체
차분 업데이트지원 안 됨 (전체 다운로드)

Tauri 업데이트 설정

// tauri.conf.json
{
"plugins": {
"updater": {
"active": true,
"dialog": false,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQgLi4u",
"endpoints": [
"https://releases.example.com/{{target}}/{{arch}}/{{current_version}}"
],
"windows": {
"installMode": "passive"
}
}
}
}

업데이트 서버 응답 형식

// GET /releases/{target}/{arch}/{current_version}
// 업데이트 없음: 204 No Content
// 업데이트 있음: 200 OK
{
"version": "2.4.0",
"notes": "새로운 기능 추가 및 버그 수정",
"pub_date": "2026-04-06T12:00:00Z",
"url": "https://releases.example.com/download/v2.4.0/app-2.4.0-x86_64.msi",
"signature": "dW50cnVzdGVkIGNvbW1lbnQgLi4u"
}

Rust 업데이트 구현

use tauri_plugin_updater::UpdaterExt;

// 업데이트 체크 + 다운로드 + 설치
async fn check_and_update(app: tauri::AppHandle) -> Result<(), Box<dyn std::error::Error>> {
let updater = app.updater_builder().build()?;

match updater.check().await? {
Some(update) => {
// 프론트엔드에 알림
app.emit("update-available", &update.version)?;

// 사용자 확인 후 다운로드 + 설치
let mut downloaded = 0;
update.download_and_install(
|chunk_length, content_length| {
downloaded += chunk_length;
// 프론트엔드에 진행률 전송
let _ = app.emit("update-progress", (downloaded, content_length));
},
|| {
// 설치 완료 → 재시작 안내
let _ = app.emit("update-installed", ());
},
).await?;
}
None => {
// 최신 버전
}
}
Ok(())
}

GitHub Releases 업데이트 서버

# Tauri가 GitHub Releases를 직접 지원
# tauri.conf.json endpoints 설정:
"endpoints": [
"https://github.com/{owner}/{repo}/releases/latest/download/latest.json"
]

# CI에서 latest.json 자동 생성 (tauri-action이 처리)

2.3 Flutter Mobile

스토어 업데이트 (기본)

항목Google PlayApple App Store
자동 업데이트사용자 설정에 따라사용자 설정에 따라
강제 업데이트자체 구현 필요자체 구현 필요
인앱 업데이트Play In-App Updates API지원 안 됨
심사 기간수 시간 ~ 3일1 ~ 7일

Google Play In-App Updates

import 'package:in_app_update/in_app_update.dart';

class UpdateService {
Future<void> checkForUpdate() async {
final updateInfo = await InAppUpdate.checkForUpdate();

if (updateInfo.updateAvailability == UpdateAvailability.updateAvailable) {
if (updateInfo.immediateUpdateAllowed) {
// 강제 업데이트 (전체 화면)
await InAppUpdate.performImmediateUpdate();
} else if (updateInfo.flexibleUpdateAllowed) {
// 유연한 업데이트 (백그라운드 다운로드)
await InAppUpdate.startFlexibleUpdate();
// 다운로드 완료 후
await InAppUpdate.completeFlexibleUpdate();
}
}
}
}

OTA 업데이트 (CodePush 유사)

솔루션설명제약
Shorebird (Dart 전용)Dart 코드 패치 배포네이티브 코드 변경 불가
자체 번들 서버에셋/설정 업데이트코드 변경 불가
// Shorebird를 사용한 OTA 업데이트
// pubspec.yaml에 shorebird 추가 후:
// shorebird release --platforms android,ios
// shorebird patch --platforms android,ios

// 앱 내에서는 자동으로 패치 적용 (Shorebird 런타임이 처리)
// 별도 코드 불필요

주의: Apple App Store 가이드라인 3.3.2에 따르면 OTA로 앱의 주요 기능을 변경하는 것은 금지됨. Shorebird는 Dart VM 수준 패치로 이 규정을 준수하지만, 큰 기능 변경은 반드시 스토어 업데이트를 통해야 함.


3. 강제 업데이트 (최소 버전 체크)

3.1 서버 API 설계

GET /api/client/version-check

요청

{
"platform": "flutter_ios", // browser_ext_chrome, tauri_windows, flutter_android, etc.
"currentVersion": "2.3.1",
"buildNumber": 142
}

응답

{
"latestVersion": "2.4.0",
"minimumVersion": "2.2.0",
"updateRequired": false,
"updateAvailable": true,
"updateUrl": "https://apps.apple.com/app/id123456",
"releaseNotes": "새 기능: 다크 모드 지원",
"maintenanceMode": false,
"maintenanceMessage": null
}

3.2 Laravel 백엔드 구현

// app/Http/Controllers/Api/ClientVersionController.php
class ClientVersionController extends Controller
{
public function check(Request $request): JsonResponse
{
$validated = $request->validate([
'platform' => 'required|string',
'currentVersion' => 'required|string',
'buildNumber' => 'nullable|integer',
]);

$config = ClientVersionConfig::where('platform', $validated['platform'])->first();

if (!$config) {
return response()->json(['error' => 'Unknown platform'], 400);
}

$currentVersion = $validated['currentVersion'];
$updateRequired = version_compare($currentVersion, $config->minimum_version, '<');
$updateAvailable = version_compare($currentVersion, $config->latest_version, '<');

return response()->json([
'latestVersion' => $config->latest_version,
'minimumVersion' => $config->minimum_version,
'updateRequired' => $updateRequired,
'updateAvailable' => $updateAvailable,
'updateUrl' => $config->store_url,
'releaseNotes' => $config->release_notes,
'maintenanceMode' => $config->maintenance_mode,
'maintenanceMessage' => $config->maintenance_message,
]);
}
}

3.3 버전 정책 테이블

-- Laravel Migration
Schema::create('client_version_configs', function (Blueprint $table) {
$table->id();
$table->string('platform')->unique(); -- browser_ext_chrome, tauri_windows, etc.
$table->string('latest_version'); -- 최신 버전
$table->string('minimum_version'); -- 최소 요구 버전
$table->string('store_url')->nullable(); -- 스토어/다운로드 URL
$table->text('release_notes')->nullable(); -- 릴리즈 노트
$table->boolean('maintenance_mode')->default(false);
$table->text('maintenance_message')->nullable();
$table->timestamps();
});

3.4 최소 버전 업데이트 시점

상황최소 버전 변경
보안 취약점 패치즉시 올림
API 호환성 깨짐 (Breaking Change)신규 API 배포와 동시
중대 버그 (데이터 손실 등)즉시 올림
일반 기능 추가변경 안 함
오래된 버전 지원 종료6개월 전 사전 공지 후

4. 점진적 롤아웃

4.1 플랫폼별 롤아웃 지원

플랫폼롤아웃 방법단계
Chrome ExtensionCWS 단계적 게시 (5%→20%→50%→100%)스토어 설정
Firefox Extension미지원 (전체 배포)-
Google Play단계적 출시 (1%→5%→10%→25%→50%→100%)Play Console
Apple App Store단계적 출시 (1%→2%→5%→10%→20%→50%→100%)App Store Connect
Tauri자체 업데이트 서버에서 구현커스텀

4.2 자체 업데이트 서버 롤아웃 (Tauri)

// 업데이트 서버 - 롤아웃 판단 로직
function shouldReceiveUpdate(
userId: string,
rolloutPercentage: number
): boolean {
// 사용자 ID를 해싱하여 결정론적으로 그룹 배정
const hash = crypto
.createHash('sha256')
.update(userId)
.digest('hex');
const userBucket = parseInt(hash.substring(0, 8), 16) % 100;
return userBucket < rolloutPercentage;
}

4.3 롤아웃 모니터링 기준

지표정상 기준롤아웃 중단 기준
크래시율< 0.5%> 2%
ANR율 (Android)< 0.5%> 1%
1점 리뷰 급증평소 대비2배 이상 증가
API 에러율< 1%> 5%
사용자 피드백긍정적부정 리포트 다수

4.4 롤아웃 의사결정 플로우

1% 배포 → 24시간 모니터링

├─ 이상 없음 → 10% 확대
│ │
│ ├─ 이상 없음 → 50% 확대
│ │ │
│ │ ├─ 이상 없음 → 100% 배포
│ │ │
│ │ └─ 이상 발견 → 롤아웃 일시 정지 → 분석
│ │
│ └─ 이상 발견 → 롤아웃 일시 정지 → 분석

└─ 이상 발견 → 즉시 롤백

5. 롤백 전략

5.1 플랫폼별 롤백 방법

플랫폼롤백 방법소요 시간비고
Chrome ExtensionCWS에서 이전 버전 재게시수 시간 (심사)긴급 시 CWS 팀 연락
Firefox ExtensionAMO에서 이전 버전 재게시수 분 (자가 서명)self-distributed는 즉시
Google PlayPlay Console에서 롤아웃 중단 + 이전 버전 프로모트수 시간단계적 출시 중에만 중단 가능
Apple App Store이전 빌드를 "현재 Live 버전에서 제거"수 시간 ~ 수 일심사 필요할 수 있음
Tauri업데이트 서버에서 manifest 변경즉시가장 빠름

5.2 Tauri 즉시 롤백

# 업데이트 서버의 latest.json을 이전 버전으로 변경
# 또는 특정 버전 이상에서만 다운그레이드 응답 반환

# 서버측 설정 업데이트
curl -X PUT https://admin.example.com/api/releases/tauri \
-H "Authorization: Bearer $TOKEN" \
-d '{
"latest_version": "2.3.1",
"rollback_from": "2.4.0",
"force_downgrade": true
}'

5.3 롤백 체크리스트

항목확인
이전 버전 바이너리/패키지가 보존되어 있는가[ ]
이전 버전의 코드 서명이 유효한가[ ]
DB 마이그레이션이 역방향 호환되는가[ ]
API 버전이 이전 클라이언트와 호환되는가[ ]
로컬 데이터 스키마가 다운그레이드 가능한가[ ]
롤백 후 사용자 알림 메시지가 준비되었는가[ ]

5.4 롤백 불가능 상황 대비

상황대응
DB 스키마가 되돌릴 수 없음핫픽스 배포 (새 버전 긴급 패치)
API 엔드포인트가 제거됨API 버전 관리 (v1, v2 병행)
로컬 데이터 형식 변경마이그레이션 시 백업 보존

6. 업데이트 알림 UX

6.1 알림 유형별 UI

유형UI 요소사용자 액션
Silent없음 (백그라운드)없음
배지 알림설정 아이콘에 빨간 점설정에서 확인
토스트/배너화면 상/하단 배너"업데이트" 또는 "나중에"
모달 (선택)중앙 모달, 업데이트 내용 표시"지금 업데이트" / "나중에"
모달 (강제)중앙 모달, 닫기 불가"업데이트" 버튼만
전체 화면 (강제)앱 전체를 가리는 화면"업데이트" 버튼만

6.2 UX 가이드라인

원칙설명
방해 최소화작업 중에는 배너만, 유휴 시 모달
진행률 표시다운로드 중 프로그레스 바 표시
릴리즈 노트업데이트 내용을 간결하게 표시
나중에 옵션강제 업데이트가 아니면 항상 "나중에" 제공
재시작 시점사용자가 재시작 시점을 선택할 수 있게
오프라인 고려네트워크 없을 때 업데이트 체크 실패 무시

6.3 "나중에" 재알림 정책

횟수재알림 간격동작
1회24시간 후배너 재표시
2회12시간 후배너 재표시
3회6시간 후모달로 변경
4회+앱 시작 시마다모달 (보안 업데이트 시 강제로 전환)

7. 데이터 마이그레이션

7.1 로컬 데이터 스키마 관리

플랫폼로컬 저장소마이그레이션 방식
Browser Extensionchrome.storage.local / IndexedDB버전 기반 순차 마이그레이션
TauriSQLite (via tauri-plugin-sql) / JSON 파일SQL 마이그레이션 스크립트
FlutterSQLite (sqflite) / SharedPreferences / Hive버전 기반 순차 마이그레이션

7.2 마이그레이션 프레임워크

// 공통 마이그레이션 패턴 (TypeScript)
interface Migration {
version: number;
description: string;
up: (data: any) => Promise<any>;
down?: (data: any) => Promise<any>; // 롤백용 (선택)
}

const migrations: Migration[] = [
{
version: 1,
description: 'Initial schema',
up: async (data) => ({
...data,
schemaVersion: 1,
settings: data.settings || {},
}),
},
{
version: 2,
description: 'Add user preferences',
up: async (data) => ({
...data,
schemaVersion: 2,
preferences: {
theme: 'system',
language: 'auto',
...data.preferences,
},
}),
down: async (data) => {
const { preferences, ...rest } = data;
return { ...rest, schemaVersion: 1 };
},
},
{
version: 3,
description: 'Rename settings.apiUrl to settings.serverUrl',
up: async (data) => ({
...data,
schemaVersion: 3,
settings: {
...data.settings,
serverUrl: data.settings.apiUrl,
apiUrl: undefined,
},
}),
},
];

async function runMigrations(currentData: any): Promise<any> {
const currentVersion = currentData.schemaVersion || 0;
let data = { ...currentData };

for (const migration of migrations) {
if (migration.version > currentVersion) {
console.log(`Running migration v${migration.version}: ${migration.description}`);
try {
data = await migration.up(data);
} catch (error) {
console.error(`Migration v${migration.version} failed:`, error);
// Sentry에 보고
Sentry.captureException(error, {
tags: { migration_version: migration.version },
});
// 마이그레이션 실패 시 데이터 손상 방지
throw new MigrationError(migration.version, error);
}
}
}

return data;
}

7.3 Flutter SQLite 마이그레이션

final database = await openDatabase(
'app.db',
version: 3,
onCreate: (db, version) async {
await db.execute('''
CREATE TABLE settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
)
''');
await db.execute('''
CREATE TABLE user_data (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content TEXT NOT NULL,
created_at TEXT NOT NULL
)
''');
},
onUpgrade: (db, oldVersion, newVersion) async {
if (oldVersion < 2) {
await db.execute('ALTER TABLE user_data ADD COLUMN updated_at TEXT');
}
if (oldVersion < 3) {
await db.execute('ALTER TABLE user_data ADD COLUMN sync_status TEXT DEFAULT "pending"');
}
},
);

7.4 마이그레이션 안전 규칙

규칙설명
백업 우선마이그레이션 전 기존 데이터 백업
순차 실행버전 건너뛰기 금지 (v1→v2→v3)
원자성하나의 마이그레이션은 전체 성공 또는 전체 실패
멱등성동일 마이그레이션 재실행 시 동일 결과
하위 호환가능하면 down 마이그레이션 제공
점진적 변경큰 스키마 변경은 여러 버전에 걸쳐 분할
테스트 필수각 마이그레이션 경로에 대한 테스트 작성

7.5 마이그레이션 실패 대응

마이그레이션 시작


[데이터 백업]


[마이그레이션 실행] ──(실패)──→ [백업에서 복원]
│ │
(성공) ▼
│ [에러 보고 (Sentry)]
▼ │
[검증 (데이터 무결성)] ▼
│ [이전 버전 데이터로 동작]
├─(성공)→ 정상 동작 (기능 제한 모드)

└─(실패)→ [백업에서 복원] → [에러 보고]

부록: 업데이트 시스템 체크리스트

구현 체크리스트

항목상태
버전 체크 API 엔드포인트 구현[ ]
최소 버전 관리 테이블 생성[ ]
플랫폼별 업데이트 메커니즘 구현[ ]
강제 업데이트 UI 구현[ ]
데이터 마이그레이션 프레임워크 구현[ ]
마이그레이션 테스트 작성[ ]
롤백 절차 문서화[ ]
점진적 롤아웃 설정[ ]
업데이트 모니터링 대시보드[ ]

릴리즈 전 체크리스트

항목상태
이전 버전에서 업데이트 테스트[ ]
데이터 마이그레이션 테스트 (이전 3개 버전)[ ]
강제 업데이트 시나리오 테스트[ ]
롤백 테스트 (다운그레이드)[ ]
오프라인 상태에서 동작 확인[ ]
업데이트 서버 부하 테스트[ ]
릴리즈 노트 준비[ ]
롤아웃 비율 결정[ ]