Alur pekerjaan impor dan ekspor melalui Django, Redis, dan worker
Alur pekerjaan impor dan ekspor melalui Django, Redis, dan worker. Ilustrasi konsep · Klik untuk memperbesar.
Contoh antarmuka impor dengan antrean, progres, dan laporan kesalahan
Contoh antarmuka impor dengan antrean, progres, dan laporan kesalahan. Ilustrasi konsep · Klik untuk memperbesar.

Impor CSV besar tetap bisa gagal meski timeout request diperpanjang. Koneksi browser bisa terputus, proxy bisa menghentikan koneksi, atau worker bisa restart setelah sebagian baris tersimpan. Pada ekspor, pembuatan file juga bisa memakan waktu lebih lama daripada batas waktu yang wajar untuk request API biasa.

Dalam rancangan ini, Django menangani otorisasi dan membuat job persisten. File input dan output disimpan di object storage, sedangkan Celery menjalankan pekerjaan dengan Redis sebagai broker. PostgreSQL menyimpan catatan utama job. Task ID atau hasil sementara di Redis saja tidak cukup untuk audit karena data broker bisa kedaluwarsa atau infrastrukturnya diganti.

Pisahkan upload, penerimaan, dan pemrosesan

Buat job impor dengan pemilik, tenant, jenis operasi, versi skema, dan idempotency key. Berikan otorisasi upload berumur pendek ke object key yang dipilih server. Klien tidak boleh memilih prefix milik tenant lain. Jika mekanisme upload mendukungnya, batasi panjang dan tipe konten melalui policy. Tetap periksa metadata serta isi file sebelum diproses.

Setelah upload selesai, endpoint memeriksa kepemilikan objek, ukuran, checksum jika tersedia, dan format yang diizinkan. Tetapkan versi objek yang akan diproses atau pindahkan objek ke key yang isinya tidak bisa diubah agar upload ulang tidak mengubah input saat job berjalan. Periksa isi file, jangan hanya mengandalkan ekstensinya. Batasi ukuran arsip setelah dekompresi dan tolak encoding yang tidak didukung dengan pesan yang jelas.

Setelah job diterima, kembalikan 202 Accepted beserta URL resource job. Status yang disimpan bisa berubah dari uploading ke queued, validating, processing, finalizing, lalu berakhir dengan succeeded, failed, atau cancelled. Simpan timestamp, penghitung baris, ringkasan kesalahan, key output, dan permintaan pembatalan secara terpisah dari tahap pekerjaan saat ini.

Tutup celah antara database dan broker

Mengirim task sebelum transaksi job selesai memungkinkan worker membaca data yang belum tersedia. transaction.on_commit Django mencegah masalah urutan tersebut, tetapi tidak membuat commit database dan publikasi pesan menjadi atomik. Proses masih bisa mati setelah commit, sebelum pesan masuk broker.

Buat baris outbox dalam transaksi yang sama dengan job agar pengiriman pesan bisa dilanjutkan jika proses berhenti. Dispatcher mengirim entri yang masih tertunda, lalu menandainya sebagai terkirim. Jika dispatcher mati di antara kedua langkah itu, pesan bisa terkirim dua kali. Konsumen harus bisa menangani pesan duplikat dengan aman. Reconciler juga dapat menemukan job dalam antrean yang tidak kunjung dimulai dan mencoba mengirimnya kembali.

Sesuaikan persistence dan eviction policy Redis serta visibility timeout Celery dengan beban pekerjaan. Jika visibility timeout lebih pendek daripada durasi task, pesan bisa dikirim ulang. Gunakan late acknowledgement hanya jika efek task idempotent; pengaturan ini tidak menjamin task dieksekusi tepat satu kali.

Pastikan baris yang sudah tersimpan aman diulang

Gunakan gabungan job ID dan nomor baris sumber untuk mengenali baris yang sama saat retry dalam satu job. Business key dalam lingkup tenant menentukan apakah record dibuat, diperbarui, atau dianggap konflik. Tentukan aturan pembaruan sebelum menulis upsert. Upsert yang idempotent pun bisa menimpa perubahan terbaru pengguna lain tanpa peringatan.

Contoh berikut mengasumsikan unique constraint pada (job_id, row_number) dan memproses batch berukuran terbatas dalam satu transaksi:

from django.db import transaction

@transaction.atomic
def apply_batch(job_id, rows):
    job = ImportJob.objects.select_for_update().get(pk=job_id)
    if job.cancel_requested or job.status != "processing":
        return False

    for row_number, payload in rows:
        receipt, created = ImportReceipt.objects.get_or_create(
            job_id=job_id,
            row_number=row_number,
        )
        if not created:
            continue
        # Kesalahan validasi yang diperkirakan dikembalikan sebagai data.
        clean, error = validate_row(payload)
        if error:
            receipt.error_code = error.code
            job.rejected_rows += 1
        else:
            upsert_record(tenant_id=job.tenant_id, data=clean)
            job.accepted_rows += 1
        receipt.save()
        job.processed_rows += 1
    job.save()
    return True

Nama model dan fungsi pembantu mengikuti kebutuhan aplikasi. Receipt, perubahan data domain, dan penghitung di-commit bersama. Kesalahan database yang tidak diperkirakan membatalkan batch sehingga tidak tersisa receipt untuk baris yang gagal diterapkan. Jika pelanggaran constraint termasuk kesalahan baris yang ingin dicatat, gunakan savepoint bersarang. Menangkap integrity error dalam transaksi luar lalu melanjutkan proses membuat transaksi itu tidak dapat dipakai.

