বিশাল ডেটা সহ স্কেলকম্যান্ড অ্যাসিঙ্ক পদ্ধতিগুলি ব্যবহার করে ভয়াবহ কার্য সম্পাদন


95

এসিঙ্ক কলগুলি ব্যবহার করার সময় আমার বড় এসকিউএল পারফরম্যান্স সমস্যা হয়। সমস্যাটি দেখানোর জন্য আমি একটি ছোট মামলা তৈরি করেছি।

আমি একটি এসকিউএল সার্ভার ২০১ a-তে একটি ডাটাবেস তৈরি করেছি যা আমাদের ল্যানে থাকে (তাই কোনও লোকালডিবি নয়)।

সেই ডাটাবেসে আমার কাছে WorkingCopyদুটি কলাম সহ একটি টেবিল রয়েছে:

Id (nvarchar(255, PK))
Value (nvarchar(max))

ডিডিএল

CREATE TABLE [dbo].[Workingcopy]
(
    [Id] [nvarchar](255) NOT NULL, 
    [Value] [nvarchar](max) NULL, 

    CONSTRAINT [PK_Workingcopy] 
        PRIMARY KEY CLUSTERED ([Id] ASC)
                    WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
                          IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, 
                          ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

সেই টেবিলটিতে আমি একটি একক রেকর্ড id=োকিয়েছি ( = 'পারফিউনিটটেষ্ট', Valueএটি একটি 1.5 মিমি স্ট্রিং (বৃহত্তর জেএসএন ডেটাসেটের একটি জিপ))।

এখন, যদি আমি এসএসএমএসে কোয়েরিটি সম্পাদন করি:

SELECT [Value] 
FROM [Workingcopy] 
WHERE id = 'perfunittest'

আমি তাত্ক্ষণিকভাবে ফলাফলটি পেয়েছি এবং আমি এসকিউএল সার্ভার প্রোফাইলারে দেখতে পাচ্ছি যে মৃত্যুদন্ড কার্যকর করার সময়টি প্রায় 20 মিলিসেকেন্ডের মতো। সব স্বাভাবিক।

একটি সমতল ব্যবহার করে .NET (4.6) কোড থেকে কোয়েরিটি সম্পাদন করার সময় SqlConnection:

// at this point, the connection is already open
var command = new SqlCommand($"SELECT Value FROM WorkingCopy WHERE Id = @Id", _connection);
command.Parameters.Add("@Id", SqlDbType.NVarChar, 255).Value = key;

string value = command.ExecuteScalar() as string;

এর কার্যকর করার সময়টিও প্রায় 20-30 মিলিসেকেন্ড।

তবে এ্যাসিঙ্ক কোডে পরিবর্তন করার সময়:

string value = await command.ExecuteScalarAsync() as string;

মৃত্যুদন্ড কার্যকর করার সময় হঠাৎ 1800 এমএস ! এসকিউএল সার্ভার প্রোফাইলার-এ, আমি দেখতে পাচ্ছি যে ক্যোয়ারি এক্সিকিউশন সময়কাল এক সেকেন্ডের চেয়ে বেশি। যদিও প্রোফাইলার দ্বারা রিপোর্ট করা মৃত্যুদণ্ডপ্রাপ্ত ক্যোয়ারী হ'ল অ্যাসিঙ্ক সংস্করণটির মতো।

তবে এটি আরও খারাপ হয়। আমি যদি সংযোগের স্ট্রিংয়ে প্যাকেট আকারের সাথে ঘুরে দেখি তবে আমি নিম্নলিখিত ফলাফলগুলি পাই:

প্যাকেটের আকার 32768: [টাইমিং]: এসকিউএলএল স্টোরে এক্সিকিউটস্ল্যাকারসাইক -> অতিবাহিত সময়: 450 এমএস

প্যাকেটের আকার 4096: [সময়সীমা]: এসকিউএলএলিউস্টোরে এক্সিকিউটস্ল্যাকারসাইক -> অতিবাহিত সময়: 3667 এমএস

প্যাকেটের আকার 512: [টাইমিং]: এসকিউএলএলিউস্টোরে এক্সিকিউটস্ল্যাকারসাইক -> অতিবাহিত সময়: 30776 এমএস

30,000 এমএস !! এটি অ-অ্যাসিঙ্ক সংস্করণের চেয়ে 1000x এরও বেশি ধীর। এবং এসকিউএল সার্ভার প্রোফাইলার রিপোর্ট করেছেন যে ক্যোয়ারি এক্সিকিউশনটি 10 ​​সেকেন্ডেরও বেশি সময় নিয়েছে। এটি অন্য 20 সেকেন্ড কোথায় গেছে তাও ব্যাখ্যা করে না!

তারপরে আমি সিঙ্ক সংস্করণে ফিরে এসেছি এবং প্যাকেট আকারের সাথেও খেললাম, যদিও এটি কার্যকর করার সময় সামান্য প্রভাব ফেলেছিল, এটি অ্যাসিঙ্ক সংস্করণের মতো নাটকীয় ছিল না।

সাইডেনোট হিসাবে, যদি এটি মানটির মধ্যে কেবল একটি ছোট স্ট্রিং (<100 বাইটস) রাখে, অ্যাসিঙ্ক ক্যোয়ারী এক্সিকিউশন সিঙ্ক সংস্করণের মতোই দ্রুত (ফলাফল 1 বা 2 এমএস) ms

আমি এটি দেখে সত্যিই হতবাক, বিশেষত যেহেতু আমি অন্তর্নির্মিত ব্যবহার করছি SqlConnection, এমনকি কোনও ওআরএমও না। এছাড়াও আশেপাশে অনুসন্ধান করার সময়, আমি এমন কোনও কিছুই পাইনি যা এই আচরণটি ব্যাখ্যা করতে পারে। কোন ধারনা?


4
@ এইচসিডি ২.৫ এমবি ????? এবং আপনি জিজ্ঞাসা করছেন কেন হ্রাস প্যাকেটের আকারের সাথে ধীরে ধীরে এটি পুনরুদ্ধার করা হচ্ছে ? বিশেষত যখন আপনি বিএলওবিগুলির জন্য ভুল কোয়েরি ব্যবহার করেন ?
পানাগিওটিস কানভোজ

4
@ পানাগিওটিসকানাভোস কেবল ওপি-র পক্ষে ছিল। আসল প্রশ্নটি হ'ল একই প্যাকেজ আকারের সাথে সিঙ্কের তুলনায় অ্যাসিঙ্ক এত ধীরে ধীরে কেন ।
ফিল্ডার

4
পরীক্ষা করে দেখুন ADO.NET মধ্যে বড়-ভ্যালু (সর্বোচ্চ) ডেটা সংশোধন CLOBs এবং ব্লব পুনরুদ্ধারের সঠিক উপায়। এগুলিকে একটি বড় মান হিসাবে পড়ার চেষ্টা করার পরিবর্তে স্ট্রিমিং ফ্যাশনে তাদের ব্যবহার করুন GetSqlCharsবা GetSqlBinaryপুনরুদ্ধার করুন। এগুলি FILESTREAM ডেটা হিসাবে সংরক্ষণের কথা বিবেচনা করুন - কোনও টেবিলের ডেটা পৃষ্ঠায় 1.5MB ডেটা সংরক্ষণ করার কোনও কারণ নেই
Panagiotis Kanavos

8
পছন্দ করুন ওপি সিঙ্ক লিখেছে: 20-30 এমএস এবং অ্যাসিঙ্ক সব কিছুর সাথে একই 1800 এমএস। প্যাকেটের আকার পরিবর্তন করার প্রভাব সম্পূর্ণ পরিষ্কার এবং প্রত্যাশিত।
ফিল্ডার

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

উত্তর:


141

উল্লেখযোগ্য লোড ছাড়াই এমন সিস্টেমে একটি অ্যাসিঙ্ক কলটির কিছুটা বড় ওভারহেড থাকে। আই / ও অপারেশন নিজেই নির্বিশেষে অবিচ্ছিন্ন হলেও থ্রেড-পুল টাস্ক স্যুইচিংয়ের চেয়ে ব্লক করা দ্রুত হতে পারে।

ওভারহেড কত? আসুন আপনার সময় সংখ্যা দেখুন। একটি ব্লকিং কলের জন্য 30ms, অ্যাসিঙ্ক্রোনাস কলের জন্য 450ms 32 কিবি প্যাকেটের আকারের অর্থ আপনার পঞ্চাশটি স্বতন্ত্র আই / ও অপারেশন প্রয়োজন। এর অর্থ হ'ল প্রতিটি প্যাকেটে আমাদের মোটামুটি 8 মিমি ওভারহেড রয়েছে যা বিভিন্ন প্যাকেটের আকারের সাথে আপনার পরিমাপের সাথে বেশ ভালভাবে মেলানো হয়। এটি অ্যাসিঙ্ক্রোনাস হওয়া থেকে ওভারহেডের মতো শোনা যায় না, যদিও অ্যাসিঙ্ক্রোনাস সংস্করণগুলিকে সিঙ্ক্রোনাসের চেয়ে অনেক বেশি কাজ করা প্রয়োজন। মনে হচ্ছে সিঙ্ক্রোনাস সংস্করণটি (সরলীকৃত) 1 অনুরোধ -> 50 টি প্রতিক্রিয়া, যখন অ্যাসিঙ্ক্রোনাস সংস্করণটি 1 অনুরোধ -> 1 প্রতিক্রিয়া -> 1 অনুরোধ -> 1 প্রতিক্রিয়া -> ... হিসাবে শেষ হচ্ছে এবং বার বার ব্যয় বহন করছে আবার।

আরও গভীরে যাচ্ছি। ExecuteReaderঠিক পাশাপাশি কাজ করে ExecuteReaderAsync। পরবর্তী ক্রিয়াকলাপটি একটি এর Readপরে GetFieldValue- এবং একটি আকর্ষণীয় জিনিস সেখানে ঘটে। দুজনের কোনওটি যদি অ্যাসিঙ্ক হয় তবে পুরো অপারেশনটি ধীর হয়। সুতরাং আপনি যখন সত্যিই অ্যাসিক্রোনাস জিনিসগুলি তৈরি করা শুরু করলেন তখন অবশ্যই খুব আলাদা কিছু ঘটবে - একটি Readদ্রুত হবে, এবং তারপরে অ্যাসিঙ্কটি GetFieldValueAsyncধীর হবে, বা আপনি ধীরের সাথে শুরু করতে পারেন ReadAsync, এবং তারপরে উভয়ই দ্রুত GetFieldValueএবং GetFieldValueAsyncদ্রুত। স্ট্রিম থেকে প্রথম অ্যাসিঙ্ক্রোনাস পঠিত ধীর এবং ধীরগতি পুরো সারিটির আকারের উপর নির্ভর করে। যদি আমি একই আকারের আরও সারি যুক্ত করি তবে প্রতিটি সারিটি পড়তে একই পরিমাণে সময় লাগে যেমন আমার কেবল একটি সারি থাকে তাই এটি স্পষ্টতই ডেটা হয়এখনও সারি সারি সারি সারি করা হচ্ছে - মনে হয় আপনি একবারে কোনও অ্যাসিনক্রোনাস পড়া শুরু করার সাথে সাথে পুরো সারিটি একবারে পড়তে পছন্দ করেন। আমি যদি প্রথম সারিটি অবিচ্ছিন্নভাবে পড়ি, এবং দ্বিতীয়টি সমকালীনভাবে - দ্বিতীয় সারিটি আবার পড়া দ্রুত হবে।

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

পুরো জিনিসটি সঠিকভাবে করার সময় আমার সেরা পারফরম্যান্সটি ছিল। এর অর্থ হ'ল ব্যবহার করা CommandBehavior.SequentialAccess, পাশাপাশি ডেটা স্পষ্টভাবে স্ট্রিমিং করা:

using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.SequentialAccess))
{
  while (await reader.ReadAsync())
  {
    var data = await reader.GetTextReader(0).ReadToEndAsync();
  }
}

