Caching static webpage in golang http server

4,302 views
Skip to first unread message

Sankar

unread,
Apr 18, 2012, 2:54:32 AM4/18/12
to golang-nuts, sankar.c...@gmail.com
Hi,

I have a static webpage in my webapp, which will be accessed by many
people frequently. Is there a built-in or best way to cache this
static page in the go http server, instead of asking it to go to the
disk everytime ? My app. is not deployed in the appengine btw.

Thanks.

Sankar
http://psankar.blogspot.com

Patrick Mylund Nielsen

unread,
Apr 18, 2012, 3:15:35 AM4/18/12
to Sankar, golang-nuts
It seems to me the best thing would be to implement a handler that
sets some time.Time indicating when the file was last read, and
another variable with the contents of the file/page, and returns those
contents unless time.Now().After(lastTime), in which case it reads the
file/generates the page again.

I've implemented a kind of "memcache without the d" which takes care
of expiring items you put in the cache -- you can find it here:
https://github.com/pmylund/go-cache. It would probably be overkill for
a single page, but if you're caching many different types of things,
and don't want to set up a distributed cache/many different variables
to keep track of each item, it may be interesting.

Sankar P

unread,
Apr 18, 2012, 3:16:39 AM4/18/12
to Patrick Mylund Nielsen, golang-nuts
On Wed, Apr 18, 2012 at 12:45 PM, Patrick Mylund Nielsen
<pat...@patrickmylund.com> wrote:
> It seems to me the best thing would be to implement a handler that
> sets some time.Time indicating when the file was last read, and
> another variable with the contents of the file/page, and returns those
> contents unless time.Now().After(lastTime), in which case it reads the
> file/generates the page again.
>
> I've implemented a kind of "memcache without the d" which takes care
> of expiring items you put in the cache -- you can find it here:
> https://github.com/pmylund/go-cache. It would probably be overkill for
> a single page, but if you're caching many different types of things,
> and don't want to set up a distributed cache/many different variables
> to keep track of each item, it may be interesting.

I was actually looking for some kind of memcache without the d. I will
have a look into this. Thank you a lot :)

>
> On Wed, Apr 18, 2012 at 08:54, Sankar <sankar.c...@gmail.com> wrote:
>> Hi,
>>
>> I have a static webpage in my webapp, which will be accessed by many
>> people frequently. Is there a built-in or best way to cache this
>> static page in the go http server, instead of asking it to go to the
>> disk everytime ? My app. is not deployed in the appengine btw.
>>
>> Thanks.
>>
>> Sankar
>> http://psankar.blogspot.com

--
Sankar P
http://psankar.blogspot.com

Simon Zimmermann

unread,
Apr 18, 2012, 3:17:22 AM4/18/12
to Sankar, golang-nuts
On 18 April 2012 08:54, Sankar <sankar.c...@gmail.com> wrote:
> I have a static webpage in my webapp, which will be accessed by many
> people frequently. Is there a built-in or best way to cache this
> static page in the go http server, instead of asking it to go to the
> disk everytime ? My app. is not deployed in the appengine btw.

If your site is hit very frequently. Avoiding to hit the app entirely
is probably preferable. Then using something like Varnish[1] can be a
good solution. If you don't mind the HTTP request reaching your
application, using Brad's memcache client[2] together with a memcache
server is also good.

[1]: http://varnish-cache.org
[2]: https://github.com/bradfitz/gomemcache/

andrey mirtchovski

unread,
Apr 18, 2012, 3:26:22 AM4/18/12
to Sankar P, Patrick Mylund Nielsen, golang-nuts
> I was actually looking for some kind of memcache without the d.

shove all your static pages into a map[string]bytes.Buffer :)

zero cache eviction, but it should work for a set size of 1 page.

Sanjay

unread,
Apr 18, 2012, 3:26:50 AM4/18/12
to golan...@googlegroups.com, Sankar
If its just a few files, you can implement a really simple in-memory cache yourself pretty trivially. Here's a really simple file cache: http://play.golang.org/p/7-CEJRnuCR

