عنوان:

‫پشتیبانی از فشرده‌سازی Zstandard (Zstd) در NET 11.


نویسنده: وحید نصیری
تاریخ: ۱۴۰۵/۰۳/۱۳ ۰۸:۲۰
آدرس: www.dntips.ir
بهینه‌سازی حجم داده‌های انتقالی و ذخیره‌شده، یکی از کلیدی‌ترین ارکان افزایش کارایی (Performance) و کاهش هزینه‌های زیرساخت است. فریم‌ورک دات‌نت، سال‌هاست که با ارائه استریم‌های فشرده‌سازی متعددی نظیر DeflateStream، GZipStream، ZLibStream و در نسخه‌های اخیر BrotliStream، ابزارهای قدرتمندی را در اختیار توسعه‌دهندگان قرار داده است. با این حال، نیاز به الگوریتمی که بتواند تعادلی بی‌نقص میان سرعت فوق‌العاده بالا و نسبت فشرده‌سازی ایده‌آل برقرار کند، همواره در پروژه‌های مقیاس‌بزرگ احساس می‌شد.
در نسخه جدید .NET 11، عضو جدیدی به نام ZstandardStream به این مجموعه اضافه شده است که به ما اجازه می‌دهد از پتانسیل عظیم الگوریتم Zstandard (یا به اختصار Zstd) به صورت بومی (Native) بدون نیاز به کتابخانه‌های شخص ثالث استفاده کنیم. در این مقاله، به بررسی ساختار این الگوریتم، قابلیت‌ها، نحوه پیاده‌سازی و ابزارهای جدید آن در دات‌نت ۱۱ خواهیم پرداخت.


الگوریتم Zstandard چیست؟

الگوریتم Zstandard یک مکانیزم فشرده‌سازی داده‌ی منبع‌باز (Open-source) و بدون اتلاف (Lossless) است که در سال ۲۰۱۵ توسط فیس‌بوک (متا) توسعه یافته و معرفی شد. هدف اصلی از طراحی Zstd، جایگزینی الگوریتم قدیمی اما محبوب DEFLATE (پایه و اساس ابزارهای ZIP و gzip توسعه یافته در سال ۱۹۹۱) بود.
مزیت رقابتی Zstd در این است که نسبت فشرده‌سازی (Compression Ratio) مشابه یا حتی بهتری نسبت به DEFLATE ارائه می‌دهد، اما در فرآیند باز کردن فشرده‌سازی (Decompression) سرعت به‌شدت بالاتری را به نمایش می‌گذارد. این الگوریتم به‌شدت انعطاف‌پذیر بوده و بازه گسترده‌ای از سطوح فشرده‌سازی را از 7- (سریع‌ترین حالت با کمترین نرخ فشرده‌سازی) تا 22 (کندترین حالت اما با بیشترین میزان فشرده‌سازی) پشتیبانی می‌کند. حالت پیش‌فرض این الگوریتم روی سطح 3 تنظیم شده است که توازن بهینه‌ای برای اکثر سناریوهای عمومی فراهم می‌سازد.
جایگاه صنعتی Zstandard: کارایی بالای Zstd باعث شده است که در پروژه‌های بزرگ جهانی به عنوان استاندارد فشرده‌سازی انتخاب شود؛ از جمله سیستم‌فایل‌های لینوکس (btrfs و SquashFS)، مدیریت بسته‌های توزیع‌های آرچ‌لینوکس و فدورا، پلتفرم ابری AWS Redshift، سیستم Core Dump در FreeBSD و حتی آرشیو بازی‌های کنسول نینتندو سوییچ.

رابط‌های برنامه‌نویسی جدید (API) در دات‌نت ۱۱
طراحی مکانیزم جدید Zstd در .NET 11 کاملاً از الگوی پیاده‌سازی الگوریتم Brotli پیروی می‌کند. بنابراین، اگر قبلاً با خانواده Brotli* کار کرده باشید، استفاده از ابزارهای جدید برای شما بسیار آشنا و ساده خواهد بود. فضای نام System.IO.Compression میزبان چهار تایپ (Type) اصلی جدید است:
  • ZstandardStream: یک کلاس بسته‌بندی استریم (Stream Wrapper) جهت مدیریت جریان‌های داده‌ای.
  • ZstandardEncoder / ZstandardDecoder: ساختارهایی مبتنی بر Struct، بهینه برای مدیریت حافظه و سازگار با Span.
  • ZstandardDictionary: قابلیتی منحصربه‌فرد جهت فشرده‌سازی داده‌های کوچک و مکرر با بازدهی بالا.
  • ZstandardCompressionOptions: کلاسی برای تنظیم دقیق کیفیت، اندازه پنجره (Window Size) و ویژگی‌های اعتبارسنجی.