এটির সাহায্যে সিঙ্ক এবং অ্যাসিঙ্কের মধ্যে পার্থক্যটি পরিমাপ করা শক্ত হয়ে যায় এবং প্যাকেটের আকার পরিবর্তন করা আর আগের মতো হাস্যকর ওভারহেডকে প্ররোচিত করে না।

আপনি যদি প্রান্তের ক্ষেত্রে ভাল পারফরম্যান্স চান, তবে উপলভ্য সেরা সরঞ্জামগুলি ব্যবহার করতে ভুলবেন না - এই ক্ষেত্রে, ExecuteScalarবা এর মতো সহায়কগুলির উপর নির্ভর না করে বড় কলামের ডেটা প্রবাহিত করুন GetFieldValue


4
দুর্দান্ত উত্তর। ওপি'র দৃশ্যের পুনরুত্পাদন করেছে। এই 1.5 মিটিংয়ের ওপির জন্য উল্লেখ করা হচ্ছে, আমি সিঙ্ক সংস্করণ বনাম 2200ms অ্যাসিঙ্কের জন্য 130ms পাই ms আপনার পদ্ধতির সাথে, 1.5 মি স্ট্রিংয়ের জন্য পরিমাপ করা সময়টি 60 মিমি, খারাপ নয়।
উইক্টর জাইখলা

4
সেখানে ভাল তদন্ত, এছাড়াও আমি আমাদের ডাল কোডের জন্য কয়েকটি মুঠো টিউনিং কৌশল শিখেছি।
অ্যাডাম হোল্ডসওয়ার্থ

সবেমাত্র অফিসে ফিরে গিয়ে এক্সিকিউটিসকলারএসেঙ্কের পরিবর্তে আমার উদাহরণে কোডটি চেষ্টা করে দেখতে পেলাম, তবুও আমি 512 বাইট প্যাকেটের আকারের সাথে 30 সেকেন্ডের এক্সিকিউশন সময় পেয়েছি :(
এইচডিডি

6
আহা, এটা সব পরে কাজ করেনি :) কিন্তু আমি এই লাইনে CommandBehavior.SequentialAccess যোগ আছে: using (var reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess))
hcd

@ এইচসিডি আমার খারাপ, আমি এটি টেক্সটে রেখেছিলাম কিন্তু নমুনা কোডে নেই :)
লুয়ান
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.