Memory Issues / Configuring For Kubernetes #1744
Replies: 2 comments 17 replies
-
I saw that you opened an issue about this with some more information but let me first answer your questions here:
The native library uses
The
The size of an image doesn't tell you anything about how much memory it needs. An image of
Below is the document for both methods: /// <summary>
/// Gets or sets the max memory request in bytes. ImageMagick maintains a separate memory pool for large
/// resource requests. If the limit is exceeded when allocating pixels, the allocation is instead memory-mapped
/// on disk.
/// </summary>
ulong MaxMemoryRequest { get; set; }
/// <summary>
/// Gets or sets the pixel cache limit in bytes. Once this memory limit is exceeded, all subsequent pixels cache
/// operations are to/from disk. The default value of this is 50% of the available memory on the machine in 64-bit mode.
/// When running in 32-bit mode this is 50% of the limit of the operating system.
/// </summary>
ulong Memory { get; set; } What could I do to make it more clear the
I do think that you should want to set the memory limit with |
Beta Was this translation helpful? Give feedback.
-
You mentioned to make sure I'm disposing correctly. I see the "release" in the logs, and I believe my code looks correct, but I'm wondering if we are perhaps doing something just plain wrong w/r/t this library. I would expect as many relinquish messages in the logs as acquire messages, but that's not what I see, so perhaps we are missing something. We generate thumbnails only. We resize the target image, decide on what format we want to convert it to and then export the bytes and try to compress. If the produced image is over some threshold and was a PNG with transparency, we convert it to a JPG instead. Here's the code we are running (error handling/our logging removed and everything moved into a single method). csharp codeinternal static byte[] GetThumbnail(ReadOnlyMemory<byte> input)
{
const uint maxSizeBytes = 65536;
const uint maxDim = 256;
// load image
using var image = new MagickImage(input.Span);
// resize to our target thumbnail size
var size = new MagickGeometry(maxDim)
{
IgnoreAspectRatio = false,
Greater = true,
Less = false
};
image.Resize(size);
// set target format and quality
// GIF->GIF, Anything With Transparency->Png, Jpeg for everything else
(image.Format, image.Quality) = image.Format switch
{
MagickFormat.Gif or MagickFormat.Gif87 => (MagickFormat.Gif, 100),
_ when image.HasAlpha => (MagickFormat.Png, 97),
_ => (MagickFormat.Jpeg, 70)
};
image.Strip();
// get bytes compressing if possible
// if the size is less than some max we define, then return it
var bytes = GetBytesAndCompress(image);
if (bytes.Length <= maxSizeBytes)
return bytes;
// if we've got a GIF/JPEG over the limits, return empty
if (image.Format != MagickFormat.Png)
return [ ];
// now try and convert a PNG (with transparency) to a JPEG
// setting the background color and removing the Alpha
image.BackgroundColor = MagickColors.White;
image.Alpha(AlphaOption.Remove);
image.Format = MagickFormat.Jpg;
image.Quality = 70;
image.Strip();
// get bytes and once again try and compress
bytes = GetBytesAndCompress(image);
// return bytes if less than max, otherwise empty
return bytes.Length <= maxSizeBytes ? bytes : [ ];
}
static readonly ImageOptimizer Optimizer = new() { IgnoreUnsupportedFormats = true, };
static byte[] GetBytesAndCompress(MagickImage image)
{
var bytes = image.ToByteArray(image.Format);
using var ms = new MemoryStream(bytes);
if (Optimizer.Compress(ms))
return ms.ToArray();
return bytes;
} Now when I run a Jpeg through these are the logs I get (slightly redacted and the timestamps removed to reduce clutter): logs
Notice that there's 5 "relinquish" messages to the 9 "acquire" messages. I'm not sure if that's expected or not. Does the above show us doing anything horribly wrong that would cause a native memory leak? |
Beta Was this translation helpful? Give feedback.
-
The Problem
We are using Magick.NET as part of an ASP.NET application that runs as a Linux Docker containers in Kubernetes. The application creates thumbnails for any images our users might upload. The maximum upload size is 30Mb. Everything has been working generally fine. However once we get a handful of user uploads (usually in short succession or triggered by QA testing) the pod's memory consumption rises and Kubernetes OOM kills the pod.
We run our containers with a memory limit of
350Mi
. Because of this, before the OOM's I originally setResourceLimits.LimitMemory
to 35% out of precaution. Once we started getting the OOM kills I lowered it even further to 20% which didn't seem to help. I even increased the container max memory to700Mi
and despite this it eventually gets OOM killed.I had read some other threads here that suggested making sure you were running as x64 (since we're using the AnyCPU build) and that
ResourceLimits
were configured properly. So I added some basic console logging to check whatResouceLimits.Memory
was actually defaulting to, and this was the output:Well,
14653595648
bytes equates to more than 13GB which is well over the memory limits set on the pods. It appears that when running in Kubernetes as a Linux container, the defaults are being taken from theMemTotal
entry in/proc/meminfo
(or it's equivalent) which equate to the node's resources, and not the pod's.Here's the relevant portion of
/proc/meminfo
:Instead, the defaults should be taken from the cgroups (v2) file at
/sys/fs/cgroup/memory.max
, which in this instance containscorrelating to the limit of
350Mi
that this pod currently has.Questions
Now that I realize what's happening, I can make changes to both the pod configuration and our application so that I'm able to set
ResourceLimits
based on the pod's actual memory availability. That leaves me with a couple of questions:ResourceLimits.Memory
in this constrained environment (350mi), where the largest images we work with are 30mb?ResourceLimits.Memory
tune the same knobs asResourceLimits.LimitMemory
? I can't use the latter since passing a percentage won't workResourceLimits.MaxMemoryRequest
as well?Memory
andMaxMemoryRequest
. The documentation doesn't really explain it to meIn case it matters:
MagickNET.SetTempDirectory
as we were expecting to spill to disk.Magick.NET-Q8-AnyCPU
version14.0.0
Beta Was this translation helpful? Give feedback.
All reactions