<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Viacheslav Poturaev</title>
    <description>The latest articles on Forem by Viacheslav Poturaev (@vearutop).</description>
    <link>https://forem.com/vearutop</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F208860%2Faa5548ca-6499-4b2d-9c40-54881625d508.jpeg</url>
      <title>Forem: Viacheslav Poturaev</title>
      <link>https://forem.com/vearutop</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/vearutop"/>
    <language>en</language>
    <item>
      <title>Finding a Practical Analytics Format for Structured JSON Logs</title>
      <dc:creator>Viacheslav Poturaev</dc:creator>
      <pubDate>Thu, 23 Apr 2026 17:01:17 +0000</pubDate>
      <link>https://forem.com/vearutop/finding-a-practical-analytics-format-for-structured-json-logs-32l1</link>
      <guid>https://forem.com/vearutop/finding-a-practical-analytics-format-for-structured-json-logs-32l1</guid>
      <description>&lt;p&gt;Structured JSON logs are easy to produce and hard to analyze at scale. They carry useful context, but that context is nested, optional, inconsistent, and often wider than expected. Before the data can be queried comfortably, it usually has to become a table.&lt;/p&gt;

&lt;p&gt;The question is not only "can we flatten it?" The more useful question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;After flattening, what should the output be?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;CSV is simple and fast. Parquet is compact and portable. DuckDB gives a ready-to-query local database. SQLite is widely available. Direct database APIs look convenient from Go, but may not behave like bulk loaders.&lt;/p&gt;

&lt;p&gt;This note walks through a small discovery process: define realistic shapes, run the same flattening flow through several output types, and compare write/export cost plus resulting artifact size.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Contestants
&lt;/h2&gt;

&lt;p&gt;The tested output types were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CSV file:&lt;/strong&gt; plain text table output, the simplest baseline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parquet Snappy:&lt;/strong&gt; portable columnar file with fast compression.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parquet Zstd:&lt;/strong&gt; portable columnar file with stronger compression.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DuckDB CLI stdin:&lt;/strong&gt; stream CSV into the &lt;code&gt;duckdb&lt;/code&gt; CLI and create a database table. This path relies on DuckDB's CSV reader with automatic type detection rather than a hand-declared schema.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite CLI import:&lt;/strong&gt; stream CSV into the &lt;code&gt;sqlite3&lt;/code&gt; CLI and create a database table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite direct inserts:&lt;/strong&gt; write rows through the Go SQLite driver.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DuckDB native appender experiment:&lt;/strong&gt; write rows through a native DuckDB Go appender driver.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The CLI output types matter because they behave like bulk loaders. They let &lt;a href="https://github.com/vearutop/flatjsonl" rel="noopener noreferrer"&gt;&lt;code&gt;flatjsonl&lt;/code&gt;&lt;/a&gt; focus on flattening and streaming rows, while the database process handles ingestion in its optimized path.&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;flatjsonl&lt;/code&gt; Flow
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/vearutop/flatjsonl" rel="noopener noreferrer"&gt;&lt;code&gt;flatjsonl&lt;/code&gt;&lt;/a&gt; uses two passes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scan the input to discover keys and infer column types.&lt;/li&gt;
&lt;li&gt;Read the input again, flatten each JSON object, and write rows to the chosen output.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first pass is mostly independent of output format. For comparing CSV, Parquet, SQLite, and DuckDB, the second pass is more interesting: it includes flattening, value conversion, writing/importing, compression, and finalization.&lt;/p&gt;

&lt;p&gt;So the main timing metric below is &lt;strong&gt;Export time&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Export time = total wall time - key scan time
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This intentionally ignores the common discovery pass and focuses on the output-side cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Data Shapes
&lt;/h2&gt;

&lt;p&gt;Flattened JSON performance depends heavily on shape. Three patterns were used:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Rows&lt;/th&gt;
&lt;th&gt;Result Columns&lt;/th&gt;
&lt;th&gt;Type Mix&lt;/th&gt;
&lt;th&gt;Shape&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Narrow&lt;/td&gt;
&lt;td&gt;5691804&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;Mostly strings, a few bools and ints.&lt;/td&gt;
&lt;td&gt;Small reporting extract: low/medium-cardinality dimensions, one numeric measure, and status flags.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Normal&lt;/td&gt;
&lt;td&gt;5691804&lt;/td&gt;
&lt;td&gt;118&lt;/td&gt;
&lt;td&gt;String-heavy with more ints and bool flags.&lt;/td&gt;
&lt;td&gt;Practical analytics extract: request metadata, timestamps, dimensions, numeric measures, duration windows and flags.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wide&lt;/td&gt;
&lt;td&gt;193217&lt;/td&gt;
&lt;td&gt;about 1680&lt;/td&gt;
&lt;td&gt;Mostly optional strings and sparse dimensions, with some ints/bools/floats.&lt;/td&gt;
&lt;td&gt;Stress case for rich nested JSON logs with many rarely-populated and high-cardinality columns.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The exact field names are not important here. The important properties are row count, column count, type mix, string cardinality, sparse optional fields, and how much text has to move through the writer.&lt;/p&gt;

&lt;p&gt;All numbers are approximate. These are production-shaped samples rather than controlled synthetic fixtures. The goal is to build intuition about tradeoffs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading The Results
&lt;/h2&gt;

&lt;p&gt;The tables use relative cost indexes.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;100%&lt;/code&gt; is the best measured result in that scenario. Higher is worse.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;250%&lt;/code&gt; write/export time means about 2.5x slower than the fastest path for that data shape.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;250%&lt;/code&gt; artifact size means about 2.5x larger than the smallest artifact for that data shape.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Write/Export Time
&lt;/h2&gt;

&lt;p&gt;This table excludes key scanning and focuses on flattening plus writing/importing.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Narrow&lt;/th&gt;
&lt;th&gt;Normal&lt;/th&gt;
&lt;th&gt;Wide&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CSV file&lt;/td&gt;
&lt;td&gt;&lt;code&gt;100%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;100%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;100%&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parquet, Snappy&lt;/td&gt;
&lt;td&gt;&lt;code&gt;102%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;130%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;253%&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parquet, Zstd&lt;/td&gt;
&lt;td&gt;&lt;code&gt;103%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;142%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;289%&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DuckDB CLI stdin&lt;/td&gt;
&lt;td&gt;&lt;code&gt;103%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;199%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;384%&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQLite CLI CSV import&lt;/td&gt;
&lt;td&gt;&lt;code&gt;103%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;246%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;596%&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQLite direct inserts&lt;/td&gt;
&lt;td&gt;&lt;code&gt;130%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;534%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2932%&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DuckDB native appender (experimental)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;253%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1089%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2440%&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The fastest absolute export times were:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Best Output&lt;/th&gt;
&lt;th&gt;Best Export Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Narrow&lt;/td&gt;
&lt;td&gt;CSV file&lt;/td&gt;
&lt;td&gt;&lt;code&gt;35.1s&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Normal&lt;/td&gt;
&lt;td&gt;CSV file&lt;/td&gt;
&lt;td&gt;&lt;code&gt;35.9s&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wide&lt;/td&gt;
&lt;td&gt;CSV file&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.6s&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The narrow case is almost flat: CSV, Parquet, DuckDB CLI, and SQLite CLI are all within a few percent. In the normal and wide cases, writer choice starts to matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Artifact Size
&lt;/h2&gt;

&lt;p&gt;This table compares resulting file/database size.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Narrow&lt;/th&gt;
&lt;th&gt;Normal&lt;/th&gt;
&lt;th&gt;Wide&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CSV file&lt;/td&gt;
&lt;td&gt;&lt;code&gt;828%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;635%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;264%&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parquet, Snappy&lt;/td&gt;
&lt;td&gt;&lt;code&gt;156%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;138%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;122%&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parquet, Zstd&lt;/td&gt;
&lt;td&gt;&lt;code&gt;100%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;100%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;101%&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DuckDB CLI stdin&lt;/td&gt;
&lt;td&gt;&lt;code&gt;113%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;141%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;100%&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQLite CLI CSV import&lt;/td&gt;
&lt;td&gt;&lt;code&gt;901%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;708%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;280%&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQLite direct inserts&lt;/td&gt;
&lt;td&gt;&lt;code&gt;901%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;670%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;274%&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DuckDB native appender (experimental)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;212%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;177%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;130%&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The winners were:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Smallest Path&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Narrow&lt;/td&gt;
&lt;td&gt;Parquet, Zstd&lt;/td&gt;
&lt;td&gt;&lt;code&gt;90M&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Normal&lt;/td&gt;
&lt;td&gt;Parquet, Zstd&lt;/td&gt;
&lt;td&gt;&lt;code&gt;502M&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wide&lt;/td&gt;
&lt;td&gt;DuckDB CLI stdin&lt;/td&gt;
&lt;td&gt;&lt;code&gt;732M&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;CSV wins on write speed, but it is consistently large. Compressed Parquet and DuckDB are much more storage-efficient.&lt;/p&gt;

&lt;p&gt;The native DuckDB appender path was an experiment, not the final export implementation. Its database schema was not identical to the CLI path: it included an extra sequence primary key and used less precise types for some fields. Treat its size and time as a cautionary data point for row-wise appender ingestion, not as a tuned DuckDB baseline.&lt;/p&gt;

&lt;p&gt;The size gap is also a useful reminder that DuckDB is columnar. Physical size is sensitive to column types, compression opportunities, and how data is loaded. SQLite behaved differently in the same experiment: CLI import and direct inserts produced similarly sized files even when their declared schemas differed, because SQLite stores row records with dynamic typing rather than compressed column segments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample Analytical Query
&lt;/h2&gt;

&lt;p&gt;Export speed is only half of the story. The point of making a table is usually to query it.&lt;/p&gt;

&lt;p&gt;As a small read-side check, I ran a grouped count over one string dimension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;os_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;flatjsonl&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;os_name&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For CSV and Parquet, DuckDB queried the files directly. They were not imported into a database first. SQLite databases were queried with &lt;code&gt;sqlite3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;These timings are single local runs and should be read as rough order-of-magnitude results.&lt;/p&gt;

&lt;p&gt;No indexes were created for this query. That is intentional: the goal is to compare raw analytical scan behavior of the artifacts as produced by the export step. Adding indexes, especially to SQLite or DuckDB tables, could change query performance drastically for selective filters or repeated lookups. At that point the benchmark would be measuring index design and maintenance cost rather than the default export artifact.&lt;/p&gt;

&lt;p&gt;Cells show relative query cost and absolute time. Percentages are normalized independently down each data-shape column, because the datasets differ in row count, width, and value distribution. &lt;code&gt;100%&lt;/code&gt; is the fastest result in that column, and higher percentages are slower.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Narrow&lt;/th&gt;
&lt;th&gt;Normal&lt;/th&gt;
&lt;th&gt;Wide&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CSV via DuckDB direct scan&lt;/td&gt;
&lt;td&gt;&lt;code&gt;600% / 0.36s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2860% / 1.43s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;9800% / 3.92s&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parquet Snappy via DuckDB&lt;/td&gt;
&lt;td&gt;&lt;code&gt;200% / 0.12s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;240% / 0.12s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1700% / 0.68s&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parquet Zstd via DuckDB&lt;/td&gt;
&lt;td&gt;&lt;code&gt;167% / 0.10s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;260% / 0.13s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1675% / 0.67s&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DuckDB database&lt;/td&gt;
&lt;td&gt;&lt;code&gt;100% / 0.06s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;100% / 0.05s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;125% / 0.05s&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQLite CLI database&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3750% / 2.25s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;8360% / 4.18s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;13450% / 5.38s&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQLite direct database&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4217% / 2.53s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;9180% / 4.59s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;14250% / 5.70s&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DuckDB native appender database&lt;/td&gt;
&lt;td&gt;&lt;code&gt;100% / 0.06s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;100% / 0.05s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;100% / 0.04s&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This query is favorable to columnar formats: it touches one dimension column and computes a small aggregation. DuckDB database files and Parquet are very fast. CSV has to parse text again and gets slower as the file gets wider/larger. SQLite is functional, but slower for this analytical scan.&lt;/p&gt;

&lt;p&gt;The wide result highlights an important distinction: Parquet is columnar, but it is still an external file format. DuckDB can query it efficiently, but it still has to read Parquet metadata, decode Parquet pages and adapt the data into DuckDB execution vectors. A DuckDB database is already in DuckDB's native storage layout, with its own column segments, compression, statistics and execution-friendly metadata. For repeated DuckDB queries, especially ones touching a tiny subset of a very wide table, the native database can be noticeably faster even when Parquet is also columnar.&lt;/p&gt;

