5

Let's say I'm designing a REST service with Spring, and I need to have a method that accepts a file, and returns some kind of ResponseDto. The application server has its POST request size limited to 100MB. Here's the hypothetical spring controller method implementation:

public ResponseEntity<ResponseDto> uploadFile(@RequestBody MultipartFile file) {
        return ResponseEntity.ok(someService.process(file));
} 

Let's assume that my server has 64GB of RAM. How do I ensure that I don't get an out of memory error if in a short period (short enough for process() method to still be running for every file uploaded), 1000 users decide to upload a 100MB file (or just 1 user concurrently uploads 1000 files)?

EDIT: To clarify, I want to make sure my application doesn't crash, but instead just stops accepting/delays new requests.

mak-g
  • 173
  • 2
  • 9
  • You don't need to stop accepting new requests. You just need to read the files more efficiently. Implementing a BufferedReader as I suggested in my answer is way easier and more user friendly. – Serr Oct 26 '17 at 11:50
  • That seems cool, but what if I need to read the whole files at once into memory (for example if I need to verify a checksum)? – mak-g Oct 26 '17 at 11:57
  • 1
    Handle big files is not a big problem anymore as used to be, there are many solutions out there. For calculating the checksum for example (I edited my answer to add the link): https://docs.oracle.com/javase/7/docs/api/java/security/DigestInputStream.html – Serr Oct 26 '17 at 12:04

4 Answers4

2

You can monitor the memory usage and see when you have to stop accepting requests or cancel existing requests. https://docs.oracle.com/javase/6/docs/api/java/lang/management/MemoryMXBean.html

https://docs.oracle.com/javase/6/docs/api/java/lang/management/MemoryPoolMXBean.html

Also you can use this

Runtime runtime = Runtime.getRuntime();
System.out.println("Free memory: " + runtime.freeMemory() + " bytes.");
shakeel
  • 1,609
  • 1
  • 14
  • 14
  • That seems cool. Is there some kind of abstraction of this mechanism that Spring/Tomcat/other application servers provide? – mak-g Oct 26 '17 at 11:35
  • Actually it depends on layer. If i were at your position i will analyze if i need a container like tomcat or i will use Spring boot . Based on this choice ....it depends where you can apply. How to make this choice depends.... if it is just a web service or you want to serve some thing else too like JSPs .. Its very hard to answer with limited knowledge of scope. – shakeel Oct 26 '17 at 11:43
  • If you want to monitor it from outside world too then try with https://stackoverflow.com/questions/6405518/how-to-monitor-memory-in-a-spring-tomcat-application – shakeel Oct 26 '17 at 11:49
1

Consider creating a database table that holds that holds the uploads being done:

CREATE TABLE PROC_FILE_UPLOAD 
(
  ID NUMBER(19,0) NOT NULL 
, USER_ID NUMBER(19,0) NOT NULL 
, UPLOAD_STATUS_ID NUMBER(19,0) NOT NULL 
, FILE_SIZE NUMBER(19,0)
, CONSTRAINT PROC_FILE_UPLOAD_PK PRIMARY KEY (ID) ENABLE
);

COMMENT ON COLUMN PROC_FILE_UPLOAD.FILE_SIZE IS 'In Bytes';

USER_ID being a FK to your users table and UPLOAD_STATUS_ID a FK to a data dictionary with the different status for your application (IN_PROGRESS, DONE, ERROR, UNKNOWN, whatever suits you).

Before your service uploads a file, it must check if the current user is already uploading a file and if the maximum number of concurrent uploads has been reached. If so, reject the upload, else update PROC_FILE_UPLOAD information with the new upload and proceed.

Bernat
  • 492
  • 5
  • 13
0

Even though you could hold many files in memory with 64 GB RAM, you don't want to waste too much resources with it. There are memory efficient ways to read files, for example, you could use a BufferedReader, it is a very memory efficient way to read files since it doesn't store the whole file in memory.

The documentation does a really good job explaining it:

Reads text from a character-input stream, buffering characters so as to provide for the efficient reading of characters, arrays, and lines. The buffer size may be specified, or the default size may be used. The default is large enough for most purposes.

In general, each read request made of a Reader causes a corresponding read request to be made of the underlying character or byte stream. It is therefore advisable to wrap a BufferedReader around any Reader whose read() operations may be costly, such as FileReaders and InputStreamReaders. For example,

BufferedReader in = new BufferedReader(new FileReader("foo.in"));

will buffer the input from the specified file. Without buffering, each invocation of read() or readLine() could cause bytes to be read from the file, converted into characters, and then returned, which can be very inefficient.

Here is another SO questions that you may find useful:

Java read large text file with 70 million lines of text

If you need to calculate the checksum of the file like you said in the comments you could use this link.

Serr
  • 303
  • 1
  • 10
0

You can either limit the number of concurrent requests or use streaming to avoid keeping the whole file in RAM.

Limiting requests

You can limit the number of concurrent incoming requests in the web server. The default web server for Spring Boot is Tomcat, which is configurable in application.properties with server.tomcat.max-connections. If you have 64 GB RAM available after the app is fully loaded and your max file size 100 MB, you should be able to accept 640 concurrent requests. After that limit is reached, you can keep incoming connections in a queue before accepting them, configurable with server.tomcat.accept-count. These properties are described here: https://tomcat.apache.org/tomcat-9.0-doc/config/http.html

(In theory you can do better. If you know the upload size in advance, you can use a counting semaphore to reserve space for a file when it's time to start processing it, and delay starting any upload until there is room to reserve space for it.)

Streaming

If you are able to implement streaming instead, you can handle many more connections at the same time by not ever keeping the whole file in RAM for any one connection but instead processing the upload one bit at a time, e.g. as you write the upload out to a file or database. It looks like Apache Commons library has a component to help you build an API which streams in the request: https://www.initialspark.co.uk/blog/articles/java-spring-file-streaming.html

Alexander Taylor
  • 16,574
  • 14
  • 62
  • 83