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