এসকিউএল সিএলআর স্কেলার ফাংশনটি ব্যবহার করে HASHBYTES সিমুলেট করার একটি মাপযোগ্য উপায় কী?


29

আমাদের ইটিএল প্রক্রিয়াটির অংশ হিসাবে, ডেটা শেষ লোড হওয়ার পরে কোনও কলাম আসলেই পরিবর্তিত হয়েছে কিনা তা নির্ধারণের জন্য আমরা রিপোর্টিং ডাটাবেসের বিরুদ্ধে মঞ্চ থেকে সারিগুলি তুলনা করি।

তুলনাটি টেবিলের অনন্য কী এবং অন্যান্য সমস্ত কলামের এক ধরণের হ্যাশের উপর ভিত্তি করে। আমরা বর্তমানে অ্যালগরিদমের HASHBYTESসাথে ব্যবহার SHA2_256করেছি এবং দেখেছি যে অনেকগুলি সহকারী কর্মী থ্রেড সমস্ত কল করে থাকলে এটি বড় সার্ভারগুলিতে স্কেল করে না HASHBYTES

96 সেকেন্ড সার্ভারে পরীক্ষার সময় প্রতি সেকেন্ডে হ্যাশগুলিতে পরিমাপ করা থ্রুপুট 16 টি সমবর্তী সামঞ্জস্য বৃদ্ধি করে না। আমি MAXDOP 81 - 12 থেকে একযোগে প্রশ্নের সংখ্যা পরিবর্তন করে পরীক্ষা করি with পরীক্ষা দিয়ে MAXDOP 1একই স্কেলিবিলিটি বাধা দেখা গেল।

কার্যকারণ হিসাবে আমি একটি এসকিউএল সিএলআর সমাধান চেষ্টা করতে চাই। প্রয়োজনীয়তাগুলি জানাতে এখানে আমার প্রচেষ্টা:

  • ফাংশনটি অবশ্যই সমান্তরাল ক্যোয়ারিতে অংশ নিতে সক্ষম হবে
  • ফাংশনটি অবশ্যই ডিটারমিনিস্টিক হতে হবে
  • ফাংশন একটি এর একটি ইনপুট নিতে হবে NVARCHARবা VARBINARYস্ট্রিং (সমস্ত প্রাসঙ্গিক কলাম একসঙ্গে ঘনিভূত হয়)
  • স্ট্রিংয়ের সাধারণ ইনপুট আকারটি দৈর্ঘ্যে 100 - 20000 অক্ষর হবে। 20000 সর্বাধিক নয়
  • হ্যাশের সংঘর্ষের সম্ভাবনা MD5 অ্যালগরিদমের তুলনায় মোটামুটি সমান বা আরও ভাল হওয়া উচিত। CHECKSUMআমাদের পক্ষে কাজ করে না কারণ প্রচুর সংঘর্ষ রয়েছে।
  • ফাংশনটি অবশ্যই বড় সার্ভারগুলিতে ভাল স্কেল করতে হবে (থ্রেড প্রতি থ্রেডপুট থ্রেডের সংখ্যা বৃদ্ধির সাথে উল্লেখযোগ্যভাবে হ্রাস করা উচিত নয়)

অ্যাপ্লিকেশন কারণগুলি For এর জন্য, ধরে নিন যে আমি রিপোর্টিং সারণির জন্য হ্যাশের মানটি সঞ্চয় করতে পারি না। এটি একটি সিসিআই যা ট্রিগার বা গণিত কলামগুলি সমর্থন করে না (এমন অন্যান্য সমস্যাও রয়েছে যা আমি getুকতে চাই না)।

HASHBYTESএসকিউএল সিএলআর ফাংশনটি ব্যবহার করে সিমুলেট করার উপায় কী ? আমার লক্ষ্যটি একটি বৃহত সার্ভারে যতটা সম্ভব হ্যাশ পাওয়া হিসাবে প্রকাশ করা যেতে পারে, তাই পারফরম্যান্সের বিষয়টিও গুরুত্বপূর্ণ। আমি সিএলআর নিয়ে ভয়ানক তাই আমি কীভাবে এটি সম্পাদন করতে জানি না। যদি এটি কাউকে উত্তর দিতে অনুপ্রাণিত করে, আমি যত তাড়াতাড়ি সক্ষম হব এই প্রশ্নটিতে অনুগ্রহ যোগ করার পরিকল্পনা করছি। নীচে একটি উদাহরণ ক্যোয়ারী যা ব্যবহারের ক্ষেত্রে মোটামুটিভাবে চিত্রিত করে:

DROP TABLE IF EXISTS #CHANGED_IDS;

SELECT stg.ID INTO #CHANGED_IDS
FROM (
    SELECT ID,
    CAST( HASHBYTES ('SHA2_256', 
        CAST(FK1 AS NVARCHAR(19)) + 
        CAST(FK2 AS NVARCHAR(19)) + 
        CAST(FK3 AS NVARCHAR(19)) + 
        CAST(FK4 AS NVARCHAR(19)) + 
        CAST(FK5 AS NVARCHAR(19)) + 
        CAST(FK6 AS NVARCHAR(19)) + 
        CAST(FK7 AS NVARCHAR(19)) + 
        CAST(FK8 AS NVARCHAR(19)) + 
        CAST(FK9 AS NVARCHAR(19)) + 
        CAST(FK10 AS NVARCHAR(19)) + 
        CAST(FK11 AS NVARCHAR(19)) + 
        CAST(FK12 AS NVARCHAR(19)) + 
        CAST(FK13 AS NVARCHAR(19)) + 
        CAST(FK14 AS NVARCHAR(19)) + 
        CAST(FK15 AS NVARCHAR(19)) + 
        CAST(STR1 AS NVARCHAR(500)) +
        CAST(STR2 AS NVARCHAR(500)) +
        CAST(STR3 AS NVARCHAR(500)) +
        CAST(STR4 AS NVARCHAR(500)) +
        CAST(STR5 AS NVARCHAR(500)) +
        CAST(COMP1 AS NVARCHAR(1)) + 
        CAST(COMP2 AS NVARCHAR(1)) + 
        CAST(COMP3 AS NVARCHAR(1)) + 
        CAST(COMP4 AS NVARCHAR(1)) + 
        CAST(COMP5 AS NVARCHAR(1)))
     AS BINARY(32)) HASH1
    FROM HB_TBL WITH (TABLOCK)
) stg
INNER JOIN (
    SELECT ID,
    CAST(HASHBYTES ('SHA2_256', 
        CAST(FK1 AS NVARCHAR(19)) + 
        CAST(FK2 AS NVARCHAR(19)) + 
        CAST(FK3 AS NVARCHAR(19)) + 
        CAST(FK4 AS NVARCHAR(19)) + 
        CAST(FK5 AS NVARCHAR(19)) + 
        CAST(FK6 AS NVARCHAR(19)) + 
        CAST(FK7 AS NVARCHAR(19)) + 
        CAST(FK8 AS NVARCHAR(19)) + 
        CAST(FK9 AS NVARCHAR(19)) + 
        CAST(FK10 AS NVARCHAR(19)) + 
        CAST(FK11 AS NVARCHAR(19)) + 
        CAST(FK12 AS NVARCHAR(19)) + 
        CAST(FK13 AS NVARCHAR(19)) + 
        CAST(FK14 AS NVARCHAR(19)) + 
        CAST(FK15 AS NVARCHAR(19)) + 
        CAST(STR1 AS NVARCHAR(500)) +
        CAST(STR2 AS NVARCHAR(500)) +
        CAST(STR3 AS NVARCHAR(500)) +
        CAST(STR4 AS NVARCHAR(500)) +
        CAST(STR5 AS NVARCHAR(500)) +
        CAST(COMP1 AS NVARCHAR(1)) + 
        CAST(COMP2 AS NVARCHAR(1)) + 
        CAST(COMP3 AS NVARCHAR(1)) + 
        CAST(COMP4 AS NVARCHAR(1)) + 
        CAST(COMP5 AS NVARCHAR(1)) )
 AS BINARY(32)) HASH1
    FROM HB_TBL_2 WITH (TABLOCK)
) rpt ON rpt.ID = stg.ID
WHERE rpt.HASH1 <> stg.HASH1
OPTION (MAXDOP 8);

জিনিসগুলি কিছুটা সহজ করার জন্য, আমি সম্ভবত বেঞ্চমার্কিংয়ের জন্য নিম্নলিখিতগুলির মতো কিছু ব্যবহার করব। আমি HASHBYTESসোমবারের সাথে ফলাফল পোস্ট করব :

CREATE TABLE dbo.HASH_ME (
    ID BIGINT NOT NULL,
    FK1 BIGINT NOT NULL,
    FK2 BIGINT NOT NULL,
    FK3 BIGINT NOT NULL,
    FK4 BIGINT NOT NULL,
    FK5 BIGINT NOT NULL,
    FK6 BIGINT NOT NULL,
    FK7 BIGINT NOT NULL,
    FK8 BIGINT NOT NULL,
    FK9 BIGINT NOT NULL,
    FK10 BIGINT NOT NULL,
    FK11 BIGINT NOT NULL,
    FK12 BIGINT NOT NULL,
    FK13 BIGINT NOT NULL,
    FK14 BIGINT NOT NULL,
    FK15 BIGINT NOT NULL,
    STR1 NVARCHAR(500) NOT NULL,
    STR2 NVARCHAR(500) NOT NULL,
    STR3 NVARCHAR(500) NOT NULL,
    STR4 NVARCHAR(500) NOT NULL,
    STR5 NVARCHAR(2000) NOT NULL,
    COMP1 TINYINT NOT NULL,
    COMP2 TINYINT NOT NULL,
    COMP3 TINYINT NOT NULL,
    COMP4 TINYINT NOT NULL,
    COMP5 TINYINT NOT NULL
);

