Bagaimana menerapkan algoritma Set based / UDF

13

Saya memiliki algoritma yang harus saya jalankan terhadap setiap baris dalam sebuah tabel dengan 800 ribu baris dan 38 kolom. Algoritme diimplementasikan dalam VBA dan melakukan banyak matematika menggunakan nilai-nilai dari beberapa kolom untuk memanipulasi kolom lain.

Saat ini saya menggunakan Excel (ADO) untuk query SQL dan menggunakan VBA dengan kursor sisi klien untuk menerapkan algoritma dengan loop melalui setiap baris. Ini bekerja tetapi membutuhkan 7 jam untuk berjalan.

Kode VBA cukup kompleks sehingga akan banyak pekerjaan untuk mengode ulang menjadi T-SQL.

Saya telah membaca tentang integrasi CLR dan UDF sebagai rute yang memungkinkan. Saya juga berpikir tentang menempatkan kode VBA dalam tugas skrip SSIS untuk lebih dekat ke database tetapi saya yakin metodologi ahli untuk jenis masalah kinerja ada.

Idealnya, saya dapat menjalankan algoritma terhadap sebanyak mungkin baris (semua?) Sebanyak mungkin dalam cara berbasis paralel.

Setiap bantuan sangat ditentukan tentang cara mendapatkan kinerja terbaik dengan masalah jenis ini.

--Edit

Terima kasih atas komentarnya, saya menggunakan MS SQL 2014 Enterprise, berikut ini beberapa perincian:

Algoritma menemukan pola karakteristik dalam data deret waktu. Fungsi-fungsi dalam algoritma melakukan perataan polinomial, windowing, dan menemukan daerah yang menarik berdasarkan kriteria input, mengembalikan selusin nilai dan beberapa hasil Boolean.

Pertanyaan saya lebih tentang metodologi daripada algoritma yang sebenarnya: Jika saya ingin mencapai komputasi paralel pada banyak baris sekaligus, apa saja pilihan saya.

Saya melihat re-code ke T-SQL direkomendasikan yang banyak pekerjaan tetapi mungkin, namun pengembang algoritma bekerja di VBA dan sering berubah jadi saya harus tetap menyinkronkan dengan versi T-SQL dan memvalidasi ulang setiap perubahan.

Apakah T-SQL satu-satunya cara untuk mengimplementasikan fungsi berbasis set?

medwar19
sumber
3
SSIS dapat menawarkan beberapa paralelisasi asli dengan asumsi Anda mendesain aliran data Anda dengan baik. Itulah tugas yang akan Anda cari karena Anda perlu melakukan perhitungan baris demi baris ini. Namun demikian, kecuali Anda dapat memberikan kami hal spesifik (skema, perhitungan yang terlibat, dan apa yang diharapkan perhitungan ini) tidak mungkin membantu Anda mengoptimalkan. Mereka mengatakan menulis hal-hal dalam perakitan dapat membuat kode tercepat tetapi jika, seperti saya, Anda mengisapnya dengan mengerikan, itu tidak akan efisien sama sekali
billinkc
2
Jika Anda memproses setiap baris secara mandiri, maka Anda dapat membagi 800 ribu baris menjadi Nkumpulan dan menjalankan Ninstance algoritma Anda pada Nprosesor / komputer yang terpisah. Di sisi lain, apa hambatan utama Anda - mentransfer data dari SQL Server ke Excel atau perhitungan aktual? Jika Anda mengubah fungsi VBA untuk mengembalikan beberapa hasil dummy dengan segera, berapa lama seluruh proses? Jika masih membutuhkan waktu berjam-jam, maka bottleneck dalam transfer data. Jika perlu beberapa detik, maka Anda perlu mengoptimalkan kode VBA yang melakukan perhitungan.
Vladimir Baranov
Ini filter yang dipanggil sebagai prosedur tersimpan: SELECT AVG([AD_Sensor_Data]) OVER (ORDER BY [RowID] ROWS BETWEEN 5 PRECEDING AND 5 FOLLOWING) as 'AD_Sensor_Data' FROM [AD_Points] WHERE [FileID] = @FileID ORDER BY [RowID] ASC Di Management Studio fungsi ini yang dipanggil untuk setiap baris membutuhkan 50
mS
1
Jadi permintaan yang membutuhkan 50 ms dan mengeksekusi 800000 kali (11 jam) adalah apa yang membutuhkan waktu. Apakah @FileID unik untuk setiap baris atau ada duplikat sehingga Anda dapat meminimalkan berapa kali Anda perlu menjalankan kueri? Anda juga bisa menghitung rata-rata rolling untuk semua fileid ke tabel pementasan dalam sekali jalan (gunakan partisi pada FileID) dan kemudian permintaan tabel itu tanpa perlu fungsi windowing untuk setiap baris. Pengaturan terbaik untuk tabel pementasan sepertinya harus dengan indeks berkerumun aktif (FileID, RowID).
Mikael Eriksson
1
Yang terbaik dari semuanya adalah jika Anda entah bagaimana bisa menghapus kebutuhan untuk menyentuh db untuk setiap baris. Itu berarti Anda harus pergi TSQL dan mungkin bergabung ke kueri avg bergulir atau mengambil cukup informasi untuk setiap baris sehingga semua yang dibutuhkan algoritma ada di sana di baris tersebut, mungkin disandikan dalam beberapa cara jika ada beberapa baris anak yang terlibat (xml) .
Mikael Eriksson

