Ducky Coder
Published on

เพิ่มประสิทธิภาพเว็บแบบง่ายๆด้วย HybridCache ใน .NET9

 
เพิ่มประสิทธิภาพเว็บแบบง่ายๆด้วย HybridCache ใน .NET9

การทำ caching ใน .NET มีอยู่ 2 แบบคือ In-Memory cache กับ Distributed caching เป็นการใช้ฐานข้อมูลเช่น Redis เพื่อเก็บ cache ทำให้เราต้องเขียนโค้ดแยกกันสำหรับแต่ละแบบ

ใน .NET 9 ได้เพิ่ม HybridCache มาแก้ปัญหานี้โดยการใช้โค้ดชุดเดียวแต่ทำงานได้ทั้งสองแบบ

ลองสร้าง API ที่ใช้เวลาในการคืนข้อมูลประมาณ 1-5 วินาที

app.MapGet(
    "/posts",
    async () =>
    {
        List<Post> posts =
        [
            new()
            {
                Id = 1,
                Title = "Post 1",
                Content = "Content 1",
            },
            new()
            {
                Id = 2,
                Title = "Post 2",
                Content = "Content 2",
            },
            new()
            {
                Id = 3,
                Title = "Post 3",
                Content = "Content 3",
            },
        ];

        Random random = new();
        int delay = random.Next(1000, 5000);
        await Task.Delay(delay);

        return Results.Ok(posts);
    }
);

มาเริ่มใช้ HybridCache โดยการติดตั้ง Nuget Microsoft.Extensions.Caching.Hybrid แล้วเพิ่ม

builder.Services.AddHybridCache();

แค่นี้เราก็พร้อมใช้ cache แบบ In-Memory แล้ว

มาเพิ่ม API อีกตัวเพื่อเรียก API ที่ไว้ก่อนหน้านี้

app.MapGet(
        // argument แรกเป็นชื่อของ API
        // ตั้งชื่อ posts ใน path รับ id ที่เป็น int
        "/posts/{id:int}",
        // argument ที่สองเป็น function เพื่อคืนค่า
        // มี parameter int id ที่รับมาจาก path แล้วก็ inject HybridCache เข้ามา
        async (int id, HybridCache cache) =>
        {
            // ใช้คำสั่ง GetOrCreateAsync ของ HybridCache เพื่อดึงหรือสร้าง cache
            List<Post>? posts = await cache.GetOrCreateAsync(
                // argument แรกเป็น key ของ cache
                "hybrid:posts",
                // arguemnt ที่สองเป็น function factory ในกรณีที่ถ้ายังไม่มีข้อมูลใน cache function จะถูกเรียกแล้วเก็บข้อมูลที่ได้ไว้ใน cache ต้องใส่ parameter CancellationToken ด้วย ให้ชื่อ cancel
                async (cancel) =>
                {
                    // แล้วก็เรียกใช้ API ที่เตรียมไว้โดยใช้ HttpClient
                    HttpClient client = new();
                    HttpResponseMessage response = await client.GetAsync(
                        "http://localhost:5228/posts",
                        cancel
                    );
                    // ถ้ามี error ก็ให้คืนเป็น array เปล่า
                    if (!response.IsSuccessStatusCode)
                    {
                        return [];
                    }
                    // ไม่มี error คืนค่าที่ได้มาจาก API
                    return await response.Content.ReadFromJsonAsync<List<Post>>(cancel);
                    // จากตรงนี้ข้อมูลจะถูกเก็บลง cache อัตโนมัติโดยที่เราไม่ต้องทำอะไรเพิ่ม
                }
            );
            // ใช้ id จาก path เพื่อหาข้อมูลจาก list
            Post? post = posts?.FirstOrDefault(x => x.Id == id);
            // ไม่เจอก็ NotFound
            if (post == null)
            {
                return Results.NotFound();
            }
            // เจอก็ Ok พร้อมข้อมูล post
            return Results.Ok(post);
        }
    );

เตรียม http request แล้วลองรันดู