INSERT INTO dbo.HASH_ME WITH (TABLOCK)
SELECT RN,
RN % 1000000, RN % 1000000, RN % 1000000, RN % 1000000, RN % 1000000,
RN % 1000000, RN % 1000000, RN % 1000000, RN % 1000000, RN % 1000000,
RN % 1000000, RN % 1000000, RN % 1000000, RN % 1000000, RN % 1000000,
REPLICATE(CHAR(65 + RN % 10 ), 30)
,REPLICATE(CHAR(65 + RN % 10 ), 30)
,REPLICATE(CHAR(65 + RN % 10 ), 30)
,REPLICATE(CHAR(65 + RN % 10 ), 30)
,REPLICATE(CHAR(65 + RN % 10 ), 1000),
0,1,0,1,0
FROM (
    SELECT TOP (100000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
) q
OPTION (MAXDOP 1);

SELECT MAX(HASHBYTES('SHA2_256',
CAST(N'' AS NVARCHAR(MAX)) + N'|' +
CAST(FK1 AS NVARCHAR(19)) + N'|' +
CAST(FK2 AS NVARCHAR(19)) + N'|' +
CAST(FK3 AS NVARCHAR(19)) + N'|' +
CAST(FK4 AS NVARCHAR(19)) + N'|' +
CAST(FK5 AS NVARCHAR(19)) + N'|' +
CAST(FK6 AS NVARCHAR(19)) + N'|' +
CAST(FK7 AS NVARCHAR(19)) + N'|' +
CAST(FK8 AS NVARCHAR(19)) + N'|' +
CAST(FK9 AS NVARCHAR(19)) + N'|' +
CAST(FK10 AS NVARCHAR(19)) + N'|' +
CAST(FK11 AS NVARCHAR(19)) + N'|' +
CAST(FK12 AS NVARCHAR(19)) + N'|' +
CAST(FK13 AS NVARCHAR(19)) + N'|' +
CAST(FK14 AS NVARCHAR(19)) + N'|' +
CAST(FK15 AS NVARCHAR(19)) + N'|' +
CAST(STR1 AS NVARCHAR(500)) + N'|' +
CAST(STR2 AS NVARCHAR(500)) + N'|' +
CAST(STR3 AS NVARCHAR(500)) + N'|' +
CAST(STR4 AS NVARCHAR(500)) + N'|' +
CAST(STR5 AS NVARCHAR(2000)) + N'|' +
CAST(COMP1 AS NVARCHAR(1)) + N'|' +
CAST(COMP2 AS NVARCHAR(1)) + N'|' +
CAST(COMP3 AS NVARCHAR(1)) + N'|' +
CAST(COMP4 AS NVARCHAR(1)) + N'|' +
CAST(COMP5 AS NVARCHAR(1)) )
)
FROM dbo.HASH_ME
OPTION (MAXDOP 1);

উত্তর:


18

যেহেতু আপনি কেবল পরিবর্তনগুলি সন্ধান করছেন, আপনার কোনও ক্রিপ্টোগ্রাফিক হ্যাশ ফাংশন দরকার নেই।

ওপেন-সোর্স ডেটাতে দ্রুত নন-ক্রিপ্টোগ্রাফিক হ্যাশগুলির মধ্যে একটি থেকে আপনি চয়ন করতে পারেন Brand ব্র্যান্ডন ডাহলারের দ্বারা অনুমোদিত লাইসেন্স এবং ওএসআই অনুমোদিত এমআইটি লাইসেন্সের অধীনে লাইসেন্সযুক্ত হ্যাশ ফাংশন লাইব্রেরিSpookyHashএকটি জনপ্রিয় পছন্দ।

উদাহরণ বাস্তবায়ন

সোর্স কোড

using Microsoft.SqlServer.Server;
using System.Data.HashFunction.SpookyHash;
using System.Data.SqlTypes;

public partial class UserDefinedFunctions
{
    [SqlFunction
        (
            DataAccess = DataAccessKind.None,
            SystemDataAccess = SystemDataAccessKind.None,
            IsDeterministic = true,
            IsPrecise = true
        )
    ]
    public static byte[] SpookyHash
        (
            [SqlFacet (MaxSize = 8000)]
            SqlBinary Input
        )
    {
        ISpookyHashV2 sh = SpookyHashV2Factory.Instance.Create();
        return sh.ComputeHash(Input.Value).Hash;
    }

    [SqlFunction
        (
            DataAccess = DataAccessKind.None,
            IsDeterministic = true,
            IsPrecise = true,
            SystemDataAccess = SystemDataAccessKind.None
        )
    ]
    public static byte[] SpookyHashLOB
        (
            [SqlFacet (MaxSize = -1)]
            SqlBinary Input
        )
    {
        ISpookyHashV2 sh = SpookyHashV2Factory.Instance.Create();
        return sh.ComputeHash(Input.Value).Hash;
    }
}

উত্সটি দুটি ফাংশন সরবরাহ করে, একটি 8000 বাইট বা তার চেয়ে কম সংখ্যক ইনপুটগুলির জন্য এবং একটি এলওবি সংস্করণ। অ-এলওবি সংস্করণটি উল্লেখযোগ্যভাবে দ্রুত হওয়া উচিত।

আপনি যদি কোনও এলওবি বাইনারিটি COMPRESS8000 বাইট সীমাতে পেতে পারেন তবে এটি যদি পারফরম্যান্সের পক্ষে সার্থক হয়। বিকল্পভাবে, আপনি এলওবিটিকে উপ -8000 বাইট বিভাগগুলিতে বিভক্ত করতে পারেন, বা কেবলমাত্র HASHBYTESএলওবি কেসের জন্য ব্যবহার সংরক্ষণ করতে পারবেন (যেহেতু দীর্ঘতর ইনপুটগুলি আরও ভাল)।

প্রাক বিল্ট কোড

আপনি অবশ্যই নিজের জন্য প্যাকেজটি দখল করতে পারেন এবং সবকিছু সংকলন করতে পারেন, তবে দ্রুত পরীক্ষার ব্যবস্থা সহজ করার জন্য আমি নীচে অ্যাসেমব্লিগুলি তৈরি করেছি:

https://gist.github.com/SQLKiwi/365b265b476bf86754457fc9514b2300

টি-এসকিউএল ফাংশন

CREATE FUNCTION dbo.SpookyHash
(
    @Input varbinary(8000)
)
RETURNS binary(16)
WITH 
    RETURNS NULL ON NULL INPUT, 
    EXECUTE AS OWNER
AS EXTERNAL NAME Spooky.UserDefinedFunctions.SpookyHash;
GO
CREATE FUNCTION dbo.SpookyHashLOB
(
    @Input varbinary(max)
)
RETURNS binary(16)
WITH 
    RETURNS NULL ON NULL INPUT, 
    EXECUTE AS OWNER
AS EXTERNAL NAME Spooky.UserDefinedFunctions.SpookyHashLOB;
GO

ব্যবহার

প্রশ্নে নমুনা তথ্য প্রদত্ত একটি উদাহরণ ব্যবহার:

SELECT
    HT1.ID
FROM dbo.HB_TBL AS HT1
JOIN dbo.HB_TBL_2 AS HT2
    ON HT2.ID = HT1.ID
    AND dbo.SpookyHash
    (
        CONVERT(binary(8), HT2.FK1) + 0x7C +
        CONVERT(binary(8), HT2.FK2) + 0x7C +
        CONVERT(binary(8), HT2.FK3) + 0x7C +
        CONVERT(binary(8), HT2.FK4) + 0x7C +
        CONVERT(binary(8), HT2.FK5) + 0x7C +
        CONVERT(binary(8), HT2.FK6) + 0x7C +
        CONVERT(binary(8), HT2.FK7) + 0x7C +
        CONVERT(binary(8), HT2.FK8) + 0x7C +
        CONVERT(binary(8), HT2.FK9) + 0x7C +
        CONVERT(binary(8), HT2.FK10) + 0x7C +
        CONVERT(binary(8), HT2.FK11) + 0x7C +
        CONVERT(binary(8), HT2.FK12) + 0x7C +
        CONVERT(binary(8), HT2.FK13) + 0x7C +
        CONVERT(binary(8), HT2.FK14) + 0x7C +
        CONVERT(binary(8), HT2.FK15) + 0x7C +
        CONVERT(varbinary(1000), HT2.STR1) + 0x7C +
        CONVERT(varbinary(1000), HT2.STR2) + 0x7C +
        CONVERT(varbinary(1000), HT2.STR3) + 0x7C +
        CONVERT(varbinary(1000), HT2.STR4) + 0x7C +
        CONVERT(varbinary(1000), HT2.STR5) + 0x7C +
        CONVERT(binary(1), HT2.COMP1) + 0x7C +
        CONVERT(binary(1), HT2.COMP2) + 0x7C +
        CONVERT(binary(1), HT2.COMP3) + 0x7C +
        CONVERT(binary(1), HT2.COMP4) + 0x7C +
        CONVERT(binary(1), HT2.COMP5)
    )
    <> dbo.SpookyHash
    (
        CONVERT(binary(8), HT1.FK1) + 0x7C +
        CONVERT(binary(8), HT1.FK2) + 0x7C +
        CONVERT(binary(8), HT1.FK3) + 0x7C +
        CONVERT(binary(8), HT1.FK4) + 0x7C +
        CONVERT(binary(8), HT1.FK5) + 0x7C +
        CONVERT(binary(8), HT1.FK6) + 0x7C +
        CONVERT(binary(8), HT1.FK7) + 0x7C +
        CONVERT(binary(8), HT1.FK8) + 0x7C +
        CONVERT(binary(8), HT1.FK9) + 0x7C +
        CONVERT(binary(8), HT1.FK10) + 0x7C +
        CONVERT(binary(8), HT1.FK11) + 0x7C +
        CONVERT(binary(8), HT1.FK12) + 0x7C +
        CONVERT(binary(8), HT1.FK13) + 0x7C +
        CONVERT(binary(8), HT1.FK14) + 0x7C +
        CONVERT(binary(8), HT1.FK15) + 0x7C +
        CONVERT(varbinary(1000), HT1.STR1) + 0x7C +
        CONVERT(varbinary(1000), HT1.STR2) + 0x7C +
        CONVERT(varbinary(1000), HT1.STR3) + 0x7C +
        CONVERT(varbinary(1000), HT1.STR4) + 0x7C +
        CONVERT(varbinary(1000), HT1.STR5) + 0x7C +
        CONVERT(binary(1), HT1.COMP1) + 0x7C +
        CONVERT(binary(1), HT1.COMP2) + 0x7C +
        CONVERT(binary(1), HT1.COMP3) + 0x7C +
        CONVERT(binary(1), HT1.COMP4) + 0x7C +
        CONVERT(binary(1), HT1.COMP5)
    );

এলওবি সংস্করণ ব্যবহার করার সময়, প্রথম প্যারামিটারটি castালাই বা রূপান্তর করা উচিত varbinary(max)

হত্যা পরিকল্পনা

পরিকল্পনা


নিরাপদ স্পোকি

Data.HashFunction গ্রন্থাগার যে বলে মনে করা হয় CLR ভাষা বৈশিষ্ট্য ব্যবহার UNSAFESQL সার্ভার দ্বারা। SAFEস্থিতির সাথে সামঞ্জস্যপূর্ণ একটি বেসিক স্পোকি হ্যাশ লেখা সম্ভব । জন হানার স্পুকিলি শার্পের উপর ভিত্তি করে আমি একটি উদাহরণ লিখেছি যা নীচে রয়েছে:

https://gist.github.com/SQLKiwi/7a5bb26b0bee56f6d28a1d26669ce8f2


16

আমি নিশ্চিত নই যে এসকিউএলসিআরআর এর সাথে সমান্তরালতা কোনও / উল্লেখযোগ্যভাবে আরও ভাল হবে কিনা। যাইহোক, এটা পরীক্ষা সত্যিই সহজ যেহেতু ফ্রি সংস্করণে একটি হ্যাশ ফাংশন এসকিউএল # SQLCLR গ্রন্থাগার (যা আমি লিখেছি) বলা Util_HashBinary । সমর্থিত অ্যালগরিদমগুলি হ'ল: MD5, SHA1, SHA256, SHA384 এবং SHA512।

এটি VARBINARY(MAX)ইনপুট হিসাবে একটি মান নেয় , সুতরাং আপনি হয় প্রতিটি ক্ষেত্রের স্ট্রিং সংস্করণকে (যেমন আপনি বর্তমানে করছেন) এবং তারপরে রূপান্তর করতে পারেন VARBINARY(MAX), বা আপনি সরাসরি VARBINARYপ্রতিটি কলামে যেতে পারেন এবং রূপান্তরিত মানগুলি সংশ্লেষ করতে পারেন (এটি সম্ভবত দ্রুত হতে পারে আপনি স্ট্রিং বা স্ট্রিং থেকে অতিরিক্ত রূপান্তর নিয়ে কাজ করছেন না VARBINARY)। নীচে এই দুটি অপশন দেখানোর একটি উদাহরণ দেওয়া আছে। এটি HASHBYTESফাংশনটিও দেখায় যাতে আপনি দেখতে পান যে মানগুলি এটির এবং এসকিউএল # .Util_HashBinary এর মধ্যে একই রকম

দয়া করে নোট করুন যে VARBINARYমানগুলি সংঘবদ্ধ করার সময় হ্যাশের ফলাফলগুলি NVARCHARমানগুলি সংঘটন করার সময় হ্যাশের ফলাফলগুলির সাথে মেলে না । এটি কারণ INT"1" মানের বাইনারি ফর্ম 0x00000001, যখন ইউটিএফ -16 এলই (অর্থাত NVARCHAR) ফর্মটি INT"1" (বাইনারি আকারে যেহেতু হ্যাশিং ফাংশনটি কাজ করবে) 0x3100 হয়।

SELECT so.[object_id],
       SQL#.Util_HashBinary(N'SHA256',
                            CONVERT(VARBINARY(MAX),
                                    CONCAT(so.[name], so.[schema_id], so.[create_date])
                                   )
                           ) AS [SQLCLR-ConcatStrings],
       HASHBYTES(N'SHA2_256',
                 CONVERT(VARBINARY(MAX),
                         CONCAT(so.[name], so.[schema_id], so.[create_date])
                        )
                ) AS [BuiltIn-ConcatStrings]
