eclipse-ee4j / jersey

Eclipse Jersey Project - Read our Wiki:
https://github.com/eclipse-ee4j/jersey/wiki
Other
691 stars 352 forks source link

Returning a complex Bean type from a resource method is very slow (Jackson provider) #3714

Open jerseyrobot opened 6 years ago

jerseyrobot commented 6 years ago

Hello,

I integrated a new RESTful endpoint in an existing Tomcat project, using Jersey. I use Jackson as my JSON support provider. For test purposes, I have a pretty simple GET resource method that populates the Bean object and returns it.

My Bean class is pretty complex. I have around 20 bean model classes in total. They are interconnected in a (possibly) circular manner, as shown in the illustration below. Please note that the real scenario and more complex and branched. (Note: The instances of those classes do not have any circular pointers. If a B2 instance points on a B1 instance, it is not the B1 it is contained in.) image

My problem started by noticing that when I'm returning such a complex structure, the performance is just too slow. Getting an answer back takes "forever" (more than a few minutes). I tried reducing the complexity of the structure and the responses got quicker. My estimate is that as the structure gets more complex, the response time gets longer in an exponential manner.

My initial thought was to put the blame on Jackson. So I tried to serialise the Bean into JSON inside the method and return the String of the JSON. All performance issues were solved.

I then tried to build a MessageBodyWriter and to serialise the Bean there. Also worked fine.

Hence, my current assumption is that maybe the default Writer, or some Mapper, are the cause of this problem. I'm wondering whether it is a bug in Jersey, or in the provider, or am I doing something wrong. Not sure how to proceed.

Would appreciate your help.

Thank you.

jerseyrobot commented 6 years ago
jerseyrobot commented 6 years ago

@jansupol Commented

I use Jackson as my JSON support provider.

How exactly you do that in the case of being slow? Can you provide a repeatable test?

jerseyrobot commented 6 years ago

@idogal Commented I created a demo that replicates the scenario.

Uploaded it to a GitLab repository. Also uploaded an archive of the project. JerseyPerfDemo.tar.gz

jerseyrobot commented 6 years ago

@idogal Commented Used this to overcome the performance issue:

@Provider
@Produces(MediaType.APPLICATION_JSON)
public class ModelMessageBodyWriter implements MessageBodyWriter<List<CategoryModel_100047130_100047130>> {

    @Override
    public boolean isWriteable(Class<?> type, Type type1, Annotation[] antns, MediaType mt) {
        return List.class.isAssignableFrom(type);
    }

    @Override
    public long getSize(List<CategoryModel_100047130_100047130> t, Class<?> type, Type type1, Annotation[] antns, MediaType mt) {
        return 0;
    }

    @Override
    public void writeTo(List<CategoryModel_100047130_100047130> t, Class<?> type, 
            Type type1, Annotation[] antns, MediaType mt, MultivaluedMap<String, Object> mm, OutputStream out) throws IOException, WebApplicationException {

        ObjectMapper mapper = new ObjectMapper();

        mapper.findAndRegisterModules();
        mapper.registerModule(new JavaTimeModule()); // Enables to serialise Java 8 dates
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // Serialises dates as "readable" strings and not as timestamps        
        String writeValueAsString = mapper.writeValueAsString(t);  

        try (Writer writer = new PrintWriter(out)) {
            writer.write(writeValueAsString);
            writer.flush();
        }        
    }
}