ข้อมูลขนาดใหญ่และประสิทธิภาพใน SQL Server


20

ฉันเขียนแอปพลิเคชันด้วยแบ็กเอนด์ SQL Server ที่รวบรวมและจัดเก็บและบันทึกจำนวนมาก ฉันได้คำนวณว่าที่จุดสูงสุดปริมาณการบันทึกโดยเฉลี่ยอยู่ที่ประมาณ 3-4 พันล้านต่อวัน (การดำเนินการ 20 ชั่วโมง)

โซลูชันดั้งเดิมของฉัน (ก่อนที่ฉันจะทำการคำนวณข้อมูลจริง) เพื่อให้แอปพลิเคชันของฉันแทรกระเบียนลงในตารางเดียวกันที่ลูกค้าของฉันสอบถาม นั่นขัดข้องและถูกเผาอย่างรวดเร็วค่อนข้างชัดเจนเพราะเป็นไปไม่ได้ที่จะสืบค้นตารางที่มีการแทรกเรคคอร์ดจำนวนมาก

โซลูชันที่สองของฉันคือการใช้ฐานข้อมูล 2 ฐานข้อมูลหนึ่งสำหรับข้อมูลที่ได้รับจากแอปพลิเคชันและอีกหนึ่งฐานข้อมูลสำหรับไคลเอ็นต์ที่พร้อมใช้งาน

แอปพลิเคชันของฉันจะรับข้อมูลและแบ่งเป็นชุด ~ 100k เรคคอร์ดและแทรกจำนวนมากในตารางการจัดเตรียม หลังจาก ~ 100k บันทึกแอปพลิเคชันทันทีสร้างตารางการแสดงละครอีกด้วยสคีมาเหมือนก่อนหน้าและเริ่มแทรกลงในตารางนั้น มันจะสร้างเรกคอร์ดในตารางงานที่มีชื่อของตารางที่มีเรกคอร์ด 100k และกระบวนงานที่เก็บไว้ในฝั่ง SQL Server จะย้ายข้อมูลจากตาราง staging ไปยังตารางการผลิตที่ไคลเอ็นต์พร้อมแล้วปล่อย ตารางตารางชั่วคราวที่สร้างโดยแอปพลิเคชันของฉัน

ฐานข้อมูลทั้งสองมีชุดตาราง 5 ชุดที่มีสคีมาเดียวกันยกเว้นฐานข้อมูลที่มีตารางงาน ฐานข้อมูลการจัดเตรียมไม่มีข้อ จำกัด ด้านความสมบูรณ์, คีย์, ดัชนี ฯลฯ ... บนตารางที่มีระเบียนจำนวนมากที่จะอยู่ SignalValues_stagingดังต่อไปนี้ชื่อตารางเป็น เป้าหมายคือเพื่อให้แอปพลิเคชันของฉันกระแทกข้อมูลลงใน SQL Server โดยเร็วที่สุด เวิร์กโฟลว์ของการสร้างตารางได้ทันทีเพื่อให้สามารถโยกย้ายได้อย่างง่ายดายทำงานได้ค่อนข้างดี

ต่อไปนี้คือ 5 ตารางที่เกี่ยวข้องจากฐานข้อมูลการจัดเตรียมของฉันรวมถึงตารางงานของฉัน:

การจัดเตรียมตาราง ขั้นตอนการจัดเก็บที่ฉันได้เขียนจัดการการเคลื่อนย้ายข้อมูลจากตาราง staging ทั้งหมดและแทรกลงในการผลิต ด้านล่างเป็นส่วนหนึ่งของขั้นตอนการจัดเก็บของฉันที่แทรกลงในการผลิตจากตารางการแสดงละคร:

-- Signalvalues jobs table.
SELECT *
      ,ROW_NUMBER() OVER (ORDER BY JobId) AS 'RowIndex'