FROM sys.objects so;


SELECT so.[object_id],
       SQL#.Util_HashBinary(N'SHA256',
                            CONVERT(VARBINARY(500), so.[name]) + 
                            CONVERT(VARBINARY(500), so.[schema_id]) +
                            CONVERT(VARBINARY(500), so.[create_date])
                           ) AS [SQLCLR-ConcatVarBinaries],
       HASHBYTES(N'SHA2_256',
                 CONVERT(VARBINARY(500), so.[name]) + 
                 CONVERT(VARBINARY(500), so.[schema_id]) +
                 CONVERT(VARBINARY(500), so.[create_date])
                ) AS [BuiltIn-ConcatVarBinaries]
FROM sys.objects so;

আপনি নন-এলওবি স্পোকি ব্যবহার করে তুলনামূলক আরও কিছু পরীক্ষা করতে পারেন:

CREATE FUNCTION [SQL#].[Util_HashBinary8k]
(@Algorithm [nvarchar](50), @BaseData [varbinary](8000))
RETURNS [varbinary](8000) 
WITH EXECUTE AS CALLER, RETURNS NULL ON NULL INPUT
AS EXTERNAL NAME [SQL#].[UTILITY].[HashBinary];

দ্রষ্টব্য: ইউটিএলহ্যাশবাইনারি পরিচালিত SHA256 অ্যালগরিদম ব্যবহার করে যা .NET এ অন্তর্নির্মিত হয় এবং "বিসিক্রিপ্ট" লাইব্রেরি ব্যবহার করা উচিত নয়।

প্রশ্নের সেই দিকটি ছাড়িয়ে কিছু অতিরিক্ত চিন্তাভাবনা রয়েছে যা এই প্রক্রিয়াটিকে সহায়তা করতে পারে:

অতিরিক্ত চিন্তা # 1 (প্রাক-গণনা করা হ্যাশগুলি, কমপক্ষে কিছু)

আপনি কয়েকটি বিষয় উল্লেখ করেছেন:

  1. আমরা ডেটাগুলি ডাটাবেসের বিরুদ্ধে স্টেজিং থেকে সারিগুলি তুলনা করি যে ডেটা শেষ লোড হওয়ার পরে কোনও কলাম আসলেই পরিবর্তিত হয়েছে কিনা তা নির্ধারণ করতে।

    এবং:

  2. আমি রিপোর্টিং টেবিলের জন্য হ্যাশের মানটি সঞ্চয় করতে পারি না। এটি একটি সিসিআই যা ট্রিগার বা গণিত কলামগুলি সমর্থন করে না

    এবং:

  3. টেবিলগুলি ইটিএল প্রক্রিয়ার বাইরে আপডেট করা যেতে পারে

দেখে মনে হচ্ছে এই প্রতিবেদনের টেবিলের ডেটা কিছু সময়ের জন্য স্থিতিশীল এবং এটি কেবলমাত্র এই ইটিএল প্রক্রিয়া দ্বারা সংশোধিত হয়েছে।

যদি অন্য কোনও কিছুই এই টেবিলটিকে সংশোধন করে না, তবে আমাদের সর্বোপরি ট্রিগার বা সূচী দৃষ্টিভঙ্গির দরকার নেই (আমি মূলত ভেবেছিলাম আপনি হতে পারেন)।

যেহেতু আপনি প্রতিবেদনের সারণির স্কিমা পরিবর্তন করতে পারবেন না, তাই প্রাক-গণনা করা হ্যাশ (এবং এটি গণনার সময় ইউটিসি সময়) অন্তর্ভুক্ত করার জন্য কি কমপক্ষে কোনও টেবিল তৈরি করা সম্ভব ছিল? এটি আপনাকে পরবর্তী সময়ের তুলনায় প্রাক-গণনা করা মান রাখতে দেয়, কেবলমাত্র আগত মানটি রেখে যা হ্যাশ গণনা করা প্রয়োজন। এটি কলগুলির সংখ্যা হয় HASHBYTESবা SQL#.Util_HashBinaryঅর্ধেকে কমিয়ে দেবে । আমদানি প্রক্রিয়া চলাকালীন আপনি কেবল হ্যাশগুলির এই টেবিলটিতে যোগ দিতে পারবেন।

আপনি একটি পৃথক সঞ্চিত পদ্ধতিও তৈরি করতে পারবেন যা এই টেবিলের হ্যাশগুলি কেবল সতেজ করে। এটি কেবলমাত্র সম্পর্কিত পরিবর্তিত কোনও সম্পর্কিত সারির হ্যাশগুলি আপডেট করে এবং সেই সংশোধিত সারিগুলির টাইমস্ট্যাম্প আপডেট করে। এই প্রকল্পটি এই টেবিলটি আপডেট করে এমন কোনও প্রক্রিয়া শেষে কার্যকর করা উচিত। এই ETL শুরু হওয়ার 30 - 60 মিনিট আগে চালানোর সময় নির্ধারণ করা যেতে পারে (এটি কার্যকর করতে কতক্ষণ সময় নেয় এবং এই অন্যান্য প্রক্রিয়াগুলির মধ্যে যখন কোনওটি চলতে পারে তার উপর নির্ভর করে)। এমনকি ম্যানুয়ালি কার্যকর করা যেতে পারে যদি আপনি কখনও সন্দেহ করেন যে এমন কোনও সারি রয়েছে যা সিঙ্কের বাইরে রয়েছে।

তখন এটি উল্লেখ করা হয়েছিল যে:

এখানে 500 টির বেশি টেবিল রয়েছে

অনেকগুলি টেবিল বর্তমান হ্যাশগুলি ধারণ করে প্রতিটি অতিরিক্ত টেবিলে রাখা আরও কঠিন করে তোলে, তবে এটি অসম্ভব নয় কারণ এটি স্ক্রিপ্ট করা যেতে পারে কারণ এটি একটি স্ট্যান্ডার্ড স্কিমা হবে। স্ক্রিপ্টিংটি কেবল উত্স টেবিলের নাম এবং উত্স সারণী পিকে কলাম (গুলি) অনুসন্ধানের জন্য অ্যাকাউন্ট প্রয়োজন।

এখনও, নির্বিশেষে যার হ্যাশ অ্যালগরিদম পরিণামে সবচেয়ে মাপযোগ্য হতে প্রমাণ, আমার এখনও অত্যন্ত খোঁজার অন্তত কয়েক টেবিল (সম্ভবত কিছু যে 500 টেবিল বাকি চেয়ে অনেক বড় হয়) এবং ক্যাপচার করার জন্য সংশ্লিষ্ট টেবিল স্থাপনের সুপারিশ কারেন্ট হ্যাশগুলি তাই ইটিএল প্রক্রিয়াটির আগে "বর্তমান" মানগুলি জানা যায়। এমনকি দ্রুততম ফাংশনটি আউট-পারফরম্যান্স করতে পারে না কখনই এটি প্রথম স্থানে কল করে না ;-)।

অতিরিক্ত চিন্তা # 2 ( VARBINARYপরিবর্তে NVARCHAR)

SQLCLR তথাপি বনাম বিল্ট-ইন HASHBYTES, আমার এখনও সরাসরি রূপান্তর সুপারিশ করবে VARBINARYযে উচিত দ্রুত হবে। স্ট্রিংগুলি সংঘবদ্ধ করা ভয়ঙ্করভাবে কার্যকর নয়। এবং এটি প্রথমে নন-স্ট্রিং মানগুলিকে স্ট্রিংগুলিতে রূপান্তরিত করার পাশাপাশি অতিরিক্ত প্রচেষ্টা প্রয়োজন (আমি ধরে নিই যে প্রচেষ্টার পরিমাণ বেস ধরণের ভিত্তিতে পরিবর্তিত হয়: এর DATETIMEচেয়ে বেশি প্রয়োজন BIGINT) তবে VARBINARYকেবল রূপান্তর করলে আপনাকে অন্তর্নিহিত মান দেয় (অধিকাংশ ক্ষেত্রে).

এবং, প্রকৃতপক্ষে, অন্যান্য পরীক্ষাগুলি যে একই ডেটাসেটটি পরীক্ষা করেছিল এবং ব্যবহার করেছে HASHBYTES(N'SHA2_256',...), এক মিনিটে গণনা করা মোট হ্যাশগুলিতে 23.415% বৃদ্ধি দেখায়। এবং যে বৃদ্ধি VARBINARYপরিবর্তে ব্যবহার ছাড়া আর কিছুই করার জন্য ছিল NVARCHAR! 😸 ( বিস্তারিত জানার জন্য সম্প্রদায় উইকি উত্তর দেখুন)

অতিরিক্ত চিন্তা # 3 (ইনপুট পরামিতি সম্পর্কে সচেতন থাকুন)

আরও পরীক্ষায় দেখা গেছে যে একটি ক্ষেত্র যা কার্যক্ষমতাকে প্রভাবিত করে (মৃত্যুদণ্ডের এই খণ্ডের উপরে) হ'ল ইনপুট পরামিতি: কতগুলি এবং কী ধরণের (গুলি)।

Util_HashBinary এক: SQLCLR ফাংশন আমার এসকিউএল # লাইব্রেরিতে বর্তমানে যে দুটি ইনপুট প্যারামিটার আছে VARBINARY(হ্যাশ মান), এবং এক NVARCHAR(ব্যবহারের অ্যালগরিদম)। এটি আমার HASHBYTESফাংশনের স্বাক্ষরটির মিররিংয়ের কারণে । যাইহোক, আমি দেখতে পেয়েছি যে আমি যদি NVARCHARপ্যারামিটারটি সরিয়ে ফাংশন তৈরি করি যা কেবল SHA256 করে, তবে পারফরম্যান্সটি বেশ দুর্দান্তভাবে উন্নত হয়েছিল। আমি ধরে নিয়েছি যে এমনকি NVARCHARপরামিতিটি স্যুইচিংয়েও INTসহায়তা করতে পারে তবে আমি আরও ধরে নিই যে অতিরিক্ত INTপ্যারামিটারটি না হওয়াও কমপক্ষে কিছুটা দ্রুত।

এছাড়াও, SqlBytes.Valueভাল পারফরম্যান্স হতে পারে SqlBinary.Value

আমি এই নতুন পরীক্ষার জন্য দুটি নতুন ফাংশন তৈরি করেছি: ইউটিএল_হ্যাশএসএইচ 256 বাইনারি এবং ইউটিলি_হ্যাশএসএইচ 256 বাইনারি 8 কে এই পরীক্ষার জন্য। এগুলি এসকিউএল # এর পরবর্তী প্রকাশে অন্তর্ভুক্ত হবে (এখনও এর জন্য কোনও তারিখ সেট করা হয়নি)।

আমি এটিও দেখতে পেলাম যে পরীক্ষার পদ্ধতিটি কিছুটা উন্নত করা যেতে পারে, তাই আমি নীচে সম্প্রদায়ের উইকি উত্তরটিতে পরীক্ষার জোড় আপডেট করেছি:

  1. এসকিউএলসিআরআর এসেমসিলগুলি প্রাক-লোডিং যাতে লোড টাইম ওভারহেড ফলাফলগুলি স্কিউ না করে তা নিশ্চিত করে to
  2. সংঘর্ষের জন্য চেক করার জন্য একটি যাচাইকরণ পদ্ধতি। যদি কোনওটি পাওয়া যায় তবে এটি অনন্য / স্বতন্ত্র সারিগুলির সংখ্যা এবং সারিগুলির মোট সংখ্যা প্রদর্শন করে। এটির সাথে সংঘর্ষের সংখ্যা (যদি থাকে তবে) প্রদত্ত ব্যবহারের ক্ষেত্রে সীমা ছাড়িয়ে গেছে কিনা তা নির্ধারণ করতে দেয়। কিছু ব্যবহারের ক্ষেত্রে অল্প সংখ্যক সংঘর্ষের জন্য মঞ্জুরি দেয়, অন্যের কোনওটির প্রয়োজনও হতে পারে। একটি সুপার-দ্রুত ফাংশন অকার্যকর যদি এটি যথার্থতার কাঙ্ক্ষিত স্তরে পরিবর্তনগুলি সনাক্ত করতে না পারে। উদাহরণস্বরূপ, ওপি কর্তৃক প্রদত্ত পরীক্ষার জোতা ব্যবহার করে, আমি সারি গণনাটি 100 কে-সারি (এটি মূলত 10 কে) বৃদ্ধি পেয়েছিলাম এবং দেখেছি যে CHECKSUM9 কে-র সংঘর্ষে নিবন্ধিত হয়েছে, যা 9% (ইয়াইক)।

অতিরিক্ত চিন্তা # 4 ( HASHBYTES+ এসকিউএলসিএলআর একসাথে?)

বাধা কোথায় রয়েছে তার উপর নির্ভর করে HASHBYTESএকই হ্যাশটি করতে এটি বিল্ট-ইন এবং এসকিউএলসিএলআর ইউডিএফ এর সংমিশ্রণটি ব্যবহার করতে সহায়তা করতে পারে । যদি অন্তর্নির্মিত ফাংশনগুলি এসকিউএলসিআরআর অপারেশন থেকে আলাদাভাবে / পৃথকভাবে সীমাবদ্ধ থাকে, তবে এই পদ্ধতির HASHBYTESপৃথকভাবে এসকিউএলসিআরআর বা পৃথকভাবে এসকিউএলসিআরআর থেকে আরও একযোগে সম্পাদন করতে সক্ষম হতে পারে । এটি অবশ্যই পরীক্ষার যোগ্য।

অতিরিক্ত চিন্তা # 5 (হ্যাশিং অবজেক্ট ক্যাশিং?)

ডেভিড ব্রাউনয়ের উত্তর অনুসারে হ্যাশিং অ্যালগরিদম অবজেক্টের ক্যাচিং অবশ্যই আকর্ষণীয় বলে মনে হয়েছে, তাই আমি এটি চেষ্টা করে নীচের দুটি পয়েন্ট পেয়েছি:

  1. যে কারণেই হোক না কেন, এটি পারফরম্যান্সের উন্নতি খুব বেশি প্রদান করে বলে মনে হয় না। আমি ভুলভাবে কিছু করতে পারতাম, তবে এখানে আমি চেষ্টা করেছি:

    static readonly ConcurrentDictionary<int, SHA256Managed> hashers =
        new ConcurrentDictionary<int, SHA256Managed>();
    
    [return: SqlFacet(MaxSize = 100)]
    [SqlFunction(IsDeterministic = true)]
    public static SqlBinary FastHash([SqlFacet(MaxSize = 1000)] SqlBytes Input)
    {
        SHA256Managed sh = hashers.GetOrAdd(Thread.CurrentThread.ManagedThreadId,
                                            i => new SHA256Managed());
    
        return sh.ComputeHash(Input.Value);
    }
    
  2. ManagedThreadIdমান একটি নির্দিষ্ট ক্যোয়ারীতে সব SQLCLR রেফারেন্স জন্য একই উপস্থিত হতে পারে। আমি একই ফাংশনে একাধিক উল্লেখের পাশাপাশি বিভিন্ন ফাংশনের রেফারেন্স পরীক্ষা করেছি, সমস্ত 3 টিকে আলাদা ইনপুট মান দেওয়া হচ্ছে, এবং পৃথক (তবে প্রত্যাশিত) ফেরতের মানগুলি দেওয়া হচ্ছে। উভয় পরীক্ষার ফাংশনগুলির জন্য, আউটপুটটি একটি স্ট্রিং ছিল ManagedThreadIdযা হ্যাশ ফলাফলের একটি স্ট্রিং প্রতিনিধিত্ব অন্তর্ভুক্ত করে । ManagedThreadIdমান ক্যোয়ারীতে সব ইউডিএফ রেফারেন্স জন্য একই ছিল, এবং সব সারি জুড়ে। তবে, হ্যাশ ফলাফল একই ইনপুট স্ট্রিংয়ের জন্য একই এবং বিভিন্ন ইনপুট স্ট্রিংয়ের জন্য পৃথক ছিল।

    যদিও আমি আমার পরীক্ষায় কোনও ভ্রান্ত ফলাফল দেখতে পেলাম না, তা কি কোনও জাতি শর্তের সম্ভাবনা বাড়িয়ে দেবে না? যদি অভিধানের কীটি কোনও নির্দিষ্ট ক্যোয়ারিতে ডাকা সমস্ত এসকিউএলসিআরআর অবজেক্টগুলির জন্য একই হয়, তবে তারা সেই কীটির জন্য সঞ্চিত একই মান বা অবজেক্টটি ভাগ করে নেবে, তাই না? বিন্দুটি, এমনকি এটি এখানে কাজ করার মতো মনে হয়েছিল (একটি ডিগ্রীতে, আবার খুব বেশি পারফরম্যান্স লাভ বলে মনে হয় নি, তবে কার্যকরীভাবে কিছুই ভাঙ্গেনি), এটি আমার আত্মবিশ্বাস দেয় না যে এই পদ্ধতিটি অন্যান্য পরিস্থিতিতে কাজ করবে।


11

এটি কোনও traditionalতিহ্যবাহী উত্তর নয়, তবে আমি ভেবেছিলাম এখন পর্যন্ত উল্লিখিত কয়েকটি কৌশলগুলির মাপদণ্ড পোস্ট করা সহায়ক হবে। আমি এসকিউএল সার্ভার 2017 সিইউ 9 সহ একটি 96 টি মূল সার্ভারে পরীক্ষা করছি।

কিছু স্কেলিবিলিটি সমস্যা কিছু বিশ্বব্যাপী রাষ্ট্রের সাথে লড়াইয়ের সময়গত থ্রেডগুলির কারণে ঘটে। উদাহরণস্বরূপ, ক্লাসিক পিএফএস পৃষ্ঠা যুক্তি বিবেচনা করুন। এটি ঘটতে পারে যদি অনেক কর্মী থ্রেডের মেমরিতে একই পৃষ্ঠাটি পরিবর্তন করতে হয়। কোডটি আরও কার্যকর হওয়ার সাথে সাথে এটি দ্রুত ল্যাচটির অনুরোধ করতে পারে। এটা বিতর্ক বাড়ায়। এটিকে সহজভাবে বলতে গেলে, দক্ষ কোডটি স্কেলাবিলিটি ইস্যুতে ঝুঁকির সম্ভাবনা বেশি কারণ বিশ্বব্যাপী রাষ্ট্র আরও কঠোরভাবে বিতর্কিত হয়। ধীর কোডটি স্কেলাবিলিটি সমস্যাগুলির কারণ হ্রাস করার সম্ভাবনা কম কারণ বিশ্বব্যাপী রাষ্ট্রটি ঘন ঘন অ্যাক্সেস করে না।

HASHBYTESস্কেলাবিলিটিটি আংশিকভাবে ইনপুট স্ট্রিংয়ের দৈর্ঘ্যের উপর ভিত্তি করে। আমার তত্ত্বটি হ'ল কেন এটি ঘটে তা হ'ল HASHBYTESফাংশনটি যখন ডাকা হয় তখন কিছু বৈশ্বিক রাষ্ট্রের অ্যাক্সেস প্রয়োজন । পর্যবেক্ষণ করার জন্য সহজ বিশ্বব্যাপী রাষ্ট্রটি এসকিউএল সার্ভারের কয়েকটি সংস্করণে কল প্রতি মেমরির পৃষ্ঠা বরাদ্দ করা দরকার। সবচেয়ে কঠিন লক্ষ্য করা যায় যে কোনও ধরণের ওএসের বিতর্ক রয়েছে tention ফলস্বরূপ, যদি HASHBYTESকোডটি কম ঘন ঘন কল করা হয় তবে বিতর্ক হ্রাস পাবে। HASHBYTESকলগুলির হার হ্রাস করার একটি উপায় হ'ল প্রতি কলটিতে হ্যাশিং কাজের পরিমাণ বাড়ানো। হ্যাশিংয়ের কাজটি আংশিকভাবে ইনপুট স্ট্রিংয়ের দৈর্ঘ্যের উপর ভিত্তি করে। স্কেল্যাবিলিটি সমস্যা পুনরুত্পাদন করার জন্য আমি অ্যাপ্লিকেশনটিতে দেখেছি আমার ডেমো ডেটা পরিবর্তন করতে হবে। একটি যুক্তিসঙ্গত খারাপ পরিস্থিতি 21 সহ একটি টেবিলBIGINTকলাম. সারণির সংজ্ঞাটি নীচে কোডে অন্তর্ভুক্ত রয়েছে। স্থানীয় উপাদানগুলি হ্রাস করতে I'm, আমি MAXDOP 1তুলনামূলকভাবে ছোট টেবিলগুলিতে চলমান সমবর্তী প্রশ্নগুলি ব্যবহার করছি । আমার দ্রুত বেনমার্ক কোডটি নীচে রয়েছে।

ফাংশনগুলি বিভিন্ন হ্যাশ দৈর্ঘ্য ফেরত নোট করুন। MD5এবং SpookyHashউভয়ই 128 বিট হ্যাশ, SHA256একটি 256 বিট হ্যাশ।

ফলাফল ( NVARCHARবনাম VARBINARYরূপান্তর এবং উপসংহার)

রূপান্তরিত, এবং কনটেনেটেটিংয়ের VARBINARYচেয়ে সত্যই কার্যকর / পারফরম্যান্ট কিনা তা দেখার জন্য, একই টেমপ্লেট থেকে সঞ্চিত পদ্ধতির NVARCHARএকটি NVARCHARসংস্করণ RUN_HASHBYTES_SHA2_256তৈরি করা হয়েছিল ( নীচে বেনমার্কিং কোডের বিভাগে "পদক্ষেপ 5" দেখুন)। কেবলমাত্র পার্থক্যগুলি হ'ল:

  1. সঞ্চিত প্রক্রিয়া নামটি শেষ হয় _NVC
  2. BINARY(8)জন্য CASTফাংশন হতে পরিবর্তন করা হয়েছিলNVARCHAR(15)
  3. 0x7C হতে পরিবর্তন করা হয়েছিল N'|'

ফলাফল এতে:

CAST(FK1 AS NVARCHAR(15)) + N'|' +

পরিবর্তে:

CAST(FK1 AS BINARY(8)) + 0x7C +

নীচের টেবিলটিতে 1 মিনিটে সঞ্চালিত হ্যাশগুলির সংখ্যা রয়েছে। নীচে উল্লিখিত অন্যান্য পরীক্ষার জন্য পরীক্ষাগুলি আলাদা সার্ভারে ব্যবহার করা হয়েছিল।

╔════════════════╦══════════╦══════════════╗
    Datatype      Test #   Total Hashes 
╠════════════════╬══════════╬══════════════╣
 NVARCHAR               1      10200000 
 NVARCHAR               2      10300000 
 NVARCHAR         AVERAGE  * 10250000 * 
 -------------- ║ -------- ║ ------------ ║
 VARBINARY              1      12500000 
 VARBINARY              2      12800000 
 VARBINARY        AVERAGE  * 12650000 * 
╚════════════════╩══════════╩══════════════╝

মাত্র গড়ের দিকে তাকালে আমরা এতে স্যুইচ করার সুবিধাটি গণনা করতে পারি VARBINARY:

SELECT (12650000 - 10250000) AS [IncreaseAmount],
       ROUND(((126500000 - 10250000) / 10250000) * 100.0, 3) AS [IncreasePercentage]

যে ফিরে:

IncreaseAmount:    2400000.0
IncreasePercentage:   23.415

ফলাফল (হ্যাশ অ্যালগরিদম এবং বাস্তবায়ন)

নীচের টেবিলটিতে 1 মিনিটে সঞ্চালিত হ্যাশগুলির সংখ্যা রয়েছে। উদাহরণস্বরূপ, CHECKSUM৮৪ টি সমবর্তী প্রশ্নের সাথে ব্যবহারের ফলে সময় শেষ হওয়ার আগেই 2 বিলিয়ন হ্যাশ সঞ্চালিত হয়েছিল।

╔════════════════════╦════════════╦════════════╦════════════╗
      Function       12 threads  48 threads  84 threads 
╠════════════════════╬════════════╬════════════╬════════════╣
 CHECKSUM             281250000  1122440000  2040100000 
 HASHBYTES MD5         75940000   106190000   112750000 
 HASHBYTES SHA2_256    80210000   117080000   124790000 
 CLR Spooky           131250000   505700000   786150000 
 CLR SpookyLOB         17420000    27160000    31380000 
 SQL# MD5              17080000    26450000    29080000 
 SQL# SHA2_256         18370000    28860000    32590000 
 SQL# MD5 8k           24440000    30560000    32550000 
 SQL# SHA2_256 8k      87240000   159310000   155760000 
╚════════════════════╩════════════╩════════════╩════════════╝

যদি আপনি প্রতি থ্রেড-সেকেন্ডের ক্ষেত্রে কাজের ক্ষেত্রে পরিমাপ করা একই সংখ্যাগুলি দেখতে পছন্দ করেন:

╔════════════════════╦════════════════════════════╦════════════════════════════╦════════════════════════════╗
      Function       12 threads per core-second  48 threads per core-second  84 threads per core-second 
╠════════════════════╬════════════════════════════╬════════════════════════════╬════════════════════════════╣
 CHECKSUM                                390625                      389736                      404782 
 HASHBYTES MD5                           105472                       36872                       22371 
 HASHBYTES SHA2_256                      111403                       40653                       24760 
 CLR Spooky                              182292                      175590                      155982 
 CLR SpookyLOB                            24194                        9431                        6226 
 SQL# MD5                                 23722                        9184                        5770 
 SQL# SHA2_256                            25514                       10021                        6466 
 SQL# MD5 8k                              33944                       10611                        6458 
 SQL# SHA2_256 8k                        121167                       55316                       30905 
╚════════════════════╩════════════════════════════╩════════════════════════════╩════════════════════════════╝

সমস্ত পদ্ধতি সম্পর্কে কিছু দ্রুত চিন্তা:

  • CHECKSUM: প্রত্যাশার মতো খুব ভাল স্কেল্যাবিলিটি
  • HASHBYTES: স্কেলাবিলিটি ইস্যুগুলির মধ্যে কল প্রতি এক মেমরি বরাদ্দ এবং ওএসে ব্যয় করা প্রচুর পরিমাণে সিপিইউ অন্তর্ভুক্ত
  • Spooky: আশ্চর্যজনকভাবে ভাল স্কেলাবিলিটি
  • Spooky LOB: স্পিনলক SOS_SELIST_SIZED_SLOCKনিয়ন্ত্রণের বাইরে চলে যায়। আমি সন্দেহ করি যে এটি সিএলআর ফাংশনগুলির মাধ্যমে এলওবি পাস করার একটি সাধারণ সমস্যা, তবে আমি নিশ্চিত নই
  • Util_HashBinary: দেখে মনে হচ্ছে এটি একই স্পিনলক দ্বারা আঘাত পেয়েছে। আমি এখন পর্যন্ত এটি সন্ধান করিনি কারণ সম্ভবত এটির জন্য খুব বেশি কিছু আমি করতে পারি না:

আপনার লক স্পিন

  • Util_HashBinary 8k: খুব অবাক করা ফলাফল, এখানে কী চলছে তা নিশ্চিত নয়

একটি ছোট সার্ভারে চূড়ান্ত ফলাফল পরীক্ষা করা হয়েছে:

╔═════════════════════════╦════════════════════════╦════════════════════════╗
     Hash Algorithm       Hashes over 11 threads  Hashes over 44 threads 
╠═════════════════════════╬════════════════════════╬════════════════════════╣
 HASHBYTES SHA2_256                     85220000               167050000 
 SpookyHash                            101200000               239530000 
 Util_HashSHA256Binary8k                90590000               217170000 
 SpookyHashLOB                          23490000                38370000 
 Util_HashSHA256Binary                  23430000                36590000 
╚═════════════════════════╩════════════════════════╩════════════════════════╝

বেনমার্কিং কোড

সেটআপ 1: টেবিল এবং ডেটা

DROP TABLE IF EXISTS dbo.HASH_SMALL;

CREATE TABLE dbo.HASH_SMALL (
    ID BIGINT NOT NULL,
    FK1 BIGINT NOT NULL,
    FK2 BIGINT NOT NULL,
    FK3 BIGINT NOT NULL,
    FK4 BIGINT NOT NULL,
    FK5 BIGINT NOT NULL,
    FK6 BIGINT NOT NULL,
    FK7 BIGINT NOT NULL,
    FK8 BIGINT NOT NULL,
    FK9 BIGINT NOT NULL,
    FK10 BIGINT NOT NULL,
    FK11 BIGINT NOT NULL,
    FK12 BIGINT NOT NULL,
    FK13 BIGINT NOT NULL,
    FK14 BIGINT NOT NULL,
    FK15 BIGINT NOT NULL,
    FK16 BIGINT NOT NULL,
    FK17 BIGINT NOT NULL,
    FK18 BIGINT NOT NULL,
    FK19 BIGINT NOT NULL,
    FK20 BIGINT NOT NULL
);

INSERT INTO dbo.HASH_SMALL WITH (TABLOCK)
SELECT RN,
4000000 - RN, 4000000 - RN
,200000000 - RN, 200000000 - RN
, RN % 500000 , RN % 500000 , RN % 500000
, RN % 500000 , RN % 500000 , RN % 500000 
, 100000 - RN % 100000, RN % 100000
, 100000 - RN % 100000, RN % 100000
, 100000 - RN % 100000, RN % 100000
, 100000 - RN % 100000, RN % 100000
, 100000 - RN % 100000, RN % 100000
FROM (
    SELECT TOP (10000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
) q
OPTION (MAXDOP 1);


DROP TABLE IF EXISTS dbo.LOG_HASHES;
CREATE TABLE dbo.LOG_HASHES (
LOG_TIME DATETIME,
HASH_ALGORITHM INT,
SESSION_ID INT,
NUM_HASHES BIGINT
);

সেটআপ 2: মাস্টার এক্সিকিউশন প্রোক

GO
CREATE OR ALTER PROCEDURE dbo.RUN_HASHES_FOR_ONE_MINUTE (@HashAlgorithm INT)
AS
BEGIN
DECLARE @target_end_time DATETIME = DATEADD(MINUTE, 1, GETDATE()),
        @query_execution_count INT = 0;

SET NOCOUNT ON;

DECLARE @ProcName NVARCHAR(261); -- schema_name + proc_name + '[].[]'

DECLARE @RowCount INT;
SELECT @RowCount = SUM(prtn.[row_count])
FROM   sys.dm_db_partition_stats prtn
WHERE  prtn.[object_id] = OBJECT_ID(N'dbo.HASH_SMALL')
AND    prtn.[index_id] < 2;


-- Load assembly if not loaded to prevent load time from skewing results
DECLARE @OptionalInitSQL NVARCHAR(MAX);
SET @OptionalInitSQL = CASE @HashAlgorithm
       WHEN 1 THEN N'SELECT @Dummy = dbo.SpookyHash(0x1234);'
       WHEN 2 THEN N'' -- HASHBYTES
       WHEN 3 THEN N'' -- HASHBYTES
       WHEN 4 THEN N'' -- CHECKSUM
       WHEN 5 THEN N'SELECT @Dummy = dbo.SpookyHashLOB(0x1234);'
       WHEN 6 THEN N'SELECT @Dummy = SQL#.Util_HashBinary(N''MD5'', 0x1234);'
       WHEN 7 THEN N'SELECT @Dummy = SQL#.Util_HashBinary(N''SHA256'', 0x1234);'
       WHEN 8 THEN N'SELECT @Dummy = SQL#.Util_HashBinary8k(N''MD5'', 0x1234);'
       WHEN 9 THEN N'SELECT @Dummy = SQL#.Util_HashBinary8k(N''SHA256'', 0x1234);'
/* -- BETA / non-public code
       WHEN 10 THEN N'SELECT @Dummy = SQL#.Util_HashSHA256Binary8k(0x1234);'
       WHEN 11 THEN N'SELECT @Dummy = SQL#.Util_HashSHA256Binary(0x1234);'
*/
   END;


IF (RTRIM(@OptionalInitSQL) <> N'')
BEGIN
    SET @OptionalInitSQL = N'
SET NOCOUNT ON;
DECLARE @Dummy VARBINARY(100);
' + @OptionalInitSQL;

    RAISERROR(N'** Executing optional initialization code:', 10, 1) WITH NOWAIT;
    RAISERROR(@OptionalInitSQL, 10, 1) WITH NOWAIT;
    EXEC (@OptionalInitSQL);
    RAISERROR(N'-------------------------------------------', 10, 1) WITH NOWAIT;
END;


SET @ProcName = CASE @HashAlgorithm
                    WHEN 1 THEN N'dbo.RUN_SpookyHash'
                    WHEN 2 THEN N'dbo.RUN_HASHBYTES_MD5'
                    WHEN 3 THEN N'dbo.RUN_HASHBYTES_SHA2_256'
                    WHEN 4 THEN N'dbo.RUN_CHECKSUM'
                    WHEN 5 THEN N'dbo.RUN_SpookyHashLOB'
                    WHEN 6 THEN N'dbo.RUN_SR_MD5'
                    WHEN 7 THEN N'dbo.RUN_SR_SHA256'
                    WHEN 8 THEN N'dbo.RUN_SR_MD5_8k'
                    WHEN 9 THEN N'dbo.RUN_SR_SHA256_8k'
/* -- BETA / non-public code
                    WHEN 10 THEN N'dbo.RUN_SR_SHA256_new'
                    WHEN 11 THEN N'dbo.RUN_SR_SHA256LOB_new'
*/
                    WHEN 13 THEN N'dbo.RUN_HASHBYTES_SHA2_256_NVC'
                END;

RAISERROR(N'** Executing proc: %s', 10, 1, @ProcName) WITH NOWAIT;

WHILE GETDATE() < @target_end_time
BEGIN
    EXEC @ProcName;

    SET @query_execution_count = @query_execution_count + 1;
END;

INSERT INTO dbo.LOG_HASHES
VALUES (GETDATE(), @HashAlgorithm, @@SPID, @RowCount * @query_execution_count);

END;
GO

সেটআপ 3: সংঘর্ষ সনাক্তকরণ প্রোক

GO
CREATE OR ALTER PROCEDURE dbo.VERIFY_NO_COLLISIONS (@HashAlgorithm INT)
AS
SET NOCOUNT ON;

DECLARE @RowCount INT;
SELECT @RowCount = SUM(prtn.[row_count])
FROM   sys.dm_db_partition_stats prtn
WHERE  prtn.[object_id] = OBJECT_ID(N'dbo.HASH_SMALL')
AND    prtn.[index_id] < 2;


DECLARE @CollisionTestRows INT;
DECLARE @CollisionTestSQL NVARCHAR(MAX);
SET @CollisionTestSQL = N'
SELECT @RowsOut = COUNT(DISTINCT '
+ CASE @HashAlgorithm
       WHEN 1 THEN N'dbo.SpookyHash('
       WHEN 2 THEN N'HASHBYTES(''MD5'','
       WHEN 3 THEN N'HASHBYTES(''SHA2_256'','
       WHEN 4 THEN N'CHECKSUM('
       WHEN 5 THEN N'dbo.SpookyHashLOB('
       WHEN 6 THEN N'SQL#.Util_HashBinary(N''MD5'','
       WHEN 7 THEN N'SQL#.Util_HashBinary(N''SHA256'','
       WHEN 8 THEN N'SQL#.[Util_HashBinary8k](N''MD5'','
       WHEN 9 THEN N'SQL#.[Util_HashBinary8k](N''SHA256'','
--/* -- BETA / non-public code
       WHEN 10 THEN N'SQL#.[Util_HashSHA256Binary8k]('
       WHEN 11 THEN N'SQL#.[Util_HashSHA256Binary]('
--*/
   END
+ N'
    CAST(FK1 AS BINARY(8)) + 0x7C +
    CAST(FK2 AS BINARY(8)) + 0x7C +
    CAST(FK3 AS BINARY(8)) + 0x7C +
    CAST(FK4 AS BINARY(8)) + 0x7C +
    CAST(FK5 AS BINARY(8)) + 0x7C +
    CAST(FK6 AS BINARY(8)) + 0x7C +
    CAST(FK7 AS BINARY(8)) + 0x7C +
    CAST(FK8 AS BINARY(8)) + 0x7C +
    CAST(FK9 AS BINARY(8)) + 0x7C +
    CAST(FK10 AS BINARY(8)) + 0x7C +
    CAST(FK11 AS BINARY(8)) + 0x7C +
    CAST(FK12 AS BINARY(8)) + 0x7C +
    CAST(FK13 AS BINARY(8)) + 0x7C +
    CAST(FK14 AS BINARY(8)) + 0x7C +
    CAST(FK15 AS BINARY(8)) + 0x7C +
    CAST(FK16 AS BINARY(8)) + 0x7C +
    CAST(FK17 AS BINARY(8)) + 0x7C +
    CAST(FK18 AS BINARY(8)) + 0x7C +
    CAST(FK19 AS BINARY(8)) + 0x7C +
    CAST(FK20 AS BINARY(8))  ))
FROM dbo.HASH_SMALL;';

PRINT @CollisionTestSQL;

EXEC sp_executesql
  @CollisionTestSQL,
  N'@RowsOut INT OUTPUT',
  @RowsOut = @CollisionTestRows OUTPUT;


IF (@CollisionTestRows <> @RowCount)
BEGIN
    RAISERROR('Collisions for algorithm: %d!!!  %d unique rows out of %d.',
    16, 1, @HashAlgorithm, @CollisionTestRows, @RowCount);
END;
GO

সেটআপ 4: ক্লিনআপ (সমস্ত পরীক্ষার জন্য ড্রপ)

DECLARE @SQL NVARCHAR(MAX) = N'';
SELECT @SQL += N'DROP PROCEDURE [dbo].' + QUOTENAME(sp.[name])
            + N';' + NCHAR(13) + NCHAR(10)
FROM  sys.objects sp
WHERE sp.[name] LIKE N'RUN[_]%'
AND   sp.[type_desc] = N'SQL_STORED_PROCEDURE'
AND   sp.[name] <> N'RUN_HASHES_FOR_ONE_MINUTE'

PRINT @SQL;

EXEC (@SQL);

সেটআপ 5: পরীক্ষার প্রকোপগুলি উত্পন্ন করুন

SET NOCOUNT ON;

DECLARE @TestProcsToCreate TABLE
(
  ProcName sysname NOT NULL,
  CodeToExec NVARCHAR(261) NOT NULL
);
DECLARE @ProcName sysname,
        @CodeToExec NVARCHAR(261);

INSERT INTO @TestProcsToCreate VALUES
  (N'SpookyHash', N'dbo.SpookyHash('),
  (N'HASHBYTES_MD5', N'HASHBYTES(''MD5'','),
  (N'HASHBYTES_SHA2_256', N'HASHBYTES(''SHA2_256'','),
  (N'CHECKSUM', N'CHECKSUM('),
  (N'SpookyHashLOB', N'dbo.SpookyHashLOB('),
  (N'SR_MD5', N'SQL#.Util_HashBinary(N''MD5'','),
  (N'SR_SHA256', N'SQL#.Util_HashBinary(N''SHA256'','),
  (N'SR_MD5_8k', N'SQL#.[Util_HashBinary8k](N''MD5'','),
  (N'SR_SHA256_8k', N'SQL#.[Util_HashBinary8k](N''SHA256'',')
--/* -- BETA / non-public code
  , (N'SR_SHA256_new', N'SQL#.[Util_HashSHA256Binary8k]('),
  (N'SR_SHA256LOB_new', N'SQL#.[Util_HashSHA256Binary](');
--*/
DECLARE @ProcTemplate NVARCHAR(MAX),
        @ProcToCreate NVARCHAR(MAX);

SET @ProcTemplate = N'
CREATE OR ALTER PROCEDURE dbo.RUN_{{ProcName}}
AS
BEGIN
DECLARE @dummy INT;
SET NOCOUNT ON;

SELECT @dummy = COUNT({{CodeToExec}}
    CAST(FK1 AS BINARY(8)) + 0x7C +
    CAST(FK2 AS BINARY(8)) + 0x7C +
    CAST(FK3 AS BINARY(8)) + 0x7C +
    CAST(FK4 AS BINARY(8)) + 0x7C +
    CAST(FK5 AS BINARY(8)) + 0x7C +
    CAST(FK6 AS BINARY(8)) + 0x7C +
    CAST(FK7 AS BINARY(8)) + 0x7C +
    CAST(FK8 AS BINARY(8)) + 0x7C +
    CAST(FK9 AS BINARY(8)) + 0x7C +
    CAST(FK10 AS BINARY(8)) + 0x7C +
    CAST(FK11 AS BINARY(8)) + 0x7C +
    CAST(FK12 AS BINARY(8)) + 0x7C +
    CAST(FK13 AS BINARY(8)) + 0x7C +
    CAST(FK14 AS BINARY(8)) + 0x7C +
    CAST(FK15 AS BINARY(8)) + 0x7C +
    CAST(FK16 AS BINARY(8)) + 0x7C +
    CAST(FK17 AS BINARY(8)) + 0x7C +
    CAST(FK18 AS BINARY(8)) + 0x7C +
    CAST(FK19 AS BINARY(8)) + 0x7C +
    CAST(FK20 AS BINARY(8)) 
    )
    )
    FROM dbo.HASH_SMALL
    OPTION (MAXDOP 1);

