Hello,
as we come close to productive deployment I benchmarked some specific
performance scenarios: 
Part of our solution serves huge files (100-500MB) in a 90% LAN, 10% WAN
environment. Because these files can come from different (re-)sources they
are filtered by a "RepresentationDirectoryCache" (code attached at end of
post) that stores them first-time in a hashed directory structure and on
subsequent calls returns a FileRepresentation to the caller. This directory
cache is also used to "record" user sessions and then "replay" them offline
for demo and testing purposes. So far during development we have always used
Jetty for the server connector, but poor performance made us take a closer
look:

Benchmark: 
One file (test.7z, 285MB) is read first time (empty cache) and second time
(from cache) on localhost and via a 100mbit LAN
Restlet: 2.0 2010-03-19, Jetty, Grizzly from that snapshot
Configuration: None, just replacing the necessary jars; no encoder 

Results: troughput in megabytes per second (MB/sec)

                                               Internal/Default       Jetty     
      
Grizzly
localhost     empty cache             > 40                      1.4             
 
16
localhost     from cache               > 120                     > 120          
 
> 120

100mbit       empty cache            10.8                       1.4             
 
0.5
100mibt       from cache               10.7                       9.8           
  
11

In short: All three serve a FileRepresentation with the maximum throughput
the media allows. But when caching for the first time (implemented by
extending WritableRepresentation) the internal http server is 3 to 20 times
faster. Especially the figures for Jetty and Grizzly 1st time via LAN are
quite scary and unfit for production mode.

I wonder, now, if there are some ways to configure Jetty and Grizzly to
better perform in these scenarios. 
Or maybe the implementation of the RepresentationDirectoryCache fits well
with the internal server but slows down Jetty and Grizzly.

Any comments and recommendations are welcome.

Guido.

>> code (sorry for the length)

case class CacheableRepresentation(representation: Representation, maxage:
Long) 
extends WrapperRepresentation(representation) with Cacheable {
    override def length: Long = representation.getSize
    override def freshness = expires - now
    lazy val expires = now + maxage
}

case class CachingRepresentations(
    cache: Cache[String, CacheableRepresentation], 
    next: Restlet, 
    maxage: Long,
    attachment: Boolean = false) 
extends Filter(Context.getCurrent.createChildContext, next) 
with Caching[String, CacheableRepresentation] { 
    filter =>
    override protected def beforeHandle(request: Request, response:
Response): Int = 
        request.getMethod match {
        case Method.GET => 
            makeKey(request)
            cache.get(key) match {
                case Some(representation) => 
                    if (attachment) representation.setDisposition(
                        new Disposition(Disposition.TYPE_ATTACHMENT))
                    response.setEntity(representation)
                    Filter.STOP
                case None => Filter.CONTINUE
            }
        case _ => Filter.CONTINUE        
    }
    override protected def afterHandle(request: Request, response: Response)
= 
        request.getMethod match {
            case Method.GET => 
                response.setEntity(cache.add(key,
CacheableRepresentation(response.getEntity, maxage)))
            case _ => 
    }
    private def makeKey(request: Request) = { 
        val ref = request.getResourceRef
        val k = ref.getPath + "?" + ref.getQuery
        key = MD5(k)
    }
    private var key: String = null    
}

case class RepresentationDirectoryCache(directorypath: String,     maxsize:
Long = -1) 
extends Cache[String, CacheableRepresentation] {
    override def contents : List[String] = Nil    
    override def get(filename: String): Option[CacheableRepresentation] = {
        try {
            val filepath = directorypath + hashDirectory(filename) +
filename
            val infofile = new File(filepath + ".cacheinfo")
            if (infofile.exists) {
                val reader = new InputStreamReader(new
FileInputStream(infofile), "UTF-8")
                val cacheinfo = Json.parse(reader)
                reader.close
                val extension = cacheinfo.asObject("extension").asString
                val datafile = new File(filepath + "." + extension)
                if (datafile.exists) {
                    val expires = cacheinfo.asObject("expires").asLong
                    if (expires > now) {
                        val maxage = expires - now
                        infofile.setLastModified(now)
                        datafile.setLastModified(now)
                        val representation = new
FileRepresentation(datafile, metadataservice.getMediaType(extension))
                        return Some(CacheableRepresentation(representation,
maxage))
                    } else {
                        infofile.delete
                        datafile.delete                    
                    }
                }
                else {
                    infofile.delete
                }                    
            }
        } catch { 
            case e: Exception => 
        }
        None
    }
    override def add(k: String, v: CacheableRepresentation) =
CacheableRepresentation(OutRepresentation(k, v), 0)
    
    // this is the benchmarked class on 1st read
    private case class OutRepresentation(filename: String, representation:
CacheableRepresentation) 
    extends WritableRepresentation(representation.getMediaType) {
        override def write(out: java.nio.channels.WritableByteChannel) = {
            if (infofile.exists) infofile.delete
            val data = try { 
                Some((new FileOutputStream(datafile)).getChannel)
            } catch { 
                case e: Exception => None
            }
            val buffer = ByteBuffer.allocate(buffersize)
            try {
                while (0 <= in.read(buffer)) {
                    buffer.flip
                    out.write(buffer)
                    if (data.isDefined) {
                        buffer.rewind
                        data.get.write(buffer)
                    }
                    buffer.clear
                }
                cacheInfo
            } finally { 
                in.close
                if (data.isDefined) data.get.close
                if (!infofile.exists && datafile.exists) datafile.delete 
            }
        }
        private def cacheInfo = {
            val cacheinfo = Map(
                "extension" -> extension, 
                "created" -> now, 
                "expires" -> representation.expires,
                "maxage" -> representation.freshness)
            val info = new OutputStreamWriter(new
FileOutputStream(infofile), "UTF-8")
            info.write(Json.build(cacheinfo))
            info.close
        }
        private val in = representation.getChannel
        private val hashdirectorypath = directorypath +
hashDirectory(filename)
        private val hashdirectory = new File(hashdirectorypath)
        if (!hashdirectory.exists) hashdirectory.mkdirs
        private lazy val extension =
getExtension(representation.getMediaType)
        private lazy val filepath = hashdirectorypath + filename
        private lazy val infofile = new File(filepath + ".cacheinfo")
        private lazy val datafile = new File(filepath + "." + extension)
    }
    private def hashDirectory(filename: String) = 
        filename.substring(0, 3).foldLeft("") { (xs, x) => xs + x +
File.separator }        
    private def getExtension(mediatype: MediaType) = {
        metadataservice.getExtension(mediatype)
    }
    val metadataservice = {
        val m = new MetadataService
        m.addCommonExtensions
        m.addExtension("jpg", MediaType.valueOf("image/pjpeg"))
        m.addExtension("7z", MediaType.APPLICATION_ZIP)
        m
    }
    private val buffersize = 51*1024    
}

-- 
View this message in context: 
http://n2.nabble.com/Why-is-the-default-http-server-so-much-faster-than-Jetty-and-Grizzly-tp4777255p4777255.html
Sent from the Restlet Discuss mailing list archive at Nabble.com.

------------------------------------------------------
http://restlet.tigris.org/ds/viewMessage.do?dsForumId=4447&dsMessageId=2463205

Reply via email to