Tiga level $export; alur kick-off → 202 → polling → manifest
Lesson pertama modul Bulk Data: kenapa search biasa tidak cukup untuk jutaan resource, dan bagaimana ekspor asinkron berjalan
Sebelumnya, Review 1 menutup modul Fondasi dan Resource. Modul ini membahas JSON massal:
dari "ekspor jutaan resource" sampai "insert ke database tanpa kehabisan memori". Lesson ini mulai dari sisi server FHIR: endpoint $export dan alur asinkronnya.
$export, kick-off dengan Prefer: respond-async,
202 + Content-Location, polling dengan Retry-After/429, sampai 200 + manifest.
Semua fakta Bulk Data di lesson ini diambil dari Bulk Data Access IG v2.0.0 (STU 2), yang dibangun di atas FHIR R4. Halaman IG itu sendiri mencatat bahwa versi 3.0.0 sudah menggantikannya [1]; alur intinya sama, tapi sebutkan versinya kalau interviewer bertanya detail. Search dan paging biasa ada di lesson 0008 dan 0009; autentikasi SMART Backend Services dibahas di lesson 0019. Lesson ini tanpa lab: potongan HTTP, JSON, dan TypeScript hanya untuk dibaca dan dijelaskan.
1. Kenapa bukan search biasa: tiga level $export
Search dengan paging (GET Observation?_count=1000 lalu ikuti link next) cocok untuk layar aplikasi, bukan untuk jutaan resource:
satu request per halaman, dan Anda yang mengatur semuanya. Bulk Data $export membalik perannya: server menyiapkan file di belakang, client mengambil hasilnya nanti.
Operasi ini "initiates the asynchronous generation of a requested export dataset - whether that be data for all patients, data for a subset (defined group) of patients, or all FHIR data in the server."
[1]
| Level | Endpoint | Isi | Contoh kebutuhan |
|---|---|---|---|
| Patient | [base]/Patient/$export | data semua pasien | migrasi rekam medis ke sistem baru |
| Group | [base]/Group/[id]/$export | data anggota satu Group | kohort pasien diabetes untuk analitik, roster pasien dari payer |
| System | [base]/$export | semua data, terkait pasien atau tidak | backup server, ekspor terminologi dengan _type |
Kutipan tabel: [1]. Untuk level Patient dan Group, Patient Compartment dipakai sebagai acuan resource yang dikembalikan; resource pendukung seperti Organization dan Practitioner MAY ikut.
Tiga hal yang perlu disebut saat menjelaskan kick-off:
- Asinkron. Header
Prefer: respond-async: "Currently, only a value of respond-async is supported. A client SHOULD provide this header."Acceptdi kick-off hanya menentukan format OperationOutcome (application/fhir+json). [1] - GET wajib, POST opsional. "A server SHALL support GET requests and MAY support POST requests that supply parameters using the FHIR Parameters Resource." [1]
- Auth server-ke-server. Tidak ada user yang login: "implementers SHOULD implement OAuth 2.0 access management in accordance with the SMART Backend Services Authorization Profile", dan server "SHALL limit the data returned to only those FHIR resources for which the client is authorized." [1] Detail client credentials + JWT di lesson 0019.
Latihan 1 — Level dan kick-off
Jawab tanpa scroll ke atas. Soal yang sama akan muncul lagi, diacak, di review campuran 0021.
Kuis di lesson ini adalah feedback cepat, bukan bukti penguasaan; yang dicatat hanya hasil review 0021.
Endpoint dan header kick-off sudah jelas. Berikutnya: apa yang terjadi setelah server menerima kick-off.
2. Alur asinkron: kick-off → 202 → polling → manifest
GET [base]/Patient/$export?_type=Patient,Observation&_since=2026-10-25T00:00:00Z
Accept: application/fhir+json
Prefer: respond-async
Authorization: Bearer eyJhbGciOi… ← token dari SMART Backend Services (0019)
HTTP/1.1 202 Accepted
Content-Location: https://fhir.example.org/bulkstatus/42 ← URL status untuk polling
GET https://fhir.example.org/bulkstatus/42
→ 202 Accepted X-Progress: "50% complete" Retry-After: 120
→ 429 Too Many Requests Retry-After: 300 (kalau polling terlalu sering)
→ 200 OK Expires: … Content-Type: application/json + body manifest
→ 4XX/5XX body OperationOutcome (export gagal)
- Kick-off sukses: "HTTP Status Code of 202 Accepted" plus "Content-Location header with the absolute URL of an endpoint for subsequent status requests (polling location)". Gagal (mis. parameter tidak didukung): 4XX/5XX dengan body OperationOutcome. [1]
- Polling: "Clients SHOULD follow an exponential backoff approach when polling for status."
Retry-Afterberisi detik (mis. 120) atau http-date. Kalau client terlalu sering, server "SHOULD respond with a 429 Too Many Requests status code in addition to a Retry-After header". Selama berjalan: 202, opsionalX-Progress(teks bebas < 100 karakter). [1] - Export lain masih jalan: server yang ingin menolak export baru SHOULD membalas 429 +
Retry-Afterjuga. [1] - Batal:
DELETEke URL status → 202; setelah itu polling ke URL yang sama SHALL menghasilkan 404 + OperationOutcome. [1]
Versi TypeScript singkat dari sisi client (untuk dibaca, bukan dijalankan):
async function waitForManifest(statusUrl: string, token: string): Promise<Manifest> {
let delay = 10; // detik
for (;;) {
const res = await fetch(statusUrl, {
headers: { Accept: "application/json", Authorization: `Bearer ${token}` },
});
if (res.status === 200) return (await res.json()) as Manifest; // manifest kecil: json() aman
if (res.status !== 202 && res.status !== 429) {
throw new Error(`export gagal: ${res.status}`); // body: OperationOutcome
}
const retryAfter = Number(res.headers.get("Retry-After")); // http-date → NaN
delay = retryAfter > 0 ? retryAfter : Math.min(delay * 2, 600); // hormati server, else backoff
await new Promise(r => setTimeout(r, delay * 1000));
}
}
Padanan yang mungkin Anda kenal: pola "job + status URL" yang sama dengan antrean job (BullMQ) atau API laporan asinkron. IG menyebutnya "FHIR Asynchronous Request Pattern". [1]
Latihan 2 — Susun urutan alur $export
Urutkan langkahnya dari awal sampai data masuk database. Gunakan ↑/↓, lalu Periksa.
- Client meminta access token lewat SMART Backend Services
GET [base]/Group/[id]/$exportdenganPrefer: respond-async- Server membalas
202 Accepted+Content-Location - Client polling URL status; menunggu sesuai
Retry-Afterselama masih 202 - Server membalas
200 OKdengan manifest JSON - Client mengunduh setiap URL di
outputdanerrorsebelumExpires - Setiap file NDJSON di-stream, di-parse per baris, di-insert per batch
transactionTimedisimpan sebagai_sinceuntuk export berikutnya
Token dulu (kick-off, status, dan file dengan requiresAccessToken: true dilindungi dengan cara yang sama), lalu kick-off → 202 → polling → 200 + manifest → unduh → proses streaming. transactionTime adalah jam server saat query dijalankan, jadi aman dipakai sebagai _since berikutnya.
Latihan 3 — Ucapkan dengan suara keras
Pasang timer dan jawab dengan suara keras, seolah sedang interview. Jangan buka jawaban contoh sebelum selesai.
Jawaban contoh dan checklist di bawah juga menyebut isi manifest, _type, dan _since; detailnya ada di lesson berikutnya, 0014.
"Bagaimana Anda mengekspor jutaan resource dari server FHIR? Jelaskan alurnya dari kick-off sampai file diunduh." (90 detik)
Bandingkan dengan jawaban contoh dan checklist-nya
"Saya tidak memakai paging search biasa, tapi Bulk Data $export. Ada tiga level: Patient/$export untuk semua pasien, Group/[id]/$export untuk satu kohort,
dan $export di base untuk seluruh server. Client mendapat token lewat SMART Backend Services, lalu kick-off dengan Prefer: respond-async, opsional _type untuk membatasi tipe
dan _since untuk export inkremental. Server membalas 202 Accepted dengan header Content-Location, yaitu URL status. Client polling URL itu: selama masih 202, ikuti Retry-After
dan exponential backoff; kalau terlalu sering, server membalas 429. Setelah selesai, 200 dengan manifest JSON: transactionTime, requiresAccessToken, array output berisi URL
file NDJSON per tipe resource, dan array error berisi OperationOutcome. File diunduh sebelum Expires, diproses secara streaming, dan transactionTime disimpan sebagai
_since untuk export berikutnya."
Checklist: apakah Anda menyebut…
- tiga level:
Patient/$export,Group/[id]/$export, system$export? - kick-off asinkron dengan
Prefer: respond-async, autentikasi lewat SMART Backend Services? - 202 Accepted +
Content-Locationsebagai URL status? - polling: 202 selama berjalan, hormati
Retry-After/backoff, 429 kalau terlalu sering? - 200 + manifest:
output(URL file NDJSON per tipe),error,transactionTime? _typeuntuk membatasi tipe dan_since=transactionTimesebelumnya untuk inkremental?
Lulus jika minimal 4 dari 6.
Berikutnya, lesson 0014: isi manifest, format NDJSON, parameter _type/_since, dan kenapa file hasilnya tidak boleh di-JSON.parse sekaligus.
Istilah: GLOSSARY.md (bagian "Bulk Data dan JSON skala besar") · Indeks: semua lesson FHIR
Ada yang belum jelas? Tanyakan ke saya (agent) di terminal, misalnya: "apa beda Patient/$export dan Group/[id]/$export untuk use case analitik?", "bagaimana menulis hasil export ke S3 dengan backpressure yang benar?", atau "kapan lebih baik memakai worker thread daripada streaming?"