END;
';

DECLARE CreateProcsCurs CURSOR READ_ONLY FORWARD_ONLY LOCAL FAST_FORWARD
FOR SELECT [ProcName], [CodeToExec]
    FROM @TestProcsToCreate;

OPEN [CreateProcsCurs];

FETCH NEXT
FROM  [CreateProcsCurs]
INTO  @ProcName, @CodeToExec;

WHILE (@@FETCH_STATUS = 0)
BEGIN
    -- First: create VARBINARY version
    SET @ProcToCreate = REPLACE(REPLACE(@ProcTemplate,
                                        N'{{ProcName}}',
                                        @ProcName),
                                N'{{CodeToExec}}',
                                @CodeToExec);

    EXEC (@ProcToCreate);

    -- Second: create NVARCHAR version (optional: built-ins only)
    IF (CHARINDEX(N'.', @CodeToExec) = 0)
    BEGIN
        SET @ProcToCreate = REPLACE(REPLACE(REPLACE(@ProcToCreate,
                                                    N'dbo.RUN_' + @ProcName,
                                                    N'dbo.RUN_' + @ProcName + N'_NVC'),
                                            N'BINARY(8)',
                                            N'NVARCHAR(15)'),
                                    N'0x7C',
                                    N'N''|''');

        EXEC (@ProcToCreate);
    END;

    FETCH NEXT
    FROM  [CreateProcsCurs]
    INTO  @ProcName, @CodeToExec;
