Artikel Teknis

Penyimpanan Tahan-Crash HotXLS: File Temp Bertahap di Delphi

Sebuah penyimpanan yang mati di tengah jalan, baik karena reboot paksa, proses yang dimatikan, atau disk yang penuh di tengah penulisan, secara tradisional berarti satu hal untuk sebuah format yang dibangun di sekitar penulisan in-place: byte apa pun yang sampai ke disk sebelum interupsi itulah yang Anda dapatkan kembali, dan sebuah workbook yang terpotong tidak akan terbuka lagi. HotXLS menutup mode kegagalan itu dengan sebuah jalur penyimpanan tahan-crash yang digunakan untuk setiap file XLSX, ODS, dan XLS klasik yang ditulisnya. Setiap pemanggilan SaveAs menulis file baru lengkap ke sebuah file sementara yang dibuat di sebelah tujuan, lalu mengomitnya dengan satu rename MoveFileExW atomik tunggal dari API Windows, sehingga sebuah penyimpanan yang terinterupsi hanya bisa gagal menghasilkan file baru, tidak pernah merusak file yang sudah Anda miliki. Disiplin stage-lalu-swap yang sama berjalan seragam di kedua mesin penyimpanan HotXLS, penulis BIFF8 di balik XLS klasik dan penulis OOXML di balik XLSX dan ODS, dan ini adalah pola yang layak dipinjam untuk file apa pun yang ditimpa langsung kode Delphi Anda sendiri, spreadsheet atau bukan

Apa yang terjadi jika penyimpanan sebuah workbook terinterupsi di tengah jalan?

Jawaban langsungnya adalah semuanya bergantung pada bagaimana penulis menyentuh file tujuan, dan implementasi umum, membuka file target dan men-stream konten baru langsung ke dalamnya, baik-baik saja selama tidak ada yang pernah salah. Begitu ada yang salah, sebuah crash, sebuah kematian proses paksa, sebuah network share yang terputus di tengah penulisan, file di disk ditinggalkan pada state perantara apa pun yang sudah dicapai penulis: sebuah central directory ZIP yang tidak pernah ditambahkan untuk XLSX atau ODS, atau sebuah stream BIFF yang kehilangan record yang diharapkan reader untuk XLS klasik. Excel tidak memperbaiki itu dengan mulus, begitu pula konsumen lain mana pun yang mengharapkan sebuah file lengkap, sehingga hasil praktisnya adalah sebuah workbook yang kemarin terbuka baik-baik saja dan hari ini menolak untuk terbuka

Bagaimana HotXLS mementaskan setiap penyimpanan di balik satu swap atomik

HotXLS tidak pernah membuka file tujuan untuk penulisan secara langsung, untuk ketiga format yang disimpannya. Urutannya berbentuk sama setiap kali: bangun output lengkap di suatu tempat yang bukan file yang sudah dimiliki pengguna di disk, dan hanya pindahkan ke tempatnya begitu pembangunan itu sepenuhnya berhasil. Secara konkret, SaveAs membuat sebuah file sementara kosong di folder yang sama dengan path target, menulis seluruh workbook baru ke dalam file sementara itu, dan hanya setelah penulisan itu kembali tanpa error barulah ia mengomit file sementara itu di atas tujuan dengan satu rename tunggal. Tidak satu pun dari ini membutuhkan sebuah properti untuk diaktifkan; ini sekadar apa yang dilakukan SaveAs untuk sebuah path file biasa, pada setiap pemanggilan

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Report');
    Sheet.Cells[1, 1].Value := 'Nothing special to enable here';
    // If this call is interrupted, monthly-report.xlsx on disk stays
    // either the old version, complete, or the new version, complete
    if Book.SaveAs('monthly-report.xlsx', xlsxOpenXMLWorkbook) <> 1 then
      raise Exception.Create('Save failed, see Book.LastDiagnostic');
  finally
    Book.Free;
  end;
end;

Disiplin yang sama berlaku pada penulis XLS klasik, tidak hanya yang OOXML, dan kedua file sementara itu bahkan berbagi konvensi penamaan: keduanya memanggil API Windows GetTempFileNameW dengan prefix hxl, sehingga sebuah penyimpanan yang terinterupsi sebelum pembersihan bisa meninggalkan sebuah file nyasar dengan nama seperti hxl4C2A.tmp duduk di sebelah workbook Anda. File itu bukan korupsi, itu bukti bahwa mekanismenya bekerja persis seperti yang dirancang: penulisan yang tidak lengkap berhenti di sana, dan workbook Anda yang sesungguhnya tidak pernah dibuka untuk penulisan sejak awal. Melihat satu setelah crash aman untuk dihapus dan tidak ada yang perlu diselidiki

Mengapa mementaskan file temp di sebelah workbook alih-alih di %TEMP%?