۱. استفاده از API به صورت استریم (Streaming)
این روش برای داده‌های حجیم که به صورت جریان داده (مانند دانلود فایل یا سوکت‌های شبکه) خوانده می‌شوند، ایده‌آل است:
// Compress (فشرده‌سازی)
using var output = new MemoryStream();
using (var zstd = new ZstandardStream(output, CompressionMode.Compress))
{
    await inputStream.CopyToAsync(zstd);
}

// Decompress (باز کردن فشرده‌سازی)
using var zstdDecompress = new ZstandardStream(compressedStream, CompressionMode.Decompress);
await zstdDecompress.CopyToAsync(outputStream);

۲. استفاده از API تک‌مرحله‌ای (One-shot Span-based API)
اگر تمام داده‌ها از قبل در حافظه بارگذاری شده‌اند و نیازی به استریم پویا ندارید، استفاده از متدهای ساختارهای ZstandardEncoder و ZstandardDecoder به دلیل عدم تخصیص حافظه اضافی در بخش Heap و هماهنگی با Span، بازدهی به مراتب بالاتری دارد:
byte[] source = File.ReadAllBytes("data.bin");

// Compress
int maxLength = ZstandardEncoder.GetMaxCompressedLength(source.Length);
var compressed = new byte[maxLength];
ZstandardEncoder.TryCompress(source, compressed, out int bytesWritten);

// Decompress
int decompressedSize = ZstandardDecoder.GetMaxDecompressedLength(compressed.AsSpan(0, bytesWritten));
var decompressed = new byte[decompressedSize];
ZstandardDecoder.TryDecompress(compressed.AsSpan(0, bytesWritten), decompressed, out int bytesRead);

۳. تنظیم سطوح کیفیت فشرده‌سازی (Compression Levels)
با استفاده از کلاس تنظیمی ZstandardCompressionOptions می‌توان کیفیت فشرده‌سازی را به صورت عددی سفارشی‌سازی کرد. در دات‌نت، گزینه عمومی CompressionLevel.Fastest به کمترین مقدار عددی و CompressionLevel.SmallestSize به سقف آن یعنی ۲۲ نگاشت می‌شود:
var options = new ZstandardCompressionOptions
{
    Quality = 6 // بازه عددی بین 1 تا 22، مقدار پیش‌فرض 3 است
};

using var zstd = new ZstandardStream(output, options);

یکپارچه‌سازی با ASP.NET Core برای فشرده‌سازی پاسخ‌ها
یکی از کاربردی‌ترین مصارف این الگوریتم، فشرده‌سازی خودکار پاسخ‌های HTTP در وب‌سرویس‌ها و APIها (Response Compression) است. با اضافه کردن الگوریتم Zstd به خط لوله (Pipeline) برنامه در فایل Program.cs، دات‌نت به طور خودکار هدر درخواست‌های حاوی Accept-Encoding: zstd را شناسایی کرده و پاسخ را فشرده می‌کند:
// Program.cs
builder.Services.AddResponseCompression(x =>
{
    x.EnableForHttps = true;
    x.Providers.Add<ZstandardCompressionProvider>();
});
همچنین، برای پردازش و باز کردن درخواست‌های فشرده ورودی کلاینت به سمت سرور، رابط IDecompressionProvider به همین سادگی مکانیزم معکوس را پیاده‌سازی و مدیریت می‌کند.