END;

CLOSE [CreateProcsCurs];
DEALLOCATE [CreateProcsCurs];

পরীক্ষা 1: সংঘর্ষের জন্য চেক করুন

EXEC dbo.VERIFY_NO_COLLISIONS 1;
EXEC dbo.VERIFY_NO_COLLISIONS 2;
EXEC dbo.VERIFY_NO_COLLISIONS 3;
EXEC dbo.VERIFY_NO_COLLISIONS 4;
EXEC dbo.VERIFY_NO_COLLISIONS 5;
EXEC dbo.VERIFY_NO_COLLISIONS 6;
EXEC dbo.VERIFY_NO_COLLISIONS 7;
EXEC dbo.VERIFY_NO_COLLISIONS 8;
EXEC dbo.VERIFY_NO_COLLISIONS 9;
EXEC dbo.VERIFY_NO_COLLISIONS 10;
EXEC dbo.VERIFY_NO_COLLISIONS 11;

পরীক্ষা 2: পারফরম্যান্স টেস্টগুলি চালান

EXEC dbo.RUN_HASHES_FOR_ONE_MINUTE 1;
EXEC dbo.RUN_HASHES_FOR_ONE_MINUTE 2;
EXEC dbo.RUN_HASHES_FOR_ONE_MINUTE 3; -- HASHBYTES('SHA2_256'
EXEC dbo.RUN_HASHES_FOR_ONE_MINUTE 4;
EXEC dbo.RUN_HASHES_FOR_ONE_MINUTE 5;
EXEC dbo.RUN_HASHES_FOR_ONE_MINUTE 6;
EXEC dbo.RUN_HASHES_FOR_ONE_MINUTE 7;
EXEC dbo.RUN_HASHES_FOR_ONE_MINUTE 8;
EXEC dbo.RUN_HASHES_FOR_ONE_MINUTE 9;
EXEC dbo.RUN_HASHES_FOR_ONE_MINUTE 10;
EXEC dbo.RUN_HASHES_FOR_ONE_MINUTE 11;
EXEC dbo.RUN_HASHES_FOR_ONE_MINUTE 13; -- NVC version of #3


SELECT *
FROM   dbo.LOG_HASHES
ORDER BY [LOG_TIME] DESC;

মূল্যায়ন সমাধানের বিষয়গুলি

একক একক এসকিউএলসিআরআর ইউডিএফের পারফরম্যান্স টেস্টিংয়ের দিকে মনোনিবেশ করার সময়, দুটি বিষয় যা প্রথম দিকে আলোচনা করা হয়েছিল তা পরীক্ষাগুলিতে অন্তর্ভুক্ত করা হয়নি, তবে কোন পদ্ধতির সমস্ত প্রয়োজনীয়তা পূরণ হয় তা নির্ধারণ করার জন্য আদর্শভাবে তদন্ত করা উচিত ।

  1. প্রতিটি ক্যোয়ারী অনুযায়ী ফাংশন দু'বার কার্যকর করা হবে (একবার আমদানি সারিটির জন্য এবং একবার বর্তমান সারির জন্য)। পরীক্ষাগুলি এখনও পর্যন্ত পরীক্ষার প্রশ্নাবলীতে কেবলমাত্র একবার ইউডিএফকে রেফারেন্স করেছে। এই ফ্যাক্টরটি বিকল্পগুলির র‌্যাঙ্কিং পরিবর্তন করতে পারে না, তবে এটি কেবল এ ক্ষেত্রে উপেক্ষা করা উচিত নয়।
  2. এরপরে মুছে ফেলা একটি মন্তব্যে পল হোয়াইট উল্লেখ করেছিলেন:

    HASHBYTESএকটি সিএলআর স্কেলার ফাংশন প্রতিস্থাপনের একটি নেতিবাচক দিক - এটি প্রদর্শিত হয় যে সিএলআর ফাংশনগুলি ব্যাচ মোড ব্যবহার করতে পারে না যেখানে HASHBYTESপারে। এটি গুরুত্বপূর্ণ, পারফরম্যান্স-ভিত্তিক হতে পারে।

    সুতরাং এটি বিবেচনার জন্য কিছু, এবং পরিষ্কারভাবে পরীক্ষা প্রয়োজন requires যদি এসকিউএলসিআরআর বিকল্পগুলি অন্তর্নির্মিতগুলির উপরে কোনও সুবিধা না দেয় HASHBYTES, তবে এটি বিদ্যমান টেবিলগুলিতে বিদ্যমান হ্যাশগুলি (কমপক্ষে বৃহত্তম টেবিলের জন্য) ক্যাপচার করার সলোমনের পরামর্শকে ওজন যুক্ত করে।


6

আপনি সম্ভবত কার্যকারিতাটি উন্নত করতে পারেন, এবং ফাংশন কলটিতে তৈরি হওয়া কোনও বস্তুকে পুলিং এবং ক্যাশে করে সমস্ত .NET পদ্ধতির স্কেল্যাবিলিটি। উপরে পল হোয়াইটের কোডের জন্য ইজি:

static readonly ConcurrentDictionary<int,ISpookyHashV2> hashers = new ConcurrentDictonary<ISpookyHashV2>()
public static byte[] SpookyHash([SqlFacet (MaxSize = 8000)] SqlBinary Input)
{
    ISpookyHashV2 sh = hashers.GetOrAdd(Thread.CurrentThread.ManagedThreadId, i => SpookyHashV2Factory.Instance.Create());

    return sh.ComputeHash(Input.Value).Hash;
}

এসকিউএল সিএলআর স্ট্যাটিক / শেয়ার্ড ভেরিয়েবলগুলি ব্যবহার করা নিরুৎসাহিত করে এবং চেষ্টা করে, তবে আপনি যদি সেগুলি কেবল পঠনযোগ্য হিসাবে চিহ্নিত করেন তবে এটি আপনাকে ভাগ করা ভেরিয়েবলগুলি ব্যবহার করতে দিবে। কোনটি, অবশ্যই, অর্থহীন আপনি শুধু, কিছু চপল ধরনের একটি একক উদাহরণস্বরূপ ধার্য করতে পারেন যেমন ভালো হয় ConcurrentDictionary


আকর্ষণীয় ... এই থ্রেডটি কি যদি একই বার বার ব্যবহার করা হয় তবে এটি নিরাপদ? আমি জানি যে পরিচালিত হ্যাশগুলির একটি Clear()পদ্ধতি আছে তবে আমি এটিকে স্পুকির দিকে দেখিনি।
সলোমন রুটজকি

@ পোলওহাইট এবং ডেভিড আমি মধ্যে সম্পন্ন কিছু ভুল করেছি পারে, অথবা এটি হতে পারে একটি পার্থক্য SHA256Managedএবং SpookyHashV2, কিন্তু আমি এই চেষ্টা এবং অনেক কর্মক্ষমতা বৃদ্ধি দেখতে পাইনি, যদি থাকে,। আমি আরও লক্ষ্য করেছি যে ManagedThreadIdনির্দিষ্ট কোয়েরিতে সমস্ত এসকিউএলসিএলআর রেফারেন্সের জন্য মানটি সমান। আমি একই ফাংশনে একাধিক উল্লেখের পাশাপাশি বিভিন্ন ফাংশনের রেফারেন্স পরীক্ষা করেছি, সমস্ত 3 টিকে আলাদা ইনপুট মান দেওয়া হচ্ছে, এবং পৃথক (তবে প্রত্যাশিত) ফেরতের মানগুলি দেওয়া হচ্ছে। এটি কি কোনও রেসের অবস্থার সম্ভাবনা বাড়িয়ে দেবে না? সত্যি কথা বলতে কি, আমার পরীক্ষায় আমি কোনও দেখতে পাইনি।
সলোমন রুটজকি
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.