INTO #JobsToProcess
FROM 
(
    SELECT JobId 
           ,ProcessingComplete  
           ,SignalValueStagingTableName AS 'TableName'
           ,(DATEDIFF(SECOND, (SELECT last_user_update
                              FROM sys.dm_db_index_usage_stats
                              WHERE database_id = DB_ID(DB_NAME())
                                AND OBJECT_ID = OBJECT_ID(SignalValueStagingTableName))
                     ,GETUTCDATE())) SecondsSinceLastUpdate
    FROM SignalValueJobs
) cte
WHERE cte.ProcessingComplete = 1
   OR cte.SecondsSinceLastUpdate >= 120

DECLARE @i INT = (SELECT COUNT(*) FROM #JobsToProcess)

DECLARE @jobParam UNIQUEIDENTIFIER
DECLARE @currentTable NVARCHAR(128) 
DECLARE @processingParam BIT
DECLARE @sqlStatement NVARCHAR(2048)
DECLARE @paramDefinitions NVARCHAR(500) = N'@currentJob UNIQUEIDENTIFIER, @processingComplete BIT'
DECLARE @qualifiedTableName NVARCHAR(128)

WHILE @i > 0
BEGIN

    SELECT @jobParam = JobId, @currentTable = TableName, @processingParam = ProcessingComplete
    FROM #JobsToProcess 
    WHERE RowIndex = @i 

    SET @qualifiedTableName = '[Database_Staging].[dbo].['+@currentTable+']'

    SET @sqlStatement = N'

        --Signal values staging table.
        SELECT svs.* INTO #sValues
        FROM '+ @qualifiedTableName +' svs
        INNER JOIN SignalMetaData smd
            ON smd.SignalId = svs.SignalId  


        INSERT INTO SignalValues SELECT * FROM #sValues

        SELECT DISTINCT SignalId INTO #uniqueIdentifiers FROM #sValues

        DELETE c FROM '+ @qualifiedTableName +' c INNER JOIN #uniqueIdentifiers u ON c.SignalId = u.SignalId

        DROP TABLE #sValues
        DROP TABLE #uniqueIdentifiers

        IF NOT EXISTS (SELECT TOP 1 1 FROM '+ @qualifiedTableName +') --table is empty
        BEGIN
            -- processing is completed so drop the table and remvoe the entry
            IF @processingComplete = 1 
            BEGIN 
                DELETE FROM SignalValueJobs WHERE JobId = @currentJob

                IF '''+@currentTable+''' <> ''SignalValues_staging''
                BEGIN
                    DROP TABLE '+ @qualifiedTableName +'
                END
            END
        END 
    '

    EXEC sp_executesql @sqlStatement, @paramDefinitions, @currentJob = @jobParam, @processingComplete = @processingParam;

    SET @i = @i - 1
END

DROP TABLE #JobsToProcess

ฉันใช้sp_executesqlเพราะชื่อตารางสำหรับตารางการจัดเตรียมมาเป็นข้อความจากระเบียนในตารางงาน

โพรซีเดอร์ที่เก็บนี้รันทุก 2 วินาทีโดยใช้กลอุบายที่ฉันเรียนรู้จากโพสต์ dba.stackexchange.comนี้

ปัญหาที่ฉันแก้ไขไม่ได้สำหรับชีวิตของฉันคือความเร็วที่เม็ดมีดเข้าสู่การผลิตถูกกระทำ แอปพลิเคชันของฉันสร้างตารางชั่วคราวและเติมข้อมูลด้วยระเบียนอย่างรวดเร็วอย่างไม่น่าเชื่อ การแทรกการผลิตไม่สามารถติดตามจำนวนของตารางและในที่สุดก็มีส่วนเกินของตารางเป็นพัน เพียงวิธีที่ฉันเคยสามารถที่จะให้ทันกับข้อมูลที่เข้ามาคือการลบคีย์ทั้งหมดดัชนี จำกัด ฯลฯ ... ในการผลิตSignalValuesตาราง ปัญหาที่ฉันเผชิญคือตารางท้ายสุดมีระเบียนจำนวนมากที่ไม่สามารถสืบค้นได้

ฉันพยายามแบ่งตารางโดยใช้[Timestamp]คอลัมน์เป็นพาร์ทิชันที่ไม่มีประโยชน์ การทำดัชนีรูปแบบใด ๆ จะทำให้เม็ดมีดช้าลงจนไม่สามารถติดตามได้ นอกจากนี้ฉันต้องสร้างหลายพันพาร์ติชัน (หนึ่งทุก ๆ นาทีหรือไม่?) ปีล่วงหน้า ฉันไม่สามารถหาวิธีสร้างมันได้ในทันที

ฉันพยายามสร้างการแบ่งพาร์ติชันโดยเพิ่มคอลัมน์จากการคำนวณลงในตารางที่เรียกว่าTimestampMinuteมีค่าอยู่, บนINSERT, DATEPART(MINUTE, GETUTCDATE()). ยังช้าเกินไป

ฉันได้พยายามทำให้มันเป็นตารางหน่วยความจำเพิ่มประสิทธิภาพตามบทความนี้ไมโครซอฟท์ บางทีฉันอาจไม่เข้าใจวิธีการใช้งาน แต่ MOT ทำให้เม็ดมีดช้าลงอย่างใด

ฉันได้ตรวจสอบแผนการดำเนินการของขั้นตอนการจัดเก็บแล้วและพบว่า (ฉันคิดว่า?) การดำเนินการที่เข้มข้นที่สุดคือ

SELECT svs.* INTO #sValues
FROM '+ @qualifiedTableName +' svs
INNER JOIN SignalMetaData smd
    ON smd.SignalId = svs.SignalId

สำหรับฉันมันไม่สมเหตุสมผล: ฉันได้เพิ่มการบันทึกนาฬิกาแขวนในขั้นตอนการจัดเก็บที่พิสูจน์เป็นอย่างอื่น

ในแง่ของการบันทึกเวลาคำสั่งนั้นข้างต้นจะดำเนินการใน ~ 300ms ในบันทึก 100k

คำสั่ง

INSERT INTO SignalValues SELECT * FROM #sValues

รันใน 2500-3000ms บนบันทึก 100k การลบระเบียนที่ได้รับผลกระทบจากตารางต่อ:

DELETE c FROM '+ @qualifiedTableName +' c INNER JOIN #uniqueIdentifiers u ON c.SignalId = u.SignalId

ใช้เวลาอีก 300 มิลลิวินาที

ฉันจะทำให้เร็วขึ้นได้อย่างไร SQL Server สามารถจัดการกับบันทึกเป็นพันล้านรายการต่อวันได้หรือไม่?

หากเกี่ยวข้องกันนี่คือ SQL Server 2014 Enterprise x64

การกำหนดค่าฮาร์ดแวร์:

ฉันลืมที่จะรวมฮาร์ดแวร์ในรอบแรกของคำถามนี้ ความผิดฉันเอง.

ฉันจะคำนำนี้ด้วยคำสั่งเหล่านี้: ฉันรู้ว่าฉันสูญเสียประสิทธิภาพเนื่องจากการกำหนดค่าฮาร์ดแวร์ของฉัน ฉันลองมาหลายครั้งแล้ว แต่ด้วยงบประมาณ C-Level การจัดตำแหน่งของดาวเคราะห์ ฯลฯ ... ไม่มีอะไรที่ฉันจะทำได้เพื่อให้การติดตั้งดีขึ้นอย่างน่าเสียดาย เซิร์ฟเวอร์กำลังทำงานบนเครื่องเสมือนและฉันไม่สามารถเพิ่มหน่วยความจำได้เพราะเราไม่มีอีกต่อไป

นี่คือข้อมูลระบบของฉัน:

ข้อมูลระบบ

ที่เก็บข้อมูลเชื่อมต่อกับเซิร์ฟเวอร์ VM ผ่านทางอินเทอร์เฟซ iSCSI ไปยังกล่อง NAS (ซึ่งจะลดประสิทธิภาพการทำงาน) กล่อง NAS มี 4 ไดรฟ์ในการกำหนดค่า RAID 10 พวกเขากำลัง 4TB WD WD4000FYYZ ดิสก์ไดรฟ์หมุนด้วยอินเตอร์เฟซ SATA 6GB / s เซิร์ฟเวอร์มีที่เก็บข้อมูลเพียงแห่งเดียวที่กำหนดค่าไว้ดังนั้น tempdb และฐานข้อมูลของฉันอยู่ในที่เก็บข้อมูลเดียวกัน

ค่าสูงสุด DOP เป็นศูนย์ ฉันควรเปลี่ยนสิ่งนี้เป็นค่าคงที่หรือปล่อยให้ SQL Server จัดการมันได้หรือไม่ ฉันอ่าน RCSI: ฉันถูกต้องแล้วหรือไม่ถ้าฉันได้รับประโยชน์อย่างเดียวจาก RCSI ที่มาพร้อมกับการอัปเดตแถว จะไม่มีการปรับปรุงใด ๆ ของระเบียนเหล่านี้โดยเฉพาะพวกเขาจะINSERTเอ็ดและSELECTเอ็ด RCSI จะยังให้ประโยชน์กับฉันหรือไม่

tempdb ของฉันคือ 8mb จากคำตอบด้านล่างจาก jyao ฉันเปลี่ยน #sValues ​​เป็นตารางปกติเพื่อหลีกเลี่ยง tempdb โดยสิ้นเชิง ประสิทธิภาพเป็นเรื่องเดียวกัน ฉันจะพยายามเพิ่มขนาดและการเติบโตของ tempdb แต่เนื่องจากขนาดของ #sValues ​​จะมากขึ้นหรือน้อยลงเป็นขนาดเดียวกับที่ฉันไม่คาดหวังว่าจะได้รับมาก

ฉันมีแผนการดำเนินการที่ฉันแนบไว้ด้านล่าง แผนการดำเนินการนี้คือการวนซ้ำหนึ่งตาราง staging - ระเบียน 100k การดำเนินการของแบบสอบถามนั้นค่อนข้างรวดเร็วประมาณ 2 วินาที แต่โปรดจำไว้ว่านี่เป็นดัชนีที่ไม่มีดัชนีในSignalValuesตารางและSignalValuesเป้าหมายของดัชนีINSERTนั้นไม่มีบันทึกอยู่ในนั้น

แผนการดำเนินการ


3
คุณได้ทดสอบด้วยความทนทานที่ล่าช้าหรือไม่?
Martin Smith

2
ดัชนีอะไรอยู่ในสถานที่ที่มีการแทรกการผลิตช้า?
paparazzo

จนถึงตอนนี้ฉันไม่คิดว่ามีข้อมูลเพียงพอที่นี่เพื่อค้นหาสิ่งที่จริงใช้เวลามาก มันเป็นซีพียูหรือไม่? มันคือ IO? เนื่องจากคุณดูเหมือนจะได้รับ 30k แถวต่อวินาทีมันไม่เหมือนกับ IO สำหรับฉัน ฉันเข้าใจถูกต้องไหมว่าคุณค่อนข้างใกล้เคียงกับการบรรลุเป้าหมายที่สมบูรณ์แบบของคุณ? คุณต้องการแถว 50k ต่อวินาทีดังนั้นหนึ่งชุด 100k ทุก 2 วินาทีควรพอเพียง ตอนนี้ดูเหมือนว่าแบทช์หนึ่งจะใช้เวลา 3 วินาที โพสต์แผนการดำเนินการจริงของการดำเนินการตัวแทนหนึ่งครั้ง ข้อเสนอแนะใด ๆ ที่ไม่ได้โจมตีการดำเนินการที่ใช้เวลามากที่สุดคือ moot
usr

ฉันโพสต์แผนการดำเนินการ
Brandon

คำตอบ:


7

ฉันได้คำนวณว่าที่จุดสูงสุดปริมาณการบันทึกโดยเฉลี่ยอยู่ที่ประมาณ 3-4 พันล้านต่อวัน (การดำเนินการ 20 ชั่วโมง)

จากภาพหน้าจอของคุณคุณมี RAM รวมหน่วยความจำ 8GB และ 6 GB ที่จัดสรรให้กับ SQL Server นี่เป็นวิธีที่ต่ำเกินไปสำหรับสิ่งที่คุณพยายามจะบรรลุ

ฉันขอแนะนำให้คุณอัพเกรดหน่วยความจำให้มีค่าสูงขึ้น - 256GB และชนกับซีพียู VM ของคุณเช่นกัน

คุณต้องลงทุนด้านฮาร์ดแวร์ ณ จุดนี้เพื่อภาระงานของคุณ

โปรดดูคู่มือประสิทธิภาพการโหลดข้อมูล - อธิบายวิธีการโหลดข้อมูลอย่างมีประสิทธิภาพ

tempdb ของฉันคือ 8mb

จากการแก้ไขของคุณ .. คุณควรมี tempdb ที่เหมาะสม - ควรมีไฟล์ข้อมูล tempdb หลายขนาดเท่า ๆ กันพร้อมกับ TF 1117 และ 1118 ที่เปิดใช้งานอินสแตนซ์กว้าง

ฉันขอแนะนำให้คุณรับการตรวจสุขภาพอย่างมืออาชีพและเริ่มจากที่นั่น

ขอเเนะนำ

  1. ชนข้อมูลจำเพาะเซิร์ฟเวอร์ของคุณ

  2. รับบุคคล * มืออาชีพที่ทำการตรวจสุขภาพของอินสแตนซ์เซิร์ฟเวอร์ฐานข้อมูลของคุณและทำตามคำแนะนำ

  3. ครั้งเดียว และ b. เสร็จสิ้นแล้วดื่มด่ำในการปรับแต่งแบบสอบถามและการเพิ่มประสิทธิภาพอื่น ๆ เช่นการดูสถิติการรอแผนแบบสอบถาม ฯลฯ

หมายเหตุ: ฉันเป็นผู้เชี่ยวชาญเซิร์ฟเวอร์ sql มืออาชีพที่hackhands.com - บริษัท ที่มีหลายฝ่าย แต่ไม่แนะนำให้คุณจ้างฉันเพื่อขอความช่วยเหลือ ฉันแค่แนะนำให้คุณใช้ความช่วยเหลือจากมืออาชีพตามการแก้ไขของคุณเท่านั้น

HTH


ฉันพยายามที่จะรวบรวมข้อเสนอ (อ่าน: ขอทาน) ฮาร์ดแวร์เพิ่มเติมสำหรับเรื่องนี้ เมื่อคำนึงถึงเรื่องนี้และคำตอบของคุณที่นี่ไม่มีอะไรอื่นจากการกำหนดค่า SQL Server หรือจุดยืนการปรับให้เหมาะสมของแบบสอบถามที่คุณอยากจะแนะนำให้ทำเร็วกว่านี้
Brandon

1

คำแนะนำทั่วไปสำหรับปัญหาดังกล่าวกับข้อมูลขนาดใหญ่เมื่อเผชิญกับกำแพงและไม่มีอะไรทำงาน:

ไข่หนึ่งฟองจะถูกปรุงสุกประมาณ 5 นาที 10 ไข่จะถูกปรุงในเวลาเดียวกันถ้ามีไฟฟ้าและน้ำเพียงพอ

หรือในคำอื่น ๆ :

ขั้นแรกดูที่ฮาร์ดแวร์ สองแยกตรรกะกระบวนการ (การเปลี่ยนแปลงข้อมูล) และทำแบบคู่ขนาน

ค่อนข้างเป็นไปได้ที่จะสร้างการแบ่งพาร์ติชันตามแนวตั้งแบบกำหนดเองแบบไดนามิกและอัตโนมัติต่อจำนวนโต๊ะและขนาดของตาราง ถ้าฉันมี Quarter_1_2017, Quarter_2_2017, Quarter_3_2017, Quarter_4_2017, Quarter_1_2018 ... และฉันไม่รู้ว่าเร็กคอร์ดของฉันอยู่ที่ไหนและมีพาร์ติชั่นเท่าไรให้เรียกใช้คิวรีเดียวกันกับพาร์ติชั่นที่กำหนดเองทั้งหมดในเวลาเดียวกัน ผลลัพธ์ที่ต้องดำเนินการต่อไปสำหรับตรรกะของฉัน


ดูเหมือนว่าปัญหาของ OP จะจัดการการแทรกและการเข้าถึงข้อมูลที่เพิ่งป้อนเข้ามาใหม่มากกว่าการประมวลผลข้อมูลจากสัปดาห์หรือเดือนที่ผ่านมา OP กล่าวถึงการแบ่งพาร์ติชั่นข้อมูลโดยนาทีในการประทับเวลาของเขา (ดังนั้น 60 พาร์ติชันแยกข้อมูลปัจจุบันเป็นที่เก็บข้อมูลแยก) การแยกรายไตรมาสไม่น่าจะช่วยได้มากนัก ประเด็นของคุณได้รับการยอมรับเป็นอย่างดี แต่ไม่น่าจะช่วยใครซักคนในสถานการณ์เฉพาะนี้
RDFozz

-1

ฉันจะทำการตรวจสอบ / การเพิ่มประสิทธิภาพต่อไปนี้:

  1. ตรวจสอบให้แน่ใจว่าทั้งข้อมูลและไฟล์บันทึกของฐานข้อมูลการผลิตไม่เติบโตในระหว่างการดำเนินการแทรก (ก่อนขยายหากจำเป็น)

  2. ไม่ได้ใช้

    select * into [dest table] from [source table];

    แต่แทนที่จะกำหนด [ตารางปลายทาง] ล่วงหน้า นอกจากนี้แทนที่จะวาง [ตารางปลายทาง] และสร้างใหม่ฉันจะตัดทอนตาราง ด้วยวิธีนี้หากจำเป็นแทนที่จะใช้ตารางชั่วคราวฉันจะใช้ตารางปกติ (ฉันอาจสร้างดัชนีบน [ตารางปลายทาง] เพื่ออำนวยความสะดวกในการทำงานของแบบสอบถามแบบเข้าร่วม)

  3. แทนที่จะใช้ไดนามิก sql ฉันควรใช้ชื่อตารางแบบเข้ารหัสด้วยตรรกะการเข้ารหัสบางอย่างเพื่อเลือกตารางที่จะใช้งาน

  4. ฉันจะตรวจสอบประสิทธิภาพของหน่วยความจำ CPU และดิสก์ I / O เพื่อดูว่ามีความอดอยากของทรัพยากรในระหว่างการทำงานปริมาณมากหรือไม่

  5. เนื่องจากคุณกล่าวถึงคุณสามารถจัดการการแทรกโดยการวางดัชนีทางด้านการผลิตฉันจะตรวจสอบว่ามีการแบ่งหน้าหลายหน้าหรือไม่ถ้าใช่ฉันจะลดการเติมของดัชนีและสร้างดัชนีใหม่ก่อนที่ฉันจะปล่อย ดัชนี

ขอให้โชคดีและรักคำถามของคุณ


ขอบคุณสำหรับคำตอบ. ฉันตั้งค่าขนาดฐานข้อมูลเป็น 1gb และเพิ่มขึ้น 1GB โดยคาดว่าการดำเนินการเติบโตจะใช้เวลาสักครู่ซึ่งช่วยเรื่องความเร็วในตอนแรก ฉันจะพยายามใช้การเติบโตก่อนวันนี้ ฉันใช้ตาราง [dest] เป็นตารางปกติ แต่ไม่ได้รับประสิทธิภาพมากนัก ฉันไม่ได้มีเวลามากในช่วงสองสามวันที่ผ่านมา แต่ฉันจะพยายามไปหาคนอื่นในวันนี้
Brandon
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.