&lt;p&gt;I also repeated the query with a second dimension that has noticeably higher cardinality than a very low-cardinality first field (&lt;code&gt;os_name&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;os_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;flatjsonl&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;os_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes every path slower than the earlier one-field query, but the overall ranking stays the same. Row-oriented databases pay much more once the grouping key widens from one dimension to two.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Narrow&lt;/th&gt;
&lt;th&gt;Normal&lt;/th&gt;
&lt;th&gt;Wide&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CSV via DuckDB direct scan&lt;/td&gt;
&lt;td&gt;&lt;code&gt;370% / 0.37s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1567% / 1.41s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;5113% / 4.09s&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parquet Snappy via DuckDB&lt;/td&gt;
&lt;td&gt;&lt;code&gt;160% / 0.16s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;167% / 0.15s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;975% / 0.78s&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parquet Zstd via DuckDB&lt;/td&gt;
&lt;td&gt;&lt;code&gt;150% / 0.15s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;178% / 0.16s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;975% / 0.78s&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DuckDB database&lt;/td&gt;
&lt;td&gt;&lt;code&gt;100% / 0.10s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;100% / 0.09s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;100% / 0.08s&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQLite CLI database&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4110% / 4.11s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;7078% / 6.37s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;7138% / 5.71s&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQLite direct database&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3990% / 3.99s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;6844% / 6.16s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;7488% / 5.99s&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DuckDB native appender database&lt;/td&gt;
&lt;td&gt;&lt;code&gt;100% / 0.10s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;100% / 0.09s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;100% / 0.08s&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;DuckDB stayed surprisingly fast even after widening the grouping key. The likely reason is that the query still touches only a tiny subset of the table: two projected dimension columns plus the row count. In a columnar, vectorized engine, adding one more grouped column is much cheaper than widening a row-store scan across the full record. The query does get slower, but not dramatically, because it still reads only a few columns and the resulting group count remains manageable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What The Shapes Teach
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Narrow
&lt;/h3&gt;

&lt;p&gt;The narrow shape has only 23 columns. Most values are short strings with low or medium cardinality, plus one numeric measure and a few boolean flags.&lt;/p&gt;

&lt;p&gt;For this shape, output choice barely affects write/export time. The data pass itself dominates. CSV, Parquet, DuckDB CLI, and SQLite CLI all finish in the same practical range.&lt;/p&gt;

&lt;p&gt;Artifact size still changes dramatically. CSV and SQLite are roughly 8-9x larger than Zstd Parquet. DuckDB CLI and Zstd Parquet are both compact enough that the choice mainly depends on whether the desired result is a database file or a portable columnar file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Normal
&lt;/h3&gt;

&lt;p&gt;The normal shape has 118 columns. It is still string-heavy, but it includes enough numeric measures, duration/window fields, timestamps, request metadata, and boolean flags to look like a practical operational analytics table.&lt;/p&gt;

&lt;p&gt;This is where writer cost becomes visible. CSV remains fastest, but produces a very large artifact. Parquet Snappy adds moderate overhead and cuts size substantially. Parquet Zstd costs a bit more time and gives the smallest file.&lt;/p&gt;

&lt;p&gt;DuckDB CLI is slower than Parquet, but it creates a ready-to-query database in one step. SQLite CLI remains workable, but its size and write cost are less attractive. Direct row-wise database APIs are already a poor fit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wide
&lt;/h3&gt;

&lt;p&gt;The wide shape has fewer rows but about 1680 columns. It is sparse and string-heavy: optional identifiers, free-form strings, long text fields, and nested dimensions dominate the schema. Numeric and boolean fields are mixed in, but they are not what makes the workload difficult.&lt;/p&gt;

&lt;p&gt;This is where format and ingestion path dominate. Parquet costs more CPU than CSV, but cuts storage substantially. DuckDB CLI produces the smallest measured artifact and avoids a separate import step, but costs more write/export time than Parquet.&lt;/p&gt;

&lt;p&gt;SQLite CLI is much faster than direct SQLite inserts, but still slow and large compared with Parquet or DuckDB. Direct row-wise database APIs spend too much time crossing driver boundaries, converting values, appending/binding thousands of cells per row, and managing memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Conclusions
&lt;/h2&gt;

&lt;p&gt;Use &lt;strong&gt;CSV&lt;/strong&gt; when the main goal is fastest raw export and temporary text output is acceptable.&lt;/p&gt;

&lt;p&gt;Use &lt;strong&gt;Parquet Snappy&lt;/strong&gt; as the default portable analytics artifact. It is much smaller than CSV and has acceptable write overhead for narrow and normal shapes.&lt;/p&gt;

&lt;p&gt;Use &lt;strong&gt;Parquet Zstd&lt;/strong&gt; when storage or transfer size matters more than a bit of extra CPU.&lt;/p&gt;

&lt;p&gt;Use &lt;strong&gt;DuckDB CLI&lt;/strong&gt; when the desired output is directly a DuckDB database. It is competitive on import across narrow, normal, and wide shapes, and then gives excellent query performance once the data is loaded. In these experiments it was especially strong for analytical scans that touched only a small subset of columns.&lt;/p&gt;

&lt;p&gt;Use &lt;strong&gt;SQLite CLI&lt;/strong&gt; when SQLite compatibility is required. Prefer CLI bulk loading over direct inserts.&lt;/p&gt;

&lt;p&gt;Avoid direct row-wise database insertion for uncertain production JSON shapes unless the dataset is small or the schema is known to stay narrow.&lt;/p&gt;

&lt;p&gt;The broader lesson is that "structured logs" are not one benchmark. Row count, column count, string cardinality, sparse optional fields, and output format all matter. For exploratory analytics at scale, the safest default is to produce a portable columnar artifact first, and only create a database file directly when that is the actual target.&lt;/p&gt;

</description>
      <category>json</category>
      <category>go</category>
      <category>analytics</category>
      <category>sql</category>
    </item>
    <item>
      <title>Publishing Markdown to Confluence using GitHub Actions</title>
      <dc:creator>Viacheslav Poturaev</dc:creator>
      <pubDate>Wed, 21 Feb 2024 23:30:11 +0000</pubDate>
      <link>https://forem.com/vearutop/publishing-markdown-to-confluence-using-github-actions-1k4g</link>
      <guid>https://forem.com/vearutop/publishing-markdown-to-confluence-using-github-actions-1k4g</guid>
      <description>&lt;p&gt;&lt;em&gt;TL;DR We're going to setup automated Markdown export from a GitHub repository to Confluence to benefit from knowledge locality in a centralized storage.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Markdown&lt;/code&gt; is a lightweight markup language for creating formatted text using a plain-text editor.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Markdown" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/Markdown&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the growing popularity of GitHub and other code hosting services, &lt;code&gt;Markdown&lt;/code&gt; has become widely used as a language for documentation. It is very convenient to keep nicely formatted documentation close to the code it describes.&lt;/p&gt;

&lt;p&gt;While &lt;code&gt;Markdown&lt;/code&gt; in code repositories may lack advanced features such as search index or rich web components, it has the advantages of simplicity, lower chance of becoming obsolete and clear versioning.&lt;/p&gt;

&lt;p&gt;With the addition of support for &lt;a href="https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/" rel="noopener noreferrer"&gt;mermaid diagrams&lt;/a&gt; in GitHub, &lt;code&gt;Markdown&lt;/code&gt; has become a very powerful tool for versioned and accurate documentation.&lt;/p&gt;

&lt;p&gt;In contrast, Atlassian Confluence acts as a centralized repository of information, often used by large organizations to share knowledge across people and departments. There are advantages to such an approach; access control and discoverability may be better. For example, access to code repositories is usually given to developers who work with it, not to people who support users of the product.&lt;/p&gt;

&lt;p&gt;However, Confluence often becomes an information graveyard. As the codebase evolves, pages become obsolete or even misleading.&lt;/p&gt;

&lt;p&gt;Could you have the best of both worlds? The answer is "Yes"!&lt;/p&gt;

&lt;p&gt;We can organize the source of truth for code-related documents into &lt;code&gt;Markdown&lt;/code&gt; files that live in code repositories, and then automatically sync changes to Confluence using GitHub Actions.&lt;/p&gt;

&lt;p&gt;There is a &lt;a href="https://markdown-confluence.com/usage/github-actions.html" rel="noopener noreferrer"&gt;&lt;code&gt;markdown-confluence&lt;/code&gt;&lt;/a&gt; tool and a GitHub action around it that makes the export process much easier than it could have been.&lt;/p&gt;

&lt;p&gt;Let's take an example repo and configure export of &lt;code&gt;OpenAPI&lt;/code&gt; documentation and handwritten &lt;code&gt;README.md&lt;/code&gt; into Confluence.&lt;/p&gt;

&lt;p&gt;First thing to do is to get an API token. If you have enough permissions, you can create one at your &lt;a href="https://id.atlassian.com/manage-profile/security/api-tokens" rel="noopener noreferrer"&gt;profile page&lt;/a&gt;, otherwise you'll probably need to contact a person that manages your Confluence.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flgd2xb4yvcxt1fi8v63u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flgd2xb4yvcxt1fi8v63u.png" alt="Confluence API token"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then you need to provide credentials to GitHub Actions with repository secrets, use email for &lt;code&gt;ATLASSIAN_USERNAME&lt;/code&gt; and API token for &lt;code&gt;ATLASSIAN_API_TOKEN&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbjyja4d93113h9kblvgb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbjyja4d93113h9kblvgb.png" alt="GitHub Actions secrets"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have a &lt;a href="https://github.com/vearutop/cache-story" rel="noopener noreferrer"&gt;toy service&lt;/a&gt; that I used for &lt;a href="https://dev.to/vearutop/implementing-robust-in-memory-cache-with-go-196e"&gt;one of my previous posts&lt;/a&gt;. It has a GitHub Action &lt;a href="https://github.com/vearutop/cache-story/blob/v1.1.5/.github/workflows/docs.yml#L34-L44" rel="noopener noreferrer"&gt;workflow&lt;/a&gt; to keep the OpenAPI documentation up to date on a GitHub wiki page.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="nn"&gt;...&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout wiki&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{github.repository}}.wiki&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wiki&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build openapi.json&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;make build&lt;/span&gt;
          &lt;span class="s"&gt;./bin/* -openapi &amp;gt; openapi.json&lt;/span&gt;
          &lt;span class="s"&gt;cat ./openapi.json&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate markdown docs&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker://swaggest/swac&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/bin/sh -c "swac markdown ./openapi.json --add-schema-url openapi.json --out ./wiki/API-Docs.md;mv -f ./openapi.json ./wiki/openapi.json"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Push to wiki&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;cd wiki&lt;/span&gt;
          &lt;span class="s"&gt;git config --local user.email "action@github.com"&lt;/span&gt;
          &lt;span class="s"&gt;git config --local user.name "GitHub Action"&lt;/span&gt;
          &lt;span class="s"&gt;git add .&lt;/span&gt;
          &lt;span class="s"&gt;git diff-index --quiet HEAD || git commit -m "Add changes" &amp;amp;&amp;amp; git push&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let's upgrade this workflow to push a document to Confluence.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: Official doc has a misleading suggestion of &lt;code&gt;markdown-confluence/publish@v1&lt;/code&gt;, workflow fails with&lt;br&gt;
&lt;em&gt;Error: Unable to resolve action markdown-confluence/publish, repository not found&lt;/em&gt;&lt;br&gt;
&lt;code&gt;markdown-confluence/publish-action@v5&lt;/code&gt; works well.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Publish Markdown to Confluence&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;markdown-confluence/publish-action@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;confluenceBaseUrl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://vearutop.atlassian.net&lt;/span&gt;
          &lt;span class="na"&gt;confluenceParentId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;65538&lt;/span&gt;
          &lt;span class="na"&gt;folderToPublish&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wiki&lt;/span&gt;
          &lt;span class="na"&gt;atlassianUserName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.ATLASSIAN_USERNAME }}&lt;/span&gt;
          &lt;span class="na"&gt;atlassianApiToken&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.ATLASSIAN_API_TOKEN }}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Unfortunately, the result wasn't that great, &lt;code&gt;Markdown&lt;/code&gt; flavors are different between GitHub and Confluence.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7x0nsmg8pezx2y6u2c3s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7x0nsmg8pezx2y6u2c3s.png" alt="Broken Markdown rendering"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Confluence doesn't allow manual anchoring with&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;

&lt;span class="gu"&gt;## &amp;lt;a id="operations"&amp;gt;&amp;lt;/a&amp;gt; Operations&lt;/span&gt;&lt;span class="sb"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Instead, it expects local anchors to be in form of urlencoded value with some filtering (spaces are replaced by hyphens, backticks are removed).&lt;/p&gt;

&lt;p&gt;Also, headers within list items are not supported. Making a spoiler with &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; does not work either. These are the few things I found while experimenting with Confluence export.&lt;/p&gt;

&lt;p&gt;Once I've updated OpenAPI Markdown generator to accommodate for these quirks, the result was much better. The list was rendered correctly, local links to schema definitions started working.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fol5uhn5y4tptmzkzvrry.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fol5uhn5y4tptmzkzvrry.png" alt="Better Markdown rendering"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now I'd like to publish a page with some images and diagrams. &lt;code&gt;README.md&lt;/code&gt; of the repo is a good candidate. It has both images and mermaid diagrams.&lt;/p&gt;

&lt;p&gt;Result is surprisingly good, images are displayed, &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fse99fk5eco830jqso2ua.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fse99fk5eco830jqso2ua.png" alt="Image rendering"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;mermaid diagrams too (though rendered as PNG).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpag8jlpm69e86nyd0i65.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpag8jlpm69e86nyd0i65.png" alt="Mermaid rendering"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Spoiler didn't work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frgguk7j8e527ysu83499.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frgguk7j8e527ysu83499.png" alt="Spoiler rendering"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub Action to publish Markdown to Confluence accepts path to directory with &lt;code&gt;.md&lt;/code&gt; files. If you need to publish under a different parent id, you'll probably need to build a directory structure and invoke publishing with different configurations as separate steps.&lt;/p&gt;

&lt;p&gt;Here is a final &lt;a href="https://github.com/vearutop/cache-story/blob/v1.1.6/.github/workflows/docs.yml" rel="noopener noreferrer"&gt;example workflow&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>confluence</category>
      <category>githubactions</category>
      <category>markdown</category>
    </item>
    <item>
      <title>Building a portable face recognition application with Go and dlib</title>
      <dc:creator>Viacheslav Poturaev</dc:creator>
      <pubDate>Thu, 01 Feb 2024 13:43:08 +0000</pubDate>
      <link>https://forem.com/vearutop/building-a-portable-face-recognition-application-with-go-and-dlib-12p1</link>
      <guid>https://forem.com/vearutop/building-a-portable-face-recognition-application-with-go-and-dlib-12p1</guid>
      <description>&lt;p&gt;&lt;em&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; We're going to build a portable &lt;a href="https://github.com/vearutop/faces"&gt;facial recognition microservice&lt;/a&gt; for Linux using static linker in Go build with CGO dependencies.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;One of the great things about Go is portability of applications built with it.&lt;/p&gt;

&lt;p&gt;You can build a binary for different platforms and run it without installing any dependencies on the target machine. This becomes especially important if such a machine is an older low-end server with limited resources that would struggle to build an app or install needed shared libraries.&lt;/p&gt;

&lt;p&gt;Building static apps is easy when all code is in Go, but it can become more complicated with CGO dependencies.&lt;/p&gt;

&lt;p&gt;Davis King has made an awesome C++ &lt;a href="https://github.com/davisking/dlib"&gt;dlib&lt;/a&gt; library, that can efficiently detect people faces in a photo. Kagami Hiiragi built a &lt;a href="https://github.com/Kagami/go-face"&gt;go-face&lt;/a&gt; library that allows using dlib from a Go application.&lt;/p&gt;

&lt;p&gt;The installation guide mentions a few dependencies needed for a successful build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt-get install libdlib-dev libblas-dev libatlas-base-dev liblapack-dev libjpeg-turbo8-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, the build will depend on shared libraries and then, if you try to run the resulting binary somewhere else, it may fail due to missing file, like here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./faces: error while loading shared libraries: libdlib.so.19: cannot open shared object file: No such file or directory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Linux has an &lt;code&gt;ldd&lt;/code&gt; tool to inspect binary dependencies, most common problems when you try to run a binary built on another Linux machine are version mismatch of &lt;code&gt;GLIBC&lt;/code&gt; and missing libraries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ldd ./faces
./faces: /usr/lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.26' not found (required by ./faces)
    linux-vdso.so.1 (0x00007fff871b5000)
    libdlib.so.19 =&amp;gt; not found
    libblas.so.3 =&amp;gt; /usr/lib/x86_64-linux-gnu/libblas.so.3 (0x00007f35f50b0000)
    liblapack.so.3 =&amp;gt; /usr/lib/x86_64-linux-gnu/liblapack.so.3 (0x00007f35f47f1000)
    libjpeg.so.8 =&amp;gt; /usr/lib/x86_64-linux-gnu/libjpeg.so.8 (0x00007f35f4589000)
    libresolv.so.2 =&amp;gt; /lib/x86_64-linux-gnu/libresolv.so.2 (0x00007f35f436f000)
    libpthread.so.0 =&amp;gt; /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f35f4150000)
    libstdc++.so.6 =&amp;gt; /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f35f3dc7000)
    libm.so.6 =&amp;gt; /lib/x86_64-linux-gnu/libm.so.6 (0x00007f35f3a29000)
    libgcc_s.so.1 =&amp;gt; /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f35f3811000)
    libc.so.6 =&amp;gt; /lib/x86_64-linux-gnu/libc.so.6 (0x00007f35f3420000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f35f5672000)
    libgfortran.so.4 =&amp;gt; /usr/lib/x86_64-linux-gnu/libgfortran.so.4 (0x00007f35f3041000)
    libquadmath.so.0 =&amp;gt; /usr/lib/x86_64-linux-gnu/libquadmath.so.0 (0x00007f35f2e01000)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm hosting my personal &lt;a href="https://vearutop.p1cs.art"&gt;photo-blog&lt;/a&gt; (&lt;a href="https://github.com/vearutop/photo-blog"&gt;github&lt;/a&gt;) on a free VM in Oracle Cloud, and because I've set this machine up quite a while ago, it is stuck at Ubuntu 18.04 with only older libraries available by default. This was a reason I've tried to enable face recognition with a statically built app.&lt;/p&gt;

&lt;p&gt;To separate fast-paced development with easy builds from complicated builds, I decided to implement facial recognition as a standalone microservice: &lt;a href="https://github.com/vearutop/faces"&gt;https://github.com/vearutop/faces&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's see if we can get rid of dynamic dependencies and improve portability with static build.&lt;/p&gt;

&lt;p&gt;Dlib already has everything needed for a build in isolation, but by default it would dynamically link with installed libs if they are available. Because of that, I'll try to build in a clean docker environment.&lt;/p&gt;

&lt;p&gt;Let's create a playground Dockerfile.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./docker/Dockerfile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ubuntu:22.04&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; build-essential cmake curl
&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-sLO&lt;/span&gt; https://go.dev/dl/go1.21.6.linux-amd64.tar.gz &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; /usr/local &lt;span class="nt"&gt;-xzf&lt;/span&gt; go1.21.6.linux-amd64.tar.gz &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; go1.21.6.linux-amd64.tar.gz
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; /dlib &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; /dlib &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; curl &lt;span class="nt"&gt;-sLO&lt;/span&gt; http://dlib.net/files/dlib-19.24.tar.bz2 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;tar &lt;/span&gt;xf dlib-19.24.tar.bz2
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /dlib/dlib-19.24 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mkdir &lt;/span&gt;build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; cmake .. &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; cmake &lt;span class="nt"&gt;--build&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--config&lt;/span&gt; Release &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /dlib
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build -f ./docker/Dockerfile -t builder .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the build is ready, we can get into a container with our app code mounted.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run --rm -v $PWD:/app -w /app -it builder /bin/bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@f08033dcccbc:/app# ls
LICENSE  Makefile  README.md  bin  dev_test.go  docker  faces.go  go.mod  go.sum  models  unit.coverprofile  vendor

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've downloaded all dependencies with &lt;code&gt;go mod vendor&lt;/code&gt; to simplify operations in the container.&lt;/p&gt;

&lt;p&gt;In order to build statically, we need to set &lt;code&gt;CGO_LDFLAGS="-static"&lt;/code&gt; for the &lt;code&gt;go build&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@f08033dcccbc:/app# CGO_LDFLAGS="-static" /usr/local/go/bin/go build .
# github.com/Kagami/go-face
jpeg_mem_loader.cc:3:10: fatal error: jpeglib.h: No such file or directory
    3 | #include &amp;lt;jpeglib.h&amp;gt;
      |          ^~~~~~~~~~~
compilation terminated.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build failed on a missing header file that was supposed to be installed with one of the dependencies. Let's see if we have that file somewhere in container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@f08033dcccbc:/app# find / -name '*jpeglib.h'
/usr/local/include/dlib/external/libjpeg/jpeglib.h
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Header files are looked up in &lt;code&gt;/usr/include/&lt;/code&gt; by default. For a quick and dirty fix, we can copy the missing file(s) there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@f08033dcccbc:/app# cp /usr/local/include/dlib/external/libjpeg/*.h /usr/include/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's run the build again!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@f08033dcccbc:/app# CGO_LDFLAGS="-static" /usr/local/go/bin/go build .
# github.com/vearutop/faces
/usr/local/go/pkg/tool/linux_amd64/link: running g++ failed: exit status 1
/usr/bin/ld: cannot find -lblas: No such file or directory
/usr/bin/ld: cannot find -lcblas: No such file or directory
/usr/bin/ld: cannot find -llapack: No such file or directory
/usr/bin/ld: cannot find -ljpeg: No such file or directory
collect2: error: ld returned 1 exit status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bad luck, it failed with another error now. Linker complains that it cannot link against a few missing libs, but all of them should already be included in &lt;code&gt;dlib&lt;/code&gt; build. &lt;/p&gt;

&lt;p&gt;If we search our codebase (including &lt;code&gt;vendor&lt;/code&gt;), we'll find this line in &lt;code&gt;face.go&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// #cgo LDFLAGS: -ldlib -lblas -lcblas -llapack -ljpeg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a linker instruction with list of libs that's causing the problem now. Fortunately, with vendored deps it is super easy to change code of dependencies, so let's change that line to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// #cgo LDFLAGS: -ldlib
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's run the build one more time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@f08033dcccbc:/app# CGO_LDFLAGS="-static" /usr/local/go/bin/go build .
# github.com/vearutop/faces
/usr/bin/ld: /tmp/go-link-3191459515/000010.o: in function `_cgo_9c8efe9babca_C2func_getaddrinfo':
/tmp/go-build/cgo-gcc-prolog:58: warning: Using 'getaddrinfo' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
root@f08033dcccbc:/app# ./faces -help
Usage of ./faces:
  -listen string
        listen address (default "localhost:8011")
root@f08033dcccbc:/app# 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It complained about something, but worked!&lt;br&gt;
Let's check the dependencies now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@f08033dcccbc:/app# ldd ./faces
        not a dynamic executable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks like a nice statically built binary! 😌&lt;/p&gt;

&lt;p&gt;Let's check if it actually works. In the same container I can run the app in background with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@f08033dcccbc:/app# ./faces &amp;amp;
[1] 1739
root@f08033dcccbc:/app# 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then invoke request with &lt;code&gt;curl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@f08033dcccbc:/app# curl -X 'POST' \
'http://localhost:8011/image' \
-H 'accept: application/json' \
-H 'Content-Type: multipart/form-data' \
-F 'image=@person.jpg;type=image/jpeg'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"elapsedSec"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;0.258315&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"found"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"faces"&lt;/span&gt;&lt;span class="p"&gt;:[{&lt;/span&gt;&lt;span class="nl"&gt;"Rectangle"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"Min"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"X"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;352&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"Y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;185&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="nl"&gt;"Max"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"X"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;567&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"Y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;&lt;span class="nl"&gt;"Descriptor"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="mf"&gt;-0.16648848&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.05050624&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.106586605&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.0867105&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.09123391&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.097584575&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.046739854&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.103373915&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.074457884&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.105268024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.20660394&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.13579035&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.2745444&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.0005242062&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.03232275&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.18681777&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.13113116&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.17754263&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.052810747&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.05957584&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.0062686643&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.03621107&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.011501403&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.1487859&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.06366991&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.33826596&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.06331841&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.08673793&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.010200936&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.0629237&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.027267495&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.11619936&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.2607339&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.04982499&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.01518264&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.12889145&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.02307811&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.118307345&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.1285096&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.048686076&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.24529652&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.12607978&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.136835&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.26203102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.162219&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.034145266&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.018228233&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.0061597005&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.040899806&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.295318&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.0031301053&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.06259319&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.0745079&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.049838964&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.00964687&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.27123472&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.07631222&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.060989577&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.12530015&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.03486493&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.035399184&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.04188027&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.04090107&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.051638283&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.36773872&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.10492739&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.14495152&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.087634355&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.21060707&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.16210485&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.00697436&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.04431132&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.16566163&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.12653385&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.31701985&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.06338993&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.31295794&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.03408507&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.17158867&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.076981254&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.09508267&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.073756054&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.02041351&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.14637248&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.0001675617&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.10626993&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.08162568&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.01661037&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.23682739&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.021808863&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.006492801&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.22029987&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.01065092&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.044090617&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.09562777&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.039906204&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.05015147&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.061895538&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.21429531&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.028714905&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.07911338&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.017555084&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.02431442&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.106665134&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.20538758&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.08050651&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.017503517&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.0074621206&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.057238452&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.036879964&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.08754097&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.09878489&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.111212455&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.24645737&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.15643074&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.21560076&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.10718059&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.13916788&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.05442419&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.053753562&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.024602186&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.011599961&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.13366313&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.02042818&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.062051836&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.0836075&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;-0.010100439&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.07831607&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Shapes"&lt;/span&gt;&lt;span class="p"&gt;:[{&lt;/span&gt;&lt;span class="nl"&gt;"X"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;529&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"Y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;253&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nl"&gt;"X"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;492&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"Y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;251&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nl"&gt;"X"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;399&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"Y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;237&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nl"&gt;"X"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;436&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"Y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;245&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nl"&gt;"X"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;457&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"Y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;309&lt;/span&gt;&lt;span class="p"&gt;}]}]}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To wrap up, let's add our findings in app codebase.&lt;/p&gt;

&lt;p&gt;If we now try to build the app outside a container with dynamic linking, the build will fail because we've removed linker instructions in vendored code. To make it work for both cases we can guard behavior with build flags. Let's remove the &lt;code&gt;// #cgo LDFLAGS: ...&lt;/code&gt; line from &lt;code&gt;face.go&lt;/code&gt; and create two new files instead.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;face_static.go&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;//go:build static&lt;/span&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;face&lt;/span&gt;

&lt;span class="c"&gt;// #cgo LDFLAGS: -ldlib&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;face_dynamic.go&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;//go:build !static&lt;/span&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;face&lt;/span&gt;

&lt;span class="c"&gt;// #cgo LDFLAGS: -ldlib -lblas -lcblas -llapack -ljpeg&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we'll need to add a build tag in order to build statically, but the default build will use dynamic linking.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@f08033dcccbc:/app# CGO_LDFLAGS="-static" /usr/local/go/bin/go build -tags static .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's update our &lt;code&gt;Dockerfile&lt;/code&gt; with more instructions to perform the actual application build.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ubuntu:22.04&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; build-essential cmake curl
&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-sLO&lt;/span&gt; https://go.dev/dl/go1.21.6.linux-amd64.tar.gz &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; /usr/local &lt;span class="nt"&gt;-xzf&lt;/span&gt; go1.21.6.linux-amd64.tar.gz &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; go1.21.6.linux-amd64.tar.gz
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; /dlib &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; /dlib &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; curl &lt;span class="nt"&gt;-sLO&lt;/span&gt; http://dlib.net/files/dlib-19.24.tar.bz2 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;tar &lt;/span&gt;xf dlib-19.24.tar.bz2
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /dlib/dlib-19.24 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mkdir &lt;/span&gt;build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; cmake .. &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; cmake &lt;span class="nt"&gt;--build&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--config&lt;/span&gt; Release &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /dlib

&lt;span class="c"&gt;# Missing header file.&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; /usr/local/include/dlib/external/libjpeg/&lt;span class="k"&gt;*&lt;/span&gt;.h /usr/include/

&lt;span class="c"&gt;# Building app.&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nv"&gt;CGO_LDFLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-static"&lt;/span&gt; /usr/local/go/bin/go build &lt;span class="nt"&gt;-tags&lt;/span&gt; static .

&lt;span class="c"&gt;# Exporting minimal docker image with pre-built binary.&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /root&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["/bin/faces", "-listen", "0.0.0.0:80"]&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/faces /bin/faces&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that we can run a clean build.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build -f ./docker/Dockerfile -t faces .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if you need resulting binary to deploy it somewhere, you can copy it from docker image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -v $PWD:/opt/mount --rm faces cp /bin/faces /opt/mount/faces
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a result you'll have &lt;code&gt;./faces&lt;/code&gt; in your current directory.&lt;/p&gt;

&lt;p&gt;Or you can start the service on &lt;a href="http://localhost:8000"&gt;http://localhost:8000&lt;/a&gt; with docker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run --rm -p 8000:80 faces
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>go</category>
      <category>dlib</category>
      <category>machinelearning</category>
      <category>facerecognition</category>
    </item>
    <item>
      <title>Streaming generated data as io.Reader at high speed in Go</title>
      <dc:creator>Viacheslav Poturaev</dc:creator>
      <pubDate>Tue, 22 Aug 2023 00:42:06 +0000</pubDate>
      <link>https://forem.com/vearutop/passing-generated-data-as-ioreader-at-high-speed-in-go-248k</link>
      <guid>https://forem.com/vearutop/passing-generated-data-as-ioreader-at-high-speed-in-go-248k</guid>
      <description>&lt;p&gt;When you want to benchmark a piece of code that processes &lt;code&gt;io.Reader&lt;/code&gt;, it is common to use &lt;code&gt;io.Pipe&lt;/code&gt; to stream generated data with an extra &lt;code&gt;io.Writer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Take a look at this trivial example benchmark.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Benchmark_ioPipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pipe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

                &lt;span class="k"&gt;return&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;this might be a dynamic piece of generated data&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;

    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReportAllocs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// io.Copy acts as a dummy processor here, in real-world &lt;/span&gt;
    &lt;span class="c"&gt;// scenario you'd have an actual io.Reader consumer.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Discard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
Benchmark_ioPipe-12      1655488           729.8 ns/op        64 B/op          1 allocs/op
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Performance of such feed is not bad, but not very impressive either. Some speed is sacrificed to enable data safety under concurrency, both reads and writes can be called in parallel with synchronization happening in the pipe.&lt;/p&gt;

&lt;p&gt;Let's try to improve performance for a particular case of generated feed exposed as &lt;code&gt;io.Reader&lt;/code&gt;, that does not need external synchronization.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;pagesReader&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// next returns contents of the next page, &lt;/span&gt;
    &lt;span class="c"&gt;// io.EOF error indicates last page.&lt;/span&gt;
    &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// buf keeps the data to be read.&lt;/span&gt;
    &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pagesReader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Fill the reader buffer with pages &lt;/span&gt;
    &lt;span class="c"&gt;// until it exceeds the incoming buffer &lt;/span&gt;
    &lt;span class="c"&gt;// or reaches the end of pages.&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nb"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Put head of reader buffer in the incoming buffer.&lt;/span&gt;
    &lt;span class="nb"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Move remaining tail into the head of reader buffer.&lt;/span&gt;
    &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="nb"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;pagesReader&lt;/code&gt; implements &lt;code&gt;io.Reader&lt;/code&gt; that feeds data from user-defined callback &lt;code&gt;next func() ([]byte, error)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, let's update the previous benchmark to use &lt;code&gt;pagesReader&lt;/code&gt; instead of &lt;code&gt;io.Pipe&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Benchmark_pagesReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;pagesReader&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EOF&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;this might be a dynamic piece of generated data&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReportAllocs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// io.Copy acts as a dummy processor here, in real-world&lt;/span&gt;
    &lt;span class="c"&gt;// scenario you'd have an actual io.Reader consumer.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Discard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
Benchmark_pagesReader-12        21766588            52.38 ns/op       64 B/op          1 allocs/op
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The unsynchronized implementation is much faster (about 14x speed) and might be more suitable for benchmarks for lower impact on overall result.&lt;/p&gt;

&lt;p&gt;When synchronization is needed, it can be managed with a mutex in the &lt;code&gt;next&lt;/code&gt; function itself.&lt;/p&gt;

</description>
      <category>benchmark</category>
      <category>go</category>
    </item>
    <item>
      <title>Using code coverage to debug large Go application</title>
      <dc:creator>Viacheslav Poturaev</dc:creator>
      <pubDate>Tue, 15 Aug 2023 12:45:25 +0000</pubDate>
      <link>https://forem.com/vearutop/using-code-coverage-to-debug-large-go-application-1gkf</link>
      <guid>https://forem.com/vearutop/using-code-coverage-to-debug-large-go-application-1gkf</guid>
      <description>&lt;p&gt;Code coverage is typically used as a code quality metric. &lt;/p&gt;

&lt;p&gt;One can run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-coverpkg&lt;/span&gt; ./... &lt;span class="nt"&gt;-coverprofile&lt;/span&gt; test.cov ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and receive a &lt;code&gt;test.cov&lt;/code&gt; file containing information on how often particular parts of code were triggered during execution.&lt;/p&gt;

&lt;p&gt;This is a great tool to keep your tests relevant.&lt;/p&gt;

&lt;p&gt;However, code coverage can also help you to debug large codebases!&lt;/p&gt;

&lt;p&gt;Imagine a situation when a seemingly innocent change in code leads to broken tests. Getting to the root cause might be non-trivial if there is a lot of code involved in execution. &lt;/p&gt;

&lt;p&gt;In smaller cases, you could walk through all the statements with a debugger and compare state and conditions, but when you have thousands of statements, this approach is not really feasible.&lt;/p&gt;

&lt;p&gt;Code coverage can help you to narrow down the scope of debugging.&lt;/p&gt;

&lt;p&gt;Here is the recipe. The prerequisite is that you have a test that passes in original code, and fails in new.&lt;/p&gt;

&lt;h3&gt;
  
  
  Collect coverage
&lt;/h3&gt;

&lt;p&gt;Collect coverages of both failure and pass. &lt;/p&gt;

&lt;p&gt;It is important to collect coverage across all packages to have a wholistic picture. You can use &lt;code&gt;./...&lt;/code&gt; or something like &lt;code&gt;my-module/...&lt;/code&gt;, &lt;code&gt;github.com/foo/my-module/...&lt;/code&gt; for &lt;code&gt;-coverpkg&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Apply the change and run the test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-cover&lt;/span&gt; &lt;span class="nt"&gt;-coverpkg&lt;/span&gt; ./... &lt;span class="nt"&gt;-coverprofile&lt;/span&gt; new.cover &lt;span class="nt"&gt;-run&lt;/span&gt; ^Test_Foo&lt;span class="nv"&gt;$ &lt;/span&gt;./path/to/tested/package
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then revert the change and collect coverage again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-cover&lt;/span&gt; &lt;span class="nt"&gt;-coverpkg&lt;/span&gt; ./... &lt;span class="nt"&gt;-coverprofile&lt;/span&gt; orig.cover &lt;span class="nt"&gt;-run&lt;/span&gt; ^Test_Foo&lt;span class="nv"&gt;$ &lt;/span&gt;./path/to/tested/package
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, you will end up having two files: &lt;code&gt;orig.cover&lt;/code&gt; and &lt;code&gt;new.cover&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;They might be big, but hopefully not very different.&lt;/p&gt;

&lt;p&gt;Coverage file may look like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mode: set
github.com/swaggest/jsonschema-go/camelcase.go:14.31,21.22 6 1
github.com/swaggest/jsonschema-go/camelcase.go:21.22,22.27 1 1
github.com/swaggest/jsonschema-go/camelcase.go:22.27,24.4 1 1
github.com/swaggest/jsonschema-go/camelcase.go:26.3,26.27 1 1
github.com/swaggest/jsonschema-go/camelcase.go:26.27,28.4 1 1
github.com/swaggest/jsonschema-go/camelcase.go:30.3,30.27 1 1
.....
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first line says which mode of coverage collection was used. &lt;/p&gt;

&lt;p&gt;All the other lines describe a span of code (&lt;code&gt;file:start_line.start_col,end_line.end_col&lt;/code&gt;) with a total number of statements in that span and a number of statements executed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create coverage diff
&lt;/h3&gt;

&lt;p&gt;Coverage files are ordered alphabetically, so they are friendly to &lt;code&gt;diff&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;diff orig.cover new.cover &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; diff.cover
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting file may look somewhat similar to this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;305c305
&lt;/span&gt;&lt;span class="gd"&gt;&amp;lt; github.com/swaggest/jsonschema-go/helper.go:75.46,78.16 2 1
&lt;/span&gt;&lt;span class="p"&gt;---
&lt;/span&gt;&lt;span class="gi"&gt;&amp;gt; github.com/swaggest/jsonschema-go/helper.go:75.46,78.16 2 0
&lt;/span&gt;&lt;span class="p"&gt;307c307
&lt;/span&gt;&lt;span class="gd"&gt;&amp;lt; github.com/swaggest/jsonschema-go/helper.go:82.2,84.46 2 1
&lt;/span&gt;&lt;span class="p"&gt;---
&lt;/span&gt;&lt;span class="gi"&gt;&amp;gt; github.com/swaggest/jsonschema-go/helper.go:82.2,84.46 2 0
&lt;/span&gt;&lt;span class="p"&gt;309c309
&lt;/span&gt;&lt;span class="gd"&gt;&amp;lt; github.com/swaggest/jsonschema-go/helper.go:88.2,88.15 1 1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can already spot that the lines here are mostly different in the number of executed statements. This is the clue for us that those places in code are the best candidates to have a deeper look with a debugger.&lt;/p&gt;

&lt;p&gt;Let's make this diff more convenient to follow and check.&lt;/p&gt;

&lt;h3&gt;
  
  
  Coverage reporting
&lt;/h3&gt;

&lt;p&gt;Lines starting with &lt;code&gt;&amp;lt;&lt;/code&gt; represent the original code, lines starting with &lt;code&gt;&amp;gt;&lt;/code&gt; are about the new code.&lt;/p&gt;

&lt;p&gt;We can build filtered coverage reports from the diff.&lt;/p&gt;

&lt;p&gt;For that, we need to restore the &lt;code&gt;mode: set&lt;/code&gt; and remove &lt;code&gt;"&amp;lt; "&lt;/code&gt; or &lt;code&gt;"&amp;gt; "&lt;/code&gt; from the lines.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"mode: set"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; orig_flt.cover &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt; "&lt;/span&gt; diff.cover | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/&amp;lt; //'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; orig_flt.cover

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"mode: set"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; new_flt.cover &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt; "&lt;/span&gt; diff.cover | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/&amp;gt; //'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; new_flt.cover
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, there are two coverage files that only contain differences between two runs.&lt;/p&gt;

&lt;p&gt;We can conveniently inspect them with standard &lt;code&gt;go tool cover&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go tool cover &lt;span class="nt"&gt;-html&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;orig_flt.cover &lt;span class="nt"&gt;-o&lt;/span&gt; orig_flt.html
go tool cover &lt;span class="nt"&gt;-html&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;new_flt.cover &lt;span class="nt"&gt;-o&lt;/span&gt; new_flt.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ac5idxpo6xoexk3crej.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ac5idxpo6xoexk3crej.png" alt="Coverage diff screenshot" width="800" height="905"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see which parts of code were executed differently, this can lead to an idea of what could be the root cause or at least can give a hint where debugger break point should be set.&lt;/p&gt;

&lt;p&gt;I hope you've enjoyed reading this as much as I enjoyed digging into a huuuuge code base with this approach. :)&lt;/p&gt;

&lt;p&gt;UPD: Check a newer relevant post from Russ Cox too &lt;a href="https://research.swtch.com/diffcover" rel="noopener noreferrer"&gt;https://research.swtch.com/diffcover&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>go</category>
      <category>testing</category>
    </item>
    <item>
      <title>Profile-guided optimization of a Go application</title>
      <dc:creator>Viacheslav Poturaev</dc:creator>
      <pubDate>Sun, 19 Mar 2023 19:49:30 +0000</pubDate>
      <link>https://forem.com/vearutop/profile-guided-optimization-of-a-go-application-l49</link>
      <guid>https://forem.com/vearutop/profile-guided-optimization-of-a-go-application-l49</guid>
      <description>&lt;p&gt;&lt;em&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; Go 1.20 PGO was applied to an existing high-performance application and lead to 2% faster execution with 4.4% less CPU cycles and 5.3% less CPU instructions for extra 100KB size of compiled binary.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A recent release of Go (1.20) introduced a preview version of a new tool to optimize an application in response to performance counters of a CPU profile.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://go.dev/doc/pgo"&gt;Profile-guided optimization&lt;/a&gt; can enable &lt;a href="https://dave.cheney.net/2020/04/25/inlining-optimisations-in-go"&gt;inlining&lt;/a&gt; for functions in hot paths, this is a trade-off between increased binary size and improved speed of execution.&lt;/p&gt;

&lt;p&gt;CPU profile makes this trade-off informed and allows a relatively low binary size penalty for a noticeable perf improvement.&lt;/p&gt;

&lt;p&gt;I have &lt;a href="https://github.com/vearutop/flatjsonl"&gt;&lt;code&gt;flatjsonl&lt;/code&gt;&lt;/a&gt;, an application that processes JSON lines and renders them as tables. Speed of processing is important, so any optimization is welcome, let's see how PGO can help. &lt;/p&gt;

&lt;h2&gt;
  
  
  CPU Profile
&lt;/h2&gt;

&lt;p&gt;First, a relevant &lt;a href="https://pkg.go.dev/runtime/pprof"&gt;CPU profile&lt;/a&gt; is needed. There is already standard tooling for that, CPU profile is basically a collection of metrics and counters observed during application execution. For PGO purposes best results can be obtained using profiles of actual production workloads.&lt;/p&gt;

&lt;p&gt;For a CLI app common practice is to define a &lt;a href="https://github.com/vearutop/flatjsonl/blob/v0.5.14/main.go#L29"&gt;flag&lt;/a&gt; to enable profile collection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"flag"&lt;/span&gt;
    &lt;span class="s"&gt;"runtime/pprof"&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cpuProfile&lt;/span&gt;    &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StringVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cpuProfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"dbg-cpu-prof"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Write CPU profile to file."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cpuProfile&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpuProfile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pprof&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StartCPUProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;pprof&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StopCPUProfile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're developing a web service, you can use &lt;a href="https://pkg.go.dev/net/http/pprof"&gt;&lt;code&gt;net/http/pprof&lt;/code&gt;&lt;/a&gt; to expose profiling handler and collect data on a running instance.&lt;/p&gt;

&lt;p&gt;I have a sample of work for &lt;code&gt;flatjsonl&lt;/code&gt;, that is a production workload. If I run it multiple times it takes roughly the same time to process and produces same output, running such command is an idempotent operation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flatjsonl -config ./cfg.json5 -csv ~/all.csv.gz -field-limit 50000 -max-lines 300000 -input ~/slow.log.zst
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, to collect CPU profile I'll run it again with &lt;code&gt;-dbg-cpu-prof&lt;/code&gt; flag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flatjsonl -config ./cfg.json5 -csv ~/all.csv.gz -field-limit 50000 -max-lines 300000 -input ~/slow.log.zst -dbg-cpu-prof default.pgo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the action is complete I can find &lt;code&gt;default.pgo&lt;/code&gt; file, name of the resulting profile file can be any, however, &lt;code&gt;default.pgo&lt;/code&gt; is a special name supported by PGO mode &lt;code&gt;auto&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;PGO is implemented in a way that it can tolerate obsolete CPU profiles (e.g. built with an older version of an application), though more obsolete profile would lead to less efficient PGO.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instrumented Build
&lt;/h2&gt;

&lt;p&gt;Once CPU profile is collected, it can be used with &lt;code&gt;go build&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go build -pgo=auto
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go build -pgo=/path/to/cpu.pprof
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;if profile is stored in other place than &lt;code&gt;./default.pgo&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In this case PGO instrumentation resulted in ~100KB extra size.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;8527872 (8.2M) flatjsonl
8626176 (8.3M) flatjsonl-pgo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance Impact
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;flatjsonl&lt;/code&gt; is optimized for multiple cores, performance was measured on a machine with 32 cores running Linux.&lt;/p&gt;

&lt;p&gt;Go has benchmarking tools in standard library, but in this case it is easier to use &lt;a href="https://github.com/sharkdp/hyperfine"&gt;&lt;code&gt;hyperfine&lt;/code&gt;&lt;/a&gt; to measure performance of a CLI app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hyperfine
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hyperfine --warmup 3 'flatjsonl -config ./cfg.json5 -csv ~/all.csv.gz -field-limit 50000 -max-lines 300000 -input ~/slow.log.zst'
Benchmark 1: flatjsonl -config ./cfg.json5 -csv ~/all.csv.gz -field-limit 50000 -max-lines 300000 -input ~/slow.log.zst
  Time (mean ± σ):      7.836 s ±  0.147 s    [User: 69.622 s, System: 4.279 s]
  Range (min … max):    7.504 s …  8.043 s    10 runs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hyperfine --warmup 3 'flatjsonl-pgo -config ./cfg.json5 -csv ~/all.csv.gz -field-limit 50000 -max-lines 300000 -input ~/slow.log.zst'
Benchmark 1: flatjsonl-pgo -config ./cfg.json5 -csv ~/all.csv.gz -field-limit 50000 -max-lines 300000 -input ~/slow.log.zst
  Time (mean ± σ):      7.660 s ±  0.305 s    [User: 66.129 s, System: 3.783 s]
  Range (min … max):    7.180 s …  8.106 s    10 runs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, binary with PGO enabled worked ~2.2% faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Perf Stat
&lt;/h3&gt;

&lt;p&gt;Linux provides another handy tool to inspect performance of an application, &lt;a href="https://perf.wiki.kernel.org/index.php/Tutorial#Counting_with_perf_stat"&gt;&lt;code&gt;perf stat&lt;/code&gt;&lt;/a&gt; can count number of CPU instructions and cycles during program execution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;perf stat -e task-clock,cycles,instructions,cache-references,cache-misses flatjsonl -config ./cfg.json5 -csv ~/all.csv.gz -field-limit 50000 -max-lines 300000 -input ~/slow.log.zst

&amp;lt;program output omitted&amp;gt;

Performance counter stats for 'flatjsonl -config ./cfg.json5 -csv ~/all.csv.gz -field-limit 50000 -max-lines 300000 -input ~/slow.log.zst':

         71,107.29 msec task-clock:u              #    9.081 CPUs utilized
   152,046,064,828      cycles:u                  #    2.138 GHz
   193,579,158,625      instructions:u            #    1.27  insn per cycle
     1,498,126,007      cache-references:u        #   21.069 M/sec
       374,577,209      cache-misses:u            #   25.003 % of all cache refs

       7.830534779 seconds time elapsed

      68.273271000 seconds user
       3.311031000 seconds sys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;perf stat -e task-clock,cycles,instructions,cache-references,cache-misses flatjsonl-pgo -config ./cfg.json5 -csv ~/all.csv.gz -field-limit 50000 -max-lines 300000 -input ~/slow.log.zst

&amp;lt;program output omitted&amp;gt;

Performance counter stats for 'flatjsonl-pgo -config ./cfg.json5 -csv ~/all.csv.gz -field-limit 50000 -max-lines 300000 -input ~/slow.log.zst':

         70,220.34 msec task-clock:u              #    9.427 CPUs utilized
   145,321,922,303      cycles:u                  #    2.070 GHz
   183,258,560,907      instructions:u            #    1.26  insn per cycle
     1,543,859,683      cache-references:u        #   21.986 M/sec
       386,600,109      cache-misses:u            #   25.041 % of all cache refs

       7.448585196 seconds time elapsed

      64.659898000 seconds user
       6.057555000 seconds sys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Binary with PGO instrumentation used ~4.4% fewer CPU cycles and ~5.3% less instructions.&lt;/p&gt;

&lt;p&gt;Such performance improvements may seem small, but instrumentation effort is almost free.&lt;/p&gt;

</description>
      <category>go</category>
      <category>performance</category>
    </item>
    <item>
      <title>Memory arenas in Go</title>
      <dc:creator>Viacheslav Poturaev</dc:creator>
      <pubDate>Thu, 09 Mar 2023 09:18:53 +0000</pubDate>
      <link>https://forem.com/vearutop/memory-arenas-in-go-j1f</link>
      <guid>https://forem.com/vearutop/memory-arenas-in-go-j1f</guid>
      <description>&lt;p&gt;Recently released &lt;code&gt;go1.20&lt;/code&gt; brought a new experimental package named &lt;code&gt;arena&lt;/code&gt; (&lt;a href="https://github.com/golang/go/issues/51317"&gt;proposal&lt;/a&gt;). &lt;/p&gt;

&lt;p&gt;The purpose of this package is to isolate multiple related allocations into a single area of memory, so that they can be freed all at once.&lt;/p&gt;

&lt;p&gt;Expected effect is improved allocation performance and reduced garbage collector (GC) pressure.&lt;/p&gt;

&lt;p&gt;Let's see it in action. As a toy problem, we can use &lt;a href="https://benchmarksgame-team.pages.debian.net/benchmarksgame/description/binarytrees.html#binarytrees"&gt;&lt;code&gt;binary-trees&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A program that idiomatically solves this problem has to put a lot of pressure on a memory management system to allocate many small objects, so it is a great candidate to assess new &lt;code&gt;arenas&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Original &lt;a href="https://benchmarksgame-team.pages.debian.net/benchmarksgame/program/binarytrees-go-2.html"&gt;source code&lt;/a&gt;:&lt;br&gt;

  binary-trees-ptr.go
  &lt;br&gt;

&lt;pre&gt;&lt;code&gt;&lt;span&gt;package&lt;/span&gt; &lt;span&gt;main&lt;/span&gt;

&lt;span&gt;import&lt;/span&gt; &lt;span&gt;(&lt;/span&gt;
   &lt;span&gt;"flag"&lt;/span&gt;
   &lt;span&gt;"fmt"&lt;/span&gt;
   &lt;span&gt;"strconv"&lt;/span&gt;
   &lt;span&gt;"sync"&lt;/span&gt;
&lt;span&gt;)&lt;/span&gt;

&lt;span&gt;type&lt;/span&gt; &lt;span&gt;Tree&lt;/span&gt; &lt;span&gt;struct&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
   &lt;span&gt;Left&lt;/span&gt;  &lt;span&gt;*&lt;/span&gt;&lt;span&gt;Tree&lt;/span&gt;
   &lt;span&gt;Right&lt;/span&gt; &lt;span&gt;*&lt;/span&gt;&lt;span&gt;Tree&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;

&lt;span&gt;// Count the nodes in the given complete binary tree.&lt;/span&gt;
&lt;span&gt;func&lt;/span&gt; &lt;span&gt;(&lt;/span&gt;&lt;span&gt;t&lt;/span&gt; &lt;span&gt;*&lt;/span&gt;&lt;span&gt;Tree&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;Count&lt;/span&gt;&lt;span&gt;()&lt;/span&gt; &lt;span&gt;int&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
   &lt;span&gt;// Only test the Left node (this binary tree is expected to be complete).&lt;/span&gt;
   &lt;span&gt;if&lt;/span&gt; &lt;span&gt;t&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Left&lt;/span&gt; &lt;span&gt;==&lt;/span&gt; &lt;span&gt;nil&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
      &lt;span&gt;return&lt;/span&gt; &lt;span&gt;1&lt;/span&gt;
   &lt;span&gt;}&lt;/span&gt;
   &lt;span&gt;return&lt;/span&gt; &lt;span&gt;1&lt;/span&gt; &lt;span&gt;+&lt;/span&gt; &lt;span&gt;t&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Right&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Count&lt;/span&gt;&lt;span&gt;()&lt;/span&gt; &lt;span&gt;+&lt;/span&gt; &lt;span&gt;t&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Left&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Count&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;

&lt;span&gt;// Create a complete binary tree of `depth` and return it as a pointer.&lt;/span&gt;
&lt;span&gt;func&lt;/span&gt; &lt;span&gt;NewTree&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;depth&lt;/span&gt; &lt;span&gt;int&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;*&lt;/span&gt;&lt;span&gt;Tree&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
   &lt;span&gt;if&lt;/span&gt; &lt;span&gt;depth&lt;/span&gt; &lt;span&gt;&amp;gt;&lt;/span&gt; &lt;span&gt;0&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
      &lt;span&gt;return&lt;/span&gt; &lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;Tree&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;Left&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;NewTree&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;depth&lt;/span&gt; &lt;span&gt;-&lt;/span&gt; &lt;span&gt;1&lt;/span&gt;&lt;span&gt;),&lt;/span&gt; &lt;span&gt;Right&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;NewTree&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;depth&lt;/span&gt; &lt;span&gt;-&lt;/span&gt; &lt;span&gt;1&lt;/span&gt;&lt;span&gt;)}&lt;/span&gt;
   &lt;span&gt;}&lt;/span&gt; &lt;span&gt;else&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
      &lt;span&gt;return&lt;/span&gt; &lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;Tree&lt;/span&gt;&lt;span&gt;{}&lt;/span&gt;
   &lt;span&gt;}&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;

&lt;span&gt;func&lt;/span&gt; &lt;span&gt;Run&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;maxDepth&lt;/span&gt; &lt;span&gt;int&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;

   &lt;span&gt;var&lt;/span&gt; &lt;span&gt;wg&lt;/span&gt; &lt;span&gt;sync&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;WaitGroup&lt;/span&gt;

   &lt;span&gt;// Set minDepth to 4 and maxDepth to the maximum of maxDepth and minDepth +2.&lt;/span&gt;
   &lt;span&gt;const&lt;/span&gt; &lt;span&gt;minDepth&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;4&lt;/span&gt;
   &lt;span&gt;if&lt;/span&gt; &lt;span&gt;maxDepth&lt;/span&gt; &lt;span&gt;&amp;lt;&lt;/span&gt; &lt;span&gt;minDepth&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;2&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
      &lt;span&gt;maxDepth&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;minDepth&lt;/span&gt; &lt;span&gt;+&lt;/span&gt; &lt;span&gt;2&lt;/span&gt;
   &lt;span&gt;}&lt;/span&gt;

   &lt;span&gt;// Create an indexed string buffer for outputing the result in order.&lt;/span&gt;
   &lt;span&gt;outCurr&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;0&lt;/span&gt;
   &lt;span&gt;outSize&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;3&lt;/span&gt; &lt;span&gt;+&lt;/span&gt; &lt;span&gt;(&lt;/span&gt;&lt;span&gt;maxDepth&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;minDepth&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;
   &lt;span&gt;outBuff&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;make&lt;/span&gt;&lt;span&gt;([]&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;outSize&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;

   &lt;span&gt;// Create binary tree of depth maxDepth+1, compute its Count and set the&lt;/span&gt;
   &lt;span&gt;// first position of the outputBuffer with its statistics.&lt;/span&gt;
   &lt;span&gt;wg&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
   &lt;span&gt;go&lt;/span&gt; &lt;span&gt;func&lt;/span&gt;&lt;span&gt;()&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
      &lt;span&gt;tree&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;NewTree&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;maxDepth&lt;/span&gt; &lt;span&gt;+&lt;/span&gt; &lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
      &lt;span&gt;msg&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;fmt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Sprintf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"stretch tree of depth %d&lt;/span&gt;&lt;span&gt;\t&lt;/span&gt;&lt;span&gt; check: %d"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
         &lt;span&gt;maxDepth&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
         &lt;span&gt;tree&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Count&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;

      &lt;span&gt;outBuff&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;msg&lt;/span&gt;
      &lt;span&gt;wg&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Done&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;
   &lt;span&gt;}()&lt;/span&gt;

   &lt;span&gt;// Create a long-lived binary tree of depth maxDepth. Its statistics will be&lt;/span&gt;
   &lt;span&gt;// handled later.&lt;/span&gt;
   &lt;span&gt;var&lt;/span&gt; &lt;span&gt;longLivedTree&lt;/span&gt; &lt;span&gt;*&lt;/span&gt;&lt;span&gt;Tree&lt;/span&gt;
   &lt;span&gt;wg&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
   &lt;span&gt;go&lt;/span&gt; &lt;span&gt;func&lt;/span&gt;&lt;span&gt;()&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
      &lt;span&gt;longLivedTree&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;NewTree&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;maxDepth&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
      &lt;span&gt;wg&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Done&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;
   &lt;span&gt;}()&lt;/span&gt;

   &lt;span&gt;// Create a lot of binary trees, of depths ranging from minDepth to maxDepth,&lt;/span&gt;
   &lt;span&gt;// compute and tally up all their Count and record the statistics.&lt;/span&gt;
   &lt;span&gt;for&lt;/span&gt; &lt;span&gt;depth&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;minDepth&lt;/span&gt;&lt;span&gt;;&lt;/span&gt; &lt;span&gt;depth&lt;/span&gt; &lt;span&gt;&amp;lt;=&lt;/span&gt; &lt;span&gt;maxDepth&lt;/span&gt;&lt;span&gt;;&lt;/span&gt; &lt;span&gt;depth&lt;/span&gt; &lt;span&gt;+=&lt;/span&gt; &lt;span&gt;2&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
      &lt;span&gt;iterations&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;1&lt;/span&gt; &lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span&gt;(&lt;/span&gt;&lt;span&gt;maxDepth&lt;/span&gt; &lt;span&gt;-&lt;/span&gt; &lt;span&gt;depth&lt;/span&gt; &lt;span&gt;+&lt;/span&gt; &lt;span&gt;minDepth&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
      &lt;span&gt;outCurr&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;

      &lt;span&gt;wg&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
      &lt;span&gt;go&lt;/span&gt; &lt;span&gt;func&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;depth&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;iterations&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;index&lt;/span&gt; &lt;span&gt;int&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
         &lt;span&gt;acc&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;0&lt;/span&gt;
         &lt;span&gt;for&lt;/span&gt; &lt;span&gt;i&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt; &lt;span&gt;i&lt;/span&gt; &lt;span&gt;&amp;lt;&lt;/span&gt; &lt;span&gt;iterations&lt;/span&gt;&lt;span&gt;;&lt;/span&gt; &lt;span&gt;i&lt;/span&gt;&lt;span&gt;++&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
            &lt;span&gt;// Create a binary tree of depth and accumulate total counter with its&lt;/span&gt;
            &lt;span&gt;// node count.&lt;/span&gt;
            &lt;span&gt;a&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;NewTree&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;depth&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
            &lt;span&gt;acc&lt;/span&gt; &lt;span&gt;+=&lt;/span&gt; &lt;span&gt;a&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Count&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;
         &lt;span&gt;}&lt;/span&gt;
         &lt;span&gt;msg&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;fmt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Sprintf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"%d&lt;/span&gt;&lt;span&gt;\t&lt;/span&gt;&lt;span&gt; trees of depth %d&lt;/span&gt;&lt;span&gt;\t&lt;/span&gt;&lt;span&gt; check: %d"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
            &lt;span&gt;iterations&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
            &lt;span&gt;depth&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
            &lt;span&gt;acc&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;

         &lt;span&gt;outBuff&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;]&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;msg&lt;/span&gt;
         &lt;span&gt;wg&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Done&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;
      &lt;span&gt;}(&lt;/span&gt;&lt;span&gt;depth&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;iterations&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;outCurr&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
   &lt;span&gt;}&lt;/span&gt;

   &lt;span&gt;wg&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Wait&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;

   &lt;span&gt;// Compute the checksum of the long-lived binary tree that we created&lt;/span&gt;
   &lt;span&gt;// earlier and store its statistics.&lt;/span&gt;
   &lt;span&gt;msg&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;fmt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Sprintf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"long lived tree of depth %d&lt;/span&gt;&lt;span&gt;\t&lt;/span&gt;&lt;span&gt; check: %d"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
      &lt;span&gt;maxDepth&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
      &lt;span&gt;longLivedTree&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Count&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;
   &lt;span&gt;outBuff&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;outSize&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;msg&lt;/span&gt;

   &lt;span&gt;// Print the statistics for all of the various tree depths.&lt;/span&gt;
   &lt;span&gt;for&lt;/span&gt; &lt;span&gt;_&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;m&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;range&lt;/span&gt; &lt;span&gt;outBuff&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
      &lt;span&gt;fmt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Println&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;m&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
   &lt;span&gt;}&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;

&lt;span&gt;func&lt;/span&gt; &lt;span&gt;main&lt;/span&gt;&lt;span&gt;()&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
   &lt;span&gt;n&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;0&lt;/span&gt;
   &lt;span&gt;flag&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Parse&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;
   &lt;span&gt;if&lt;/span&gt; &lt;span&gt;flag&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NArg&lt;/span&gt;&lt;span&gt;()&lt;/span&gt; &lt;span&gt;&amp;gt;&lt;/span&gt; &lt;span&gt;0&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
      &lt;span&gt;n&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;_&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;strconv&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Atoi&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;flag&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Arg&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;
   &lt;span&gt;}&lt;/span&gt;

   &lt;span&gt;Run&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;




&lt;/p&gt;

&lt;p&gt;If I compile and run this program, I can get a baseline performance metric.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;time ./binary-trees-ptr 21
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stretch tree of depth 22         check: 8388607
2097152  trees of depth 4        check: 65011712
524288   trees of depth 6        check: 66584576
131072   trees of depth 8        check: 66977792
32768    trees of depth 10       check: 67076096
8192     trees of depth 12       check: 67100672
2048     trees of depth 14       check: 67106816
512      trees of depth 16       check: 67108352
128      trees of depth 18       check: 67108736
32       trees of depth 20       check: 67108832
long lived tree of depth 21      check: 4194303

real    0m7.232s
user    1m9.929s
sys     0m0.959s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual program output is not so important, but the execution time is. &lt;/p&gt;

&lt;p&gt;This result was obtained with a general purpose Macbook Pro (Intel) and in consecutive runs the numbers were slightly different. However, this post does not aim for scientific accuracy, the goal is to have rough understanding what to expect from arenas. Performance impact may vary depending on the code, so please run proper benchmarks on your actual cases.&lt;/p&gt;

&lt;p&gt;Most allocations in the app are happening in&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;depth&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Tree&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Left&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;NewTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Right&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;NewTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Enter &lt;code&gt;arena&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This new package is available in std lib with &lt;code&gt;"arena"&lt;/code&gt; import. &lt;br&gt;
The flow is&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create arena instance with &lt;code&gt;arena.NewArena()&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;use it to allocate instances of any type with &lt;code&gt;arena.New[T any](a *Arena) *T&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;use it to allocate slices with &lt;code&gt;arena.MakeSlice[T any](a *Arena, len, cap int) []T&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;free arena instance once the data is no longer needed with &lt;code&gt;(*Arena).Free()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because this package is experimental, you'd need to explicitly enable it with &lt;code&gt;GOEXPERIMENT=arenas&lt;/code&gt; env var and/or with equivalent build tag &lt;code&gt;//go:build goexperiment.arenas&lt;/code&gt; during build.&lt;/p&gt;
&lt;h2&gt;
  
  
  Upgrade the app
&lt;/h2&gt;

&lt;p&gt;Let's isolate tree constructor to allocate in an arena.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Arena&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Tree&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NewTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NewTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when we build a tree, we have control of where to put its data and when to free it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="o"&gt;.....&lt;/span&gt;
    &lt;span class="c"&gt;// Create binary tree of depth maxDepth+1, compute its Count and set the&lt;/span&gt;
    &lt;span class="c"&gt;// first position of the outputBuffer with its statistics.&lt;/span&gt;
    &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewArena&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;tree&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxDepth&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"stretch tree of depth %d&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s"&gt; check: %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;maxDepth&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

        &lt;span class="n"&gt;outBuff&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Free&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;
&lt;span class="o"&gt;.....&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Upgraded app:&lt;/p&gt;

&lt;p&gt;
  binary-trees-ptr-arena.go
  &lt;br&gt;

&lt;pre&gt;&lt;code&gt;&lt;span&gt;//go:build goexperiment.arenas&lt;/span&gt;

&lt;span&gt;package&lt;/span&gt; &lt;span&gt;main&lt;/span&gt;

&lt;span&gt;import&lt;/span&gt; &lt;span&gt;(&lt;/span&gt;
    &lt;span&gt;"arena"&lt;/span&gt;
    &lt;span&gt;"flag"&lt;/span&gt;
    &lt;span&gt;"fmt"&lt;/span&gt;
    &lt;span&gt;"strconv"&lt;/span&gt;
    &lt;span&gt;"sync"&lt;/span&gt;
&lt;span&gt;)&lt;/span&gt;

&lt;span&gt;type&lt;/span&gt; &lt;span&gt;Tree&lt;/span&gt; &lt;span&gt;struct&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
    &lt;span&gt;Left&lt;/span&gt;  &lt;span&gt;*&lt;/span&gt;&lt;span&gt;Tree&lt;/span&gt;
    &lt;span&gt;Right&lt;/span&gt; &lt;span&gt;*&lt;/span&gt;&lt;span&gt;Tree&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;

&lt;span&gt;// Count the nodes in the given complete binary tree.&lt;/span&gt;
&lt;span&gt;func&lt;/span&gt; &lt;span&gt;(&lt;/span&gt;&lt;span&gt;t&lt;/span&gt; &lt;span&gt;*&lt;/span&gt;&lt;span&gt;Tree&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;Count&lt;/span&gt;&lt;span&gt;()&lt;/span&gt; &lt;span&gt;int&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
    &lt;span&gt;// Only test the Left node (this binary tree is expected to be complete).&lt;/span&gt;
    &lt;span&gt;if&lt;/span&gt; &lt;span&gt;t&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Left&lt;/span&gt; &lt;span&gt;==&lt;/span&gt; &lt;span&gt;nil&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
        &lt;span&gt;return&lt;/span&gt; &lt;span&gt;1&lt;/span&gt;
    &lt;span&gt;}&lt;/span&gt;
    &lt;span&gt;return&lt;/span&gt; &lt;span&gt;1&lt;/span&gt; &lt;span&gt;+&lt;/span&gt; &lt;span&gt;t&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Right&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Count&lt;/span&gt;&lt;span&gt;()&lt;/span&gt; &lt;span&gt;+&lt;/span&gt; &lt;span&gt;t&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Left&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Count&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;

&lt;span&gt;// Create a complete binary tree of `depth` and return it as a pointer.&lt;/span&gt;
&lt;span&gt;func&lt;/span&gt; &lt;span&gt;NewTree&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;a&lt;/span&gt; &lt;span&gt;*&lt;/span&gt;&lt;span&gt;arena&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Arena&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;depth&lt;/span&gt; &lt;span&gt;int&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;*&lt;/span&gt;&lt;span&gt;Tree&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
    &lt;span&gt;if&lt;/span&gt; &lt;span&gt;depth&lt;/span&gt; &lt;span&gt;&amp;gt;&lt;/span&gt; &lt;span&gt;0&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
        &lt;span&gt;t&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;arena&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;New&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Tree&lt;/span&gt;&lt;span&gt;](&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
        &lt;span&gt;t&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Left&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;NewTree&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;depth&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
        &lt;span&gt;t&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Right&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;NewTree&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;depth&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
        &lt;span&gt;return&lt;/span&gt; &lt;span&gt;t&lt;/span&gt;
    &lt;span&gt;}&lt;/span&gt; &lt;span&gt;else&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
        &lt;span&gt;return&lt;/span&gt; &lt;span&gt;arena&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;New&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Tree&lt;/span&gt;&lt;span&gt;](&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
    &lt;span&gt;}&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;

&lt;span&gt;func&lt;/span&gt; &lt;span&gt;Run&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;maxDepth&lt;/span&gt; &lt;span&gt;int&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;

    &lt;span&gt;var&lt;/span&gt; &lt;span&gt;wg&lt;/span&gt; &lt;span&gt;sync&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;WaitGroup&lt;/span&gt;

    &lt;span&gt;// Set minDepth to 4 and maxDepth to the maximum of maxDepth and minDepth +2.&lt;/span&gt;
    &lt;span&gt;const&lt;/span&gt; &lt;span&gt;minDepth&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;4&lt;/span&gt;
    &lt;span&gt;if&lt;/span&gt; &lt;span&gt;maxDepth&lt;/span&gt; &lt;span&gt;&amp;lt;&lt;/span&gt; &lt;span&gt;minDepth&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;2&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
        &lt;span&gt;maxDepth&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;minDepth&lt;/span&gt; &lt;span&gt;+&lt;/span&gt; &lt;span&gt;2&lt;/span&gt;
    &lt;span&gt;}&lt;/span&gt;

    &lt;span&gt;// Create an indexed string buffer for outputing the result in order.&lt;/span&gt;
    &lt;span&gt;outCurr&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;0&lt;/span&gt;
    &lt;span&gt;outSize&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;3&lt;/span&gt; &lt;span&gt;+&lt;/span&gt; &lt;span&gt;(&lt;/span&gt;&lt;span&gt;maxDepth&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;minDepth&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;
    &lt;span&gt;outBuff&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;make&lt;/span&gt;&lt;span&gt;([]&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;outSize&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;

    &lt;span&gt;// Create binary tree of depth maxDepth+1, compute its Count and set the&lt;/span&gt;
    &lt;span&gt;// first position of the outputBuffer with its statistics.&lt;/span&gt;
    &lt;span&gt;wg&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
    &lt;span&gt;go&lt;/span&gt; &lt;span&gt;func&lt;/span&gt;&lt;span&gt;()&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
        &lt;span&gt;a&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;arena&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NewArena&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;
        &lt;span&gt;tree&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;NewTree&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;maxDepth&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
        &lt;span&gt;msg&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;fmt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Sprintf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"stretch tree of depth %d&lt;/span&gt;&lt;span&gt;\t&lt;/span&gt;&lt;span&gt; check: %d"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
            &lt;span&gt;maxDepth&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
            &lt;span&gt;tree&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Count&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;

        &lt;span&gt;outBuff&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;msg&lt;/span&gt;
        &lt;span&gt;a&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Free&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;
        &lt;span&gt;wg&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Done&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;
    &lt;span&gt;}()&lt;/span&gt;

    &lt;span&gt;// Create a long-lived binary tree of depth maxDepth. Its statistics will be&lt;/span&gt;
    &lt;span&gt;// handled later.&lt;/span&gt;
    &lt;span&gt;var&lt;/span&gt; &lt;span&gt;longLivedTree&lt;/span&gt; &lt;span&gt;*&lt;/span&gt;&lt;span&gt;Tree&lt;/span&gt;
    &lt;span&gt;la&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;arena&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NewArena&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;
    &lt;span&gt;wg&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
    &lt;span&gt;go&lt;/span&gt; &lt;span&gt;func&lt;/span&gt;&lt;span&gt;()&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
        &lt;span&gt;longLivedTree&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;NewTree&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;la&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;maxDepth&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
        &lt;span&gt;wg&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Done&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;
    &lt;span&gt;}()&lt;/span&gt;

    &lt;span&gt;// Create a lot of binary trees, of depths ranging from minDepth to maxDepth,&lt;/span&gt;
    &lt;span&gt;// compute and tally up all their Count and record the statistics.&lt;/span&gt;
    &lt;span&gt;for&lt;/span&gt; &lt;span&gt;depth&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;minDepth&lt;/span&gt;&lt;span&gt;;&lt;/span&gt; &lt;span&gt;depth&lt;/span&gt; &lt;span&gt;&amp;lt;=&lt;/span&gt; &lt;span&gt;maxDepth&lt;/span&gt;&lt;span&gt;;&lt;/span&gt; &lt;span&gt;depth&lt;/span&gt; &lt;span&gt;+=&lt;/span&gt; &lt;span&gt;2&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
        &lt;span&gt;iterations&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;1&lt;/span&gt; &lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span&gt;(&lt;/span&gt;&lt;span&gt;maxDepth&lt;/span&gt; &lt;span&gt;-&lt;/span&gt; &lt;span&gt;depth&lt;/span&gt; &lt;span&gt;+&lt;/span&gt; &lt;span&gt;minDepth&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
        &lt;span&gt;outCurr&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;

        &lt;span&gt;wg&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
        &lt;span&gt;go&lt;/span&gt; &lt;span&gt;func&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;depth&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;iterations&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;index&lt;/span&gt; &lt;span&gt;int&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
            &lt;span&gt;acc&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;0&lt;/span&gt;
            &lt;span&gt;for&lt;/span&gt; &lt;span&gt;i&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt; &lt;span&gt;i&lt;/span&gt; &lt;span&gt;&amp;lt;&lt;/span&gt; &lt;span&gt;iterations&lt;/span&gt;&lt;span&gt;;&lt;/span&gt; &lt;span&gt;i&lt;/span&gt;&lt;span&gt;++&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
                &lt;span&gt;// Create a binary tree of depth and accumulate total counter with its&lt;/span&gt;
                &lt;span&gt;// node count.&lt;/span&gt;
                &lt;span&gt;ar&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;arena&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NewArena&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;
                &lt;span&gt;a&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;NewTree&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ar&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;depth&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
                &lt;span&gt;acc&lt;/span&gt; &lt;span&gt;+=&lt;/span&gt; &lt;span&gt;a&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Count&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;
                &lt;span&gt;ar&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Free&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;
            &lt;span&gt;}&lt;/span&gt;
            &lt;span&gt;msg&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;fmt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Sprintf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"%d&lt;/span&gt;&lt;span&gt;\t&lt;/span&gt;&lt;span&gt; trees of depth %d&lt;/span&gt;&lt;span&gt;\t&lt;/span&gt;&lt;span&gt; check: %d"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
                &lt;span&gt;iterations&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
                &lt;span&gt;depth&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
                &lt;span&gt;acc&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;

            &lt;span&gt;outBuff&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;]&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;msg&lt;/span&gt;
            &lt;span&gt;wg&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Done&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;
        &lt;span&gt;}(&lt;/span&gt;&lt;span&gt;depth&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;iterations&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;outCurr&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
    &lt;span&gt;}&lt;/span&gt;

    &lt;span&gt;wg&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Wait&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;

    &lt;span&gt;// Compute the checksum of the long-lived binary tree that we created&lt;/span&gt;
    &lt;span&gt;// earlier and store its statistics.&lt;/span&gt;
    &lt;span&gt;msg&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;fmt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Sprintf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"long lived tree of depth %d&lt;/span&gt;&lt;span&gt;\t&lt;/span&gt;&lt;span&gt; check: %d"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
        &lt;span&gt;maxDepth&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
        &lt;span&gt;longLivedTree&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Count&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;
    &lt;span&gt;outBuff&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;outSize&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;msg&lt;/span&gt;
    &lt;span&gt;la&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Free&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;

    &lt;span&gt;// Print the statistics for all of the various tree depths.&lt;/span&gt;
    &lt;span&gt;for&lt;/span&gt; &lt;span&gt;_&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;m&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;range&lt;/span&gt; &lt;span&gt;outBuff&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
        &lt;span&gt;fmt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Println&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;m&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
    &lt;span&gt;}&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;

&lt;span&gt;func&lt;/span&gt; &lt;span&gt;main&lt;/span&gt;&lt;span&gt;()&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
    &lt;span&gt;n&lt;/span&gt; &lt;span&gt;:=&lt;/span&gt; &lt;span&gt;0&lt;/span&gt;
    &lt;span&gt;flag&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Parse&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;
    &lt;span&gt;if&lt;/span&gt; &lt;span&gt;flag&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NArg&lt;/span&gt;&lt;span&gt;()&lt;/span&gt; &lt;span&gt;&amp;gt;&lt;/span&gt; &lt;span&gt;0&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
        &lt;span&gt;n&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;_&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;strconv&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Atoi&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;flag&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Arg&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;
    &lt;span&gt;}&lt;/span&gt;

    &lt;span&gt;Run&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;




&lt;/p&gt;

&lt;p&gt;Now is there any performance difference?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go build -tags goexperiment.arenas ./binary-trees-ptr-arena.go
time ./binary-trees-ptr-arena 21
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stretch tree of depth 22         check: 8388607
2097152  trees of depth 4        check: 65011712
524288   trees of depth 6        check: 66584576
131072   trees of depth 8        check: 66977792
32768    trees of depth 10       check: 67076096
8192     trees of depth 12       check: 67100672
2048     trees of depth 14       check: 67106816
512      trees of depth 16       check: 67108352
128      trees of depth 18       check: 67108736
32       trees of depth 20       check: 67108832
long lived tree of depth 21      check: 4194303

real    0m5.777s
user    0m38.207s
sys     0m4.537s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Time difference is around &lt;strong&gt;20%&lt;/strong&gt; which is very impressive given such a small change to original program.&lt;/p&gt;

&lt;p&gt;Keep in mind that a single arena can not be used concurrently (at least as of current implementation), but you can create multiple arenas for multiple goroutines.&lt;/p&gt;

</description>
      <category>go</category>
    </item>
    <item>
      <title>Efficient code review</title>
      <dc:creator>Viacheslav Poturaev</dc:creator>
      <pubDate>Fri, 14 Oct 2022 12:15:52 +0000</pubDate>
      <link>https://forem.com/vearutop/efficient-code-review-3p50</link>
      <guid>https://forem.com/vearutop/efficient-code-review-3p50</guid>
      <description>&lt;p&gt;Code review can be lengthy and stressful, especially if change is complex. &lt;/p&gt;

&lt;p&gt;If the change in question is very important and may have severe consequences, it is important to weigh diverse input and to arrive at an agreement or a reasonable compromise. Addressing valid concerns can reduce the risks of introducing a change.&lt;/p&gt;

&lt;p&gt;Sometimes code review turns into an arena with clashes of strong opinions, this is not always necessary.&lt;/p&gt;

&lt;p&gt;Code review takes resources that could have been spent in development. What does it bring in return?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Meaningfulness&lt;/strong&gt;: reviewer can check that change makes sense from both problem and solution standpoints,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correctness&lt;/strong&gt;: reviewer can check that code change is sufficiently tested and does not have obvious logical flaws, typos/misspells, obsolete documentation,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safety&lt;/strong&gt;: reviewer can flag malicious, obscure or unreliable code,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suitability&lt;/strong&gt;: reviewer can check if the change meets performance expectations, reasonably utilizes available resources, and does not conflict with already existing code. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency&lt;/strong&gt;: reviewer can flag code that violates project conventions and/or common best practices,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Knowledge&lt;/strong&gt;: reviewer and contributor can learn from each other by asking questions about particular decisions or by suggesting alternatives.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All the checks above are usually the best effort to the best knowledge of a reviewer, because doing a thorough and comprehensive assessment may be prohibitively expensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope Of a Change
&lt;/h2&gt;

&lt;p&gt;Smaller changes are easier to manage. Small atomically correct changes are typically less risky. Small changes are easier to review and comprehend. Small changes are not always feasible to deliver (especially in code with tight coupling between the components), but they are something to strive for.&lt;/p&gt;

&lt;p&gt;The pull request (change set) should have a primary goal clearly articulated in description. If code changes are directly relevant to the primary goal, that often allows reaching a minimal acceptable size of changes.&lt;/p&gt;

&lt;p&gt;It might be tempting to put more changes in, to follow boy scout rule. But this bears risks of unbounded growth which may delay the primary goal delivery or even make the whole change set too big for a confident review. If an improvement idea arises during code review and if it does not directly contribute to the primary goal, it is worth considering delivering such improvement as a separate change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Suggestions And Change Requests
&lt;/h2&gt;

&lt;p&gt;Apart from clarification questions, code review can end with approval, suggestions, and change requests.&lt;/p&gt;

&lt;p&gt;Change requests are blocking pull requests from being merged.&lt;br&gt;
A change request should indicate a reason why original changes are not ready to be merged.&lt;/p&gt;

&lt;p&gt;Change requests should be used to address insufficient meaningfulness, correctness, safety, suitability, and consistency (core properties).&lt;/p&gt;

&lt;p&gt;If those properties are not violated, suggestions are preferred.&lt;/p&gt;

&lt;p&gt;Suggestions, as opposed to change requests, do not block pull requests from being merged. They invite pull request authors to consider alternative solutions or additional changes that may be beneficial from reviewer perspective.&lt;/p&gt;

&lt;p&gt;Suggestions can convey opinions on naming, layout, design patterns and other things that can not be backed by existing team conventions. Suggestions help to bring different perspectives in a friendly way and are often accepted by pull request authors. However, pull request authors have a right to politely decline a suggestion if they think it does not bring enough value for the risks (growth of scope, unclear impact on core properties).&lt;/p&gt;

&lt;p&gt;Declined suggestions can still be delivered in a separate pull request to avoid scope creep, and they can pass through a separate code review.&lt;/p&gt;

&lt;p&gt;Both suggestions and change requests can vary in how helpful they are. Helpful messages may contain actual changes that can be applied to the code in question.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attitude
&lt;/h2&gt;

&lt;p&gt;Miscommunications and misunderstandings can happen often. They can happen even more often in a diverse team, where people have their own biases and cultural background. &lt;/p&gt;

&lt;p&gt;Miscommunication is harmful, from being just counter-productive time spending to a more severe issue when feelings are hurt.&lt;/p&gt;

&lt;p&gt;It is very important to have positive assumptions. If a person is having negative preliminary bias or negative assumptions, such bias can color messages with neutral original intent into offensive ones, and can even lead to painful escalations.&lt;/p&gt;

&lt;p&gt;While we're all people with emotions, pull request is a technical ground, sticking to technical language and technical argumentation together with explicit articulation can help to reduce miscommunications. If you feel offended by a message, take time to calm down and re-read the context with an effort to discover positive/neutral intent. There is a high chance that this was the original intent conveyed by the author.&lt;/p&gt;

&lt;p&gt;Code review is a collaboration tool, we're doing it to be more successful as a team. The best outcome of a review is a quick and confident approval without any suggestions, such cases usually indicate a high level of harmony in a team. &lt;/p&gt;

&lt;p&gt;Code review is not the best place to exercise one's own importance by nitpicking or asking for cosmetic/out-of-scope changes. If the suggestion feels important, but it meets justified resistance from a pull request owner and/or other reviewers, it might be a good idea to submit such a suggestion as a separate pull request.&lt;/p&gt;

&lt;p&gt;Try to avoid making suggestions/change requests based solely on opinion. Opinions can vary from one person to another, and they can easily be a point of locked disagreement. The opinion of one person is not necessarily more significant than the opinion of another person, so there is a fundamental issue that may need the involvement of an authorized arbiter. &lt;/p&gt;

&lt;p&gt;Technical argumentation with pros and cons (as opposed to just opinion) can lead to a balanced compromise or consensus.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rules And Principles
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;All parties are egoless and committed to reach ideal handling&lt;/li&gt;
&lt;li&gt;All parties take review process as an opportunity to learn&lt;/li&gt;
&lt;li&gt;All parties are trying to communicate in a timely manner to avoid blocking each other&lt;/li&gt;
&lt;li&gt;Review process does not take longer time than necessary&lt;/li&gt;
&lt;li&gt;Contributor articulates the problem to be solved, comprehensible by reviewer&lt;/li&gt;
&lt;li&gt;If the problem is a bug, contributor creates a test to reproduce the bug with CI before pushing a fix&lt;/li&gt;
&lt;li&gt;Contributor makes changes that are directly relevant to the problem described in PR&lt;/li&gt;
&lt;li&gt;Contributor avoids unnecessary changes&lt;/li&gt;
&lt;li&gt;Contributor formats changes in a way that is consistent with existing code&lt;/li&gt;
&lt;li&gt;Contributor applies &lt;a href="https://martinfowler.com/bliki/OpportunisticRefactoring.html"&gt;boy scout rule&lt;/a&gt; when that does not violate above points&lt;/li&gt;
&lt;li&gt;Contributor creates additional tests to cover new behavior&lt;/li&gt;
&lt;li&gt;Contributor updates the tests to match changed behavior&lt;/li&gt;
&lt;li&gt;Contributor makes sure the problem is solved (best effort)&lt;/li&gt;
&lt;li&gt;Contributor is ready to provide justification for every change that was made&lt;/li&gt;
&lt;li&gt;Reviewer comprehends PR description and the problem&lt;/li&gt;
&lt;li&gt;If reviewer has a doubt or lack of understanding, they request a clarification&lt;/li&gt;
&lt;li&gt;If clarification is requested, contributor reasonably elaborates the topic&lt;/li&gt;
&lt;li&gt;Reviewer checks that problem of PR is worth solving&lt;/li&gt;
&lt;li&gt;Reviewer checks changes in tests to confirm that the goal of PR is achieved&lt;/li&gt;
&lt;li&gt;Reviewer checks changes in tests to confirm there are no unnecessary changes in behavior&lt;/li&gt;
&lt;li&gt;Reviewer checks that changes do not introduce a security/performance issue (best effort)&lt;/li&gt;
&lt;li&gt;Reviewer checks that changes do not have logical conflict with existing code (best effort)&lt;/li&gt;
&lt;li&gt;Reviewer makes helpful change requests if necessary&lt;/li&gt;
&lt;li&gt;Reviewer is ready to provide justification for change request&lt;/li&gt;
&lt;li&gt;Helpful change request is suggestive, precise, and relevant to the problem of PR&lt;/li&gt;
&lt;li&gt;Reviewer avoids unhelpful change requests&lt;/li&gt;
&lt;li&gt;Unhelpful change request is broad, opinionated, or not relevant to the problem of PR&lt;/li&gt;
&lt;li&gt;Contributor can challenge any change request&lt;/li&gt;
&lt;li&gt;If justification of change request is shown to be weak, change request can be discarded&lt;/li&gt;
&lt;li&gt;If helpfulness of a change request is questionable, reviewer can elaborate it to clearly helpful, or it can be discarded&lt;/li&gt;
&lt;li&gt;Reviewer can challenge any change of PR&lt;/li&gt;
&lt;li&gt;If justification of change is shown to be weak, contributor has to revert the change&lt;/li&gt;
&lt;li&gt;If there are helpful change requests that could not be discarded, contributor has to address them with PR changes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  See Also
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://google.github.io/eng-practices/review/"&gt;Code Review Developer Guide&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>codereview</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Using Nginx as a proxy to multiple Unix sockets</title>
      <dc:creator>Viacheslav Poturaev</dc:creator>
      <pubDate>Mon, 08 Aug 2022 08:59:00 +0000</pubDate>
      <link>https://forem.com/vearutop/using-nginx-as-a-proxy-to-multiple-unix-sockets-3c7a</link>
      <guid>https://forem.com/vearutop/using-nginx-as-a-proxy-to-multiple-unix-sockets-3c7a</guid>
      <description>&lt;p&gt;TL;DR &lt;em&gt;Listening port may be a contended resource on a busy shared machine, unix sockets are virtually unlimited. Nginx can expose them with a single port and prefixed URLs.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In some situations you may want to run many (instances of) applications on a single machine. Each instance may need to provide internal information (e.g. Prometheus &lt;code&gt;/metrics&lt;/code&gt;, profiling/debug handlers) over restricted HTTP.&lt;/p&gt;

&lt;p&gt;When number of instances grows it becomes a burden to provision listening ports without conflicts. In contrast, using Unix sockets allows for more transparency (readable filenames) and scalability (easy to come up with unique name).&lt;/p&gt;

&lt;p&gt;Here is a small demo program written in Go that would serve trivial HTTP service with Unix socket.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"flag"&lt;/span&gt;
    &lt;span class="s"&gt;"io/fs"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"net"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"os/signal"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;socketPath&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;

    &lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StringVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;socketPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"socket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"./soc1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Path to unix socket."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;socketPath&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Usage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unix"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socketPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// By default, unix socket would only be available to same user.&lt;/span&gt;
    &lt;span class="c"&gt;// If we want access it from Nginx, we need to loosen permissions.&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chmod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socketPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModePerm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;httpServer&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Setting up graceful shutdown to clean up Unix socket.&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;sigint&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sigint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interrupt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;sigint&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;httpServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shutdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HTTP Server Shutdown Error: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;

    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Service is listening on socket file %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socketPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's run a couple of instances in separate shells.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./soc -socket /home/ubuntu/soc1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./soc -socket /home/ubuntu/soc2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a minimal Nginx config to serve those instances with URL prefixes. It would receive &lt;code&gt;http://my-host/soc1/foo/bar&lt;/code&gt;, strip path prefix &lt;code&gt;/soc1&lt;/code&gt; and pass &lt;code&gt;/foo/bar&lt;/code&gt; to &lt;code&gt;soc1&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server {
    listen 80 default;

    location /soc1/ {
        proxy_pass http://soc1/;
    }
    location /soc2/ {
        proxy_pass http://soc2/;
    }
}

upstream soc1 {
    server unix:/home/ubuntu/soc1;
}

upstream soc2 {
    server unix:/home/ubuntu/soc2;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every Unix socket is defined as &lt;code&gt;upstream&lt;/code&gt; and has &lt;code&gt;/location&lt;/code&gt; statement in &lt;code&gt;server&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It is also possible to use Unix sockets directly in &lt;code&gt;/location&lt;/code&gt;, like in&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    location /soc1/ {
        proxy_pass http://unix:/home/ubuntu/soc1;
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;however it has an unwanted limitation that you can not add trailing &lt;code&gt;/&lt;/code&gt; to &lt;code&gt;proxy_pass&lt;/code&gt;. And this means that URL will be passed as is, e.g. &lt;code&gt;soc1&lt;/code&gt; will receive &lt;code&gt;/soc1/foo&lt;/code&gt; instead of &lt;code&gt;/foo&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To avoid such limitation we can use named upstream and add trailing &lt;code&gt;/&lt;/code&gt; to &lt;code&gt;proxy_pass&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    location /soc1/ {
        proxy_pass http://soc1/; # Mind trailing "/".
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>nginx</category>
      <category>go</category>
      <category>unixsocket</category>
    </item>
    <item>
      <title>Implementing robust in-memory cache with Go</title>
      <dc:creator>Viacheslav Poturaev</dc:creator>
      <pubDate>Thu, 12 May 2022 00:55:35 +0000</pubDate>
      <link>https://forem.com/vearutop/implementing-robust-in-memory-cache-with-go-196e</link>
      <guid>https://forem.com/vearutop/implementing-robust-in-memory-cache-with-go-196e</guid>
      <description>&lt;p&gt;&lt;em&gt;TL;DR&lt;/em&gt; In-memory caching is a great way to improve performance and resiliency of an application at cost of memory and delayed data consistency. You need to take care of concurrent updates, error caching, failover handling, background updates, expiration jitter and cache warmup with transfer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Story
&lt;/h2&gt;

&lt;p&gt;Caching is one of the most efficient techniques to improve performance, because the fastest way to get rid of a task is skipping it. Unfortunately caching is not a silver bullet, in some cases you can not afford reusing result of a task due to transactionality/consistency constraints. Cache invalidation is notoriously one of &lt;a href="https://martinfowler.com/bliki/TwoHardThings.html" rel="noopener noreferrer"&gt;two hard things&lt;/a&gt; in Computer Science.&lt;/p&gt;

&lt;p&gt;It is best when domain operates on immutable data and so cache invalidation is not necessary. In such case cache is usually a net benefit. However, if there are requirements to keep mutable data in sync, cache invalidation is necessary.&lt;br&gt;
The simplest strategy is to invalidate cache based on time to live (TTL). Even if seems like a bad fit compared to event-based invalidation, consider simplicity and portability. Events do not guarantee timely delivery, in worst case scenarios (for example if event broker is temporary down or overloaded) events could be even less precise than TTL.&lt;/p&gt;

&lt;p&gt;Short TTL is often a good compromise between performance and consistency. It would reduce the load under heavy traffic acting as a barrier to the data source. For the low traffic impact would be negligible.&lt;/p&gt;
&lt;h3&gt;
  
  
  Demo Application
&lt;/h3&gt;

&lt;p&gt;Let's start with a simple demo application. It will receive URL with query parameters and respond with a JSON object determined by those parameters. Unique results will be stored in database to make things realistically slow.&lt;/p&gt;

&lt;p&gt;We're going to put some load on the application with a &lt;a href="https://github.com/vearutop/cache-story/blob/master/cmd/cplt/cplt.go" rel="noopener noreferrer"&gt;custom&lt;/a&gt; &lt;a href="https://github.com/vearutop/plt" rel="noopener noreferrer"&gt;&lt;code&gt;plt&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Custom &lt;code&gt;plt&lt;/code&gt; has additional parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cardinality&lt;/code&gt; - number of unique URLs to be generated, this affects cache hit rate,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;group&lt;/code&gt; - number of requests with similar URL being sent at once, this imitates concurrent access to the same key.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

go run ./cmd/cplt --cardinality 10000 --group 100 --live-ui --duration 10h --rate-limit 5000 curl --concurrency 200 -X 'GET'   'http://127.0.0.1:8008/hello?name=World&amp;amp;locale=ru-RU'   -H 'accept: application/json'


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Such a command will start a client that will send 10000 different URLs in the loop, trying keep rate of 5000 requests per second by using up to 200 concurrent requests. Every URL would be sent in a batch of 100 requests to imitate concurrency on a single resource.&lt;/p&gt;

&lt;p&gt;It will show live performance statistics and overall results.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffzcfh87spbds94prew9m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffzcfh87spbds94prew9m.png" alt="cplt screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Demo app has three modes of operation controlled by &lt;code&gt;CACHE&lt;/code&gt; environment variable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;none&lt;/code&gt; - no caching, all requests are served with involvement of the database,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;naive&lt;/code&gt; - naive caching with a simple map and TTL of 3 minutes,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;advanced&lt;/code&gt; - caching using &lt;a href="https://github.com/bool64/cache" rel="noopener noreferrer"&gt;&lt;code&gt;github.com/bool64/cache&lt;/code&gt;&lt;/a&gt; library that implements a
number of features to improve performance and resiliency, TTL is also 3 minutes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Application is available at &lt;a href="https://github.com/vearutop/cache-story" rel="noopener noreferrer"&gt;github.com/vearutop/cache-story&lt;/a&gt;.&lt;br&gt;
If you would like to experiment yourself with it, you can start it with &lt;code&gt;make start-deps run&lt;/code&gt;.&lt;br&gt;
It depends on &lt;code&gt;docker-compose&lt;/code&gt; to spin up database, prometheus, grafana (&lt;a href="http://localhost:3001" rel="noopener noreferrer"&gt;http://localhost:3001&lt;/a&gt;) and jaeger (&lt;a href="http://localhost:16686/" rel="noopener noreferrer"&gt;http://localhost:16686/&lt;/a&gt;). You can stop dependencies with &lt;code&gt;make stop-deps&lt;/code&gt; later.&lt;/p&gt;

&lt;p&gt;On my machine I was able to achieve ~500 RPS with no cache. After ~130 concurrent requests DB starts choking with &lt;code&gt;Too many connections&lt;/code&gt;. Such result is not great, not terrible, but looks like an improvement opportunity.&lt;br&gt;
Let's see what we can achieve with help of caching.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsyv6xepamz7e8ohkj24z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsyv6xepamz7e8ohkj24z.png" alt="Baseline Performance"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;advanced&lt;/code&gt; cache same laptop was able to show these results (roughly 60x throughput with lower latency).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd0t8zb4yixkbzuvg30md.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd0t8zb4yixkbzuvg30md.png" alt="Advanced Performance"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

go run ./cmd/cplt --cardinality 10000 --group 100 --live-ui --duration 10h curl --concurrency 100 -X 'GET'   'http://127.0.0.1:8008/hello?name=World&amp;amp;locale=ru-RU'   -H 'accept: application/json'


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Requests per second: 25064.03
Successful requests: 15692019
Time spent: 10m26.078s

Request latency percentiles:
99%: 28.22ms
95%: 13.87ms
90%: 9.77ms
50%: 2.29ms


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Bytes VS Structures
&lt;/h3&gt;

&lt;p&gt;Which one is better?&lt;/p&gt;

&lt;p&gt;That depends on the use case, byte cache (or storing data as &lt;code&gt;[]byte&lt;/code&gt;) have some advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it grants immutability, because you'll need to decode a new value every time you need it,&lt;/li&gt;
&lt;li&gt;it generally takes less memory, because of less fragmentation,&lt;/li&gt;
&lt;li&gt;it is more friendly to garbage collector, because there is nothing to traverse through,&lt;/li&gt;
&lt;li&gt;it can be easily sent over the wire, because it is exactly what wire expects,&lt;/li&gt;
&lt;li&gt;allows precise memory limit, bytes are so easy to count.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Main disadvantage is the cost of encoding and decoding. In hot loops it can become prohibitively expensive.&lt;/p&gt;

&lt;p&gt;Advantages of structures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no need to encode/decode a value every time you need it,&lt;/li&gt;
&lt;li&gt;better expressiveness as you can potentially cache things that can not be serialized,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Disadvantages of structure cache:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mutability, because you reuse same value multiple times it is quite easy to change it without intention,&lt;/li&gt;
&lt;li&gt;memory usage, structures take relatively sparse areas of memory,&lt;/li&gt;
&lt;li&gt;garbage collector pressure, if you have a large set of long-living structures, GC may spend significant time traversing them and proving they are still in use,&lt;/li&gt;
&lt;li&gt;nearly impossible to impose a total memory limit on a cache instance, dynamically-sized items are stored on the heap together with everything else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article we will use structure cache.&lt;/p&gt;
&lt;h3&gt;
  
  
  Naive Cache
&lt;/h3&gt;

&lt;p&gt;The simplest in-memory cache is a &lt;a href="https://github.com/vearutop/cache-story/blob/master/internal/infra/cached/naive.go" rel="noopener noreferrer"&gt;&lt;code&gt;map&lt;/code&gt; guarded by a mutex&lt;/a&gt;.&lt;br&gt;
When you need a value for a key, you first check if it's in the cache and not expired.&lt;br&gt;
If it's not, you build it from the data source and put it in the cache.&lt;br&gt;
Then you return the value to the caller.&lt;/p&gt;

&lt;p&gt;This logic is simple, but it has some drawbacks that may lead to critical issues.&lt;/p&gt;
&lt;h3&gt;
  
  
  Concurrent Updates
&lt;/h3&gt;

&lt;p&gt;When multiple callers simultaneously miss the same key, they will all try to build the value. This can lead to a deadlock or to resource exhaustion with &lt;a href="https://en.wikipedia.org/wiki/Cache_stampede" rel="noopener noreferrer"&gt;cache stampede&lt;/a&gt; failure.&lt;/p&gt;

&lt;p&gt;Additionally, there will be extra latency for all the callers that would try to build the value.&lt;br&gt;
If some of those builds fail, parent callers will fail even though there might be a valid value in the cache.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ejk4kc13gzwe2jwdse9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ejk4kc13gzwe2jwdse9.png" alt="Naive Cache Diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The issue can be simulated by using low cardinality with high grouping, so that many similar requests are sent at once.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

go run ./cmd/cplt --cardinality 100 --group 1000 --live-ui --duration 10h --rate-limit 5000 curl --concurrency 150 -X 'GET'   'http://127.0.0.1:8008/hello?name=World&amp;amp;locale=ru-RU'   -H 'accept: application/json'


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6vek8tunyvd365xk2rlg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6vek8tunyvd365xk2rlg.png" alt="Key Locking"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This chart shows application started with &lt;code&gt;naive&lt;/code&gt; cache and then, on the blue marker it was restarted with &lt;code&gt;advanced&lt;/code&gt; cache. As you can see key locking can have a significant impact on performance (mind &lt;em&gt;Incoming Request Latency&lt;/em&gt;) and resource usage (mind &lt;em&gt;DB Operation Rate&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;The solution could be to block parallel builds, so that only one build is in progress at a time. But this would suffer from contention if there are many concurrent callers asking for a variety of keys.&lt;/p&gt;

&lt;p&gt;A better solution is to lock the builds per key, so that one of the callers acquires the lock and owns the build, while all the others wait for the value.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fid50q78vc75gyt361s5z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fid50q78vc75gyt361s5z.png" alt="Locked Cache Diagram"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Background Updates
&lt;/h3&gt;

&lt;p&gt;When cached entry expires it needs a new value, building new value can be slow. If we do it synchronously, we'll slow down tail latency (99+ percentile). For cache entries that are in high demand it is feasible to start the build in advance, even before the value is expired. It can also work if we can afford some level of staleness for the expired value.&lt;/p&gt;

&lt;p&gt;In such case we can immediately serve stale/soon-to-be-expired value and start update in background. One caveat here is that if build depends on parent context, the context may be cancelled right after we served stale value (for example when parent HTTP request was fulfilled). If we use such context to access database, we'll get a &lt;code&gt;context canceled&lt;/code&gt; error.&lt;br&gt;
Solution for this problem is to "&lt;a href="https://github.com/bool64/cache/blob/v0.2.5/context.go#L66-L85" rel="noopener noreferrer"&gt;detach&lt;/a&gt;" the context from the parent context and ignore parent cancellation.&lt;br&gt;
&lt;strong&gt;Update:&lt;/strong&gt; Go 1.21 added &lt;a href="https://pkg.go.dev/context#WithoutCancel" rel="noopener noreferrer"&gt;&lt;code&gt;WithoutCancel&lt;/code&gt;&lt;/a&gt; to the context package.&lt;/p&gt;

&lt;p&gt;Another strategy might be to proactively rebuild cached entries that are soon to be expired, without a parent request, but this may lead to resource exhaustion due to keeping obsolete cache entries that are of no interest to anybody.&lt;/p&gt;
&lt;h3&gt;
  
  
  Expiration Sync
&lt;/h3&gt;

&lt;p&gt;Imagine situation that we start a new instance with TTL cache enabled, cache is empty and almost every request leads to cache miss and value creation. This will spike the load on the data source and store cached entries with very close expiration time. Once TTL have passed, the majority of cached entries will expire almost simultaneously, this will lead to a new load spike. Updated values will have close expiration time again and situation will repeat.&lt;/p&gt;

&lt;p&gt;This is a common problem for hot cache entries.&lt;/p&gt;

&lt;p&gt;Eventually cache entries will come out of sync, but this may take a while.&lt;/p&gt;

&lt;p&gt;Solution to this problem is to break the sync by adding jitter to the expiration time.&lt;br&gt;
If expiration jitter is 10% (0.1) it means TTL will vary from &lt;code&gt;0.95 * TTL&lt;/code&gt; to &lt;code&gt;1.05 * TTL&lt;/code&gt;. Even such a small jitter will already help to reduce expiration synchronization.&lt;/p&gt;

&lt;p&gt;Here is an example, we're pushing load with high cardinality and high concurrency on the service. It will require many entries to be available in short period of time, enough to form an expiration spike.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

go run ./cmd/cplt --cardinality 10000 --group 1 --live-ui --duration 10h --rate-limit 5000 curl --concurrency 200 -X 'GET' 'http://127.0.0.1:8008/hello?name=World&amp;amp;locale=ru-RU' -H 'accept: application/json'


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fevzpepptmq3unp4ahp44.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fevzpepptmq3unp4ahp44.png" alt="Expiration Sync"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The chart starts with &lt;code&gt;naive&lt;/code&gt; cache that does not do anything to avoid the sync, second marker indicates service restart with &lt;code&gt;advanced&lt;/code&gt; cache that has 10% jitter added to the expiration time. Spikes are wider and shorter and fall faster, overall service stability is better.&lt;/p&gt;
&lt;h3&gt;
  
  
  Errors Caching
&lt;/h3&gt;

&lt;p&gt;When value build fails the easiest thing to do is just return that error to the caller and forget about it.&lt;br&gt;
This can lead to severe issues.&lt;/p&gt;

&lt;p&gt;For example, your service works well and handles 10K RPS with help of cache, but suddenly cache builds start to fail (be it because of temporary database overload, network issue or maybe even logical error like failing validation).&lt;br&gt;
At this point all 10K RPS (instead of usual 100 RPS) will hit data source directly because there will be no cache to serve.&lt;/p&gt;

&lt;p&gt;Minor temporary outage would escalate exponentially.&lt;/p&gt;

&lt;p&gt;For high load systems it is very important to cache failures with short TTL to avoid cascading failures.&lt;/p&gt;

&lt;p&gt;Having a situation when high performance system can drop out of its pattern and face drastic degradation is sometimes called a &lt;a href="https://youtu.be/5DwScOLRuPM?si=bD1Wd8kp2X4dV3Ez&amp;amp;t=1614" rel="noopener noreferrer"&gt;"Performance Cliff"&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Failover Mode
&lt;/h3&gt;

&lt;p&gt;Sometimes serving obsolete value is better than returning error. Especially if obsolete value expired recently and there is still high chance that it is equal to an up-to-date value.&lt;/p&gt;

&lt;p&gt;Failover mode helps to improve resiliency at cost of accuracy, which is often a fair tradeoff in distributed systems.&lt;/p&gt;
&lt;h3&gt;
  
  
  Cache Transfer
&lt;/h3&gt;

&lt;p&gt;Cache works best when it has relevant data.&lt;br&gt;
When a new instance of application is started, cache is empty.&lt;br&gt;
Populating helpful data takes time, during this time cache efficiency may degrade significantly.&lt;/p&gt;

&lt;p&gt;There are a few ways to work around the issue of "cold" cache.&lt;/p&gt;

&lt;p&gt;You can warm up the cache by iterating over the data that is assumed to be useful. For example, you can fetch recent contents of a database table and store them in cache. This approach is complex and not always effective.&lt;br&gt;
You need to decide what data to use and rebuild cache entries with a piece of bespoke code. This may put excessive load on the database (or other sources of data).&lt;/p&gt;

&lt;p&gt;You can also avoid this issue by using a shared instance of cache, like redis or memcached. It has another issue, reading data over the network is much slower, than from local memory. Also, network bandwidth may become a scalability bottleneck. Wired data needs to be deserialized which adds on latency and resource usage.&lt;/p&gt;

&lt;p&gt;The simple solution to this problem is to transfer cache from active instance to the newly started. Cached data of active instance naturally has high relevance, because it was populated in response to actual user requests. Transferring cache does not need to rebuild data and so it won't abuse data sources.&lt;/p&gt;

&lt;p&gt;Usually production systems have multiple instances of application running in parallel. During deployment, these instances are restarted sequentially, so there is always an instance that is active and has high quality cache.&lt;/p&gt;

&lt;p&gt;Go has a built-in binary serialization format &lt;code&gt;encoding/gob&lt;/code&gt;. It helps to transfer data over the wire with minimal effort. The limitation is that it is based on reflection and needs data to have exported fields.&lt;/p&gt;

&lt;p&gt;Another caveat with cache transfer is that different versions of application may have different data structures that are not necessarily compatible. To mitigate this issue, you can fingerprint cached structures (using reflection) and abort transfer in case of discrepancy.&lt;/p&gt;

&lt;p&gt;
  Here is &lt;a href="https://github.com/bool64/cache/blob/v0.2.5/gob.go#L49-L90" rel="noopener noreferrer"&gt;a sample implementation&lt;/a&gt;.
  &lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="c"&gt;// RecursiveTypeHash hashes type of value recursively to ensure structural match.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;recursiveTypeHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hash64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;met&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Kind&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ptr&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Elem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;met&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;met&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;

    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Kind&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Struct&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NumField&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c"&gt;// Skip unexported field.&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToLower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Anonymous&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;recursiveTypeHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;met&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Slice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;recursiveTypeHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Elem&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;met&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;recursiveTypeHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;met&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;recursiveTypeHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Elem&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;met&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;/p&gt;

&lt;p&gt;Transfer can be done with HTTP or any other suitable protocol. In this example, we will use HTTP, served at &lt;a href="https://pkg.go.dev/github.com/bool64/cache#HTTPTransfer.Export" rel="noopener noreferrer"&gt;&lt;code&gt;/debug/transfer-cache&lt;/code&gt;&lt;/a&gt;. Please be aware that cache may contain sensitive information and should not have exposure to public.&lt;/p&gt;

&lt;p&gt;For sake of this example, we can perform transfer with help of a separate instance of an application serving on a different port.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

CACHE_TRANSFER_URL=http://127.0.0.1:8008/debug/transfer-cache HTTP_LISTEN_ADDR=127.0.0.1:8009 go run main.go


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

2022-05-09T02:33:42.871+0200    INFO    cache/http.go:282       cache restored  {"processed": 10000, "elapsed": "12.963942ms", "speed": "39.564084 MB/s", "bytes": 537846}
2022-05-09T02:33:42.874+0200    INFO    brick/http.go:66        starting server, Swagger UI at http://127.0.0.1:8009/docs
2022-05-09T02:34:01.162+0200    INFO    cache/http.go:175       cache dump finished     {"processed": 10000, "elapsed": "12.654621ms", "bytes": 537846, "speed": "40.530944 MB/s", "name": "greetings", "trace.id": "31aeeb8e9e622b3cd3e1aa29fa3334af", "transaction.id": "a0e8d90542325ab4"}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn3ueq5zysxofde50bj01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn3ueq5zysxofde50bj01.png" alt="Cache Transfer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This chart shows application restarts at blue markers, last two are made with cache transfer. You can see that performance remains unaffected, while when there is no cache transfer there is a significant warmup penalty.&lt;/p&gt;

&lt;p&gt;A less obvious benefit is that cache can also be transferred to a local instance on developer machine, this can help to reproduce and debug production issues much easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lock Contention And Low-level Performance
&lt;/h3&gt;

&lt;p&gt;Essentially every cache implementation acts as a map of values by keys with concurrent (mostly read) access.&lt;/p&gt;

&lt;p&gt;Low-level performance concerns can be considered irrelevant in many cases. For example, if you use in-memory cache to power your HTTP API, even the simplest solution with map and mutex will likely be sufficient. That is because IO operations that are performed to serve HTTP are way slower than memory operations (even with synchronization penalties). This is important to keep in mind to avoid premature optimizations and unjustified complexity.&lt;/p&gt;

&lt;p&gt;If the application that relies on in-memory cache is mostly CPU-bound, lock contention may become significant in overall performance.&lt;/p&gt;

&lt;p&gt;Problem with lock contention is that concurrent reads and writes have to be synchronized in order to avoid data conflicts. In case of single mutex such synchronization would impose a limitation of only one operation at a time. This means modern CPUs with multiple cores would not be able to use all the capacity.&lt;/p&gt;

&lt;p&gt;For mostly-read workloads standard &lt;a href="https://pkg.go.dev/sync#Map" rel="noopener noreferrer"&gt;&lt;code&gt;sync.Map&lt;/code&gt;&lt;/a&gt; offers great performance, however, it degrades with more writes. There is a another custom implementation that outperforms &lt;code&gt;sync.Map&lt;/code&gt; thanks to &lt;a href="https://github.com/LPD-EPFL/CLHT" rel="noopener noreferrer"&gt;Cache-Line Hash Table&lt;/a&gt; (CLHT) data structure: &lt;a href="https://pkg.go.dev/github.com/puzpuzpuz/xsync#Map" rel="noopener noreferrer"&gt;&lt;code&gt;github.com/puzpuzpuz/xsync.Map&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Another popular approach to reduce lock contention is map sharding (&lt;a href="https://github.com/VictoriaMetrics/fastcache/blob/v1.10.0/fastcache.go#L112" rel="noopener noreferrer"&gt;fastcache&lt;/a&gt;, &lt;a href="https://github.com/allegro/bigcache/blob/v3.0.2/bigcache.go#L16" rel="noopener noreferrer"&gt;bigcache&lt;/a&gt;, &lt;a href="https://github.com/bool64/cache/blob/v0.2.5/sharded_map.go#L35" rel="noopener noreferrer"&gt;bool64/cache&lt;/a&gt;), when values are deterministically distributed in separate buckets based on keys. It has a good balance of simplicity and performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory Management
&lt;/h3&gt;

&lt;p&gt;Memory is a limited resource, so cache should not grow indefinitely.&lt;/p&gt;

&lt;p&gt;Expired items should eventually be removed from the cache. &lt;br&gt;
This can be done synchronously or in background. Background janitor is a good idea, because it will not block the application. &lt;br&gt;
Also, background janitor can be configured to remove items with a delay, so that expired value is still available as a failover backup.&lt;/p&gt;

&lt;p&gt;Removing expired items might be not enough to keep memory usage under control.&lt;br&gt;
There are &lt;a href="https://en.wikipedia.org/wiki/Cache_replacement_policies" rel="noopener noreferrer"&gt;different strategies&lt;/a&gt; to decide which items to evict.&lt;br&gt;
When deciding on a strategy you should find a balance between CPU/memory usage and hit/miss ratio. Ultimately, goal of eviction is to optimize hit/miss ratio while remaining in acceptable performance budget, so this is the metric you need to look at when evaluating eviction strategies.&lt;/p&gt;

&lt;p&gt;Here are some popular criteria to pick an item:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;least-frequently used (LFU), relatively expensive as it needs to maintain counters that should be updated on every access,&lt;/li&gt;
&lt;li&gt;least-recently used (LRU), relatively expensive as it needs to update item timestamps or reorder the list of keys on every access,&lt;/li&gt;
&lt;li&gt;first in first out (FIFO), relatively cheap as it can use values that are populated once item cache is created,&lt;/li&gt;
&lt;li&gt;random item, most performant, does not need any kinds of sorting, the least accurate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I would suggest to evaluate FIFO and random eviction first, and only invest in LRU/LFU if it has proven significant impact on hit/miss ratio.&lt;/p&gt;

&lt;p&gt;Now that you have a good idea about how to pick an eviction strategy, next question is "when and how many items should be evicted?".&lt;/p&gt;

&lt;p&gt;This is a trivial question for &lt;code&gt;[]byte&lt;/code&gt; cache, most of the implementations provide you precise control over how much memory to use.&lt;/p&gt;

&lt;p&gt;The question is tricky for structure cache. There is no feasible and reliable way to determine impact of a particular structure on the heap memory usage during application execution, this information may be available to GC, but not to the application itself.&lt;/p&gt;

&lt;p&gt;Two less precise, but still useful, metrics are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;total count of items in the cache,&lt;/li&gt;
&lt;li&gt;total memory usage of the whole application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both metrics can be easily obtained during application execution, and then lead to eviction.&lt;br&gt;
Because these metrics are not linearly proportional to cache memory usage, they can not be used to calculate the exact list of items to evict.&lt;br&gt;
An appropriate response would be to evict a configurable fraction of the items (for example 10% of the items), once eviction is triggered.&lt;/p&gt;

&lt;p&gt;Heap impact of cached data largely depends on mapping implementation. &lt;br&gt;
As you can see from the benchmark below, &lt;code&gt;map[string]struct{...}&lt;/code&gt; may have 4x memory consumption compared to efficient binary serialization (uncompressed).&lt;/p&gt;

&lt;h3&gt;
  
  
  Baseline Benchmark
&lt;/h3&gt;

&lt;p&gt;Here is a baseline benchmark for storing 1M small structures (&lt;code&gt;struct { int, bool, string }&lt;/code&gt;) and accessing them with 10% and 0.1% writes.&lt;br&gt;
Byte caches are instrumented with binary de/encoder of structure.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

name             MB/inuse   time/op (10%) time/op (0.1%)      
sync.Map         192 ± 0%   142ns ± 4%    29.8ns ±10%   // Great for read-heavy workloads.
shardedMap       196 ± 0%   53.3ns ± 3%   28.4ns ±11%   
mutexMap         182 ± 0%   226ns ± 3%    207ns ± 1%    
rwMutexMap       182 ± 0%   233ns ± 2%    67.8ns ± 2%   // RWMutex perf degrades with more writes.
shardedMapOf     181 ± 0%   50.3ns ± 3%   27.3ns ±13%   
ristretto        346 ± 0%   167ns ± 8%    54.1ns ± 4%   // Failed to keep full working set, ~7-15% of the items are evicted.
xsync.Map        380 ± 0%   31.4ns ± 9%   22.0ns ±14%   // Fastest, but a bit hungry for memory.
patrickmn        184 ± 0%   373ns ± 1%    72.6ns ± 5%   
bigcache         340 ± 0%   75.8ns ± 8%   72.9ns ± 3%   // Byte cache.
freecache        333 ± 0%   98.1ns ± 0%   77.8ns ± 2%   // Byte cache.
fastcache       44.9 ± 0%   60.6ns ± 8%   64.1ns ± 5%   // A true champion for memory usage, while having decent performance.


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If serialization is not a problem &lt;a href="https://github.com/VictoriaMetrics/fastcache" rel="noopener noreferrer"&gt;&lt;code&gt;fastcache&lt;/code&gt;&lt;/a&gt; is the best for its outstanding memory usage.&lt;/p&gt;

&lt;p&gt;For CPU-bound applications &lt;a href="https://github.com/puzpuzpuz/xsync#Map" rel="noopener noreferrer"&gt;&lt;code&gt;xsync.Map&lt;/code&gt;&lt;/a&gt; offers the best performance.&lt;/p&gt;

&lt;p&gt;Also, byte cache does not necessarily mean efficient memory usage, as shown by &lt;code&gt;bigcache&lt;/code&gt; (ha!) and &lt;code&gt;freecache&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Developer Friendliness
&lt;/h3&gt;

&lt;p&gt;Programs that we write do not always behave like we want them to. Complexity leads to unexpected issues, that are hard to debug.&lt;br&gt;
Unfortunately, caching makes this problem even worse. That's why it is especially important to make caching developer-friendly.&lt;/p&gt;

&lt;p&gt;Cache may become poisoned for a variety of reasons, it should be possible to clean it up quickly and safely.&lt;br&gt;
For that, consider having a special control to invalidate all cached items.&lt;br&gt;
Under heavy load invalidation does not necessarily mean "deletion", if all cache is deleted at once data sources will receive excessive load and may fail.&lt;br&gt;
A more gentle invalidation is "expiration" of all items with background update, while serving stale data.&lt;/p&gt;

&lt;p&gt;Cache entry can become outdated and would mislead if somebody is investigating particular data source issues. It is convenient to disable cache for a particular request, so that cache inaccuracy can be ruled out. This may be implemented with a special header and then &lt;a href="https://pkg.go.dev/github.com/bool64/cache#SkipRead" rel="noopener noreferrer"&gt;context instrumentation&lt;/a&gt; in middleware. Please be aware that such controls should not be available for public users as they can be used as for DOS attack. &lt;/p&gt;

&lt;p&gt;Fine control over logs and metrics of cache operations/state is also important.&lt;/p&gt;

&lt;p&gt;And finally, now that Go introduced type parameters (generics), it is possible to leverage type-safe APIs for cache interfaces and offset more burden on compiler.&lt;/p&gt;

</description>
      <category>go</category>
      <category>cache</category>
      <category>performance</category>
    </item>
    <item>
      <title>GitHub Actions with unstable Go</title>
      <dc:creator>Viacheslav Poturaev</dc:creator>
      <pubDate>Sat, 26 Feb 2022 08:34:47 +0000</pubDate>
      <link>https://forem.com/vearutop/github-actions-with-unstable-go-30fn</link>
      <guid>https://forem.com/vearutop/github-actions-with-unstable-go-30fn</guid>
      <description>&lt;p&gt;Upcoming Go 1.18 release brings very interesting new features (including generics and fuzz testing). Libraries can get prepared for the release by supporting and exploring new features in advance, this also helps gaining preliminary experience.&lt;/p&gt;

&lt;p&gt;Standard GitHub Action &lt;code&gt;actions/setup-go&lt;/code&gt; allows using pre-releases with &lt;code&gt;stable: false&lt;/code&gt; flag.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;go-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;1.16.x&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;1.17.x&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;1.18.0-rc1&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Go&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-go@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;stable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
          &lt;span class="na"&gt;go-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.go-version }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In some cases you may want to run tests against &lt;code&gt;gotip&lt;/code&gt; which is a head of development tree. Standard action does not support &lt;code&gt;gotip&lt;/code&gt;, but it is easy to install it manually from build snapshots.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;go-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;1.16.x&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;1.17.x&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;1.18.0-rc1&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;tip&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Go&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;matrix.go-version != 'tip'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-go@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;stable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
          &lt;span class="na"&gt;go-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.go-version }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Go tip&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;matrix.go-version == 'tip'&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;curl -sL https://storage.googleapis.com/go-build-snap/go/linux-amd64/$(git ls-remote https://github.com/golang/go.git HEAD | awk '{print $1;}').tar.gz -o gotip.tar.gz&lt;/span&gt;
          &lt;span class="s"&gt;ls -lah gotip.tar.gz&lt;/span&gt;
          &lt;span class="s"&gt;mkdir -p ~/sdk/gotip&lt;/span&gt;
          &lt;span class="s"&gt;tar -C ~/sdk/gotip -xzf gotip.tar.gz&lt;/span&gt;
          &lt;span class="s"&gt;~/sdk/gotip/bin/go version&lt;/span&gt;
          &lt;span class="s"&gt;echo "PATH=$HOME/go/bin:$HOME/sdk/gotip/bin/:$PATH" &amp;gt;&amp;gt; $GITHUB_ENV&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Install Go tip&lt;/code&gt; step here would download pre-built Go for a latest commit in &lt;code&gt;master&lt;/code&gt;. There is a caveat though, for a very recent commit the build might be not finished by the time of download and this may result in CI failure. It does not happen very often and retry usually helps.&lt;/p&gt;

</description>
      <category>go</category>
      <category>github</category>
    </item>
    <item>
      <title>Tutorial: Developing a RESTful API with Go, JSON Schema validation and OpenAPI docs</title>
      <dc:creator>Viacheslav Poturaev</dc:creator>
      <pubDate>Tue, 08 Feb 2022 23:32:18 +0000</pubDate>
      <link>https://forem.com/vearutop/tutorial-developing-a-restful-api-with-go-json-schema-validation-and-openapi-docs-2490</link>
      <guid>https://forem.com/vearutop/tutorial-developing-a-restful-api-with-go-json-schema-validation-and-openapi-docs-2490</guid>
      <description>&lt;p&gt;This tutorial continues &lt;a href="https://go.dev/doc/tutorial/web-service-gin" rel="noopener noreferrer"&gt;Developing a RESTful API with Go and Gin&lt;/a&gt; featured in Go documentation. Please check it first.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; We're going to replace &lt;a href="https://github.com/gin-gonic/gin" rel="noopener noreferrer"&gt;&lt;code&gt;gin-gonic/gin&lt;/code&gt;&lt;/a&gt; with &lt;a href="https://github.com/swaggest/rest" rel="noopener noreferrer"&gt;&lt;code&gt;swaggest/rest&lt;/code&gt;&lt;/a&gt; to obtain type-safe &lt;a href="https://swagger.io/" rel="noopener noreferrer"&gt;OpenAPI&lt;/a&gt; spec with &lt;a href="https://swagger.io/tools/swagger-ui/" rel="noopener noreferrer"&gt;Swagger UI&lt;/a&gt; and &lt;a href="https://json-schema.org/" rel="noopener noreferrer"&gt;JSON Schema&lt;/a&gt; request validation.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Providing reliable and accurate documentation becomes increasingly important thanks to growing integrations between the services. Whether those integrations are between your own microservices, or you are serving an API to 3rd party. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://swagger.io/specification/" rel="noopener noreferrer"&gt;OpenAPI v3&lt;/a&gt; is currently a dominating standard to describe REST API in machine-readable format. There is a whole ecosystem of tools for variety of platforms and languages that help automating integrations and documentation using OpenAPI schema. For example, 3rd party can generate SDK from schema to use your API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Let's start with the result of &lt;a href="https://go.dev/doc/tutorial/web-service-gin" rel="noopener noreferrer"&gt;previous tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/gin-gonic/gin"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// album represents data about a record album.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ID&lt;/span&gt;     &lt;span class="kt"&gt;string&lt;/span&gt;  &lt;span class="s"&gt;`json:"id"`&lt;/span&gt;
    &lt;span class="n"&gt;Title&lt;/span&gt;  &lt;span class="kt"&gt;string&lt;/span&gt;  &lt;span class="s"&gt;`json:"title"`&lt;/span&gt;
    &lt;span class="n"&gt;Artist&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;  &lt;span class="s"&gt;`json:"artist"`&lt;/span&gt;
    &lt;span class="n"&gt;Price&lt;/span&gt;  &lt;span class="kt"&gt;float64&lt;/span&gt; &lt;span class="s"&gt;`json:"price"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// albums slice to seed record album data.&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;albums&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;album&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Blue Train"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Artist&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"John Coltrane"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Price&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;56.99&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Jeru"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Artist&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Gerry Mulligan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Price&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;17.99&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Sarah Vaughan and Clifford Brown"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Artist&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Sarah Vaughan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Price&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;39.99&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/albums"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;getAlbums&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/albums/:id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;getAlbumByID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/albums"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;postAlbums&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"localhost:8080"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// getAlbums responds with the list of all albums as JSON.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;getAlbums&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;albums&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// postAlbums adds an album from JSON received in the request body.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;postAlbums&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;newAlbum&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt;

    &lt;span class="c"&gt;// Call BindJSON to bind the received JSON to&lt;/span&gt;
    &lt;span class="c"&gt;// newAlbum.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BindJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;newAlbum&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Add the new album to the slice.&lt;/span&gt;
    &lt;span class="n"&gt;albums&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;albums&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newAlbum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCreated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newAlbum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// getAlbumByID locates the album whose ID value matches the id&lt;/span&gt;
&lt;span class="c"&gt;// parameter sent by the client, then returns that album as a response.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;getAlbumByID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Loop through the list of albums, looking for&lt;/span&gt;
    &lt;span class="c"&gt;// an album whose ID value matches the parameter.&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;albums&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusNotFound&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"album not found"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Initialize web service
&lt;/h2&gt;

&lt;p&gt;Let's update &lt;code&gt;main&lt;/code&gt; function to use a &lt;a href="https://pkg.go.dev/github.com/swaggest/rest@v0.2.20/web#Service" rel="noopener noreferrer"&gt;web service&lt;/a&gt; for our use case interactors, it will be capable of collecting automated documentation and applying request validation.&lt;/p&gt;

&lt;p&gt;Also we can provide basic information about our API using type-safe OpenAPI bindings.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultService&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenAPI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Albums API"&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenAPI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"This service provides API to manage albums."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenAPI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"v1.0.0"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Add &lt;code&gt;web&lt;/code&gt; to imports.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

    &lt;span class="s"&gt;"github.com/swaggest/rest/web"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Upgrade a handler to return all items
&lt;/h2&gt;

&lt;p&gt;In order to express more information about our http handler, we need refactor it to a &lt;a href="https://pkg.go.dev/github.com/swaggest/usecase#Interactor" rel="noopener noreferrer"&gt;use case interactor&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The constructor &lt;a href="https://pkg.go.dev/github.com/swaggest/usecase#NewInteractor" rel="noopener noreferrer"&gt;&lt;code&gt;usecase.NewInteractor&lt;/code&gt;&lt;/a&gt; takes a generic interact function that should be called for input and prepare data at output pointer.&lt;/p&gt;

&lt;p&gt;When web service receives request it will determine correct use case based on route and will prepare instances of input and output for further interaction (call of a function).&lt;/p&gt;

&lt;p&gt;Input instance will be filled with data from http request, output instance will be created as a pointer to new output value.&lt;/p&gt;

&lt;p&gt;In this case we don't need any request parameters, so input type can be &lt;code&gt;struct{}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This action will provide a list of albums. So the output type would be a pointer to slice of albums &lt;code&gt;*[]album&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;getAlbums&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;usecase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interactor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;usecase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewInteractor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;album&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;albums&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetTags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Album"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Input and output types are most important for automated http mapping and documentation generation. You can provide more information for the use case, for example tags to group multiple use cases together.&lt;/p&gt;

&lt;p&gt;Now we can add upgraded use case to web service (in &lt;code&gt;main&lt;/code&gt; function).&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/albums"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;getAlbums&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Upgrade a handler to create new item
&lt;/h2&gt;

&lt;p&gt;In this case we receive input as a JSON payload of &lt;code&gt;album&lt;/code&gt;, so input type would be &lt;code&gt;album&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;We also return received album in response, so the output would be &lt;code&gt;*album&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Output instance is provided as a placeholder for data, so it has to be a pointer. In contrast, input is not used after interact function is invoked, so it can be a non-pointer value.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;postAlbums&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;usecase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interactor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;usecase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewInteractor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;album&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Add the new album to the slice.&lt;/span&gt;
        &lt;span class="n"&gt;albums&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;albums&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetTags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Album"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let's implement additional logic in this use case, to restrict &lt;code&gt;id&lt;/code&gt; duplicates in the &lt;code&gt;albums&lt;/code&gt;. In such case we can return conflict error.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;postAlbums&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;usecase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interactor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;usecase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewInteractor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;album&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Check if id is unique.&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;albums&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AlreadyExists&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// Add the new album to the slice.&lt;/span&gt;
        &lt;span class="n"&gt;albums&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;albums&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetTags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Album"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetExpectedErrors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AlreadyExists&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you can see, we've also added &lt;code&gt;u.SetExpectedErrors(status.AlreadyExists)&lt;/code&gt; to inform documentation collector that this use case may fail in a particular way.&lt;/p&gt;

&lt;p&gt;Now we can add the upgraded use case to web service (in &lt;code&gt;main&lt;/code&gt; function).&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/albums"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;postAlbums&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;nethttp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SuccessStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCreated&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Mind the additional option that changes successful status from default &lt;code&gt;http.StatusOK&lt;/code&gt; to &lt;code&gt;http.StatusCreated&lt;/code&gt;. This fine control is left outside of use case definition because it is specific to http, use case interactor can potentially be used with other transports (see &lt;a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html" rel="noopener noreferrer"&gt;Clean Architecture&lt;/a&gt; for more details on this concept).&lt;/p&gt;

&lt;h2&gt;
  
  
  Add validation to &lt;code&gt;album&lt;/code&gt; structure
&lt;/h2&gt;

&lt;p&gt;Now let's add some validation rules to our &lt;code&gt;album&lt;/code&gt; structure.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="c"&gt;// album represents data about a record album.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ID&lt;/span&gt;     &lt;span class="kt"&gt;string&lt;/span&gt;  &lt;span class="s"&gt;`json:"id" required:"true" minLength:"1" description:"ID is a unique string that determines album."`&lt;/span&gt;
    &lt;span class="n"&gt;Title&lt;/span&gt;  &lt;span class="kt"&gt;string&lt;/span&gt;  &lt;span class="s"&gt;`json:"title" required:"true" minLength:"1" description:"Title of the album."`&lt;/span&gt;
    &lt;span class="n"&gt;Artist&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;  &lt;span class="s"&gt;`json:"artist,omitempty" description:"Album author, can be empty for multi-artist compilations."`&lt;/span&gt;
    &lt;span class="n"&gt;Price&lt;/span&gt;  &lt;span class="kt"&gt;float64&lt;/span&gt; &lt;span class="s"&gt;`json:"price" minimum:"0" description:"Price in USD."`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://pkg.go.dev/github.com/swaggest/jsonschema-go#Reflector.Reflect" rel="noopener noreferrer"&gt;Validation rules&lt;/a&gt; can be added with field tags (or &lt;a href="https://pkg.go.dev/github.com/swaggest/jsonschema-go#Preparer" rel="noopener noreferrer"&gt;special interfaces&lt;/a&gt;). Along with validation rules you can supply brief descriptions of field values.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ID&lt;/code&gt; is a required field that can not be empty,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Title&lt;/code&gt; as well,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Artist&lt;/code&gt; is an optional field,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Price&lt;/code&gt; can't be negative.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Validation is powered by JSON Schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upgrade a handler to return specific item
&lt;/h2&gt;

&lt;p&gt;In this case we need to read request parameter from URL path. For that our input structure should contain a field with &lt;code&gt;path&lt;/code&gt; tag to enable &lt;a href="https://github.com/swaggest/rest#request-decoder" rel="noopener noreferrer"&gt;data mapping&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Given this use case can end up with &lt;code&gt;Not Found&lt;/code&gt; status, we add the status to expected errors for documentation. We also wrap the error in use case body to have a correct http status.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;getAlbumByID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;usecase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interactor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;getAlbumByIDInput&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`path:"id"`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;usecase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewInteractor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="n"&gt;getAlbumByIDInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;album&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;albums&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"album not found"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetTags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Album"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetExpectedErrors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now we can add this use case to web service (in &lt;code&gt;main&lt;/code&gt; function).&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/albums/{id}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;getAlbumByID&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Mind the path placeholder has changed from &lt;code&gt;:id&lt;/code&gt; to &lt;code&gt;{id}&lt;/code&gt; to comply with OpenAPI standard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mount Swagger UI
&lt;/h2&gt;

&lt;p&gt;You can add a web interface to the API with Swagger UI.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Docs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/docs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v4emb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Add &lt;a href="https://pkg.go.dev/github.com/swaggest/swgui/v4emb" rel="noopener noreferrer"&gt;&lt;code&gt;v4emb&lt;/code&gt;&lt;/a&gt; to imports.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

    &lt;span class="s"&gt;"github.com/swaggest/swgui/v4emb"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then documentation will be served at &lt;a href="http://localhost:8080/docs" rel="noopener noreferrer"&gt;http://localhost:8080/docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resulting program
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"errors"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/swaggest/rest/nethttp"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/swaggest/rest/web"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/swaggest/swgui/v4emb"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/swaggest/usecase"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/swaggest/usecase/status"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// album represents data about a record album.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ID&lt;/span&gt;     &lt;span class="kt"&gt;string&lt;/span&gt;  &lt;span class="s"&gt;`json:"id" required:"true" minLength:"1" description:"ID is a unique string that determines album."`&lt;/span&gt;
    &lt;span class="n"&gt;Title&lt;/span&gt;  &lt;span class="kt"&gt;string&lt;/span&gt;  &lt;span class="s"&gt;`json:"title" required:"true" description:"Title of the album."`&lt;/span&gt;
    &lt;span class="n"&gt;Artist&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;  &lt;span class="s"&gt;`json:"artist,omitempty" description:"Album author, can be empty for multi-artist compilations."`&lt;/span&gt;
    &lt;span class="n"&gt;Price&lt;/span&gt;  &lt;span class="kt"&gt;float64&lt;/span&gt; &lt;span class="s"&gt;`json:"price" minimum:"0" description:"Price in USD."`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// albums slice to seed record album data.&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;albums&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;album&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Blue Train"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Artist&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"John Coltrane"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Price&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;56.99&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Jeru"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Artist&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Gerry Mulligan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Price&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;17.99&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Sarah Vaughan and Clifford Brown"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Artist&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Sarah Vaughan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Price&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;39.99&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultService&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenAPI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Albums API"&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenAPI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"This service provides API to manage albums."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenAPI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"v1.0.0"&lt;/span&gt;

    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/albums"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;getAlbums&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/albums/{id}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;getAlbumByID&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/albums"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;postAlbums&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;nethttp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SuccessStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCreated&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Docs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/docs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v4emb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting service"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"localhost:8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;getAlbums&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;usecase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interactor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;usecase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewInteractor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;album&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;albums&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetTags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Album"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;postAlbums&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;usecase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interactor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;usecase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewInteractor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;album&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Check if id is unique.&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;albums&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AlreadyExists&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// Add the new album to the slice.&lt;/span&gt;
        &lt;span class="n"&gt;albums&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;albums&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetTags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Album"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetExpectedErrors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AlreadyExists&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;getAlbumByID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;usecase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interactor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;getAlbumByIDInput&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`path:"id"`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;usecase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewInteractor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="n"&gt;getAlbumByIDInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;album&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;albums&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"album not found"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetTags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Album"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetExpectedErrors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Tidy modules and start the app!
&lt;/h2&gt;

&lt;p&gt;In order to download necessary modules run &lt;code&gt;go mod tidy&lt;/code&gt; in the directory of your module.&lt;/p&gt;

&lt;p&gt;Then run the app with &lt;code&gt;go run main.go&lt;/code&gt; and open &lt;a href="http://localhost:8080/docs" rel="noopener noreferrer"&gt;http://localhost:8080/docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmduhu3u8xqsnwcuts5tc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmduhu3u8xqsnwcuts5tc.png" alt="Swagger UI Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OpenAPI schema will be available at &lt;a href="http://localhost:8080/docs/openapi.json" rel="noopener noreferrer"&gt;http://localhost:8080/docs/openapi.json&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;
  openapi.json
  &lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"openapi"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3.0.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Albums API"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"This service provides API to manage albums."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"v1.0.0"&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"paths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"/albums"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"get"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="s2"&gt;"Album"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Get Albums"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"operationId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"getAlbums"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"responses"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"200"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OK"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"array"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"$ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#/components/schemas/Album"&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"post"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="s2"&gt;"Album"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Post Albums"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"operationId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"postAlbums"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"requestBody"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"$ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#/components/schemas/Album"&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"responses"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"201"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Created"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"$ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#/components/schemas/Album"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"409"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Conflict"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"$ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#/components/schemas/RestErrResponse"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"/albums/{id}"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"get"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="s2"&gt;"Album"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Get Album By ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"operationId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"getAlbumByID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"parameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"in"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"responses"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"200"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OK"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"$ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#/components/schemas/Album"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"404"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Not Found"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"$ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#/components/schemas/RestErrResponse"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"components"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schemas"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"Album"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="s2"&gt;"title"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"artist"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Album author, can be empty for multi-artist compilations."&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"minLength"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ID is a unique string that determines album."&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"minimum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Price in USD."&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Title of the album."&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"RestErrResponse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"integer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Application-specific error code."&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"additionalProperties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Application context."&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Error message."&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Status text."&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/vearutop/rest-tutorial" rel="noopener noreferrer"&gt;Source code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;P.S. Original tutorial has a &lt;a href="https://github.com/golang/go/issues/51097" rel="noopener noreferrer"&gt;race condition&lt;/a&gt; on &lt;code&gt;albums&lt;/code&gt;, this is not fixed here as out of scope of the article, but please be aware. :)&lt;/p&gt;

</description>
      <category>go</category>
      <category>openapi</category>
      <category>tutorial</category>
      <category>rest</category>
    </item>
  </channel>
</rss>
