-->

Are there major scaling limits with play framework

2019-05-04 19:34发布

问题:

I am using the playframework (2.4) for Java and connecting it to Postgres. The play framework is being used as a restful service and all it is doing is insert,updates,reads and deletes using JDBC. On this play page https://www.playframework.com/documentation/2.3.x/JavaAsync it states clearly that JDBC is blocking and that play has few threads. For the people who know about this, how limiting could this be and is there some way I can work around this? My specific app can have a few hundred database calls per second. I will have all the hardware and extra servers but do not know how play can handle this or scale to handle this in the code. My code in play looks like this:

public static Result myprofile() {
        DynamicForm requestData = Form.form().bindFromRequest();
        Integer id = Integer.parseInt(requestData.get("id"));

        try {
            JSONObject jo = null;
            Connection conn = DB.getConnection();
            ResultSet rs;
            JSONArray ja = new JSONArray();
            PreparedStatement ps = conn.prepareStatement("SELECT p.fullname as fullname, s.post as post,to_char(s.created_on, 'MON DD,YYYY') as created_on,s.last_reply as last_reply,s.id as id,s.comments as comments,s.state as state,s.city as city,s.id as id FROM profiles as p INNER JOIN streams as s ON (s.profile_id=p.id) WHERE s.profile_id=? order by created_on desc");
            ps.setInt(1, id);
            rs = ps.executeQuery();

            while (rs.next()) {
                jo = new JSONObject();
                jo.put("fullname", rs.getString("fullname"));
                jo.put("post", rs.getString("post"));
                jo.put("city", rs.getString("city"));
                jo.put("state", rs.getString("state"));
                jo.put("comments", rs.getInt("comments"));
                jo.put("id", rs.getInt("id"));
                jo.put("last_reply", difference(rs.getInt("last_reply"), rs.getString("created_on")));
                ja.put(jo);
            }
            JSONObject mainObj = new JSONObject();
            mainObj.put("myprofile", ja);


            String total = mainObj.toString();
            System.err.println(total);
            conn.close();
            return ok(total);
        } catch (Exception e) {
            e.getMessage();
        }
        return ok();
    }

I also know that I can try to wrap that in a futures promise however the blocking still occurs. As stated before I will have all the servers and the other stuff taken care of, but would the play framework be able to scale to hundreds of requests per second using jdbc? I am asking and learning now to avoid serious mistakes later on.

回答1:

Play can absolutely handle this load.

The documentation states that blocking code should be avoided inside controller methods - the default configuration is tuned for them to have asynchronous execution. If you stick some blocking calls in there, your controller will now be waiting for that call to finish before it can process another incoming request - this is bad.

You can’t magically turn synchronous IO into asynchronous by wrapping it in a Promise. If you can’t change the application’s architecture to avoid blocking operations, at some point that operation will have to be executed, and that thread is going to block. So in addition to enclosing the operation in a Promise, it’s necessary to configure it to run in a separate execution context that has been configured with enough threads to deal with the expected concurrency. See Understanding Play thread pools for more information. https://www.playframework.com/documentation/2.4.x/JavaAsync#Make-controllers-asynchronous

I believe you are aware of this but I wanted to point out the bolded section. Your database has a limited number of threads that are available for applications to make calls on - it may be helpful to track this number down, create a new execution context that is turned for these threads, and assign that new execution context to a promise that wraps your database call.

Check out this post about application turning for Play, it should give you an idea of what this looks like. I believe he is using Akka Actors, something that might be out of scope for you, but the idea for thread tuning is the same:

Play 2 is optimized out-of-the-box for HTTP requests which don’t contain blocking calls (i.e. asynchronous). Most database-driven apps in Java use synchronous calls via JDBC so Play 2 needs a bit of extra configuration to tune Akka for these types of requests. http://www.jamesward.com/2012/06/25/optimizing-play-2-for-database-driven-apps

If you try to execute a massive number of requests on the database without turning the threads, you run the risk of starving the rest of your application of threads, which will halt your application. For the load you are expecting, the default tuning might be ok, but it is worth performing some additional investigating.

Getting started with thread tuning: https://www.playframework.com/documentation/2.4.x/ThreadPools

You should update your controller to return Promise and there is also no reason to make it static anymore with Play 2.4. https://www.playframework.com/documentation/2.4.x/Migration24#Routing


Define an execution context in the application.conf with name "jdbc-execution-context"

//reference to context
ExecutionContext jdbcExecutionContext = Akka.system().dispatchers()
     .lookup("jdbc-execution-context");

return promise(() -> {
    //db call
}, jdbcExecutionContext)
.map(callResult -> ok(callResult));