Some interesting features that can be added relatively easily:
  • Pre-size the buffer with the size of the file to avoid over-allocating memory
  • Send the Last-Modified header based on info from the file
  • Pre gzip the file, and cache both gzipped and non-gzipped versions, and serve the appropriate ones. (Don't forget Vary: Accept-Encoding)
  • Don't make it panic when the file doesn't exist, just return an error, so the user of the function can deal with it.
  • With this model, every file you cache will increase your app's startup time. You could do the load in a goroutine. Or maybe the first time ServeHTTP is called, using sync.Once.
Sanjay

Sankar P

unread,
Apr 18, 2012, 3:50:13 AM4/18/12
to Sanjay, golan...@googlegroups.com

Yes, I was planning to implement a FileCache myself, but wanted to
find out if there are any other options provided by golang already,
which I can just consume. Thanks.

Sankar P

unread,
Apr 18, 2012, 3:53:58 AM4/18/12
to Simon Zimmermann, golang-nuts

This is not for a production app. It is just a fun project which I am
doing to learn something, and hoping that it will be useful for
someone too. I will try to use memcached and will fallback to loading
from a custom filecache implementation if memcached is not running.

Thanks a lot.

ranveer

unread,
Jun 30, 2013, 5:45:00 AM6/30/13
to golan...@googlegroups.com, sankar.c...@gmail.com
Just wrote a filecache with fsnotify support.

Warning: May have some bugs. I just wrote it now.

Brad Fitzpatrick

unread,
Jun 30, 2013, 10:45:28 AM6/30/13
to ranveer, golang-nuts, sankar.c...@gmail.com
Btw, when you go "to disk" to serve a file, it's not always going to disk.  The kernel has a cache too.  Also, when you use the default Go file serving APIs, Go uses sendfile to serve files, which eliminates the copies from userspace to the kernel.  You don't get that if you're manually caching things.





--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

ranveer

unread,
Jun 30, 2013, 10:55:50 PM6/30/13
to golan...@googlegroups.com, ranveer, sankar.c...@gmail.com
Nice to know that. This means that caching files in memory can actually be worse than not caching.
I was trying to find the sendfile code. But I don't see it, it looks like current http.FileServer implementation is actually copying stuff(Read and Write) in and out of kernel.

Can you point me to the code which uses sendfile?

Another orthogonal question. Does it make sense to load data files in memory? Files which I do not want to send, just use within server. I guess so.

Regards,
Ranveer.

Ranveer Kunal

unread,
Jun 30, 2013, 11:02:06 PM6/30/13
to golan...@googlegroups.com, ranveer, sankar.c...@gmail.com
But again such data files can be loaded in a better data structure.
--
Ranveer Kunal | Software Engineer | ran...@google.com | +55 31 93135963
   

Andrew Gerrand

unread,
Jun 30, 2013, 11:12:26 PM6/30/13
to ranveer, golang-nuts, Sankar P
On 1 July 2013 12:55, ranveer <ranvee...@gmail.com> wrote:
Nice to know that. This means that caching files in memory can actually be worse than not caching.
I was trying to find the sendfile code. But I don't see it, it looks like current http.FileServer implementation is actually copying stuff(Read and Write) in and out of kernel.

Can you point me to the code which uses sendfile?
 
*net.TCPConn implements io.ReaderFrom that—when passed an *os.File—uses sendfile to do the copy. Then http.Serve uses io.Copy, which sniffs its writer argument for an io.ReaderFrom with which to do the copy.

Here's the change that introduced it: https://code.google.com/p/go/source/detail?r=535caa895f21

Andrew

Ranveer Kunal

unread,
Jun 30, 2013, 11:18:47 PM6/30/13
to Andrew Gerrand, golang-nuts, Sankar P
Perfect. I skipped the two line while reading the code of io.Copy.
Thanks for pointing it out.

Regards,
Ranveer.

Ranveer Kunal

unread,
Jul 1, 2013, 7:14:24 AM7/1/13
to Andrew Gerrand, golang-nuts, Sankar P
Have you guys benchmarked the access of non-existent files.
If I continuously bombard the server with non existent files in the correct path. How fast can you detect that?
In my cached server, it is a map lookup.

Regards,
Ranveer. 

Brad Fitzpatrick

unread,
Jul 1, 2013, 11:19:06 AM7/1/13
to Ranveer Kunal, Andrew Gerrand, golang-nuts, Sankar P
Have you?

If you need to ever mutate your map, that means you have to also lock your map.  An RLock+lookup+RUnlock might be more expensive than an open system call.  The kernel's negative dentry cache (at least on Linux) has finer grained locking than a map around the whole VFS, and probably on other systems too.

Writing Benchmark functions in Go is trivial (see pkg testing) so you should try that first.  See net/http's unit tests for examples (in server_test.go mostly)



--

ranveer

unread,
Jul 1, 2013, 2:40:23 PM7/1/13
to golan...@googlegroups.com, Ranveer Kunal, Andrew Gerrand, Sankar P
I will try it tonight. I agree upon that, I do have RLock and RUnlock. I was thinking on putting finer mutex too. But more I think, more I am convinced that at least on linux the disk access is fast.
I will write benchmarks (mac and linux) before I jump to any conclusion.

Regards,
Ranveer.

Ranveer Kunal

unread,
Jul 2, 2013, 1:04:42 AM7/2/13
to golang-nuts, Ranveer Kunal, Andrew Gerrand, Sankar P
Here is the benchmark:

As expected sendfile kicks in and makes it slightly faster than the cached filesystem.
But for non existent files, cached filesystem performs much better than default one.
I have submitted the code for benchmarking too.

If I just call MLock on the created content buffers, the cached filesystem would work perfectly.
Benchmark on mac: darwin 64
~/gocode/src/github.com/ranveerkunal/memfs % go test memfs_test.go -bench=. -cpu=4 -parallel=4
temp dir: /tmp/memfs731592791
writing big file
ready to benchmark ...
testing: warning: no tests to run
PASS
BenchmarkNonExistentMemFS-4       500000              2779 ns/op
BenchmarkNonExistentDiskFS-4      200000              7513 ns/op
BenchmarkBigFileMemFS-4               20          88782691 ns/op
BenchmarkBigFileDiskFS-4              20          86461808 ns/op
ok      command-line-arguments  20.301s

Ranveer Kunal

unread,
Jul 2, 2013, 1:54:33 AM7/2/13
to golang-nuts, Ranveer Kunal, Andrew Gerrand, Sankar P
I did tests for small files and MemFS performs at par or slightly better than diskfs.

Ranveer Kunal

unread,
Jul 2, 2013, 6:33:03 AM7/2/13
to golang-nuts, Ranveer Kunal, Andrew Gerrand, Sankar P
Here are stats for linux:
~/gocode/src/github.com/ranveerkunal/memfs % go test memfs_test.go -bench=. -cpu=4 -parallel=4
temp dir: /tmp/memfs016684973
writing small file
writing big file
ready to benchmark ...
testing: warning: no tests to run
PASS
BenchmarkNonExistentMemFS-4        50000             43828 ns/op
BenchmarkNonExistentDiskFS-4         50000             37428 ns/op
BenchmarkSmallFileMemFS-4              1000           1763882 ns/op
BenchmarkSmallFileDiskFS-4               1000           1507493 ns/op
BenchmarkBigFileMemFS-4                       1        1550468000 ns/op
BenchmarkBigFileDiskFS-4                        1        1261333000 ns/op
ok      command-line-arguments  63.824s


Regular filesystem work much better.

Ranveer Kunal

unread,
Jul 2, 2013, 9:13:29 AM7/2/13
to golang-nuts, Ranveer Kunal, Andrew Gerrand, Sankar P
Ok, I had some lingering print statements in the code. Here is the cleaned version and benchmarks.
MemFS does extremely well for non-existent files both for linux and windows.

And it does work pretty well better than diskFS for small and big files too.
Benchmark on mac: darwin 64
~/gocode/src/github.com/ranveerkunal/memfs
 % go test memfs_test.go -bench=. -cpu=4 -parallel=4
temp dir: /tmp/memfs406771321
writing small file
writing big file
ready to benchmark ...
testing: warning: no tests to run
PASS
BenchmarkNonExistentMemFS-4      5000000               700 ns/op
BenchmarkNonExistentDiskFS-4      500000              3996 ns/op
BenchmarkSmallFileMemFS-4          10000            111634 ns/op
BenchmarkSmallFileDiskFS-4         10000            128475 ns/op
BenchmarkBigFileMemFS-4               20          83455262 ns/op
BenchmarkBigFileDiskFS-4              20          96320175 ns/op
ok      command-line-arguments  26.610s
Benchmark on linux:
~/gocode/src/github.com/ranveerkunal/memfs % go test memfs_test.go -bench=. -cpu=4 -parallel=4
temp dir: /tmp/memfs945391658
writing small file
writing big file
ready to benchmark ...
testing: warning: no tests to run
PASS
BenchmarkNonExistentMemFS-4       500000              7918 ns/op
BenchmarkNonExistentDiskFS-4      100000             16862 ns/op
BenchmarkSmallFileMemFS-4           2000            811382 ns/op
BenchmarkSmallFileDiskFS-4          2000            836498 ns/op
BenchmarkBigFileMemFS-4                5        4629695200 ns/op
BenchmarkBigFileDiskFS-4               1       11134569000 ns/op
ok      command-line-arguments  109.350s
Reply all
Reply to author
Forward
0 new messages