Jawaban singkatnya adalah rename MoveFileExW hanya atomik ketika sumber dan tujuan berada pada volume yang sama, dan cara paling pasti untuk menjamin itu tanpa meminta pemanggil mengonfigurasi apa pun adalah menurunkan lokasi file sementara dari path tujuan itu sendiri. HotXLS menghitung folder target itu sendiri dan menyerahkan direktori itu langsung ke GetTempFileNameW, sehingga file sementara selalu dibuat pada drive yang sama, volume yang sama, dengan file yang akan digantikannya, secara otomatis, untuk setiap penyimpanan. Seandainya library itu justru mementaskan penulisan di folder temp sistem, sebuah path target pada drive yang berbeda atau sebuah volume jaringan yang di-mapping akan mengubah langkah terakhir menjadi sebuah operasi lintas-volume, yang ditolak API Windows secara langsung atau, jika seorang pemanggil secara eksplisit memilih dengan sebuah flag tambahan yang tidak diatur HotXLS di sini, diam-diam turun menjadi sebuah copy non-atomik yang diikuti sebuah delete, membuka kembali persis jendela interupsi yang ingin ditutup seluruh mekanisme ini

Langkah commit: MoveFileExW, write-through, dan apa yang terjadi saat gagal

Langkah terakhir dari setiap penyimpanan adalah persis satu pemanggilan API Windows, MoveFileExW, membawa dua flag yang masing-masing melakukan pekerjaan berbeda. MOVEFILE_REPLACE_EXISTING adalah yang mengizinkan rename mendarat pada sebuah file yang sudah ada; tanpanya, sebuah rename yang menargetkan sebuah path yang sudah ada sekadar gagal, yang akan mengalahkan seluruh maksud sebuah penyimpanan yang dimaksudkan untuk menggantikan sebuah workbook yang sudah Anda miliki. MOVEFILE_WRITE_THROUGH mencakup daya tahan: ia memberi tahu fungsi tersebut untuk tidak kembali sampai perpindahan itu benar-benar selesai di disk, alih-alih kembali begitu rename itu sekadar diantrekan, menutup sebuah race yang lebih sempit tetapi nyata di mana sebuah crash segera setelah SaveAs kembali masih bisa menangkap swap tersebut sedang berlangsung. Jika file sementara tidak bisa dibuat, atau rename final gagal karena alasan apa pun (sebuah masalah izin, sebuah tujuan yang terkunci, sebuah ketidakcocokan volume), HotXLS menghapus sendiri file sementara itu alih-alih meninggalkan sampah, dan file tujuan ditinggalkan persis seperti sebelum pemanggilan tersebut

Result := Book.SaveAs(TargetPath, xlsxOpenXMLWorkbook);
if Result <> 1 then
begin
  // TargetPath on disk is unchanged; safe to retry, alert, or
  // fall back to a different path without touching prior output
  LogWriter.Write(Format('SaveAs failed (%d): %s',
    [Book.LastDiagnostic.Code, Book.LastDiagnostic.Message]));
  Exit(False);
end;

SaveAs sendiri mempertahankan konvensi kembalian yang dibagi di seluruh HotXLS, satu untuk sukses, sebuah angka negatif untuk gagal, tetapi sebuah integer polos tidak mengatakan mengapa sebuah penyimpanan gagal, dan memperlakukan setiap hasil negatif dengan cara yang sama membuang informasi yang sebenarnya bisa digunakan sebuah kebijakan retry. Properti LastDiagnostic, dan koleksi Diagnostics yang lebih lengkap di baliknya, membawa pesan yang dihasilkan HotXLS secara internal, membedakan sebuah file temp yang tidak bisa dibuat dari sebuah rename yang ditolak Windows. Sebuah job batch yang mencatat Code dan Message pada setiap SaveAs yang gagal membangun persis bukti yang Anda inginkan pada satu saat seorang pelanggan melaporkan sebuah penyimpanan yang diam-diam tidak melakukan apa-apa

XLS klasik membayar dengan memori, XLSX dan ODS membayar dengan disk

Kedua mesin penyimpanan mencapai hasil tahan-crash yang sama lewat rute berbeda, dan perbedaannya penting jika Anda sudah menyetel salah satunya untuk sebuah job batch besar. Penulis XLS klasik membangun seluruh dokumen compound OLE di memori lebih dulu, menggunakan structured storage yang didukung sebuah memory handle, dan hanya menyalin buffer jadi itu keluar ke file sementara sebelah dalam satu penulisan; alasan dalam source code HotXLS sendiri langsung: membangun seluruh file di memori lebih dulu adalah yang mencegah sebuah penyimpanan yang gagal atau dibatalkan pernah memotong tujuan. Penulis XLSX dan ODS sebaliknya men-stream entri ZIP-nya ke dalam file sementara saat entri itu dihasilkan, staging tingkat-file yang sama dengan profil memori berbeda. Jika Anda sudah mengandalkan StreamingWrite untuk menjaga ekspor XLSX besar tetap di dalam batas memori sebuah container, ketahuilah bahwa tuas setara untuk ekspor XLS lawas tidak ada dalam bentuk yang sama: jaminan tahan-crash tidak bersyarat dalam kedua kasus, tetapi sebuah ekspor .xls lawas yang sangat besar tetap memegang output lengkapnya di RAM terlepas dari itu, sebuah trade-off yang dibahas lebih dalam di artikel kami tentang streaming write untuk job batch server