Jawaban:

8

Berkenaan dengan metodologi, saya percaya Anda menggonggong b-tree yang salah ;-).

Apa yang kita ketahui:

Pertama, mari berkonsolidasi dan tinjau apa yang kita ketahui tentang situasi ini:

  • Perhitungan yang agak rumit perlu dilakukan:
    • Ini perlu terjadi di setiap baris tabel ini.
    • Algoritma sering berubah.
    • Algoritma ... [menggunakan] nilai dari beberapa kolom untuk memanipulasi kolom lain
    • Waktu pemrosesan saat ini adalah: 7 jam
  • Meja:
    • mengandung 800.000 baris.
    • memiliki 38 kolom.
  • Aplikasi back-end:
  • Database adalah SQL Server 2014, Edisi Perusahaan.
  • Ada Prosedur Tersimpan yang dipanggil untuk setiap baris:

    • Ini membutuhkan 50 ms (pada rata-rata, saya berasumsi) untuk dijalankan.
    • Ini mengembalikan sekitar 4000 baris.
    • Definisi (setidaknya sebagian) adalah:

      SELECT AVG([AD_Sensor_Data])
                 OVER (ORDER BY [RowID] ROWS BETWEEN 5 PRECEDING AND 5 FOLLOWING)
                 as 'AD_Sensor_Data'
      FROM   [AD_Points]
      WHERE  [FileID] = @FileID
      ORDER BY [RowID] ASC

Apa yang bisa kita duga:

Selanjutnya, kita dapat melihat semua titik data ini bersama-sama untuk melihat apakah kita dapat mensintesis detail tambahan yang akan membantu kita menemukan satu atau lebih leher botol, dan menunjuk pada suatu solusi, atau setidaknya mengesampingkan beberapa solusi yang mungkin keluar.

Arah pemikiran saat ini dalam komentar adalah bahwa masalah utama adalah transfer data antara SQL Server dan Excel. Apakah benar hal itu merupakan masalahnya? Jika Prosedur Tersimpan dipanggil untuk masing-masing 800.000 baris dan membutuhkan 50 ms per setiap panggilan (yaitu per setiap baris), itu menambah hingga 40.000 detik (bukan ms). Dan itu setara dengan 666 menit (hhmm ;-), atau hanya lebih dari 11 jam. Namun seluruh proses dikatakan hanya memakan waktu 7 jam untuk berjalan. Kami sudah 4 jam dari total waktu, dan kami bahkan telah menambahkan waktu untuk melakukan perhitungan atau menyimpan hasilnya kembali ke SQL Server. Jadi ada sesuatu yang tidak beres di sini.

