4 Mei 20247
Penggunaan caching merupakan salah satu cara untuk meningkatkan performa aplikasi kita. Caching sendiri adalah proses untuk menyimpan data dari aplikasi kita dengan tujuan agar data tersebut dapat ditampilkan atau diakses dengan mengurangi beban pada proses yang dibutuhkan ketika mengakses data. Efeknya, data yang dibutuhkan dapat diakses secara lebih cepat. Proses ini sangat efektif untuk data yang jarang berubah ataupun data yang ‘mahal’ prosesnya ketika diakses.
Saat ini, cache sudah sangat lazim digunakan. Cache dapat diterapkan dengan cara menyimpan cache pada memori (in-memory cache), menggunakan layanan caching seperti AWS Elasticache, atau dengan metode-metode lain.
Pada artikel ini, kita akan menggunakan cache dengan konsep in-memory cache untuk aplikasi ASP.NET Core.
Framework .NET sendiri sudah mempunyai IMemoryCache
yang bisa kita gunakan untuk membuat caching dengan penggunaan yang sangat mudah. Hal ini membantu
kita untuk mengoptimalkan performa aplikasi kita dengan lebih efisien.
Saat menggunakan caching, kawan-kawan harus mempertimbangkan poin-poin ini.
Selanjutnya, mari kita langsung implementasi caching. Contoh yang akan kita gunakan adalah default ASP.NET Core Web API project yaitu Weather Forecast.
dotnet new webapi -o in-memory-caching-weather-forecast
Lalu masuk ke direktori `in-memory-caching-weather-forecast’
cd in-memory-caching-weather-forecast
Selanjutnya, buka project ini melalui IDE favorit kawan-kawan. Buka file Program.cs
dan tambahkan informasi mengenai CreatedAt dengan tipe data Datetime pada record WeatherForecast
sehingga menjadi seperti ini.
...
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary, DateTime CreatedAt)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
Kemudian ubah API /weatherforecast
dengan menambahkan informasi waktu saat ini dengan DateTime.now
.
Kode ini akan menghasilkan data berupa ramalan cuaca secara acak sesuai informasi pada variable summaries
.
...
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)],
DateTime.Now // Penambahan informasi meengenai waktu
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();
...
Setelah itu, mari kita tambahkan IMemoryCache pada Program.cs dengan singleton seperti ini.
using Microsoft.Extensions.Caching.Memory; // menambahkan IMemoryCache
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSingleton<IMemoryCache, MemoryCache>(); // menambahkan singleton
...
Langkah selanjutnya, kita akan menggunakan TryGetValue
untuk cek apakah info ramalan cuaca sudah ada cache.
Apabila belum ada cache, maka akan dihasilkan data ramalan cuaca dan membuat cache dari data tersebut.
app.MapGet("/weatherforecast", (IMemoryCache memoryCache) =>
{
WeatherForecast[] forecast = []; // variable kosong
if (!memoryCache.TryGetValue("forecastData", out WeatherForecast[] cacheValue)) // cek cache
{
// generate data
cacheValue = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)],
DateTime.Now
))
.ToArray();
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromSeconds(15));
memoryCache.Set("forecastData", cacheValue, cacheEntryOptions);
}
forecast = cacheValue.ToArray();
return forecast;
}
)
.WithName("GetWeatherForecast")
.WithOpenApi();
Pada kode di atas, "forecastData"
menjadi cache key. SetSlidingExpiration
berfungsi untuk menghapus cache apabila tidak diakses dalam jangka waktu 15 detik. Apabila
dikases dalam janghka waktu 15 detik, maka cache akan diperpanjang selama 15 detik lagi.
Mari kita tes aplikasi ini. Pada root folder ‘in-memory-caching-weather-forecast’, jalankan ini.
dotnet run
Lalu buka aplikasi melalui browser. Kemudian teman-teman bisa mengunjungi rute /weatherforecast
seperti ini http://localhost:5246/weatherforecast
.
Maka akan muncul JSON seperti ini. Apabila teman-teman mengakses data ini dalam kurun waktu 15 detik,
maka data yang muncul akan selalu sama karena data ini tersimpan sebagai cache. Berikut contoh datanya.
[
{
"date": "2024-05-14",
"temperatureC": 37,
"summary": "Chilly",
"createdAt": "2024-05-13T00:01:24.222915+07:00",
"temperatureF": 98
},
{
"date": "2024-05-15",
"temperatureC": 23,
"summary": "Hot",
"createdAt": "2024-05-13T00:01:24.22302+07:00",
"temperatureF": 73
},
{
"date": "2024-05-16",
"temperatureC": 51,
"summary": "Balmy",
"createdAt": "2024-05-13T00:01:24.2230203+07:00",
"temperatureF": 123
},
{
"date": "2024-05-17",
"temperatureC": 32,
"summary": "Hot",
"createdAt": "2024-05-13T00:01:24.2230205+07:00",
"temperatureF": 89
},
{
"date": "2024-05-18",
"temperatureC": 40,
"summary": "Chilly",
"createdAt": "2024-05-13T00:01:24.2230215+07:00",
"temperatureF": 103
}
]
Apabila teman-teman mengakses kemabli rute /weatherforecast
setelah 15 detik, maka datanya akan berubah
karena dihasilkan data baru dari aplikasi kita. Contohnya seperti ini.
{
"date": "2024-05-14",
"temperatureC": 39,
"summary": "Hot",
"createdAt": "2024-05-13T00:02:35.0194849+07:00",
"temperatureF": 102
},
{
"date": "2024-05-15",
"temperatureC": 47,
"summary": "Chilly",
"createdAt": "2024-05-13T00:02:35.0194861+07:00",
"temperatureF": 116
},
{
"date": "2024-05-16",
"temperatureC": 36,
"summary": "Chilly",
"createdAt": "2024-05-13T00:02:35.0194911+07:00",
"temperatureF": 96
},
{
"date": "2024-05-17",
"temperatureC": 53,
"summary": "Chilly",
"createdAt": "2024-05-13T00:02:35.0194918+07:00",
"temperatureF": 127
},
{
"date": "2024-05-18",
"temperatureC": 32,
"summary": "Scorching",
"createdAt": "2024-05-13T00:02:35.0194923+07:00",
"temperatureF": 89
}
]
Jangka waktu kadaluarsa cache ini dapat kita ubah menggunakan method Set
tanpa harus
menggunakan MemoryCacheEntryOptions
seperti kode sebelumnya. Apabila menggunakan metode ini,
maka cache akan kadaluarsa dan terhapus sesuai durasi yang digunakan walaupun cache diakses berkali-kali.
Mari kita set agar cache terhapus setelah 15 detik.
memoryCache.Set("forecastData", cacheValue, TimeSpan.FromSeconds(15));
Bila teman-teman mengunjungi rute ‘/weatherforecast’ maka akan muncul data yang akan selalu berubah setiap 15 detik karena cache akan selalu kadaluarsa setiap 15 detik.
Selanjuntya, cache yang sudah disimpan didalam memori ini bisa kita akses menggunakan
cache key yaitu "forecastData"
. Kita bisa menggunakan Get
method untuk mengaksesnya.
Okay, mari kita buat rute lain yaitu /cacheforecast
untuk mengambil data dari cache.
app.MapGet("/cacheforecast", (IMemoryCache memoryCache) =>
{
var cacheEntry = memoryCache.Get<WeatherForecast[]?>("forecastData");
return cacheEntry!.ToList();
}
)
.WithName("GetCacheForecast")
.WithOpenApi();
Lalu teman-teman bisa membuka rute ‘/weatherforecast’ terlebih dahulu, lalu membuka rute ‘/cacheforecast’. Data yang ditampilkan akan sama karena keduanya mengambil data dari cache yang sama.
Kita juga bisa meenggunakan GetOrCreate
dan GetOrCreateAsync
untuk menambahkan fungsi apabila
cache belum ada ketika diakses, maka dapat dibuat cache dengan data yang dibutuhkan. Kita modifikasi
rute /cacheforecast
dengan menggunakan GetOrCreate
.
...
// mengambil data dari cache
app.MapGet("/cacheforecast", (IMemoryCache memoryCache) =>
{
// Mengakses Cache
// var cacheEntry = memoryCache.Get<WeatherForecast[]?>("forecastData");
// Mengakses Cache atau Membuat Cache
var cacheData = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)],
DateTime.Now
))
.ToArray();
var cacheEntry = memoryCache.GetOrCreate(
"forecastData",
cacheEntry =>
{
cacheEntry.SlidingExpiration = TimeSpan.FromSeconds(15);
return cacheData;
});
return cacheEntry!.ToList();
}
)
.WithName("GetCacheForecast")
.WithOpenApi();
...
Ketika mengunjungi rute /cacheforecast
maka akan dibuat data mengenai ramalan cuaca apabila tidak
cache tentang data ini.
Selanjutnya, kita akan melakukan implementasi untuk menghapus cache. Cache bisa terhapus
apabila sudah melebihi masa kadaluarsanya. Namun, kita bisa menggunakan method Remove
untuk menghapus
cache. Mari kita modifikasi rute /cacheforecast
dengan menambahkan Remove
. Ketike rute ini dipanggil,
kita akan hapus cache forecastData
dan memberikan data baru untuk cache.
app.MapGet("/cacheforecast", (IMemoryCache memoryCache) =>
{
// Mengakses Cache
// var cacheEntry = memoryCache.Get<WeatherForecast[]?>("forecastData");
// Mengakses Cache atau Membuat Cache
var cacheData = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)],
DateTime.Now
))
.ToArray();
memoryCache.Remove("forecastData"); // menghapus cache dengan key forecastData
var cacheEntry = memoryCache.GetOrCreate(
"forecastData",
cacheEntry =>
{
cacheEntry.SlidingExpiration = TimeSpan.FromSeconds(15);
return cacheData;
});
return cacheEntry!.ToList();
}
)
.WithName("GetCacheForecast")
.WithOpenApi();
Sebagai perbandingan, ketika rute /weatherforecast
diakses lebih dulu sebelum /cacheforest
, maka kedua
rute tersebut akan memberikan data yang berbeda. Setelah teman-teman mengakses /cacheforecast
dan kembali
mengakses rute /weatherforecast
maka akan muncul data yang sama karena sudah ada cache yang dibuat dengan data
saat ada kunjungan ke rute cacheforecast
.
Sebagai penerapan caching yang mengikuti best practices, teman-teman bisa menggunakan strategi kombinasi Sliding Expiration dan Absolute Expiration serta membatasi size dari cache.
Kombinasi Sliding Expiration dan Absolute Expiration dapat kita terapkan pada rute cacheforecast
dengan
menambahkan SetAbsoluteExpiration
untuk menghindari resiko cache yang tidak pernah kadaluarsa karena sering diakses dan masih
menggunakan Sliding Expiration.
...
app.MapGet("/cacheforecast", (IMemoryCache memoryCache) =>
{
// Mengakses Cache
// var cacheEntry = memoryCache.Get<WeatherForecast[]?>("forecastData");
// Mengakses Cache atau Membuat Cache
var cacheData = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)],
DateTime.Now
))
.ToArray();
memoryCache.Remove("forecastData");
var cacheEntry = memoryCache.GetOrCreate(
"forecastData",
cacheEntry =>
{
cacheEntry.SlidingExpiration = TimeSpan.FromSeconds(15);
cacheEntry.SetAbsoluteExpiration(TimeSpan.FromSeconds(60)); // best practice
return cacheData;
});
return cacheEntry!.ToList();
}
)
.WithName("GetCacheForecast")
.WithOpenApi();
...
Maka cache dapat diakses berkali-kali dengan maksimal satu menit. Apabila lebih dari satu menit, cache tersebut dibuat kadaluarsa.
Selanjutnya, kita juga bisa menggunakan size untuk membatasi ukuran cache. Rute /cacheforecast
dapat
kita tambahkan size dengan method SetSize
.
...
app.MapGet("/cacheforecast", (IMemoryCache memoryCache) =>
{
// Mengakses Cache
// var cacheEntry = memoryCache.Get<WeatherForecast[]?>("forecastData");
// Mengakses Cache atau Membuat Cache
var cacheData = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)],
DateTime.Now
))
.ToArray();
memoryCache.Remove("forecastData");
var cacheEntry = memoryCache.GetOrCreate(
"forecastData",
cacheEntry =>
{
cacheEntry.SlidingExpiration = TimeSpan.FromSeconds(15);
cacheEntry.SetAbsoluteExpiration(TimeSpan.FromSeconds(60)); // best practice
cacheEntry.SetSize(2); // best practice
return cacheData;
});
return cacheEntry!.ToList();
}
)
.WithName("GetCacheForecast")
.WithOpenApi();
...
SetSize
dengan angka 2 ini berarti cache dapat menyimpan sebanyak 2 byte.
Ada hal yang menarik ketika kita menggunakan caching. Apabila cache melebihi limit, maka cache tidak akan menghapus data cache yang lama untuk data yang baru. Proses yang terjadi adalah data cahce yang baru akan diabaikan dan tidak ada error yang muncul. Oleh karena itu, teman-teman harus hati-hati ketika ingin menerapkan caching.
Okay, sangat mudah bukan menggunakan In-Memory Cache di ASP.NET Core. Caching merupakan metode yang bagus untuk meningkatkan performa aplikasi kita. Teman-teman bisa mengikuti langkah-langkah caching pada artikel ini untuk aplikasi teman-teman. Semoga artikel ini bermanfaat buat teman-teman semua.
Happy coding!