@HyBridCacheDemo_HostAddress = http://localhost:8080

GET {{HyBridCacheDemo_HostAddress}}/posts
Request /posts

แน่นอนว่าใช้เวลาไม่ต่ำกว่า 1-5 วินาที แล้วลองเรียก API ที่มีการเก็บ cache

GET {{HyBridCacheDemo_HostAddress}}/posts/1

เรียกครั้งแรกจะให้เวลาไม่ต่ำกว่า 1-5 วินาทีเหมือนกัน

Request /posts/1 ที่ไมีมี cache

ลองเรียกอีกครั้งจะใช้เวลาแค่เสี้ยววินาทีเท่านั้น

Request /posts/1 ที่ไมีมี cache

ตอนนี้ cache ถูกเก็บไว้ใน memory เมื่อ restart แล้วเรียก GET /posts/1 อีกทีก็จะใช้เวลา 1-5 วินาทีเพื่อเก็บข้อมูลเข้า cache ใหม่

คราวนี้มาทำให้ข้อมูล cache ถูกเก็บไว้ในฐานข้อมูลบ้าง โดยใช้ Redis เป็นฐานข้อมูล

ติดตั้ง Nuget Microsoft.Extensions.Caching.StackExchangeRedis แล้วเพิ่ม code

// เรียก AddStackExchangeRedisCache เพื่อเชื่อมต่อ Redis
builder.Services.AddStackExchangeRedisCache(options =>
{
    // ใช้ Redis connection ที่อยู่ใน appSettings
    options.Configuration = builder
	    .Configuration.GetConnectionString("RedisConnection");
});

มาดูวีธีเรียกใช้ Distributed caching เพื่อเก็บ cache ไว้ในฐานข้อมูลแบบเก่ากันก่อน

app.MapGet(
    "/dist-posts",
    async (IDistributedCache cache) =>
    {
        string key = $"dist:posts";
        // ทำการเช็คก่อนว่ามี cache เก็บอยู่รึป่าว
        string? json = await cache.GetStringAsync(key);
        List<Post>? posts = [];
        // ถ้าไม่มีก็ให้ดึงข้อมูลเพื่อทำ cache
        if (string.IsNullOrEmpty(json))
        {
            HttpClient client = new();
            HttpResponseMessage response = await client.GetAsync("http://localhost:5228/posts");
            if (response.IsSuccessStatusCode)
            {
                posts = await response.Content.ReadFromJsonAsync<List<Post>>();
                // ได้ข้อมูลเสร็จต้อง serialize แล้วเก็บลอง cache
                json = JsonSerializer.Serialize(posts);
                await cache.SetStringAsync(key, json);
            }
        }
        // ถ้ามีก็ deserialize เป็นค่าที่ต้องการ
        else
        {
            posts = JsonSerializer.Deserialize<List<Post>>(json) ?? [];
        }

        return posts;
    }
);

จะเห็นว่าการใช้ Distributed caching มีขั้นตอนที่เยอะกว่า HybridCache อยู่ เพราะต้อง Serialize และ Deserialize เอง

HybridCache เราสามารถใช้ GetOrCreateAsync โดยที่ไม่ได้เช็คอะไรเลย ลอง request /posts/1 อีกที

แล้วไปดูที่ Redis ก็จะมีข้อมูลที่เราส่งไปทำ cache

Request /posts/1 ที่ไมีมี cache

การทำงานของ HybridCache จะเช็คว่ามีการตั้งค่าใช้งาน Distributed caching อยู่รึป่าว ถ้ามีก็จะทำการเก็บ cache ไว้ใน memory ก่อนจากนั้นก็เก็บลงฐานข้อมูล

HybridCache ทำให้การ caching ใน .NET ง่ายขึ้นมาก ที่เด็ดกว่านั้นคือ แต่ไม่ใช่แค่ .NET 9 เท่านั้นที่สามารถใช้งานได้ .NET Framework ตั้งแต่ 4.7.2 ก็สามารถใช้งานความสามารถนี้ได้เช่นกัน