فشرده‌سازی پیشرفته مبتنی بر دیکشنری (Dictionary Compression)
بزرگترین تمایز Zstd با سایر الگوریتم‌های سنتی، قابلیت Dictionary Compression است. زمانی که شما تعداد زیادی داده کوچک و مشابه (مانند پاسخ‌های JSON یک API، پیام‌های سریالایز شده در کلافکا، یا تنظیمات مکرر) دارید، الگوهای ساختاری تکراری (مثل نام کلیدها در کدهای JSON) در تک‌تک این پیام‌ها حجم بیهوده‌ای اشغال می‌کنند. در روش‌های معمولی، الگوریتم به دلیل کوچک بودن حجم داده، فرصتی برای پیدا کردن و بهینه‌سازی این الگوها ندارد. Zstd با آموزش یک دیکشنری مشترک، این مشکل را حل می‌کند.
برای استفاده از این قابلیت، ابتدا باید الگوریتم را با داده‌های نمونه واقعی آموزش داد، سپس دیکشنری تولید شده را در محیطی که هم تولیدکننده (Producer) و هم مصرف‌کننده (Consumer) به آن دسترسی دارند، ذخیره کرد:
// آموزش دیکشنری از روی نمونه‌ها
byte[] allSamples = ...; // الحاق تمام داده‌های نمونه به هم
int[] lengths = ...;     // طول تک‌تک نمونه‌ها

using ZstandardDictionary dict = ZstandardDictionary.TrainFromSamples(allSamples, lengths, maxDictionarySize: 100_000);

// فشرده‌سازی با کمک دیکشنری - هر دو سمت باید از یک دیکشنری یکسان استفاده کنند
using var encoder = new ZstandardEncoder(dict);
encoder.Compress(payload, compressed, out int consumed, out int written, isFinalBlock: true);
نکته: طبق توصیه‌های استاندارد، حجم بهینه برای دیکشنری حداکثر ۱۰۰ کیلوبایت است و برای آموزش بهینه آن بهتر است حجم داده‌های نمونه حدود ۱۰۰ برابر حجم نهایی دیکشنری (حدود ۱۰ مگابایت داده نمونه واقعی و متناوب) باشد تا بالاترین نرخ بازدهی حاصل شود.

نتیجه‌گیری
اضافه شدن بومی Zstandard به فریم‌ورک .NET 11 نقطه عطفی در مدیریت کارآمد منابع، کاهش هزینه‌های پهنای باند و افزایش سرعت سیستم‌ها به شمار می‌رود. Zstd با ارائه سرعت خارق‌العاده در باز کردن داده‌ها (Decompression) و انعطاف‌پذیری بالا در تعیین سطوح فشرده‌سازی، در کنار قابلیت انقلابی فشرده‌سازی مبتنی بر دیکشنری، گزینه‌ای بی‌نقص برای سیستم‌های مبتنی بر میکروسرویس، پلتفرم‌های ابری و وب‌سایت‌های پرترافیک است. استفاده از این ابزار جدید گامی مهم در جهت ارتقای استانداردهای کارایی (Performance) در اکوسیستم دات‌نت خواهد بود.

