AWS Bites Podcast

124. S3 Performance

In this episode, we discuss some tips and tricks for optimizing performance when working with Amazon S3 at scale. We start by giving an overview of how S3 works, highlighting the distributed nature of the service and how data is stored redundantly across multiple availability zones for durability. We then dive into specific tips like using multipart uploads and downloads, spreading the load across key namespaces, enabling transfer acceleration, and using S3 byte-range fetches. Overall, we aim to provide developers building S3-intensive applications with practical guidance to squeeze the most performance out of the service.

AWS Bites is brought to you by fourTheorem an AWS consulting partner with tons of experience with S3. If you need someone to work with to optimize your S3-based workloads, check out at fourtheorem.com!

In this episode, we mentioned the following resources:

Let's talk!

Do you agree with our opinions? Do you have interesting AWS questions you'd like us to chat about? Leave a comment on YouTube or connect with us on:

Luciano
00:08:38

Another common thing is to try to spread the load across different key namespaces. And I think to really understand this one, we need to explain a little bit how some of the details of how S3 stores the object and what are some of the limits. Because if you look at the documentation, what the documentation says is that you can do 3500 put, copy, post, or delete operations, and 5500 get and head operations per prefix. And this is where things get a little bit confusing, because what does it mean per prefix? And if you look at other parts of the documentation, there is an official definition that says a prefix is a string of characters at the beginning of the object key name. A prefix can be of any length, subject to the maximum length of the object key name, which is 1204 bytes. You can think of prefixes as a way to organize your data in a similar way to directories. However, prefixes are not directories. So you can kind of make the parallel that a prefix is like saying, I don't know, "/home," "/luciano," "/documents," and then the name of your object. But behind the scenes, AWS is not really maintaining a file system. It's just a way for you to organize your data. What is interesting, though, is that somehow AWS is using this information to distribute the data across multiple partitions. And this is probably where the limit conversation comes from. You can do a certain amount of operations per prefix, but that probably really means per partition. And this is something that is not always entirely clear. What is the logic that AWS uses there to define how prefix maps to actual physical partitions? So it's something that AWS tries to determine automatically, depending on your usage patterns. But what we have seen in the wild is that if you really do lots of requests, even if you have different prefixes, you can still get throttled and see 503 errors. So it is really important if you're running at such scale to monitor the number of 503s, because if you're using the SDK, there are retries. So eventually you might be able to get your operation successfully performed. But that operation might take a long time, because there is a loop of retries that is happening behind the scenes. So you need to be aware if you're trying to get the best performance when retries are happening. Another interesting thing that we bumped into working with one of our customers is that we were still getting lots of 503s and at some point we decided to talk with support. And it was a long conversation. We got lots of help from AWS, but it seems to be possible to get AWS to tweak whatever is the internal mechanism for your specific use case. So if you're really hitting all these limits and you don't know what else can you do, I think the best course to action right now is to just open a ticket, try to talk with AWS, explain your use case. And I think they might be able to discuss with you very custom options that are the best solution for your particular use case. I think this is still very rare in the industry. We only had one use case, at least that I can remember on in my career. But again, if you happen to do thousands and thousands of requests to AWS per second, it's not unlikely that you're going to bump in this particular limit action. So just be aware that there are solutions, even though the solution is not necessarily well documented, but you can talk with AWS and they will help you to figure out the solution. Overall, the idea is to try to think about namespaces that make sense and then distribute your access, your operations to different namespaces if you want to leverage as much requests per second as possible. What's the next one you have, Eoin?

00:12:43

And a VPC flow is defined as combination of source IP, source port, and then a destination IP and destination port. And if you're just doing a fairly simple HTTP request to an S3 endpoint, you're going to get a DNS lookup. It's going to give you back an IP or a set of IP addresses. Your client is going to pick one and make the connection to that IP address. But if you're a little bit smarter about it, you can take all of the IP addresses for S3 back and use that to get multiple connections from your source to the destination. And that's exactly what that clever library from Cloudonaut did. It's something this load balancing on the client is something that the AWS CRT, the Common Runtime does as well. So the AWS CRT library, which is used in the Java SDK and Boto3 as well, has the capability to do that and do all this download performance optimization too. So it's worth checking out. And then as well, on the topic of network connections, different environments vary, different EC2 instances have different bandwidth characteristics on the network devices. And then you have enhanced networking and elastic fabric adapter to really squeeze more performance out of it. Also bear in mind that when you're running an AWS Lambda, your network connection size depends on your memory configuration because it's linearly proportional. So if you're finding that bandwidth is a constraint, you might think about, "Okay, well, can I do multiple downloads and multiple functions or do I just need to up the memory so that I get maximum IO throughput on that as well?" So that's the lower level performance tips. What else do we have, Luciano?

00:18:37

Now, if you're doing something a bit more complicated and you're in this space, you might be familiar with the arrow, which is core to a lot of these data science tools, the arrow library and format tools built on top of it, like pandas and polars and DuckDB. These all ensure you don't have to read all of the data if you don't need it, particularly if you're using optimized column number formats like Parquet. All of these tools can intelligently like with Parquet file, for example, the metadata describing what data is in it is at the bottom of it. So those tools will go read the footer from the Parquet file, then they can figure out where the columns are stored in the file and where the row groups are that's split into groups. And then they can retrieve only the data you need. Polars and DuckDB are particularly fast when it comes to this kind of use case. They'll leverage those byte range queries automatically for you and are surprisingly fast in how they can run and are already putting a lot of engineering effort into optimizing things like object retrieval from S3. So you don't even have to think about it. So in terms of additional resources, we're going to throw a bunch of links in the show notes, which we hope are valuable, including some performance guidelines on S3 and design patterns. Apart from that, let us know if you have any more S3 performance tips. I'm sure there's more out there. Just let us know in the comments. Thanks very much for joining us this time, and we'll see you in the next episode.

Help us to make this transcription better! If you find an error, please submit a PR with your corrections.