Melihat definisi Prosedur Tersimpan, hanya ada parameter input untuk @FileID; tidak ada filter aktif @RowID. Jadi saya menduga bahwa salah satu dari dua skenario berikut sedang terjadi:

  • Prosedur Tersimpan ini tidak benar - benar dipanggil per setiap baris, tetapi sebaliknya per masing-masing @FileID, yang tampaknya menjangkau sekitar 4000 baris. Jika 4000 baris yang dikembalikan adalah jumlah yang cukup konsisten, maka hanya ada 200 dari pengelompokan dalam 800.000 baris. Dan 200 eksekusi mengambil 50 ms setiap jumlah hanya 10 detik dari 7 jam itu.
  • Jika prosedur yang tersimpan ini benar-benar dipanggil untuk setiap baris, maka bukankah pertama kali sebuah baru @FileIDdilewatkan akan membutuhkan waktu sedikit lebih lama untuk menarik baris baru ke dalam Buffer Pool, tetapi kemudian 3999 eksekusi berikutnya biasanya akan kembali lebih cepat karena sudah menjadi di-cache, kan?

Saya pikir fokus pada Prosedur Tersimpan "filter" ini, atau transfer data apa pun dari SQL Server ke Excel, adalah herring merah .

Untuk saat ini, saya pikir indikator yang paling relevan dari kinerja loyo adalah:

  • Ada 800.000 baris
  • Operasi bekerja pada satu baris sekaligus
  • Data disimpan kembali ke SQL Server, karenanya "[menggunakan] nilai dari beberapa kolom untuk memanipulasi kolom lain " [my em phas is ;-)]

Saya menduga bahwa:

  • sementara ada beberapa ruang untuk perbaikan pada pengambilan data dan perhitungan, menjadikannya lebih baik tidak akan berarti pengurangan yang signifikan dalam waktu pemrosesan.
  • kemacetan utama adalah mengeluarkan 800.000 UPDATEpernyataan terpisah , yang merupakan 800.000 transaksi terpisah.