Lock pada job membuat worker yang menangani job yang sama berjalan bergantian. Jaga durasi batch tetap singkat supaya proses pembatalan bisa memperoleh lock tersebut. Untuk pemrosesan partisi secara paralel, gunakan checkpoint per partisi dan tetapkan cara memperbarui penghitung agar semua worker tidak harus menunggu satu baris job. Efek samping seperti pengiriman email memerlukan outbox dan kunci deduplikasi tersendiri.

Gunakan streaming untuk membatasi penggunaan memori

Baca input melalui text wrapper yang mendukung streaming dan parser CSV yang bisa menangani field bertanda kutip. Field tersebut dapat memuat newline, jadi memecah input berdasarkan pergantian baris saja akan salah membacanya. Proses batch dengan ukuran terbatas, lalu lepaskan dari memori setelah commit. Hindari membuat daftar semua baris atau memuat seluruh workbook sekaligus. Format spreadsheet memerlukan library streaming khusus serta batas jumlah sheet, sel, dan ukuran data setelah ekspansi.

Untuk ekspor, iterasi queryset dengan urutan eksplisit dan kolom terpilih, atau gunakan batch berbasis keyset. Tulis CSV bertahap ke multipart object storage atau file sementara yang memiliki kuota, kemudian upload. Jangan merangkai seluruh ekspor menjadi satu string Python. Batalkan multipart upload yang tidak selesai dan bersihkan file sementara ketika terjadi kegagalan.

Tentukan bagaimana ekspor menangani record yang diedit saat proses berjalan. Keyset pagination menghindari pergeseran offset, tetapi nilai record masih bisa berubah antarbatch. Snapshot repeatable-read memberi hasil yang konsisten, dengan konsekuensi transaksi database harus tetap terbuka lebih lama. Menyimpan daftar ID menetapkan record mana saja yang diekspor, tetapi tidak mempertahankan nilai record saat ID dicatat. Dokumentasikan perilaku yang dipilih dan batasi durasi pekerjaan.

Kesalahan, retry, dan pembatalan memengaruhi pengguna

Simpan kesalahan per baris dengan nomor sumber, nama field, dan kode yang stabil. Jangan memasukkan nilai sensitif mentah ke log. Tampilkan sampel terbatas dalam respons job dan simpan laporan lengkap di storage terlindungi dengan kebijakan retensi. Jelaskan apakah baris valid tetap tersimpan ketika baris lain gagal. Impor yang wajib sepenuhnya berhasil atau sepenuhnya batal biasanya membutuhkan staging table dan transaksi publikasi terpisah.

Coba ulang operasi yang gagal karena gangguan sementara, seperti masalah jaringan dan deadlock, dengan exponential backoff terbatas serta jitter. Jangan terus mencoba memproses CSV yang rusak tanpa batas. Retry atas permintaan pengguna bisa melanjutkan job lama dari checkpoint atau membuat job baru; tentukan aturannya secara jelas. Jika idempotency key digunakan lagi dengan input berbeda, kembalikan konflik, bukan hasil sebelumnya yang tidak sesuai.

Simpan permintaan pembatalan sebagai flag persisten. Periksa flag di antara batch dan sebelum publikasi. Baris yang sudah di-commit tetap ada kecuali aplikasi menyediakan tindakan kompensasi. Jangan menyatakan pembatalan selesai ketika worker masih menuntaskan batch. Gunakan row lock atau pembaruan status bersyarat agar hanya satu status akhir yang tersimpan ketika pembatalan dan penyelesaian terjadi bersamaan.

Atur akses unduhan dan laporkan progres

Setiap endpoint status, pembatalan, retry, dan download memerlukan otorisasi tenant serta pengguna. Periksa ulang izin yang relevan saat pekerjaan dimulai dan ketika hasil diminta. Berikan signed URL berumur pendek hanya setelah otorisasi. Perlakukan URL sebagai bearer credential: jangan mencatatnya ke log, pertahankan bucket privat, dan atur penghapusan artefak secara terpisah dari kedaluwarsa URL.

Pengutipan CSV tidak mencegah formula injection di spreadsheet. Untuk teks yang akan dibuka pengguna spreadsheet, deteksi karakter awal formula seperti =, +, -, dan @, termasuk whitespace atau karakter kontrol mencurigakan di depannya. Terapkan kebijakan escaping yang terdokumentasi. Apostrof di awal sering dipakai, tetapi mengubah data dan bukan jaminan universal pada semua aplikasi spreadsheet. Jika ketepatan data penting, pertimbangkan ekspor spreadsheet dengan tipe sel teks eksplisit. Uji aplikasi pembaca yang didukung dan bedakan ekspor untuk mesin dari ekspor untuk manusia.

Saat total pekerjaan belum diketahui, tampilkan status seperti “Memvalidasi file”, “18.200 baris diproses”, atau “Menyiapkan unduhan”. Tampilkan persentase hanya jika total pekerjaan pada tahap itu sudah diketahui. Laporkan progres upload terpisah dari progres impor. Gunakan polling dengan backoff, hentikan sementara polling saat tab tidak terlihat, dan lanjutkan dari URL job setelah refresh. Nyatakan sukses hanya setelah objek akhir tersedia dan key-nya tersimpan.

Uji apa yang terjadi ketika worker mati di tengah batch, pesan diterima dua kali, atau URL unduhan kedaluwarsa. Uji juga pembatalan yang terjadi bersamaan dengan penyelesaian serta pemulihan database job sebelum mengandalkan alur ini.

Referensi