DEV Community

Cover image for Turbocharge Your Go App: Mastering Blazing-Fast Static File Serving Over TCP
aymane aallaoui
aymane aallaoui

Posted on

Turbocharge Your Go App: Mastering Blazing-Fast Static File Serving Over TCP

Hey gophers ๐Ÿน!

Have you ever thought of the best ways to ensure faster serving of static files using TCP in Go? Although there are built-in functions such as http.ServeFile that get the job done in simple file serving tasks, these functions become a hindrance when it comes to extremely large files or when carried out under a substantial load. In this article, we want to tackle advanced problem areas of this process so that people who want to go beyond the typical level of Go development will be pleased.

The Problem

Special attention has to be paid to the file serving speed as it is particularly important in case of heavy traffic. When serving static content through solutions such as http.ServeFile, there are following problems to address:

  • Buffering in One Layer: Data is loaded into memory first and only then sent over the network, creating unneeded memory footprint and delays.

  • Blocking I/O: Performing blocking operations on files can negatively affect the speed, especially if the files are several megabytes.

  • Poor Load Balance: There is no provision for performing file transfers in a more concurrent manner which means that speed is lost.

New Solution: More Optimizations

This is how you can get around these constraints and improve performance:

Zero-Copy File Transfer

Reduce on memory consumption and increase the speed of transfers by using the sendfile system call from the syscall package to accomplish a zero-copy file transfer. Memory in the user space is not involved and the data is โ€˜sentโ€™ directly from the file descriptor to the socket.



import (
"syscall"
"net"
"os"
)

func serveFile(conn net.Conn, filePath string) error {
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()

<span class="n">fileStat</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">file</span><span class="o">.</span><span class="n">Stat</span><span class="p">()</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">err</span>
<span class="p">}</span>

<span class="c">// Directly transfer file content to the connection socket</span>
<span class="n">_</span><span class="p">,</span> <span class="n">err</span> <span class="o">=</span> <span class="n">syscall</span><span class="o">.</span><span class="n">Sendfile</span><span class="p">(</span><span class="kt">int</span><span class="p">(</span><span class="n">conn</span><span class="o">.</span><span class="p">(</span><span class="o">*</span><span class="n">net</span><span class="o">.</span><span class="n">TCPConn</span><span class="p">)</span><span class="o">.</span><span class="n">File</span><span class="p">()</span><span class="o">.</span><span class="n">Fd</span><span class="p">()),</span> <span class="kt">int</span><span class="p">(</span><span class="n">file</span><span class="o">.</span><span class="n">Fd</span><span class="p">()),</span> <span class="no">nil</span><span class="p">,</span> <span class="kt">int</span><span class="p">(</span><span class="n">fileStat</span><span class="o">.</span><span class="n">Size</span><span class="p">()))</span>
<span class="k">return</span> <span class="n">err</span>
Enter fullscreen mode Exit fullscreen mode

}

Enter fullscreen mode Exit fullscreen mode




Gouroutines As An External Async I/O Mechanism

Utilise the concurrency framework in Go by dividing a file transfer into asynchronous pieces. Offload these pieces in parallel employing goroutines to shorten the time wasted in waiting for I/O call to finish.



func asyncServeFile(conn net.Conn, filePath string) error {
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()

<span class="n">buf</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">([]</span><span class="kt">byte</span><span class="p">,</span> <span class="m">32</span><span class="o">*</span><span class="m">1024</span><span class="p">)</span> <span class="c">// 32KB buffer</span>
<span class="k">var</span> <span class="n">wg</span> <span class="n">sync</span><span class="o">.</span><span class="n">WaitGroup</span>

<span class="k">for</span> <span class="p">{</span>
    <span class="n">n</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">file</span><span class="o">.</span><span class="n">Read</span><span class="p">(</span><span class="n">buf</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">n</span> <span class="o">&gt;</span> <span class="m">0</span> <span class="p">{</span>
        <span class="n">wg</span><span class="o">.</span><span class="n">Add</span><span class="p">(</span><span class="m">1</span><span class="p">)</span>
        <span class="k">go</span> <span class="k">func</span><span class="p">(</span><span class="n">data</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">defer</span> <span class="n">wg</span><span class="o">.</span><span class="n">Done</span><span class="p">()</span>
            <span class="n">conn</span><span class="o">.</span><span class="n">Write</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
        <span class="p">}(</span><span class="n">buf</span><span class="p">[</span><span class="o">:</span><span class="n">n</span><span class="p">])</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
        <span class="k">if</span> <span class="n">err</span> <span class="o">==</span> <span class="n">io</span><span class="o">.</span><span class="n">EOF</span> <span class="p">{</span>
            <span class="k">break</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="n">err</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="n">wg</span><span class="o">.</span><span class="n">Wait</span><span class="p">()</span>
<span class="k">return</span> <span class="no">nil</span>
Enter fullscreen mode Exit fullscreen mode

}

Enter fullscreen mode Exit fullscreen mode




Focus On The Critical Sections

All the sections of the file may not be of equal merit. For illustration, video files which can start playing may require video metadata. Focus on such sections in order to enhance the perceived speed within the user interface.



func serveCriticalSections(conn net.Conn, filePath string, criticalSections []fileRange) error {
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()

<span class="k">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">section</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">criticalSections</span> <span class="p">{</span>
    <span class="n">buf</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">([]</span><span class="kt">byte</span><span class="p">,</span> <span class="n">section</span><span class="o">.</span><span class="n">length</span><span class="p">)</span>
    <span class="n">_</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">file</span><span class="o">.</span><span class="n">ReadAt</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="n">section</span><span class="o">.</span><span class="n">offset</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">err</span>
    <span class="p">}</span>
    <span class="n">conn</span><span class="o">.</span><span class="n">Write</span><span class="p">(</span><span class="n">buf</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">return</span> <span class="no">nil</span>
Enter fullscreen mode Exit fullscreen mode

}

Enter fullscreen mode Exit fullscreen mode




Conclusion

There is more to optimizing the handling of static file transfers over TCP in Go than just making use of the built-in facilities. Enhanced performance of the application can be achieved through the utilization of zero-copy transfer of files, asynchronous file I/O and management of critical segments of files. These methods enable high traffic and handling of huge files without losing user satisfaction.

That said happy coding and hope that you will not have any problem transferring your files next time. and always remember to just beat it

gopher

Top comments (0)