Rekomendasi saya (berdasarkan informasi yang tersedia saat ini):

  1. Bidang peningkatan terbesar Anda adalah memperbarui beberapa baris sekaligus (yaitu dalam satu transaksi). Anda harus memperbarui proses Anda agar berfungsi dalam hal masing-masing FileIDalih-alih masing-masing RowID. Begitu:

    1. baca di semua 4000 baris tertentu FileIDke dalam array
    2. array harus mengandung elemen yang mewakili bidang yang sedang dimanipulasi
    3. siklus melalui array, memproses setiap baris seperti yang Anda lakukan saat ini
    4. setelah semua baris dalam array (yaitu untuk ini FileID) telah dihitung:
      1. memulai transaksi
      2. panggil setiap pembaruan per masing-masing RowID
      3. jika tidak ada kesalahan, lakukan transaksi
      4. jika terjadi kesalahan, kembalikan dan tangani dengan tepat
  2. Jika indeks cluster Anda belum didefinisikan (FileID, RowID)maka Anda harus mempertimbangkan itu (seperti yang disarankan @MikaelEriksson dalam komentar pada Pertanyaan). Ini tidak akan membantu UPDATE tunggal ini, tetapi setidaknya akan sedikit meningkatkan operasi agregat, seperti apa yang Anda lakukan dalam prosedur tersimpan "filter" karena semuanya didasarkan pada FileID.

  3. Anda harus mempertimbangkan untuk memindahkan logika ke bahasa yang dikompilasi. Saya akan menyarankan membuat aplikasi .NET WinForms atau bahkan Aplikasi Konsol. Saya lebih suka Aplikasi Konsol karena mudah menjadwalkan melalui SQL Agent atau Tugas Terjadwal Windows. Seharusnya tidak masalah apakah itu dilakukan dalam VB.NET atau C #. VB.NET mungkin lebih cocok untuk pengembang Anda, tetapi masih ada beberapa kurva belajar.

    Saya tidak melihat alasan pada saat ini untuk pindah ke SQLCLR. Jika algoritma sering berubah, itu akan mengganggu harus menggunakan kembali Majelis sepanjang waktu. Membangun kembali aplikasi konsol dan menempatkan .exe ditempatkan di folder bersama yang tepat di jaringan sehingga Anda hanya menjalankan program yang sama dan kebetulan selalu up-to-date, harus cukup mudah dilakukan.

    Saya tidak berpikir memindahkan pemrosesan sepenuhnya ke T-SQL akan membantu jika masalahnya adalah apa yang saya duga dan Anda hanya melakukan satu PEMBARUAN sekaligus.

  4. Jika pemrosesan dipindahkan ke .NET, Anda kemudian dapat menggunakan Table-Valued Parameters (TVPs) sehingga Anda akan meneruskan array ke Prosedur Tersimpan yang akan memanggil seorang UPDATEyang BERGABUNG ke variabel tabel TVP dan karenanya merupakan satu transaksi . TVP harus lebih cepat daripada melakukan 4000 INSERTyang dikelompokkan ke dalam satu transaksi. Tetapi keuntungan yang didapat dari menggunakan TVPs lebih dari 4000 INSERTdetik dalam 1 transaksi kemungkinan tidak akan sama pentingnya dengan peningkatan yang terlihat ketika beralih dari 800.000 transaksi terpisah menjadi hanya 200 transaksi masing-masing dari 4000 baris.

    Opsi TVP tidak tersedia secara native untuk sisi VBA, tetapi seseorang datang dengan solusi yang mungkin layak untuk diuji:

    Bagaimana cara meningkatkan kinerja basis data saat beralih dari VBA ke SQL Server 2008 R2?

  5. JIKA proc filter hanya menggunakan FileIDdalam WHEREklausa, dan JIKA proc itu benar-benar dipanggil per setiap baris, maka Anda dapat menghemat waktu pemrosesan dengan cache hasil run pertama dan menggunakannya untuk sisa baris per itu FileID, Baik?

  6. Setelah Anda menyelesaikan pemrosesan per FileID , maka kita dapat mulai berbicara tentang pemrosesan paralel. Tapi itu mungkin tidak perlu pada saat itu :). Mengingat bahwa Anda berurusan dengan 3 bagian non-ideal yang cukup besar: transaksi Excel, VBA, dan 800 ribu, pembicaraan SSIS, atau jajaran genjang, atau siapa yang tahu apa, adalah pengoptimalan dini / jenis barang sebelum kuda . Jika kita bisa mendapatkan proses 7 jam ini menjadi 10 menit atau kurang, apakah Anda masih memikirkan cara lain untuk membuatnya lebih cepat? Apakah ada target waktu penyelesaian yang Anda pikirkan? Perlu diingat bahwa setelah pemrosesan dilakukan pada per FileID dasar, jika Anda memiliki VB.NET Console App (yaitu command-line .EXE), tidak akan ada yang menghentikan Anda dari menjalankan beberapa FileID tersebut sekaligus :), baik melalui langkah SQL Agent CmdExec atau Windows Scheduled Tasks, dll.

DAN, Anda selalu dapat mengambil pendekatan "bertahap" dan melakukan beberapa peningkatan sekaligus. Seperti memulai dengan melakukan pembaruan per FileIDdan karenanya menggunakan satu transaksi untuk grup itu. Kemudian, lihat apakah Anda bisa membuat TVP berfungsi. Kemudian lihat tentang mengambil kode itu dan memindahkannya ke VB.NET (dan TVPs bekerja di .NET sehingga akan port dengan baik).


Apa yang kita tidak tahu yang masih bisa membantu:

  • Apakah "Stored" Stored Procedure berjalan per RowID atau per FileID ? Apakah kita bahkan memiliki definisi penuh tentang Prosedur Tersimpan itu?
  • Skema penuh dari tabel. Seberapa lebar tabel ini? Berapa banyak bidang panjang variabel? Berapa banyak bidang yang NULLable? Jika ada yang NULLable, berapa banyak yang mengandung NULLs?
  • Indeks untuk tabel ini. Apakah dipartisi? Apakah ROW atau PAGE Compression digunakan?
  • Seberapa besar tabel ini dalam hal MB / GB?
  • Bagaimana pemeliharaan indeks ditangani untuk tabel ini? Seberapa terfragmentasi indeks? Bagaimana statistik terkini diperbarui?
  • Apakah ada proses lain menulis ke tabel ini saat proses 7 jam ini berlangsung? Kemungkinan sumber pertikaian.
  • Apakah ada proses lain yang dibaca dari tabel ini saat proses 7 jam ini berlangsung? Kemungkinan sumber pertikaian.

