{"id":55408,"date":"2020-01-20T00:00:00","date_gmt":"2020-01-19T21:00:00","guid":{"rendered":"https:\/\/prohoster.info\/blog\/blog_prohoster\/effektivnoe-hranenie-soten-millionov-malenkih-fajlov-self-hosted-reshenie"},"modified":"2020-02-18T14:03:31","modified_gmt":"2020-02-18T11:03:31","slug":"effektivnoe-hranenie-soten-millionov-malenkih-fajlov-self-hosted-reshenie","status":"publish","type":"post","link":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/effektivnoe-hranenie-soten-millionov-malenkih-fajlov-self-hosted-reshenie","title":{"rendered":"Efficient storage of hundreds of millions of small files. Self-Hosted solution","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><img decoding=\"async\" alt=\"Efficient storage of hundreds of millions of small files. Self-Hosted solution\" src=\"\/wp-content\/uploads\/2020\/01\/39cbe3b73cf4a20dba7e9383f922704f.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nDear community, this article will focus on effective storage and retrieval of hundreds of millions of small files. At this stage, a final solution for POSIX-compatible file systems with full locking support, including clustered locks, is proposed, seemingly even without hacks.<\/p>\n<p>Therefore, for this purpose, I wrote my own specialized server.<br \/>\nIn the course of implementing this task, I managed to solve the main problem while simultaneously achieving savings in disk space and RAM, which was ruthlessly consumed by our clustered file system. Such a quantity of files is detrimental to any clustered file system. <noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<p>The idea is as follows:<\/p>\n<p>Simply put, small files are uploaded through the server, they are saved directly into an archive, and read from it as well, while larger files are stored alongside. The scheme: 1 folder = 1 archive, resulting in several million archives of small files instead of several hundred million files. And all this is implemented in full, without any scripts and not distributing files into tar\/zip archives.<\/p>\n<p>I will try to summarize, I apologize in advance if the post turns out to be extensive.<\/p>\n<p>It all started with the fact that I could not find a suitable server in the world that could save data received via the HTTP protocol directly into archives, avoiding the shortcomings inherent in ordinary archives and object storage. The reason for this search was a sprawling Origin cluster of 10 servers, which has already accumulated 250,000,000 small files, and the growth trend showed no signs of stopping.<\/p>\n<p><b>For those who do not like reading articles and prefer brief documentation:<\/b><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/eltaline\/wzd\/blob\/master\/README-RUS.md\">here<\/a><\/noindex> and <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/eltaline\/wza\/blob\/master\/README-RUS.md\">here<\/a><\/noindex>.<\/p>\n<p>And docker at the same time, currently there is only an option to run it together with nginx just in case:<\/p>\n<pre><code class=\"bash\">docker run -d --restart=always -e host=localhost -e root=\\\/var\\\/storage \n-v \\\/var\\\/storage:\\\/var\\\/storage --name wzd -p 80:80 eltaline\\\/wzd<\/code><\/pre>\n<p>\nNext:<\/p>\n<p>When there are a lot of files, significant resources are required, and unfortunately, some of them go to waste. For example, when using a clustered file system (in this case, MooseFS), a file, regardless of its actual size, always occupies at least 64 KB. That means for files sized 3, 10, or 30 KB, 64 KB is taken up on the disk. If there are a quarter of a billion files, we lose between 2 to 10 terabytes. It won't be possible to endlessly create new files, as MooseFS has a limit: no more than 1 billion with one replica of each file.<\/p>\n<p>As the number of files increases, a lot of RAM is needed for the metadata. Additionally, frequent large metadata dumps contribute to SSD wear.<\/p>\n<p><b>wZD Server. Organizing the disks.<\/b><\/p>\n<p>The server is written in Go. First of all, I needed to reduce the number of files. How can this be done? Through archiving, but in this case without compression, as my files are solid compressed images. BoltDB came to the rescue, but it had to be refined, which is reflected in the documentation.<\/p>\n<p>So instead of a quarter billion files, in my case, there are only 10 million Bolt archives left. If I had the opportunity to change the current directory structure for filling files, it might be possible to reduce it to about 1 million files. <\/p>\n<p>All small files are packed into Bolt archives, automatically acquiring the names of the directories they reside in, while larger files remain next to the archives, as there's no point in packing them, which is configurable. Small ones are archived, large ones are left unchanged. The server operates transparently with both types.<\/p>\n<p><b>Architecture and features of the wZD server.<\/b><\/p>\n<p><img decoding=\"async\" alt=\"Efficient storage of hundreds of millions of small files. Self-Hosted solution\" src=\"\/wp-content\/uploads\/2020\/01\/38e2c6ad50bffe6c2ced460f8dc3e65a.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nThe server operates under Linux, BSD, Solaris, and OSX operating systems. I've only tested it for AMD64 architecture under Linux, but it should also work for ARM64, PPC64, and MIPS64.<\/p>\n<p><b>Key features:<\/b><\/p>\n<ul>\n<li>Multithreading;<\/li>\n<li>Multi-server capabilities providing fault tolerance and load balancing;<\/li>\n<li>Maximum transparency for the user or developer;<\/li>\n<li>Supported HTTP methods: GET, HEAD, PUT, and DELETE;<\/li>\n<li>Control over reading and writing behavior through client headers;<\/li>\n<li>Support for flexibly configurable virtual hosts;<\/li>\n<li>CRC data integrity support during write\/read;<\/li>\n<li>Semi-dynamic buffers for minimal memory consumption and optimal network performance tuning;<\/li>\n<li>Deferred data compaction;<\/li>\n<li>In addition, a multithreaded archiver wZA is offered for migrating files without downtime of the service.<\/li>\n<\/ul>\n<p>\n<b>Real experience:<\/b><\/p>\n<p>I developed and tested a server and archiver on live data for quite a long time, and it now successfully operates on a cluster comprising 250,000,000 small files (images), located in 15,000,000 directories on separate SATA disks. The cluster consists of 10 servers acting as an Origin server, installed behind a CDN network. It is managed using 2 Nginx servers + 2 wZD servers.<\/p>\n<p>For those who decide to use this server, it makes sense to plan the directory structure in advance, if applicable. I should clarify right away that the server is not intended for cramming everything into a single Bolt archive.<\/p>\n<p><b>Performance testing:<\/b><\/p>\n<p>The smaller the size of the archived file, the faster the GET and PUT operations are performed with it. Let\u2019s compare the total writing time of HTTP clients to regular files and to Bolt archives, as well as reading. We compare operations with files of sizes 32 KB, 256 KB, 1024 KB, 4096 KB, and 32768 KB.<\/p>\n<p>When working with Bolt archives, the integrity of each file's data is checked (using CRC), with reading occurring on-the-fly both before and after writing, which naturally introduces delays, but most importantly \u2014 ensures data security. <\/p>\n<p>I conducted performance tests on SSD drives, as tests on SATA disks do not show a clear difference.<\/p>\n<p><b>Charts based on test results:<\/b><\/p>\n<p><img decoding=\"async\" alt=\"Efficient storage of hundreds of millions of small files. Self-Hosted solution\" src=\"\/wp-content\/uploads\/2020\/01\/298ae08420f476ebb128d5254921973a.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<img decoding=\"async\" alt=\"Efficient storage of hundreds of millions of small files. Self-Hosted solution\" src=\"\/wp-content\/uploads\/2020\/01\/a7b081be88d894b50942a98f5a666e0e.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nAs you can see, for small files, the difference in reading and writing time between archived and non-archived files is minimal.<\/p>\n<p>A completely different picture emerges when testing the reading and writing of files sized 32 MB:<\/p>\n<p><img decoding=\"async\" alt=\"Efficient storage of hundreds of millions of small files. Self-Hosted solution\" src=\"\/wp-content\/uploads\/2020\/01\/d908560a26df34c4253c5ed5bd879727.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nThe difference in time between reading files is within 5-25 ms. With writing, the situation is worse, with a difference of about 150 ms. However, in this case, there is no need to upload large files, as it simply makes no sense; they can exist separately from the archives.<\/p>\n<p>*Technically, this server can also be used for tasks requiring NoSQL.<\/p>\n<p><b>Main methods of working with the wZD server:<\/b><\/p>\n<p>Uploading a regular file:<\/p>\n<pre><code class=\"bash\">curl -X PUT --data-binary @test.jpg http:\/\/localhost\/test\/test.jpg<\/code><\/pre>\n<p>\nUploading a file to a Bolt archive (if the server parameter fmaxsize, which defines the maximum file size that can be included in the archive, is not exceeded; otherwise, the file will be uploaded normally next to the archive):<\/p>\n<pre><code class=\"bash\">curl -X PUT -H &quot;Archive: 1&quot; --data-binary @test.jpg http:\/\/localhost\/test\/test.jpg<\/code><\/pre>\n<p>\nFile download (if there are files with the same names on the disk and in the archive, the unarchived file takes precedence by default):<\/p>\n<pre><code class=\"bash\">curl -o test.jpg http:\/\/localhost\/test\/test.jpg<\/code><\/pre>\n<p>\nForce download of a file from the Bolt archive:<\/p>\n<pre><code class=\"bash\">curl -o test.jpg -H &quot;FromArchive: 1&quot; http:\/\/localhost\/test\/test.jpg<\/code><\/pre>\n<p>Descriptions of other methods can be found in the documentation.<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/eltaline\/wzd\/blob\/master\/README-RUS.md\">wZD Documentation<\/a><\/noindex><br \/>\n<noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/eltaline\/wza\/blob\/master\/README-RUS.md\">wZA Documentation<\/a><\/noindex><\/p>\n<p>The server currently only supports the HTTP protocol; HTTPS is not yet operational. The POST method is also unsupported (it is still undecided if it is needed).<\/p>\n<p>Anyone who digs into the source code will discover a caramel treat; not everyone likes it, but I haven't tied the main code to web framework functions, except for the interrupt handler, so I can quickly rewrite it for almost any engine in the future.<\/p>\n<p><b>ToDo:<\/b><\/p>\n<ul>\n<li>Development of a custom replicator and distributor + geo for use in large systems without clustered file systems (All serious)<\/li>\n<li>The possibility of full reverse recovery of metadata in case of complete loss (when using the distributor)<\/li>\n<li>Native protocol for maintaining persistent network connections and drivers for various programming languages<\/li>\n<li>Enhanced capabilities for using NoSQL components<\/li>\n<li>Compression of various types (gzip, zstd, snappy) for files or values within Bolt archives and for regular files<\/li>\n<li>Encryption of various types for files or values within Bolt archives and for regular files<\/li>\n<li>Deferred server-side video conversion, including on GPU<\/li>\n<\/ul>\n<p>\nThat's all from me, I hope this server will be useful to someone. BSD-3 license, dual copyright, as I would not have written the server without the company where I work. I am the sole developer. I would appreciate any bugs found and feature requests.<br \/>\n<br \/>Source: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/484312\/\">habr.com<\/a><\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u0423\u0432\u0430\u0436\u0430\u0435\u043c\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u043e, \u044d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0441\u0432\u044f\u0449\u0435\u043d\u0430 \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u043c\u0443 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044e \u0438 \u0432\u044b\u0434\u0430\u0447\u0435 \u0441\u043e\u0442\u0435\u043d \u043c\u0438\u043b\u043b\u0438\u043e\u043d\u043e\u0432 \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u0445 \u0444\u0430\u0439\u043b\u043e\u0432. \u041d\u0430 \u0434\u0430\u043d\u043d\u043e\u043c \u044d\u0442\u0430\u043f\u0435 \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0435\u0442\u0441\u044f \u043a\u043e\u043d\u0435\u0447\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0434\u043b\u044f POSIX \u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u044b\u0445 \u0444\u0430\u0439\u043b\u043e\u0432\u044b\u0445 \u0441\u0438\u0441\u0442\u0435\u043c \u0441 \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u043e\u0439 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u043e\u0439 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043e\u043a, \u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435 \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u043d\u044b\u0445, \u0438 \u0432\u0440\u043e\u0434\u0435 \u0431\u044b \u0434\u0430\u0436\u0435 \u0443\u0436\u0435 \u0431\u0435\u0437 \u043a\u043e\u0441\u0442\u044b\u043b\u0435\u0439. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u043b\u044f \u044d\u0442\u043e\u0439 \u0446\u0435\u043b\u0438 \u044f \u043d\u0430\u043f\u0438\u0441\u0430\u043b \u0441\u0432\u043e\u0439 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440. \u041f\u043e \u0445\u043e\u0434\u0443 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u044d\u0442\u043e\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-55408","post","type-post","status-publish","format-standard","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.2 - aioseo.com -->\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/effektivnoe-hranenie-soten-millionov-malenkih-fajlov-self-hosted-reshenie\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2\" \/>\n\t\t<meta property=\"og:locale\" content=\"en_US\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u042d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0441\u043e\u0442\u0435\u043d \u043c\u0438\u043b\u043b\u0438\u043e\u043d\u043e\u0432 \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u0445 \u0444\u0430\u0439\u043b\u043e\u0432. Self-Hosted \u0440\u0435\u0448\u0435\u043d\u0438\u0435 | ProHoster\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/effektivnoe-hranenie-soten-millionov-malenkih-fajlov-self-hosted-reshenie\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2020-01-19T21:00:00+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-02-18T11:03:31+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47Efficient storage of hundreds of millions of small files. Self-Hosted solution | ProHoster","description":"","canonical_url":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/effektivnoe-hranenie-soten-millionov-malenkih-fajlov-self-hosted-reshenie","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"en_US","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u042d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0441\u043e\u0442\u0435\u043d \u043c\u0438\u043b\u043b\u0438\u043e\u043d\u043e\u0432 \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u0445 \u0444\u0430\u0439\u043b\u043e\u0432. Self-Hosted \u0440\u0435\u0448\u0435\u043d\u0438\u0435 | ProHoster","og:url":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/effektivnoe-hranenie-soten-millionov-malenkih-fajlov-self-hosted-reshenie","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2020-01-19T21:00:00+00:00","article:modified_time":"2020-02-18T11:03:31+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"55408","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":null,"breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-02-28 19:44:26","updated":"2022-09-30 03:35:08","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts\/55408","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/comments?post=55408"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts\/55408\/revisions"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/media?parent=55408"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/categories?post=55408"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/tags?post=55408"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}