نظرات

  • وحید نصیری در ۱۴۰۵/۰۴/۲۳ ۰۹:۲۲
    راهنمای تکمیلی و کاربردی فشرده‌سازی Zstandard در NET 11.: از مقایسه عملی تا وب‌سرویس‌ها و کلاینت‌ها

    در مطلب فوق، به معرفی بومی الگوریتم فشرده‌سازی Zstandard (Zstd) در دات‌نت ۱۱ پرداختیم و ساختارهای اصلی آن را بررسی کردیم. در این مطلب تکمیلی، قصد داریم سناریوهای واقعی‌تر و پیشرفته‌تری از کاربرد این الگوریتم را پوشش دهیم. ابتدا کارایی آن را با اجرای یک بنچمارک روی یک فایل متنی بزرگ می‌سنجیم، سپس نحوه مدیریت آن را در سمت کلاینت با HttpClient و در نهایت پیکربندی پیشرفته و انتخابی آن را در سرور وب Kestrel بررسی می‌کنیم.

    ۱. مقایسه عملی کارایی فشرده‌سازی بر روی داده‌های واقعی
    فشرده‌سازی داده‌ها همواره نیازمند ایجاد تعادل و موازنه (Trade-off) میان سه فاکتور اساسی است: حجم فایل نهایی، زمان صرف‌شده برای فشرده‌سازی و میزان مصرف پردازنده (CPU).
    برای درک رفتار Zstd در مقایسه با الگوریتم‌های سنتی دات‌نت (Brotli ،GZip و Deflate)، می‌توانیم یک فایل متنی بزرگ (مانند رمان معروف «جنگ و صلح» اثر لئو تولستوی) را به عنوان ورودی در نظر بگیریم. ابتدا فایل متنی را در تنظیمات پروژه (.csproj) کپی می‌کنیم:
    <ItemGroup>
      <None Include="war-and-peace.txt">
          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      </None>
    </ItemGroup>
    سپس با استفاده از کدهای استریمِ زیر، نسخه فشرده‌شده فایل را با هر چهار الگوریتم تولید می‌کنیم:
    using System.IO.Compression;
    using System.Reflection;
    
    const string fileName = "war-and-peace.txt";
    var targetPath = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory!.FullName;
    
    // 1. فشرده‌سازی با ZStandard
    await using (var inputStream = File.OpenRead(fileName))
    await using (var outputStream = File.Create(Path.Combine(targetPath, "ZStandard.compressed")))
    await using (var compressStream = new ZstandardStream(outputStream, CompressionMode.Compress))
    {
        await inputStream.CopyToAsync(compressStream);
    }
    
    // 2. فشرده‌سازی با Brotli
    await using (var inputStream = File.OpenRead(fileName))
    await using (var outputStream = File.Create(Path.Combine(targetPath, "Brotli.compressed")))
    await using (var compressStream = new BrotliStream(outputStream, CompressionMode.Compress))
    {
        await inputStream.CopyToAsync(compressStream);
    }
    
    // 3. فشرده‌سازی با Gzip
    await using (var inputStream = File.OpenRead(fileName))
    await using (var outputStream = File.Create(Path.Combine(targetPath, "Gzip.compressed")))
    await using (var compressStream = new GZipStream(outputStream, CompressionMode.Compress))
    {
        await inputStream.CopyToAsync(compressStream);
    }
    
    // 4. فشرده‌سازی با Deflate
    await using (var inputStream = File.OpenRead(fileName))
    await using (var outputStream = File.Create(Path.Combine(targetPath, "Deflate.compressed")))
    await using (var compressStream = new DeflateStream(outputStream, CompressionMode.Compress))
    {
        await inputStream.CopyToAsync(compressStream);
    }
    تحلیل خروجی آزمایش
    پس از اجرای کد بالا، مقایسه اندازه فایل‌های تولید شده در سطح پیش‌فرض فشرده‌سازی (به ترتیب کاهش حجم فایل از بزرگ‌ترین به کوچک‌ترین) نتایج زیر را نشان می‌دهد:
    • ZStandard (بزرگ‌ترین حجم خروجی در سطح پیش‌فرض)
    • Brotli
    • Deflate
    • GZip (کوچک‌ترین حجم خروجی در سطح پیش‌فرض)

    نکته کلیدی: اگرچه Zstd در سطح تنظیمات پیش‌فرض (Quality = 3) ممکن است حجم خروجی بزرگ‌تری نسبت به Brotli یا GZip ارائه دهد، اما برگ برنده آن سرعت بسیار بالاتر در عملیات فشرده‌سازی و بخصوص باز کردن فشرده‌سازی (Decompression) همراه با مصرف بهینه CPU است. برای دستیابی به حجم کمتر در Zstd، می‌توان به سادگی پارامتر Quality را روی مقادیر بالاتر (تا ۲۲) تنظیم کرد.

    ۲. استفاده از فشرده‌سازی Zstandard در HttpClient
    در زمان برقراری ارتباط با وب‌سرویس‌های خارجی، فشرده‌سازی پاسخ‌ها به شدت به کاهش ترافیک مصرفی شبکه کمک می‌کند. دات‌نت ۱۱ فرآیند باز کردن فشرده‌سازی (Decompression) در سمت کلاینت را بسیار تسهیل کرده است.
    شما می‌توانید با فعال کردن ویژگی AutomaticDecompression در HttpClientHandler به راحتی داده‌های فشرده‌سازی شده با Zstd را به صورت خودکار از حالت فشرده خارج کنید.
    var handler = new HttpClientHandler
    {
        AutomaticDecompression = DecompressionMethods.Zstandard
    };
    
    var client = new HttpClient(handler);
    // سرور فیس‌بوک به طور پیش‌فرض از Zstandard پشتیبانی می‌کند
    var response = await client.GetStringAsync("https://facebook.com");
    
    Console.WriteLine($"Length of decompressed content: {response.Length}");

    راهکار منعطف (Fallback Setup)
    از آنجا که ممکن است برخی از سرورها از پروتکل Zstandard پشتیبانی نکنند، همواره توصیه می‌شود یک الگوریتم پشتیبان (مانند GZip) نیز تعریف کنید. در این حالت کلاینت هر دو گزینه را در هدر درخواست خود اعلام می‌کند و سرور بر اساس توانایی خود یکی را برمی‌گزیند:
    var handler = new HttpClientHandler
    {
        // استفاده از عملگر بیتی OR برای تعریف چند الگوریتم به طور همزمان
        AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Zstandard
    };

    ۳. پیکربندی Kestrel در ASP.NET Core: فعال‌سازی پیش‌فرض و هوشمند
    یکی از تغییرات هیجان‌انگیز در ASP.NET Core با دات‌نت ۱۱ این است که برای استفاده از Zstandard نیازی به ثبت دستی ارائه‌دهنده‌ها (Providers) ندارید. دات‌نت ۱۱ این الگوریتم را به عنوان یک استاندارد تراز اول (First-class citizen) می‌شناسد و با فعال کردن میان‌افزار (Middleware) فشرده‌سازی، Zstd به همراه Brotli و GZip به طور خودکار فعال می‌شود:
    var builder = WebApplication.CreateBuilder(args);
    
    // ثبت سرویس‌های فشرده‌سازی پاسخ (به صورت خودکار Zstandard فعال می‌شود)
    builder.Services.AddResponseCompression();
    
    var app = builder.Build();
    
    // فعال‌سازی میان‌افزار در خط لوله پردازش درخواست‌ها
    app.UseResponseCompression();
    
    app.MapGet("/Hello", () => "The quick brown fox jumped over the lazy dog");
    
    app.Run();

    نحوه تعامل کلاینت و سرور (Content Negotiation)
    برای فعال شدن فرآیند فشرده‌سازی، کلاینت باید صراحتاً تمایل خود را با ارسال هدر Accept-Encoding نشان دهد:
    • درخواست معمولی (بدون فشرده‌سازی): اگر درخواستی بدون هدر ارسال شود، هدر پاسخ فاقد Content-Encoding خواهد بود و داده‌ها خام انتقال می‌یابند.
    • درخواست با پشتیبانی از Zstandard: کلاینت هدر زیر را ارسال می‌کند: Accept-Encoding: zstd
    • در این حالت، سرور داده‌ها را با فرمت zstd فشرده کرده و هدر پاسخ زیر را اضافه می‌کند: Content-Encoding: zstd
    • ارسال مقادیر ناشناخته: اگر کلاینت مقداری تعریف‌نشده (مثلاً Accept-Encoding: potato) ارسال کند، سرور درخواست را نادیده گرفته و پاسخ را بدون فشرده‌سازی ارسال می‌کند.

    ۴. مدیریت و غیرفعال‌سازی انتخابی الگوریتم‌ها در Kestrel
    با وجود مزایای بی‌شمار Zstd، گاهی به دلایل سازگاری با سیستم‌های قدیمی‌تر یا محدودیت‌های سخت‌افزاری خاص، تمایل دارید که این الگوریتم را غیرفعال کرده و تنها از گزینه‌هایی مانند Brotli یا Gzip استفاده کنید.
    برای این منظور، می‌توانید تنظیمات ارائه‌دهندگان را شخصی‌سازی کنید. با این کار ارائه‌دهنده Zstandard به طور خودکار حذف می‌شود:
    using Microsoft.AspNetCore.ResponseCompression;
    
    var builder = WebApplication.CreateBuilder(args);
    
    builder.Services.AddResponseCompression(options =>
    {
        // 1. پاکسازی تمام ارائه‌دهندگان پیش‌فرض (شامل Zstandard، Brotli و GZip)
        options.Providers.Clear();
    
        // 2. افزودن مجدد صرفاً ارائه‌دهنده‌های مدنظر
        options.Providers.Add<BrotliCompressionProvider>();
        options.Providers.Add<GzipCompressionProvider>();
    });
    
    var app = builder.Build();
    app.UseResponseCompression();
    
    app.MapGet("/Hello", () => "The quick brown fox jumped over the lazy dog");
    
    app.Run();
    با پیاده‌سازی قطعه کد فوق، حتی اگر کلاینت هدر Accept-Encoding: zstd را ارسال کند، سرور به دلیل عدم برخورداری از Provider مربوطه، پاسخ را فشرده نکرده یا در صورت تعریف گزینه‌های جایگزین در هدر کلاینت، از Brotli یا GZip استفاده خواهد کرد.

    جمع‌بندی مطالب تکمیلی
    پشتیبانی بومی از Zstandard در دات‌نت ۱۱ فراتر از یک کلاس ساده برای فشرده‌سازی فایل است؛ این به‌روزرسانی با یکپارچگی عمیق در وب‌سرویس‌های ASP.NET Core و کلاینت پیش‌فرض HttpClient همراه شده است. امکان سوئیچ هوشمند بین الگوریتم‌ها و تعریف Fallback در کلاینت در کنار پیکربندی بدون دردسر در سمت سرور، نشان‌دهنده بلوغ فوق‌العاده این ابزار جدید و آماده بودن آن برای بهینه‌سازی پروژه‌های مقیاس‌بزرگ تجاری است.
  • وحید نصیری در ۱۴۰۵/۰۵/۲۴ ۱۳:۲۸
    فشرده‌سازی خودکار بدنه درخواست‌های HTTP در دات‌نت 11

    در نسخه جدید System.Net.Http، سه رَپر (HttpContent Wrapper) اختصاصی برای فشرده‌سازی بلادرنگ داده‌های ارسالی به کلاینت اضافه شده است:
    • GZipCompressedContent
    • BrotliCompressedContent
    • ZstandardCompressedContent

    سازوکار فنی و نحوه عملکرد
    - مدیریت خودکار هدرها: این کلاس‌ها مقدار Content-Encoding مناسب (مانند gzip، br یا zstd) را به‌صورت خودکار تنظیم کرده و مقدار Content-Length را حذف می‌کنند تا فشرده‌سازی به‌صورت جریانی (Streaming) انجام شود.
    - سازنده‌ها (Constructors): هر رَپر دو روش برای مقداردهی اولیه دارد:
    • استفاده از CompressionLevel برای تنظیم ساده بین سرعت و نسبت فشرده‌سازی.
    • استفاده از کلاس‌های تنظیمات پیشرفته (ZLibCompressionOptions، BrotliCompressionOptions و ZstandardCompressionOptions) برای کنترل دقیق‌تر پارامترها.
    - پایبندی به استانداردهای Zstandard: در پیاده‌سازی ZstandardCompressedContent، الزامات استاندارد RFC 9659 برای تعیین و محدودسازی اندازه پنجره حافظه (Window-size) به‌صورت خودکار رعایت می‌شود.

    نکات مهم پیاده‌سازی
    • ماهیت اختیاری (Opt-in): کلاینت HttpClient به هیچ عنوان با سرور درباره فشرده‌سازی بدنه درخواست مذاکره (Content Negotiation) نمی‌کند. بنابراین فقط در صورتی باید از این قابلیت استفاده کرد که از پشتیبانی سرور مقصد از انکودینگ مدنظر اطمینان داشته باشید.
    • ارسال به صورت استریم: بایت‌های فشرده‌شده مستقیماً حین ارسال بر روی بستر شبکه سریالایز و استریم می‌شوند که از مصرف حافظه اضافه (Buffering) جلوگیری می‌کند.

    using System.IO.Compression;
    using System.Net.Http;
    
    using var client = new HttpClient();
    
    var payload = new StringContent("""{"greeting":"hello"}""");
    using var request = new HttpRequestMessage(HttpMethod.Post, "https://example.com/api")
    {
        Content = new ZstandardCompressedContent(payload, CompressionLevel.Optimal),
    };
    
    using HttpResponseMessage response = await client.SendAsync(request);