PEMBARUAN 1:

** Tampaknya ada beberapa kebingungan tentang apa yang VBA (Visual Basic for Applications) dan apa yang dapat dilakukan dengannya, jadi ini hanya untuk memastikan kita semua berada di halaman web yang sama:


PEMBARUAN 2:

Satu hal lagi yang perlu dipertimbangkan: Bagaimana koneksi ditangani? Apakah kode VBA membuka dan menutup Koneksi per setiap operasi, atau apakah itu membuka koneksi pada awal proses dan menutupnya pada akhir proses (yaitu 7 jam kemudian)? Bahkan dengan penyatuan koneksi (yang, secara default, harus diaktifkan untuk ADO), masih harus ada dampak yang cukup antara membuka dan menutup sekali sebagai lawan membuka dan menutup baik 800.200 atau 1.600.000 kali. Nilai-nilai tersebut didasarkan pada setidaknya 800.000 UPDATE ditambah 200 atau 800k EXEC (tergantung pada seberapa sering prosedur yang tersimpan filter sebenarnya dieksekusi).

Masalah terlalu banyak koneksi ini secara otomatis dikurangi dengan rekomendasi yang saya uraikan di atas. Dengan membuat transaksi dan melakukan semua UPDATE dalam transaksi itu, Anda akan menjaga agar koneksi tetap terbuka dan menggunakannya kembali untuk masing-masingnya UPDATE. Apakah koneksi tetap terbuka dari panggilan awal untuk mendapatkan 4000 baris per yang ditentukan FileID, atau ditutup setelah operasi "dapatkan" dan dibuka lagi untuk UPDATE, jauh lebih tidak berdampak karena kita sekarang berbicara tentang perbedaan antara keduanya 200 atau 400 total koneksi di seluruh proses.

PEMBARUAN 3:

Saya melakukan beberapa pengujian cepat. Perlu diingat bahwa ini adalah tes skala yang agak kecil, dan bukan operasi yang sama persis (INSERT murni vs EXEC + PEMBARUAN). Namun, perbedaan waktu terkait dengan bagaimana koneksi dan transaksi ditangani masih relevan, maka informasi tersebut dapat diekstrapolasi untuk memiliki dampak yang relatif sama di sini.

Parameter uji:

  • Edisi Pengembang SQL Server 2012 (64-bit), SP2
  • Meja:

     CREATE TABLE dbo.ManyInserts
     (
        RowID INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
        InsertTime DATETIME NOT NULL DEFAULT (GETDATE()),
        SomeValue BIGINT NULL
     );
  • Operasi:

    INSERT INTO dbo.ManyInserts (SomeValue) VALUES ({LoopIndex * 12});
  • Total Sisipan per setiap tes: 10.000
  • Reset per setiap tes: TRUNCATE TABLE dbo.ManyInserts;(mengingat sifat dari tes ini, melakukan FREEPROCCACHE, FREESYSTEMCACHE, dan DROPCLEANBUFFERS tampaknya tidak menambah banyak nilai.)
  • Model Pemulihan: SIMPLE (dan mungkin 1 GB gratis di file Log)
  • Tes yang menggunakan Transaksi hanya menggunakan Koneksi tunggal terlepas dari berapa banyak Transaksi.

Hasil:

Test                                   Milliseconds
-------                                ------------
10k INSERTs across 10k Connections     3968 - 4163
10k INSERTs across 1 Connection        3466 - 3654
10k INSERTs across 1 Transaction       1074 - 1086
10k INSERTs across 10 Transactions     1095 - 1169