Menerapkan pola yang sama di luar HotXLS, dan di mana jaminan itu berakhir

Meminjam polanya sebagian besar merupakan urusan menghubungkan dua pemanggilan API Windows yang sama yang diandalkan HotXLS secara internal. GetTempFileNameW menyerahkan kepada Anda sebuah file kosong bernama unik dalam sebuah folder yang Anda pilih, dan MoveFileExW mengomit penulisan jadi Anda di atas tujuan sesungguhnya dalam satu langkah; sebuah versi minimal dari rutin yang sama yang dijalankan HotXLS sebelum setiap SaveAs terlihat seperti ini

function SaveFileAtomically(const Path: WideString; const Contents: TBytes): Boolean;
var
  Dir, TempName: WideString;
  Buffer: array[0..MAX_PATH] of WideChar;
  FS: TFileStream;
begin
  Result := False;
  Dir := ExtractFilePath(ExpandFileName(Path));
  FillChar(Buffer, SizeOf(Buffer), 0);
  if GetTempFileNameW(PWideChar(Dir), 'app', 0, @Buffer[0]) = 0 then
    Exit;
  TempName := PWideChar(@Buffer[0]);
  try
    FS := TFileStream.Create(TempName, fmCreate or fmShareExclusive);
    try
      FS.WriteBuffer(Contents[0], Length(Contents));
    finally
      FS.Free;
    end;
    Result := MoveFileExW(PWideChar(TempName), PWideChar(ExpandFileName(Path)),
      MOVEFILE_REPLACE_EXISTING or MOVEFILE_WRITE_THROUGH);
  finally
    if not Result then
      DeleteFileW(PWideChar(TempName));
  end;
end;

Jaminan ini memiliki batas-batas nyata yang layak diketahui sebelum Anda mengandalkannya secara membabi buta. Mementaskan sebuah salinan lengkap sebelum menggantikan yang asli berarti sebuah penyimpanan sesaat membutuhkan ruang disk untuk file lama dan yang baru, kira-kira dua kali ukuran workbook selama durasi penulisan, yang baik-baik saja untuk sebuah laporan dan layak diperiksa untuk sebuah ekspor multi-gigabyte yang berjalan terhadap sebuah volume yang hampir penuh. File sementara juga harus mendarat di folder yang sama dengan tujuan, sehingga akun apa pun yang menjalankan HotXLS membutuhkan izin create-file pada folder itu secara spesifik, bukan sekadar izin menimpa satu file yang sudah diketahuinya; sebuah deployment yang mengunci sebuah folder tujuan hanya untuk edit in-place pada nama file tertentu yang sudah ada, alih-alih akses tulis tingkat-folder, akan melihat SaveAs gagal pada langkah file-temp meski penulisan langsung yang setara akan berhasil

Dua batas lagi layak ditandai secara jelas. Sebuah tujuan pada sebuah network share atau di dalam sebuah folder yang disinkronkan OneDrive atau klien serupa bisa berperilaku berbeda dari NTFS lokal meski Windows tetap melaporkannya sebagai satu volume tunggal, karena driver filesystem di depannya mungkin tidak mengimplementasikan rename dengan cara yang sama; jika target deployment Anda menyimpan lewat sebuah path jaringan, layak menguji sebuah interupsi paksa di sana secara spesifik alih-alih mengasumsikan perilaku disk-lokal berlaku sama. Dan seluruh mekanisme ini terlingkup pada penyimpanan ke dalam sebuah file bernama. Panggil SaveAs terhadap sebuah TStream sebagai gantinya, dan HotXLS menulis langsung ke dalam stream apa pun yang Anda serahkan, tanpa file tujuan untuk dipentaskan atau dilindungi, karena daya tahan stream itu (sebuah buffer memori, sebuah upload jaringan, sebuah blob database) sepenuhnya menjadi tanggung jawab kode Anda dari titik itu

Sebuah langkah verifikasi bisa mengandalkan persis jaminan ini setelahnya, termasuk yang dibangun ke dalam sebuah workbench audit dan konversi workbook: sebuah file yang dibuka ulang dan kembali pendek atau hilang adalah sebuah masalah konversi sungguhan untuk ditelusuri, tidak pernah sebuah penyimpanan yang terinterupsi di tengah jalan dan meninggalkan sesuatu yang ambigu di disk. Penulisan bertahap tahan-crash dibangun ke dalam SaveAs untuk setiap workbook XLSX, ODS, dan XLS klasik yang dihasilkan HotXLS Component untuk Delphi dan C++Builder, tanpa konfigurasi yang dibutuhkan untuk mengaktifkannya