Seperti yang Anda lihat, bahkan jika koneksi ADO ke DB sudah dibagikan di semua operasi, pengelompokan mereka ke dalam batch menggunakan transaksi eksplisit (objek ADO harus dapat menangani ini) dijamin secara signifikan (yaitu lebih dari 2x peningkatan) mengurangi waktu proses keseluruhan.

Solomon Rutzky
sumber
Ada pendekatan "perantara" yang bagus untuk apa yang disarankan srutzky, dan itu adalah menggunakan PowerShell untuk mendapatkan data yang Anda butuhkan dari SQL Server, panggil skrip VBA Anda untuk mengerjakan data, dan kemudian panggil pembaruan SP di SQL Server , meneruskan kunci dan nilai yang diperbarui kembali ke SQL server. Dengan cara ini Anda menggabungkan pendekatan berbasis set dengan apa yang sudah Anda miliki.
Steve Mangiameli
@SteveMangiameli Hai Steve dan terima kasih atas komentarnya. Saya akan menjawab lebih cepat tetapi sakit. Saya ingin tahu bagaimana ide Anda jauh berbeda dari apa yang saya sarankan. Semua indikasi adalah bahwa Excel masih diperlukan untuk menjalankan VBA. Atau Anda menyarankan agar PowerShell akan menggantikan ADO, dan jika jauh lebih cepat di I / O, akan sia-sia meskipun hanya untuk mengganti hanya I / O?
Solomon Rutzky
1
Jangan khawatir, senang perasaanmu lebih baik. Saya tidak tahu bahwa itu akan lebih baik. Kami tidak tahu apa yang tidak kami ketahui dan Anda telah melakukan beberapa analisis hebat tetapi masih harus membuat beberapa asumsi. I / O mungkin cukup signifikan untuk diganti sendiri; kita tidak tahu. Saya hanya ingin menyajikan pendekatan lain yang mungkin membantu dengan hal-hal yang Anda sarankan.
Steve Mangiameli
@SteveMangiameli Terima kasih. Dan terima kasih telah menjelaskannya. Saya tidak yakin dengan arah pasti Anda dan berpikir sebaiknya tidak berasumsi. Ya, saya setuju bahwa memiliki lebih banyak opsi lebih baik karena kita tidak tahu kendala apa yang ada pada perubahan apa yang dapat dilakukan :).
Solomon Rutzky
Hai srutzky, terima kasih atas pemikiran terperinci! Saya telah kembali menguji di sisi SQL mendapatkan indeks dan permintaan dioptimalkan dan mencoba untuk menemukan kemacetan. Saya telah berinvestasi di server yang tepat sekarang, 36cores, 1TB dilucuti SSD PCIe karena IO macet. Sekarang untuk memanggil kode VB langsung di SSIS yang tampaknya membuka banyak utas untuk eksekusi paralel.
medwar19
2

IMHO dan bekerja dari asumsi bahwa tidak layak untuk kembali kode sub VBA ke dalam SQL, sudahkah Anda mempertimbangkan untuk membiarkan skrip VBA selesai mengevaluasi dalam file Excel dan kemudian menulis hasilnya kembali ke SQL server melalui SSIS?

Anda bisa membuat sub VBA mulai dan berakhir dengan membalik indikator baik dalam objek sistem file atau di server (jika Anda sudah mengkonfigurasi koneksi untuk menulis kembali ke server) dan kemudian menggunakan ekspresi SSIS untuk memeriksa indikator ini untuk disableproperti dari tugas yang diberikan dalam solusi SSIS Anda (sehingga proses impor menunggu sampai sub VBA selesai jika Anda khawatir tentang hal itu melampaui jadwalnya).

Selain itu, Anda dapat membuat skrip VBA mulai terprogram (sedikit miring, tetapi saya telah menggunakan workbook_open()properti untuk memicu tugas "memecat dan melupakan" jenis ini di masa lalu).

Jika waktu evaluasi skrip VB mulai menjadi masalah, Anda bisa melihat apakah pengembang VB Anda bersedia dan mampu mem-port-kan kodenya ke dalam tugas skrip VB dalam solusi SSIS - dalam pengalaman saya aplikasi Excel menarik banyak overhead saat bekerja dengan data pada volume ini.

Peter Vandivier
sumber