<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

 <title>omniverse</title>
 <link href="https://omniverse.ru/atom.xml" rel="self"/>
 <link href="https://omniverse.ru/"/>
 <updated>2026-03-09T13:34:36+00:00</updated>
 <id>https://omniverse.ru</id>
 <author>
   <name>Evgeny Seliverstov</name>
   <email>theirix@gmail.com</email>
 </author>

 
 <entry>
   <title>Using gbp-buildpackage in a Docker way</title>
   <link href="https://omniverse.ru/blog/2026/02/23/debian-gbp/"/>
   <updated>2026-02-23T00:00:00+00:00</updated>
   <id>https://omniverse.ru/blog/2026/02/23/debian-gbp</id>
   <content type="html">&lt;p&gt;To build a package on Debian, I was always using my server called “atom” (more on it in later posts). With Debian stable installed, I can build packages for Debian Sid as intended. My favourite way was to use &lt;a href=&quot;https://www.debian.org/doc/manuals/maint-guide/build.en.html#pbuilder&quot;&gt;pbuilder&lt;/a&gt; for most packages and &lt;a href=&quot;https://wiki.debian.org/sbuild&quot;&gt;sbuild&lt;/a&gt; for heavier software. It is fast and reliable, without having to spin up a standalone isolated virtual machine.&lt;/p&gt;

&lt;p&gt;Sometimes you are not able to use a server, or it is not convenient. It is easier to spin up a Docker container to build a package or introduce a small fix. That’s why I wrote a small automation around Docker and gbp.&lt;/p&gt;

&lt;p&gt;It is available in &lt;a href=&quot;https://github.com/theirix/debian-docker-build&quot;&gt;my GitHub repo&lt;/a&gt;. The main idea is to build a prebaked image with all the required tools. The user launches a container. Inside it, package-specific build dependencies are installed, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gbp buildpackage&lt;/code&gt; is executed with post-build checks, lints are run, the package is optionally signed with GPG, and then the container exits. So it allows you to boil down the whole process to a single line:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker compose run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; debian &lt;span class=&quot;nt&quot;&gt;--workdir&lt;/span&gt; /workspace/asn1c
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All the sources are in the host machine’s directory (so I can easily edit them with my favourite editor) and the build artefacts (.dsc, .build, and .changes files) are placed in the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/workspace&lt;/code&gt; directory. No copying, no rsyncing. Feel free to use!&lt;/p&gt;

&lt;p&gt;Also, the modern Debian infrastructure in &lt;a href=&quot;https://wiki.debian.org/SalsaCI&quot;&gt;SalsaCI&lt;/a&gt; builds and tests packages for you via familiar GitLab CI. It’s a slightly longer feedback loop and is well-suited for a final check. I prefer iterating fast with local builds.&lt;/p&gt;

&lt;p&gt;That said, I tested this suite while maintaining two new versions of my packages, which recently landed in Debian testing:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://tracker.debian.org/pkg/asn1c&quot;&gt;asn1c 0.9.28+dfsg-6&lt;/a&gt;. It includes fixes to man pages and fonts, new standards, and general cleanup&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://tracker.debian.org/pkg/pstreams&quot;&gt;pstreams 1.0.4-2&lt;/a&gt;. It’s a new upstream version, with new Debian standards and build tweaks.&lt;/li&gt;
&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>Meeting C++ 2025 Trip Report</title>
   <link href="https://omniverse.ru/blog/2025/11/27/meetingcpp-trip-report/"/>
   <updated>2025-11-27T00:00:00+00:00</updated>
   <id>https://omniverse.ru/blog/2025/11/27/meetingcpp-trip-report</id>
   <content type="html">&lt;p&gt;This November, I had the opportunity to attend the famous Meeting C++ conference. Located in Germany, it attracts many C++ developers not only from continental Europe, but also from the UK and the USA. I am grateful to the organisers, especially Jens Weller, for accepting my proposal to be part of such a great lineup of talks across five tracks during three days. So yes, it was huge.&lt;/p&gt;

&lt;p&gt;Let me share my thoughts about a few talks and what I’ve found especially interesting.&lt;/p&gt;

&lt;h2 id=&quot;software-and-safety-keynote&quot;&gt;&lt;a href=&quot;https://meetingcpp.com/mcpp/schedule/talkview.php?th=kldfkdsafadlkadkfaai7ad9f7adjnkjadfs&quot;&gt;Software and Safety keynote&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;Anthony Williams worked on the C++ concurrency standard, wrote a lot of articles and a book &lt;a href=&quot;https://www.manning.com/books/c-plus-plus-concurrency-in-action-second-edition&quot;&gt;“C++ Concurrency in Action”&lt;/a&gt;, which is an excellent crash course into (non-)crashing multi-threaded programs. Now he explores the automotive industry, which has a special interest in concurrency and safety from undefined behaviour in C++.&lt;/p&gt;

&lt;p&gt;I like the &lt;a href=&quot;https://en.wikipedia.org/wiki/Swiss_cheese_model&quot;&gt;“Swiss Cheese”&lt;/a&gt; concept Anthony carried throughout his keynote talk, “Software and Safety”. It likens the system to a stack of Swiss cheese layers with randomly placed and sized holes in each slice, mitigating the overall risk. There is no silver bullet. You can only decrease the failure risk by having multiple techniques in place, like:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Design your system to minimise the potential for problems&lt;/li&gt;
  &lt;li&gt;Use static analysis&lt;/li&gt;
  &lt;li&gt;Test with potentially problematic input&lt;/li&gt;
  &lt;li&gt;Fuzz test&lt;/li&gt;
  &lt;li&gt;Use sanitisers and hardened libraries&lt;/li&gt;
  &lt;li&gt;Use contracts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It was a perfect start to the conference, providing the tone of safety for critical systems in C++. I’ve seen and spoken with many attendees working in highly regulated environments, such as automotive, aerospace, and healthcare. All of us would prefer these safety-critical systems to be written with these ideas in mind. For regulated financial institutions, it’s not that critical, but it’s also a key factor in ensuring a safe and secure language and foundational libraries.&lt;/p&gt;

&lt;h2 id=&quot;from-acrobatics-to-ergonomics---a-field-report-on-how-to-make-libraries-helpful&quot;&gt;&lt;a href=&quot;https://meetingcpp.com/mcpp/schedule/talkview.php?th=5c5428e8dde5585176e2a8618a46b5c87d2bb7d5&quot;&gt;From Acrobatics to Ergonomics - A Field Report on How to Make Libraries Helpful&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;Then I moved to an excellent and entertaining talk by Joel Falcou from INRIA on designing software libraries for scientists to use. It’s a different approach compared to writing libraries for hardcore C++ developers. You cannot just throw kilobytes of template errors at users without a reasonable explanation of the errors and, more importantly, actions to fix them from a user perspective. Joel explained how concepts, type systems, and proper API design can greatly simplify the developer experience. Joel believes that library developers should care more about users, and modern C++ makes it easier.&lt;/p&gt;

&lt;p&gt;In Rust, developers have excellent diagnostics support from the compiler, and rich support for custom diagnostics is also provided to libraries via the form of &lt;a href=&quot;https://github.com/rust-lang/annotate-snippets-rs&quot;&gt;annotate-snippets-rs&lt;/a&gt; crate and at the attribute-level with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#[diagnostic::on_unimplemented]&lt;/code&gt; (&lt;a href=&quot;https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-diagnosticon_unimplemented-attribute&quot;&gt;docs&lt;/a&gt;). C++ compilers are becoming increasingly capable of reducing error clutter, but there is still a long way to go.&lt;/p&gt;

&lt;p&gt;Also, a simple and intuitive design doesn’t have to be non-extensible. The speaker explained that intuitive means the incorrect usage is flagged clearly, and all components adhere to the Single Responsibility SOLID principles. Extensibility means that users should be able to combine pieces of the API and obtain sensible results. Power users should be able to customise their API usage to their own corner cases, and finally, developers should have a clear path to extend the API.&lt;/p&gt;

&lt;p&gt;Lessons learned from the talk.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Use Concepts and static asserts to enforce API design&lt;/li&gt;
  &lt;li&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if constexpr&lt;/code&gt; and static asserts to simplify metaprogramming&lt;/li&gt;
  &lt;li&gt;Prevent problems earlier with proper diagnostics&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;harnessing-constexpr-a-path-to-safer-c&quot;&gt;&lt;a href=&quot;https://meetingcpp.com/mcpp/schedule/talkview.php?th=017b8292e897d1202bf0fe713f0d127e2ae486c9&quot;&gt;Harnessing constexpr: A Path to Safer C++&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;Mikhail Svetkin spoke about memory safety. Undefined behaviour can be easily stopped by LLVM sanitisers, but it’s not the only way. Developers could start sprinkling constexpr into functions, allowing them to operate not only at runtime but also at compile-time. The main side effect of constexpr is the lack of undefined behaviour. So if your constexpr unit tests work on code, it’s free of undefined behaviour. It allows unit tests for such functions to run even before they are executed! Maxing this out requires some tricky techniques that Mikhail has shown, and the result is fascinating — unit test errors are displayed in your IDE before compilation.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;constexpr&lt;/code&gt; support has grown extensively during the last standards. I cannot resist mentioning this evolution list:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;C++11: expression&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;C++14: variables, control flow, array&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;C++17: lambdas, string view, if constexpr&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;C++20: string, vector, virtual functions, try, new/delete&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;C++23: basic math, unique_ptr, optional, variant&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;C++26: sorting, floating-point math, atomic, hashes, exceptions&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;C++29: format&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;monadic-operations-in-c23&quot;&gt;&lt;a href=&quot;https://meetingcpp.com/mcpp/schedule/talkview.php?th=119bf11eeda59831958a1f79698f3e28c8f1cb21&quot;&gt;Monadic Operations in C++23&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;Robert Schimkowitsch delivered a great, well-structured and perfectly paced talk that brought functional concepts to engineers who used to develop in an imperative style.&lt;/p&gt;

&lt;p&gt;The outcomes of this talk are to understand what functors and monads actually do, how to use monadic operations from the standard library and how to build a functional-style program based on this foundation.&lt;/p&gt;

&lt;p&gt;It is a useful talk for whoever is not so familiar with monadic concepts. Users of Haskell and more practically oriented languages with functional concepts, like Rust or Swift, could also benefit from this knowledge to find the subtle differences in behaviour.&lt;/p&gt;

&lt;h2 id=&quot;asking-stoopid-questions&quot;&gt;&lt;a href=&quot;https://meetingcpp.com/mcpp/schedule/talkview.php?th=kdfjadwkfaldfjaldjfadkfadsfakdfajdk786tzdh&quot;&gt;Asking Stoopid questions&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;The keynote, as it should be. Frances Buontempo, a seasoned math teacher and educator, explained how to teach people, how to learn, and how to tackle the learning process on one’s own. A philosophical keynote, with food for thought and references to my favourite cyberpunk writers, Neal Stephenson and William Gibson.&lt;/p&gt;

&lt;h2 id=&quot;int--safe--int--ℤ&quot;&gt;&lt;a href=&quot;https://meetingcpp.com/mcpp/schedule/talkview.php?th=09b7233a511545fed01d9020567a08fc2ccbc9c6&quot;&gt;int != safe &amp;amp;&amp;amp; int != ℤ&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;An insightful talk from Peter Sommerlad about another safety facet of C++ — undefined behaviour with integer overflow. Regulated environments like MISRA require careful handling of overflows and the resulting undefined behaviour.&lt;/p&gt;

&lt;p&gt;Built-in types are limited in size (they are not math integers), we can do different things for, say, addition:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Wrap them (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-1&lt;/code&gt; after &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0xFFFFFFFFF&lt;/code&gt;)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Saturate (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0xFFFFFFFFF&lt;/code&gt; + 1 stays &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0xFFFFFFFFU&lt;/code&gt;)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Panic or throw&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It’s an inherent property of machine types. Safety-aware languages like Rust have a variety of operations on the same integer type:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;assert!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;101&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;assert!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.checked_add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;101&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;assert!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.strict_add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;101&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// panics on overflow&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;assert!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.overflowing_add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;101&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;assert!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.saturating_add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;101&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0xFFFFFFFF&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// assert!(x+1 == 0); // panics in Debug mode, overflows in Release&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;assert!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.checked_add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// assert!(x.strict_add(1) == 0); // panics on overflow&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;assert!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.overflowing_add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;assert!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.saturating_add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0xFFFFFFFF&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To be fair, it’s not MISRA-compatible, since it provides a set of specific requirements, such as not mixing signed and unsigned types, restricting type promotions and limiting operations for signed/unsigned types&lt;/p&gt;

&lt;p&gt;The idea Peter proposed is to use special wrapper types that simulate the ℤ algebraic group (signed integers) with arbitrary precision and the mentioned operation semantics without overflows. His &lt;a href=&quot;https://github.com/PeterSommerlad/PSsimplesafeint&quot;&gt;set of libraries&lt;/a&gt; are header-only C++20, providing the following types:&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#include&lt;/span&gt; &lt;span class=&quot;cpf&quot;&gt;&amp;lt;pssodin.h&amp;gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;constexpr&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pssodin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cui32&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;static_assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pssodin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;from_int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It also works with primitive types:&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;int32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pssodin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_overflow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A distinctive feature of this library is compile-time checks for type eligibility for the majority of operations. Hence, the library is almost entirely &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;constexpr&lt;/code&gt;, greatly simplifying testing for undefined behaviour. The API design is based on lean enum classes, which are more performant than a class with an integer data member.&lt;/p&gt;

&lt;p&gt;What about having some of these features in a C++ standard? Not so good as with external libraries and other languages. With C++26, some saturation helpers are available in the std library, but not overflow:&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;static_assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_sat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;C++20 also improved the situation a bit with tiny helpers &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::cmp_equal&lt;/code&gt; to avoid common pitfalls with signed/unsigned comparison:&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;static_assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1U&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;static_assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cmp_less&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1U&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;static_assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xFFFFFFFFU&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;static_assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cmp_not_equal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xFFFFFFFFU&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So, for developers dealing with integer arithmetic, MISRA requirements, and older standards, Peter’s library is a godsend. Impressive work.&lt;/p&gt;

&lt;h2 id=&quot;the-missing-step-making-data-oriented-design-one-million-times-faster&quot;&gt;&lt;a href=&quot;https://meetingcpp.com/mcpp/schedule/talkview.php?th=60f831827b3cc25a7210c20d67e4992e08775601&quot;&gt;The Missing Step: Making Data Oriented Design One Million Times Faster&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;A keynote-style talk from Andrew Drakeford. He explained how a methodological and systematic approach allows for solving complex problems in financial applications. He outlined ideas from George Polya’s book &lt;a href=&quot;https://en.wikipedia.org/wiki/How_to_Solve_It&quot;&gt;“How to Solve it”&lt;/a&gt;] (goes to my reading list).&lt;/p&gt;

&lt;p&gt;It’s much better to listen to this talk and experience a problem-solving journey with the speaker.&lt;/p&gt;

&lt;p&gt;Still, I’d like to mention key takeaways  (credits to the author):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Design addresses a highly dimensional problem.
    &lt;ul&gt;
      &lt;li&gt;Logical algorithm design&lt;/li&gt;
      &lt;li&gt;Physical optimising spatio-temporal memory use patterns&lt;/li&gt;
      &lt;li&gt;Idiosyncratic aspects of the actual problem itself.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Working through the problem space in a structured way&lt;/li&gt;
  &lt;li&gt;Always look to expand the context of your thinking around problem areas&lt;/li&gt;
  &lt;li&gt;Draw pictures and solve easier ancillary problem&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;unlocking-the-value-of-c20-features&quot;&gt;&lt;a href=&quot;https://meetingcpp.com/mcpp/schedule/talkview.php?th=f47cea4aeed5de351b6612e851c03a55a99265a9&quot;&gt;Unlocking the Value of C++20 Features&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;A day wrapped with a good overview by Alex Dathskovsky from ScyllaDB of the new C++20 and C++23 features, spanning two talks. It’s handy to have an overview and a reminder to evaluate some new C++ features, given the vast scope of the recent standard. There are a few things I’ve found especially useful and interesting.&lt;/p&gt;

&lt;p&gt;Generic lambdas and simplified metaprogramming:&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gen_lambda&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Constexpr’s are everywhere, even in virtual hierarchy — what a blasphemy:&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Animal&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;constexpr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Animal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;noexcept&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{};&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;constexpr&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;say&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Animal&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;constexpr&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;say&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Meow&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;constexpr&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Cat&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;constexpr&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Animal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;animal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;animal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;say&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Transparent comparison for containers. It’s a tricky technique that unlocks more performance while working with standard containers:&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;less&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Since C++20, it is allowed to specify a transparent hasher for hashing containers, skipping key copy:&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;transparent_hash&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is_transparent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// a key ingredient&lt;/span&gt;

  &lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string_view&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string_view&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string_view&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unordered_set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transparent_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;equal_to&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// no intermediate std::string constructed&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;a C string&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string_view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;a string view&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;speed-for-free---current-state-of-auto-vectorizing-compilers&quot;&gt;&lt;a href=&quot;https://meetingcpp.com/mcpp/schedule/talkview.php?th=63f0bc7c4c40655074450a2f6d279f04c07d9db0&quot;&gt;Speed for free - current state of auto-vectorizing compilers&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;Who doesn’t want speed for free? Stefan Fuhrmann agrees and explains how the automatic vectorisation works in modern compilers. Of course, developers can call compiler intrinsics or provide compiler hints to insert SIMD instructions here and there. It turns out tha modern clang with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-O2&lt;/code&gt; and gcc with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-O3&lt;/code&gt; optimisation levels can vectorise simple scalar loops. They are still unable to produce masked instructions to care about data which doesn’t fill the whole SIMD lane. So your typical loop over data should be split into two: a main loop for evenly sized chunks and a tail loop. Compiler engineers do a fantastic job of optimisation. Figuring out whether you can rely on them and bring it to the audience — it’s a great job too!&lt;/p&gt;

&lt;h2 id=&quot;why-managing-c-dependencies-is-hard-and-what-to-do-about-it&quot;&gt;&lt;a href=&quot;https://meetingcpp.com/mcpp/schedule/talkview.php?th=47cc0bcd278d6409fe8dfc74a9bbf07ead3259b9&quot;&gt;Why managing C++ dependencies is hard (and what to do about it)&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;Kerstin Keller presented the journey of one software company from a bunch of prebuilt libraries committed to VCS to a modern Conan 2.0-based build system, with a special emphasis on Windows. It’s always great to hear about experience with Conan across different areas and for different platforms.&lt;/p&gt;

&lt;h2 id=&quot;sanitize-for-your-sanity-sanitizers-tools-for-modern-c&quot;&gt;&lt;a href=&quot;https://meetingcpp.com/mcpp/schedule/talkview.php?th=25c709e9343fb2c91be5ec75ed1a77496d4bc7f7&quot;&gt;Sanitize for your Sanity: Sanitizers tools for Modern C++&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;Last but not least — my humble talk.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/photos/photo-meetingcpp-2025.jpg&quot; alt=&quot;Presenting at Meeting C++&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The slides are &lt;a href=&quot;https://omniverse.ru/talks/meetingcpp-2025-sanitizers/&quot;&gt;here on my website&lt;/a&gt;. The recordings would eventually be available, too.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Update&lt;/em&gt;: YouTube recording &lt;a href=&quot;https://www.youtube.com/watch?v=p_tqzPAM9tY&quot;&gt;is here&lt;/a&gt;:&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/p_tqzPAM9tY?si=qAWiPqDTDfJ5APfL&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;According to the Swiss Cheese concept, it is mandatory to use multiple levels of defence. Type system, linters, undefined behaviour check via constexpr sprinkles — it is only a part of the whole deal. At runtime, you can do much more with sanitizers (address sanitizers, thread, memory and undefined). I explained the best ways to work with them, especially how to tailor them to a complex mix of libraries, instrumented and non-instrumented, which is an extremely common but usually overlooked scenario. Also, I tackled a problem of multi-language integration of Rust and C++ code, when using sanitizers to track cross-language problems. So it’s not a choice between Rust and C++, it is a question of making them work better together. As usual, when I am anxious, I speak very fast, which is definitely a problem when delivering complex topics to the public, so there is room for improvement. Nevertheless, slides are always available online, and I am happy to help and give advice on memory safety and sanitizers usage.&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;This year was turbulent for the world, the industry, developers, and conference organisers worldwide. Why even attend conferences and read books when LLMs could provide all the knowledge of mankind (and mistakes, too)? Despite this, Meeting C++ was filled with great talks, enthusiastic speakers, and curious attendees. The most appealing to me was a shared sense of C++ moving forward — especially towards greater safety. I also shared my knowledge in the talk, and I hope it will help make C++ a better and safer place.&lt;/p&gt;

&lt;p&gt;It was terrific to meet developers from many industries, with expertise across different languages and domains. It is energising and makes you feel more confident about the community, the ecosystem, and the people behind it.&lt;/p&gt;

&lt;p&gt;See you all next time!&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>uv for fast wheels</title>
   <link href="https://omniverse.ru/blog/2025/08/11/uv/"/>
   <updated>2025-08-11T00:00:00+00:00</updated>
   <id>https://omniverse.ru/blog/2025/08/11/uv</id>
   <content type="html">&lt;h2 id=&quot;to-build-or-not-to-build&quot;&gt;To build or not to build?&lt;/h2&gt;

&lt;p&gt;You have a Python package. It solves a technical or business problem. You have decided to share it with the open-source community. Okay, just set up GitHub Actions and publish it to the PyPI. Periodically, you carve a new version and publish it. Everybody is happy – developers, consumers, and the community.&lt;/p&gt;

&lt;p&gt;Then you suddenly discover that Python is slow. It is perfectly fine for many use cases, but yours is doing CPU-intensive work. You think it’s worth shifting CPU-intensive work from the interpreted language to a native extension written in C, C++ or Rust. There are a few different approaches to do this – Cython, CFFI, pure low-level CPython extensions. CFFI is the most widely used. You need to write native code in C, build it somehow into a library, load the library from Python and call it while adhering to call conventions and ownership semantics.&lt;/p&gt;

&lt;p&gt;The problem is how to build and package a binary distribution. Different projects go in different directions. Here lies my experience of trying and using them.&lt;/p&gt;

&lt;h2 id=&quot;poetry&quot;&gt;Poetry&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://python-poetry.org&quot;&gt;Poetry&lt;/a&gt; prefers having explicit build scripts, and the Poetry build backend is aware of them.&lt;/p&gt;

&lt;div class=&quot;language-toml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;build-system&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;requires&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;poetry-core&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;setuptools&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;build-backend&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;poetry.core.masonry.api&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;poetry&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;script&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;scripts/build.py&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The build script is a simple Python module invoking &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setuptools.command.build_ext&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cffi&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cython&lt;/code&gt;. The downsides of this approach – it is still &lt;a href=&quot;https://github.com/python-poetry/poetry/issues/2740&quot;&gt;unofficial and unstable&lt;/a&gt;, and the behaviour changed with Poetry 2.&lt;/p&gt;

&lt;h2 id=&quot;setuptools&quot;&gt;Setuptools&lt;/h2&gt;

&lt;p&gt;With old, good setuptools, it is easy to use setuptools’ official and supported integrations with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ext_modules&lt;/code&gt; using &lt;a href=&quot;https://cython.readthedocs.io/en/latest/src/userguide/source_files_and_compilation.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cythonize&lt;/code&gt;&lt;/a&gt; or &lt;a href=&quot;https://cffi.readthedocs.io/en/latest/cdef.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ffibuilder&lt;/code&gt;&lt;/a&gt; using the following &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setup.py&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;setup_requires&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;cffi&amp;gt;=1.0.0&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;cffi_modules&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;scripts/build.py:ffibuilder&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;install_requires&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;cffi&amp;gt;=1.0.0&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Worked like a charm for decades, but now it’s outdated. Even with dependency control via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pip freeze&lt;/code&gt; you will run into many well-known Python packaging problems.&lt;/p&gt;

&lt;h2 id=&quot;uv&quot;&gt;uv&lt;/h2&gt;

&lt;p&gt;What if you want to combine a modern packaging tool like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uv&lt;/code&gt; and build binary distributions?&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uv&lt;/code&gt; uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pyproject.toml&lt;/code&gt; for project configuration. The main benefit of the &lt;a href=&quot;https://packaging.python.org/en/latest/specifications/pyproject-toml/#pyproject-toml-spec&quot;&gt;pyproject format&lt;/a&gt; is the standardisation of storing metadata for a project, build backend and external tools. It’s a big step forward from the imperative &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setup.py&lt;/code&gt;. It allows not only storing metadata, but also simplifying tools and significantly improving performance, because dependency resolution no longer requires evaluating Python code in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setup.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Of course, all the dependencies – build or runtime – can be specified in the same file. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uv&lt;/code&gt; has a great build backend &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uv_build&lt;/code&gt; (used by default) – extremely fast (because, you know, it’s in Rust :zap:) and reliable. It fully supports the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pyproject.toml&lt;/code&gt; standard, including the standard &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;project&lt;/code&gt; tag.&lt;/p&gt;

&lt;h2 id=&quot;binary-distributions-and-uv&quot;&gt;Binary distributions and uv&lt;/h2&gt;

&lt;p&gt;Unfortunately, this &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uv_build&lt;/code&gt;backend only supports pure Python. For binary extensions, &lt;a href=&quot;https://github.com/astral-sh/uv/issues/7222&quot;&gt;the easiest way&lt;/a&gt; to overcome this limitation is to use a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setuptools&lt;/code&gt; build backend instead.&lt;/p&gt;

&lt;div class=&quot;language-toml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;build-system&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;requires&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;setuptools&amp;gt;=61&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;cffi&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;build-backend&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;setuptools.build_meta&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setuptools&lt;/code&gt; itself perfectly &lt;a href=&quot;https://setuptools.pypa.io/en/latest/userguide/dependency_management.html&quot;&gt;supports multiple formats&lt;/a&gt; for configuration – TOML.&lt;/p&gt;

&lt;div class=&quot;language-toml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;dependencies&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;numpy&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;h3&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setup.py&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-py highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;install_requires&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;numpy&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;h3&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But that’s not for all tools. All the integrated tools should decide for themselves where to get data from.
For example, CFFI &lt;a href=&quot;https://github.com/python-cffi/cffi/issues/55&quot;&gt;doesn’t support&lt;/a&gt; declarative &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pyproject.toml&lt;/code&gt; as a data source for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cffi_module&lt;/code&gt;, which is sad because you have to store a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setup.py&lt;/code&gt; file, albeit simple:&lt;/p&gt;

&lt;div class=&quot;language-py highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;setuptools&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;setup&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cffi_modules&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;scripts/build.py:ffibuilder&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Okay, now we’re back to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setup.py&lt;/code&gt;. Poetry 1.x was able to generate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setup.py&lt;/code&gt; with build instructions under the hood, so this approach is more or less familiar.&lt;/p&gt;

&lt;p&gt;The most challenging part is to configure package discovery and file packaging properly. Here’s how we did it in the open-source &lt;a href=&quot;https://github.com/jannikmi/timezonefinder&quot;&gt;timezonefinder package&lt;/a&gt; with &lt;a href=&quot;https://github.com/jannikmi&quot;&gt;@jannikm&lt;/a&gt;. It has a &lt;a href=&quot;https://setuptools.pypa.io/en/latest/userguide/package_discovery.html#flat-layout&quot;&gt;flat layout&lt;/a&gt;. Having a flat layout can bring unnecessary files into the package compared to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src&lt;/code&gt; layout, and that can be improved with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;where = [&quot;src&quot;]&lt;/code&gt; directive. If you have a flat-layout, but still follow standard naming conventions, then &lt;a href=&quot;https://setuptools.pypa.io/en/latest/userguide/package_discovery.html#flat-layout&quot;&gt;automatic package discovery&lt;/a&gt; could work without any additional tweaks. It excludes typical directories like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tests&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;examples&lt;/code&gt;, etc. To include new files, like built binaries in a module &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timezonefinder/inside_poly_extension&lt;/code&gt;, you have to include them in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;packages&lt;/code&gt; directive explicitly:&lt;/p&gt;

&lt;div class=&quot;language-toml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setuptools&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;packages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;tests&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;timezonefinder&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;timezonefinder.inside_poly_extension&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setuptools&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;package-data&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;timezonefinder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;**/*.json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;**/*.c&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;**/*.h&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Since automatic package discovery is used, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build.py&lt;/code&gt; script cannot be stored at the root of the repository (it is in the exclusion list). Placing the script in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timezonefinder&lt;/code&gt; package eliminates this problem. Also, in this example, the tests are packaged too, because we need them in the distribution. The build script itself &lt;a href=&quot;https://github.com/jannikmi/timezonefinder/blob/master/timezonefinder/build.py&quot;&gt;is simple&lt;/a&gt; and compiles a dynamic library from sources.&lt;/p&gt;

&lt;p&gt;Essentially, the whole process of building the package is either&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uv build &lt;span class=&quot;nt&quot;&gt;--sdist&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uv build &lt;span class=&quot;nt&quot;&gt;--wheel&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;uv-and-ci&quot;&gt;uv and CI&lt;/h2&gt;

&lt;p&gt;uv also simplifies CI setup for many projects.&lt;/p&gt;

&lt;p&gt;If a project is pure Python, nothing special is required. Run tests, run a linter, make an sdist and upload it to PyPI. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uv&lt;/code&gt; can streamline commands and simplify tool installation.&lt;/p&gt;

&lt;p&gt;Binary packaging complicates the CI build. Now the project has multiple binary targets. Possible dimensions are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cp39&lt;/code&gt;,&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cp310&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cp311&lt;/code&gt; for Python versions; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;musllinux&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;manylinux&lt;/code&gt; for ABI targets. There are two approaches to this.&lt;/p&gt;

&lt;p&gt;First, the &lt;a href=&quot;https://cibuildwheel.pypa.io&quot;&gt;cibuildwheel tool&lt;/a&gt;. It manages the whole process automatically with a single GitHub Actions step by creating Docker containers for all permutations of specified Python versions and architectures. Inside each container, it runs a specified command (setuptools by default) and exports the produced wheels.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;wheels&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;pypa/cibuildwheel@v3.1.3&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;output-dir&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dist&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;CIBW_MANYLINUX_X86_64_IMAGE&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;quay.io/pypa/manylinux2014_x86_64:2025.03.09-1&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;CIBW_MUSLLINUX_X86_64_IMAGE&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;quay.io/pypa/musllinux_1_1_x86_64:2024.10.26-1&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;CIBW_BUILD&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;cp39-*&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;cp310-*&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;cp311-*&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;cp312-*&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;cp313-*&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;CIBW_BUILD_FRONTEND&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;pip&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;CIBW_BEFORE_ALL_LINUX_MANYLINUX2014&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;yum install -y libffi-devel clang make&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;CIBW_BEFORE_ALL_LINUX_MUSLLINUX_1_1&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;apk add --no-cache libffi-dev clang make&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The second approach is to utilise a matrix build. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uv&lt;/code&gt; can manage Python versions by itself by downloading prebuilt CPython binaries and executing scripts in their context. So the GitHub Actions build becomes even simpler:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;strategy&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;matrix&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;python-version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;cp310&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;cp311&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;cp312&quot;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v4&lt;/span&gt;

  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Make wheel&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;uv build --python $ --wheel&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Of course, if you want to achieve &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;manylinux&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;abi3&lt;/code&gt; compatibility, you’ll need to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cibuildwheel&lt;/code&gt; in combination with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uv&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You could also use the GitHub action &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;astral-sh/setup-uv@v6&lt;/code&gt;, but the example cited above is the shortest version to demonstrate the flow.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uv&lt;/code&gt; provides an excellent experience when working with modern Python packaging. Lightning-fast dependency resolution and tool ergonomics are the main benefits.&lt;/p&gt;

&lt;p&gt;Binary distribution is not a first-class citizen in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uv&lt;/code&gt; and requires a fallback to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setuptools&lt;/code&gt; build backend, while still using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uv&lt;/code&gt; as a frontend and keeping its ergonomics.&lt;/p&gt;

&lt;p&gt;The problem of standardised binary distribution building is not yet solved by any tool. Approaching this could be the next big step for the Python ecosystem, especially when Python is increasingly accelerated with native code.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>How Jenkins Age Becomes Tech Debt</title>
   <link href="https://omniverse.ru/blog/2025/07/15/jenkins-jdk/"/>
   <updated>2025-07-15T00:00:00+00:00</updated>
   <id>https://omniverse.ru/blog/2025/07/15/jenkins-jdk</id>
   <content type="html">&lt;p&gt;Jenkins is a very popular and highly customisable CI system. It is so customisable that it could even become a problem, especially if you use plugins.&lt;/p&gt;

&lt;p&gt;I am maintaining a really old and weird CI system. The master worked on 2.289, which dates back to 2021. What is even worse is that agent machines were running the operating systems openSUSE 12 and Debian 7 from 2011 and 2013. Those were glorious times — the Higgs boson was discovered, athletes ran at the London Olympics, and Snowden leaked tons of classified data. Anyway, I didn’t think it was such an ancient system – master was only five years ago. How naive was I…&lt;/p&gt;

&lt;h2 id=&quot;the-plan&quot;&gt;The Plan&lt;/h2&gt;

&lt;p&gt;The usual upgrade plan for Jenkins is:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Upgrade plugins&lt;/li&gt;
  &lt;li&gt;Restart&lt;/li&gt;
  &lt;li&gt;Upgrade Jenkins WAR to the next version&lt;/li&gt;
  &lt;li&gt;Restart&lt;/li&gt;
  &lt;li&gt;Upgrade plugins again because Jenkins is newer now&lt;/li&gt;
  &lt;li&gt;Enjoy&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And it usually works – but not this time.&lt;/p&gt;

&lt;h2 id=&quot;plugins-disaster&quot;&gt;Plugins disaster&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/jenkins-fire.png&quot; alt=&quot;Jenkins Fire&quot; /&gt;
&lt;em&gt;Image by &lt;a href=&quot;https://jenkins.io/&quot;&gt;Jenkins Project&lt;/a&gt; &lt;a href=&quot;https://creativecommons.org/licenses/by-sa/3.0/&quot;&gt;CC BY-SA 3.0&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Of course, I didn’t upgrade to the version past 2.479 on Java 17, because Debian 10 only supports OpenJDK 11 out of the box. So it was the easy part.&lt;/p&gt;

&lt;p&gt;Then an innocent plugin upgrade led to unforeseen circumstances. It turns out the plugin centre doesn’t provide data for plugins older than one year. It is &lt;a href=&quot;https://www.jenkins.io/doc/developer/plugin-development/choosing-jenkins-baseline/#choosing-a-version&quot;&gt;documented&lt;/a&gt; with an obscure wording: “Do not use versions no longer supported by the update centre”. I think it’s crucial information and providing factually incorrect information is just wrong. In practice, it means that you could be provided with an ill-defined set of versions without a proper baseline Jenkins version check.&lt;/p&gt;

&lt;p&gt;That’s exactly what happened in my case. I discovered Jenkins in a state where almost all plugins complained about incompatibility with the Jenkins version. I started downgrading them one by one. It’s quite difficult since the Jenkins update centre doesn’t provide a way to downgrade a plugin version, not to mention downgrading to a specific version.&lt;/p&gt;

&lt;p&gt;It means that for each plugin, you must navigate to &lt;a href=&quot;https://plugins.jenkins.io&quot;&gt;the plugins website&lt;/a&gt;, perform version bisecting for a plugin version targeting Jenkins 2.479 at most, download the HPI from releases, upload it to the Jenkins Plugin Manager, and restart. Of course, the dependency graph for failed plugins must be constructed by yourself! The easiest way is to find the most used plugin, whose node has the highest out-degree in the graph, and try to upgrade it. Then move to dependencies. Hopefully, you won’t encounter plugin conflicts with each other (I did).&lt;/p&gt;

&lt;p&gt;After a few hours, the plugin hell was finally frozen, and all plugins and Jenkins were in a consistent state with plugins.&lt;/p&gt;

&lt;h2 id=&quot;in-search-of-openjdk&quot;&gt;In search of OpenJDK&lt;/h2&gt;

&lt;p&gt;Jenkins agents must run the same version of JDK as the master, or newer. This fact made life a little more complicated, since Debian 7 only supported Java 7, and openSUSE was stuck at 6. Previously, I put OpenJDK 8 in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/opt/jdk8&lt;/code&gt; and specified the path to the JDK binary for Jenkins agent in “Launch Method -&amp;gt; Advanced -&amp;gt; JavaPath” without exposing this Java to the system or applications via the environment. It worked well.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/jdk-jenkins-agent.png&quot; alt=&quot;JDK Jenkins Agent&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, JDK 11 builds are not widely available for these operating systems. Ten years ago, we were able to navigate to the &lt;del&gt;Sun Microsystems&lt;/del&gt; Oracle website and download JDK builds for a variety of Unix systems. Sometimes, after an easy registration wall, but it was possible at least, although the options were limited.&lt;/p&gt;

&lt;p&gt;Nowadays, Oracle doesn’t provide free JDK builds. The stewardship of Java went to OpenJDK, which is the official reference Java implementation. It produces source code, but not distributions. Surely, you can compile it on your own, but testing, tracking security issues, and ensuring correct platform support are up to you. So, plenty of JDK distributions had emerged (alphabetically):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;AdoptOpenJDK&lt;/li&gt;
  &lt;li&gt;Azul Zulu&lt;/li&gt;
  &lt;li&gt;Amazon Corretto&lt;/li&gt;
  &lt;li&gt;Eclipse Temurin&lt;/li&gt;
  &lt;li&gt;Liberica OpenJDK&lt;/li&gt;
  &lt;li&gt;Microsoft OpenJDK&lt;/li&gt;
  &lt;li&gt;Oracle Java&lt;/li&gt;
  &lt;li&gt;Red Hat OpenJDK&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To be precise, Oracle provides support only for the latest JDK version, so there is no such thing as Oracle LTS.&lt;/p&gt;

&lt;p&gt;So the plan is to go and grab any distribution for OpenJDK 11, right? That’s true only if it supports an old enough libc. If somebody builds a distribution on a newer build machine, binaries will use the latest system glibc; hence, it cannot be run on an older machine. So you should either have a dedicated old build machine, which sets a baseline glibc version, or bump glibc requirements.&lt;/p&gt;

&lt;p&gt;I found a few distributions which were built a long time ago and are still available at a vendor’s website. For example, for Liberica jdk11.0.27+9:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;% nm /opt/jdk11/bin/java | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;@GLIBC&apos;&lt;/span&gt;
  U getenv@@GLIBC_2.2.5
% /lib/x86_64-linux-gnu/libc.so.6
  GNU C Library &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Debian EGLIBC 2.13-38+deb7u11&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; stable release version 2.13, by Roland McGrath et al.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It works because the system glibc version is newer than any symbol referenced from the Java ELF binary. Of course, it is even easier just to launch it and see whether it immediately emits any errors.&lt;/p&gt;

&lt;p&gt;Sometimes vendors are kind enough to list system requirements for versions in terms of operating system versions (like Debian 9, RedHat 7 and so on). Only a few of them specify the kernel and glibc version, and in this case one can consult &lt;a href=&quot;https://distrowatch.com/table.php?distribution=debian&quot;&gt;distrowatch&lt;/a&gt; or check the glibc version on a real system. Finding a matching and working version was a trial-and-error process.&lt;/p&gt;

&lt;p&gt;Things became even more complicated for non-Intel build farms. If you’re thinking about a modern AArch64 ARM – no way, it is a 32-bit ARM7 hard-float. So you have an even smaller chance of finding a binary distribution for this old obscure architecture. The only distribution providing 32-bit ARM7 HF is &lt;a href=&quot;https://www.azul.com/downloads/?package=jdk#&quot;&gt;Azul Zulu&lt;/a&gt;. However, all of their recent binaries are for glibc 2.15. And there is only one old build 11.1.8 targeting older glibc 2.13, which is able to run on that machine:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/jdk-azul-arm.png&quot; alt=&quot;Azul ARM7&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The fun doesn’t stop with Linux. Turns out, an old but stable OS X 10.7 machine cannot run any builds from OpenJDK, Azul, or Corretto. It is not only a matter of using a new operating system API, but also of using a newer kernel. All these builds crashed with SIGSEGV upon launch. Suddenly, I discovered a custom OpenJDK build from the open-source enthusiast &lt;a href=&quot;https://github.com/Jazzzny&quot;&gt;Jazzzny&lt;/a&gt;, who had kindly built JDK 11 for OS X. The binaries are available at &lt;a href=&quot;https://github.com/Jazzzny/jdk-macos-legacy-old/releases/tag/old-releases&quot;&gt;GitHub releases page&lt;/a&gt;, and binary &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jdk-11-snowleopard-r1.pkg&lt;/code&gt; (sha256 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;7c51f13993a7f38575d82aa2a7691aace6170c59074847e779a3d9f903f044fb&lt;/code&gt;) built for Snow Leopard (10.7) worked perfectly for me. Minecraft gamers used these builds to deal with older Java versions, which proves again that gaming is the driving force of the computer industry.&lt;/p&gt;

&lt;h2 id=&quot;outcome&quot;&gt;Outcome&lt;/h2&gt;

&lt;p&gt;Ten hours into the upgrade, I’d successfully fixed core Jenkins, all the plugins, and agents, and I was able to run a few test builds.&lt;/p&gt;

&lt;p&gt;What went well? The diversity of OpenJDK distributions and the openness of the community greatly enhance your chances of finding a proper distribution suited to your needs.&lt;/p&gt;

&lt;p&gt;What could go better? The Jenkins plugin management system. The older your system is, the less likely you are able to upgrade. The cut-off of the plugin compatibility information after a year or two, lack of built-in downgrade functionality and clear machine-readable requirement annotation drags you into the manual dependency resolution nightmare. Making a copy of the Jenkins installation for upgrading and switching it later could be a good solution.&lt;/p&gt;

&lt;p&gt;What had I learned? The age of your CI infrastructure is the tech debt. Contrary to popular opinion, please &lt;em&gt;do&lt;/em&gt; upgrade your Jenkins at the LTS track and the plugins, at least yearly. It enables the possibility of a much smoother upgrade.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Talk at Rust Nation UK 2025</title>
   <link href="https://omniverse.ru/blog/2025/02/28/rustnationuk/"/>
   <updated>2025-02-28T00:00:00+00:00</updated>
   <id>https://omniverse.ru/blog/2025/02/28/rustnationuk</id>
   <content type="html">&lt;p&gt;Last week, I had the chance to give a talk at &lt;a href=&quot;https://www.rustnationuk.com&quot;&gt;Rust Nation UK 2025&lt;/a&gt;, one of the biggest Rust conferences.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://www.youtube.com/watch?v=zQgN75kdR9M&quot;&gt;YouTube talk&lt;/a&gt; is already up, thanks to the conference team.
If you prefer slides, &lt;a href=&quot;/assets/rustnation-talk-2025.pdf&quot;&gt;&lt;img src=&quot;/assets/pdf-icon.gif&quot; alt=&quot;pdf&quot; /&gt; here they are&lt;/a&gt;.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/zQgN75kdR9M?si=qSOX_rjLHu0ERhTd&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;As always, I love talking about performance, optimisation, and Rust.
So my topic was both useful and engaging (with a pun) - &lt;strong&gt;“Parallel Programming in Rust: Techniques for Blazing Speed”&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We covered a lot: high-level parallelism with Rayon and thread pools, the parallelism theory, concurrency primitives, low-level primitives (atomics, lock-free algorithms, SIMD, cache efficiency, profiling) and even GPUs - diving deeper and deeper to the hardware. The talk was probably a bit dense, but I hope it provides a solid overview of modern parallel computing approaches and gives ideas on where to go next.&lt;/p&gt;

&lt;p&gt;I was impressed by the conference content. The keynotes from Mark Russinovich and Celina Val were inspiring and reassuring about integrating Rust into existing codebases. There were in-depth technical talks about libraries, language and kernel bindings, the evolution of both the ecosystem and the language. The workshops were also useful and practical about async and C++ interop.&lt;/p&gt;

&lt;p&gt;And, of course, one of the best part of the event was the vibrant, friendly, and passionate community, always ready for a great discussion. Looking forward to attending the next one!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Scrobbling to Last.fm offline</title>
   <link href="https://omniverse.ru/blog/2024/08/27/lastfm-scrobble/"/>
   <updated>2024-08-27T00:00:00+00:00</updated>
   <id>https://omniverse.ru/blog/2024/08/27/lastfm-scrobble</id>
   <content type="html">&lt;p&gt;As a passionate stereo and vinyl enthusiast, I keep track of all my favourite tracks using Lastfm. But how about scrobbling the tracks spinning on my turntable? Here is how I made offline scrobbling happen using blazingly fast 🦀™ offline scrobbler!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/turntable.jpg&quot; alt=&quot;Turntable with a vinyl record&quot; /&gt;
&lt;em&gt;Image by  M. Johnson under &lt;a href=&quot;https://creativecommons.org/licenses/by/2.0/&quot;&gt;CC BY 2.0&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Imagine: you are sitting in a comfy chair near your stereo system, smelling the scent of a vinyl record spinning on your turntable, looking at the artwork, and listening to music. Is there something you might have forgotten to do?&lt;/p&gt;

&lt;p&gt;It is not a problem at all since Last.fm allows you to scrobble using its API. That’s how your scrobblers for desktops and mobile devices work. So, I wrote a utility called &lt;a href=&quot;https://github.com/theirix/offline-scrobbler&quot;&gt;offline-scrobbler&lt;/a&gt; to scrobble music to Last.fm for a given artist and album.&lt;/p&gt;

&lt;h2 id=&quot;how-to-set-it-up&quot;&gt;How to set it up&lt;/h2&gt;

&lt;p&gt;Install the utility. The simplest way to do it, if you have &lt;a href=&quot;https://doc.rust-lang.org/cargo/getting-started/installation.html&quot;&gt;Cargo&lt;/a&gt; installed, is to run:&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cargo &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;offline-scrobbler
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;GitHub &lt;a href=&quot;https://github.com/theirix/offline-scrobbler/releases&quot;&gt;releases&lt;/a&gt; contain Linux binaries. They are automatically built for Linux using musl to avoid problems with glibc linkage. Building macOS and Windows executables is a little more challenging. If anyone wants them, please ping me.&lt;/p&gt;

&lt;p&gt;You need to create a Last.fm application account. Since this scrobbler is not a public service and it just runs on your machine, you need to create an account for yourself. Head to Last.fm, and log in as usual. Then go to &lt;a href=&quot;https://www.last.fm/login?next=/api/account/create&quot;&gt;Create API account page&lt;/a&gt;. Here, you need to enter your “Contact e-mail”, an arbitrary name to “Application name” field, and nothing more.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/lastfm-account-1.png&quot; alt=&quot;account step 1&quot; /&gt;&lt;/p&gt;

&lt;p&gt;On the next screen, you will be presented with unique account keys for this application. Write down the “API Key” and “Shared secret”.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/lastfm-account-2.png&quot; alt=&quot;account step 2&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Provide the keys to the utility with the following call:&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;offline-scrobbler auth &lt;span class=&quot;nt&quot;&gt;--api-key&lt;/span&gt; API_KEY &lt;span class=&quot;nt&quot;&gt;--secret-key&lt;/span&gt; SHARED_SECRET
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You are all set!&lt;/p&gt;

&lt;h2 id=&quot;scrobbling-music&quot;&gt;Scrobbling music&lt;/h2&gt;

&lt;p&gt;That’s why we are here, right?&lt;/p&gt;

&lt;p&gt;I supported three typical scrobbling scenarios.&lt;/p&gt;

&lt;p&gt;First of all, scrobble one song. It’s pretty easy:&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;offline-scrobbler scrobble &lt;span class=&quot;nt&quot;&gt;--artist&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Hooverphonic &lt;span class=&quot;nt&quot;&gt;--track&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Eden
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then, you can scrobble the whole album:&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;offline-scrobbler scrobble &lt;span class=&quot;nt&quot;&gt;--artist&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Hooverphonic &lt;span class=&quot;nt&quot;&gt;--album&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;A New Stereophonic Sound Spectacular&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, if you have a Last.fm album page open, just grab the URL and feed it to the scrobbler:&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;offline-scrobbler scrobble-url &lt;span class=&quot;nt&quot;&gt;--url&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;https://www.last.fm/music/Hooverphonic/Blue+Wonder+Power+Milk&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;internals&quot;&gt;Internals&lt;/h2&gt;

&lt;p&gt;Did I mention that the scrobbler is blazingly fast 🚀 because it is written in Rust 🦀 ? :)&lt;/p&gt;

&lt;p&gt;The scrobbler relies on Last.fm artist and album names. So, it’s advisable to first run scrobbler in preview mode (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--dryrun&lt;/code&gt; argument) to verify you’re scrobbling precisely the tracks you want. If there are any scrobble problems or tracks are ignored, they will be reported to the console.&lt;/p&gt;

&lt;p&gt;I was surprised that Rust is missing a good scrobbler library. Anyway, it is a common problem. I wrote mine in Objective C while developing a desktop scrobbler for OS X a decade ago. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reqwest&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;serde&lt;/code&gt; simplify writing API clients a lot. Unfortunately, last.fm / audioscrobbler API is XML-based, so you have to write some boilerplate.&lt;/p&gt;

&lt;p&gt;The core types for scrobbling logic are pretty simple:&lt;/p&gt;
&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;nd&quot;&gt;#[derive(Debug)]&lt;/span&gt;
 &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Track&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

 &lt;span class=&quot;nd&quot;&gt;#[derive(Debug)]&lt;/span&gt;
 &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Album&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tracks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Track&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Option&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All the error handling logic is nicely supported by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;anyhow&lt;/code&gt; (for propagating errors in CLI application) and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;thiserror&lt;/code&gt; (to model library error enum).&lt;/p&gt;

&lt;p&gt;Writing CLI applications in Rust is a breeze. Combining &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clap&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;serde&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;env_logger&lt;/code&gt; is one of the finest ways to make a complicated CLI with parameter groups, validation, colour handling and other nice things. The only unfortunate thing I’ve noticed is that authors of clap v4 recently decided to unconditionally &lt;a href=&quot;https://github.com/clap-rs/clap/issues/3234&quot;&gt;drop colour support&lt;/a&gt; in favour of more generic ANSI underlining. Let’s check the state of this issue.&lt;/p&gt;

&lt;h2 id=&quot;date-and-time&quot;&gt;Date and time&lt;/h2&gt;

&lt;p&gt;One curious thing I’ve discovered is that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std&lt;/code&gt; library doesn’t provide formatters for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::time::Duration&lt;/code&gt;. It has &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Debug&lt;/code&gt; trait implementation, but the formatting is excluded explicitly because: “there are a variety of ways to format periods for human readability”. So, one should use an implementation from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chrono&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;time&lt;/code&gt;. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;time&lt;/code&gt; crate has a totally different &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Duration&lt;/code&gt; type apart from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std&lt;/code&gt;, which is not so convenient. Finally, there are ongoing debates which external datetime library to use. Maintainers of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chrono&lt;/code&gt; returned to support the crate, and one more library called &lt;a href=&quot;https://github.com/BurntSushi/jiff&quot;&gt;jiff&lt;/a&gt; just arrived from BurntSushi, so the choice became even harder. The main downside of a well-supported and lightweight &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;time&lt;/code&gt; crate for me is the lack of strftime-style format. It is so ubiquitous to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%H-%M-%S&lt;/code&gt; instead of&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;format_description!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;[hour]:[minute]:[second]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To mimic normal human behaviour, I added pauses between tracks in the duration of each track. What’s more, you can scrobble into the past. Remember, you had been playing that album 6 hours ago? That is no problem - just specify an offset in human-readable form.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;offline-scrobbler scrobble &lt;span class=&quot;nt&quot;&gt;--artist&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Hooverphonic &lt;span class=&quot;nt&quot;&gt;--album&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;A New Stereophonic Sound Spectacular&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;6h
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Kudos to the great &lt;a href=&quot;https://docs.rs/humantime&quot;&gt;humantime&lt;/a&gt; library for this functionality.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;My life has many entertaining activities, and music-related ones make up a significant part. I’ve worked on different tools to ease managing music libraries, encoding files and synchronizing with services. While building such tools, always remember that it could also be fun and rewarding.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Testing PostgreSQL Extensions</title>
   <link href="https://omniverse.ru/blog/2023/05/20/testing-postgresql-extensions/"/>
   <updated>2023-05-20T00:00:00+00:00</updated>
   <id>https://omniverse.ru/blog/2023/05/20/testing-postgresql-extensions</id>
   <content type="html">&lt;p&gt;You have written a useful PostgreSQL extension. It works perfectly, includes some tests and has excellent documentation. But can it be tested properly and automatically for multiple PostgreSQL versions?&lt;/p&gt;

&lt;h2 id=&quot;single-machine-build&quot;&gt;Single-machine build&lt;/h2&gt;

&lt;p&gt;First, the best way to build an extension on a single machine is to use the standard &lt;a href=&quot;https://www.postgresql.org/docs/current/extend-pgxs.html&quot;&gt;PGXS&lt;/a&gt; build system.
It allows writing a simple declarative Makefile that includes a lot of PGXS machinery:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-Makefile&quot;&gt;PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Only a few Makefile variables with extension metadata must be added, and PGXS does the rest. Just type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make&lt;/code&gt;, and the platform-specific binary and SQL scripts for extension are built. Target &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make install&lt;/code&gt; install the extension to the current (pointed by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pg_config&lt;/code&gt;) PostgreSQL installation.&lt;/p&gt;

&lt;p&gt;PGXS also provides regression tests with a tool &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pg_regress&lt;/code&gt; and makefile wrappers. It’s enough to put SQL inputs to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/sql/test1.sql&lt;/code&gt; and expected text output to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/expected/test1.out&lt;/code&gt;. Invoking &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make installcheck&lt;/code&gt; runs all regression tests and provides a report and a status code.&lt;/p&gt;

&lt;p&gt;It’s an easy and powerful system.&lt;/p&gt;

&lt;h2 id=&quot;testing-at-scale&quot;&gt;Testing at scale&lt;/h2&gt;

&lt;p&gt;But what about testing against multiple PostgreSQL versions? Typical package managers like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;homebrew&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apt&lt;/code&gt; don’t allow the installation of a large selection of PostgreSQL binaries because older versions are quickly replaced with newer ones. For a specific Debian installation, only one PostgreSQL version is supported at any moment, which the Debian community chooses. Homebrew allows to have multiple servers installed side-by-side. To select a specific version, pass a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PG_CONFIG&lt;/code&gt; variable before calling make:&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;PG_CONFIG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/usr/local/opt/postgresql@14/bin/pg_config make
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Another way is to build multiple PostgreSQL versions from the source and install them side-by-side. Usually, it works well because the build system works fine on a range of operating systems versions.&lt;/p&gt;

&lt;p&gt;But we don’t want to build everything from scratch. Okay, let’s use official Docker images:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; postgres:14-bullseye find / &lt;span class=&quot;nt&quot;&gt;-name&lt;/span&gt; postgres.h
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Oops, no headers. The image is not suitable for building extensions.&lt;/p&gt;

&lt;p&gt;How can we avoid building code by ourselves?&lt;/p&gt;

&lt;p&gt;It turned out that there are official APT repositories provided not by Debian but by the Postgres community itself. These &lt;a href=&quot;https://wiki.postgresql.org/wiki/Apt&quot;&gt;APT&lt;/a&gt; and &lt;a href=&quot;https://wiki.postgresql.org/wiki/Apt&quot;&gt;APT FAQ&lt;/a&gt; describe them in details.
You can install &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;postgresql-14&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;postgresql-server-dev-14&lt;/code&gt; (any version here) on any Debian distribution. Packages provide all needed headers and development files.&lt;/p&gt;

&lt;h2 id=&quot;test-harness-for-images&quot;&gt;Test harness for images&lt;/h2&gt;

&lt;p&gt;Unfortunately, there aren’t any prebuild Docker images with these packages.
With a prebuilt server binary, it’s a much simpler task.&lt;/p&gt;

&lt;p&gt;All CI-related files for my &lt;a href=&quot;https://github.com/theirix/parray_gin&quot;&gt;parray_gin&lt;/a&gt; extension lay inside &lt;a href=&quot;https://github.com/theirix/parray_gin/tree/master/docker&quot;&gt;this directory&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First of all, the image contains everything needed to run actual tests.&lt;/p&gt;

&lt;p&gt;Here we specify the build argument with a version.&lt;/p&gt;
&lt;div class=&quot;language-dockerfile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; debian:bullseye&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# PostgreSQL version (like 15)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; PGVERSION&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; PGCHANNEL=bullseye-pgdg&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We use the same compiler (gcc10 in bullseye) for all different PostgreSQL versions:&lt;/p&gt;
&lt;div class=&quot;language-dockerfile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; DEBIAN_FRONTEND=noninteractive&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Install core packages&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;apt-get update &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;    apt-get &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--no-install-recommends&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;curl gnupg tzdata &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;    locales lsb-release ca-certificates &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;    make gcc libssl-dev libkrb5-dev libicu-dev libdpkg-perl
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The APT repository for PostgreSQL must be configured in a modern way. Do not use apt-key anymore because &lt;a href=&quot;https://opensource.com/article/22/9/deprecated-linux-apt-key&quot;&gt;it is deprecated&lt;/a&gt;.
Remember to raise the priority of new packages because Debian packages could be pulled in instead of the community’s.&lt;/p&gt;
&lt;div class=&quot;language-dockerfile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Configure repository&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /etc/apt/keyrings &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;    curl &lt;span class=&quot;nt&quot;&gt;-fsSL&lt;/span&gt; https://www.postgresql.org/media/keys/ACCC4CF8.asc | &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;    gpg &lt;span class=&quot;nt&quot;&gt;--dearmor&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; /etc/apt/keyrings/postgres.gpg &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;    &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;deb [arch=amd64 signed-by=/etc/apt/keyrings/postgres.gpg] http://apt.postgresql.org/pub/repos/apt/ &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PGCHANNEL&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; main &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PGVERSION&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;    &lt;span class=&quot;nb&quot;&gt;tee&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-a&lt;/span&gt; /etc/apt/sources.list.d/postgres.list

&lt;span class=&quot;c&quot;&gt;# Prefer packages from the Postgres repository&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Package: *&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Pin: release o=apt.postgresql.org&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Pin-Priority: 900&quot;&lt;/span&gt; | &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;    &lt;span class=&quot;nb&quot;&gt;tee&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-a&lt;/span&gt; /etc/apt/preferences.d/pgdg.pref

&lt;span class=&quot;c&quot;&gt;# Install PostgreSQL&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;apt-get update &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;    apt-get &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--no-install-recommends&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; postgresql-&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PGVERSION&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;      postgresql-server-dev-&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PGVERSION&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;    &lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, bootstrap the cluster and prepare the environment for building extension:&lt;/p&gt;
&lt;div class=&quot;language-dockerfile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Create cluster&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; PGBIN=/usr/lib/postgresql/${PGVERSION}/bin&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; PGDATA=&quot;/var/lib/postgresql/${PGVERSION}/test&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; PATH=&quot;${PATH}:${PGBIN}&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;postgres ALL=(ALL) NOPASSWD:ALL&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/sudoers

&lt;span class=&quot;k&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; docker/entrypoint.sh /entrypoint.sh&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; docker/test.sh /test.sh&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;chmod &lt;/span&gt;a+x /entrypoint.sh /test.sh

&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; /src &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;chown &lt;/span&gt;postgres /src

&lt;span class=&quot;k&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; postgres&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;initdb

&lt;span class=&quot;k&quot;&gt;WORKDIR&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; /src&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;ENTRYPOINT&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; [ &quot;/entrypoint.sh&quot; ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The entrypoint launches the cluster with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pg_ctl start&lt;/code&gt; in the background so the command in a container will be executed with an environment with an already running cluster:&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/sh&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt;
pg_ctl start
&lt;span class=&quot;nb&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$@&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Images could be prebuilt in advance:&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker build &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; pgx:14 &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; docker/Dockerfile &lt;span class=&quot;nt&quot;&gt;--build-arg&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PGVERSION&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;14 &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Container builds and runs tests for an extension whose source code is in a given directory. The directory is mounted as a read-only volume. Everything from this directory is copied to the intermediate directory and built there.&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;:/workspace:ro pgx:14 /test.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There is one tricky part. Sometimes at slow CI machines cluster has yet to start when the test is begun. We need to wait until Postgres becomes ready.
For newer versions &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pg_isready&lt;/code&gt; command can be used, but for older versions (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9.x&lt;/code&gt;), the fallback with a blind timeout is used.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;command &lt;/span&gt;pg_isready &lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
  &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;timeout &lt;/span&gt;600 bash &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;until pg_isready; do sleep 10; done&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else
  &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sleep &lt;/span&gt;30
&lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So the typical local workflow for testing against arbitrary versions is a one-liner:&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PGVERSION&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;14 &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  docker build &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; pgx:&lt;span class=&quot;nv&quot;&gt;$PGVERSION&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; docker/Dockerfile &lt;span class=&quot;nt&quot;&gt;--build-arg&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PGVERSION&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PGVERSION&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  docker run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;:/workspace:ro pgx:&lt;span class=&quot;nv&quot;&gt;$PGVERSION&lt;/span&gt; /test.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;testing-on-nightly&quot;&gt;Testing on nightly&lt;/h2&gt;

&lt;p&gt;If you are going to test an extension against unreleased PostgreSQL, there are packages with development nightly snapshots. You don’t need to build a database from the trunk!&lt;/p&gt;

&lt;p&gt;My scripts allow specifying a development channel &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PGCHANNEL=bullseye-pgdg-snapshot&lt;/code&gt; instead of standard &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PGCHANNEL=bullseye-pgdg&lt;/code&gt; for the image so the nightly package version will be used for tests.&lt;/p&gt;

&lt;h2 id=&quot;wrapping-up-in-github-actions&quot;&gt;Wrapping up in GitHub Actions&lt;/h2&gt;

&lt;p&gt;Since all tests are parametrised with a database version and channel, it’s easy to wrap them in any CI that supports matrix builds.&lt;/p&gt;

&lt;p&gt;Here we go with GitHub Actions &lt;a href=&quot;https://github.com/theirix/parray_gin/actions/workflows/test.yml&quot;&gt;matrix build&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;branches&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;master&quot;&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;pull_request&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;branches&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;master&quot;&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;strategy&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;matrix&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;pg-version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;9.1&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;9.5&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;9.6&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;10&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;11&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;12&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;13&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;14&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;15&quot;&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Checkout&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Build the Docker image&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;docker build . --file docker/Dockerfile --tag &quot;pgxtest:$&quot; --build-arg &quot;PGVERSION=$&quot;&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Test&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;docker run --rm -v `pwd`:/workspace &quot;pgxtest:$&quot; /test.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/theirix/parray_gin/actions/workflows/test-snapshot.yml&quot;&gt;Snapshot workflow&lt;/a&gt; is more straightforward and includes the only job for the snapshot.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;The build and testing system for PostgreSQL extensions is stable and useful for local development. More efforts are needed when you need to test it against multiple PostgreSQL versions automatically. Code and approaches introduced in this post could greatly simplify the development experience with local development and public CIs.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Why the Riak matters</title>
   <link href="https://omniverse.ru/blog/2023/04/23/riak/"/>
   <updated>2023-04-23T00:00:00+00:00</updated>
   <id>https://omniverse.ru/blog/2023/04/23/riak</id>
   <content type="html">&lt;p&gt;Riak is one of the first and most prominent key-value databases implementing the famous &lt;a href=&quot;https://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf&quot;&gt;Amazon Dynamo paper&lt;/a&gt;. The paper was unveiled in 2007. Sixteen years ago, it could even get a passport!&lt;/p&gt;

&lt;p&gt;The more widespread database that follows the paper is Apache Cassandra. Funny enough that AWS DynamoDB had walked away from original ideas (leaderless replication), and all of these systems have different storage engines.&lt;/p&gt;

&lt;p&gt;Riak differs by following the original ideas from Dynamo. So if you’d like to explore those principles and design a system for database, or maybe even enhance a database, Riak is a very good candidate, with a tiny peculiarity. It is written in Erlang and OTP which raises a barrier to entry.&lt;/p&gt;

&lt;p&gt;In this post, I’ll describe the architecture of Riak, map-reduce pipeline development in Erlang and the Bitcask storage engine.&lt;/p&gt;

&lt;h2 id=&quot;dynamo-foundations&quot;&gt;Dynamo foundations&lt;/h2&gt;

&lt;p&gt;Attempts to speak with the Riak Ring in Erlang as shown in the Arrival movie:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/arrival-poster.jpg&quot; alt=&quot;arrival&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Maybe Riak’s glory days are passed. After Basho’s departure and giving away projects to the community, it lacks support. For example, you cannot download binary releases for macOS since the bucket with artifacts has some permission errors. So it’s required to build it from the source. And it’s impossible to build Riak with the latest Erlang 25, I only managed to do it with Erlang 22. A lot of dead links in the documentation. So you were warned.&lt;/p&gt;

&lt;p&gt;Riak has a nice CLI to manage a cluster. Each node is accessible by HTTP and protobuf protocols. You can even fire an Erlang shell and work with a node directly because it is an OTP application. It really shines with distributed deployments since it is fully decentralised and uses leaderless replication. Membership and state are replicated across the cluster with gossip protocols. Riak is leaderless, therefore a client can connect to any node and perform operations. Depending on request types, a node can interact with other nodes to execute the request. Regarding CAP, Riak is called an AP system, but I’ll talk more about consistency guarantees later.&lt;/p&gt;

&lt;p&gt;Data is partitioned between nodes with consistent hashing at the ring. The ring of ordered hashes represents the partitioning of keyspace to multiple continuous partitions identified by start and end keys. Each partition on the ring is handled by a vnode. Each Riak node is mapped to multiple vnodes. To fetch or store an object in the cluster, Riak first hashes the key, finds a small sequence (depending on availability requirements) of hashes on the ring, finds the vnodes, maps them onto physical nodes, perform reads/writes and waits for a read/write quorum until returning an answer to the client. The idea of consistent hashing lies in a faster rehashing when a node joins and leaves a cluster, becaise it affects only a small part of the ring instead of the whole ring. If a node leaves the cluster temporarily (i.e., network problems), rehashing is still unnecessary because this situation can be handled by hinted handoff. If a failure is permament, differences in keys between nodes can be found using anti-entropy procedures.&lt;/p&gt;

&lt;p&gt;So many concepts from Dynamo are directly implemented in Riak without major changes. There is a &lt;a href=&quot;https://docs.riak.com/riak/kv/2.2.3/learn/dynamo/index.html&quot;&gt;good page in documentation&lt;/a&gt; about how closely these concepts are mapped to Riak’s.&lt;/p&gt;

&lt;h2 id=&quot;client-interfaces&quot;&gt;Client interfaces&lt;/h2&gt;

&lt;p&gt;Riak is widely known for its good interface. It had been thoroughly following the REST principles. According to &lt;a href=&quot;https://martinfowler.com/articles/richardsonMaturityModel.html&quot;&gt;Richardson Maturity Model&lt;/a&gt;, which describes the adoption of REST concepts at Level 0 (HTTP transport), Level 1 (resources), Level 2 (HTTP verbs), Riak stays at the highest Level 3 (Hypermedia Control). It provides walking across linked keys using standard &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Link&lt;/code&gt; headers. Multiple values are served with the standard HTTP &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;multipart/mixed&lt;/code&gt; responses. Different types of media are handled with the MIME types.&lt;/p&gt;

&lt;p&gt;I’ve seen such powerful HTTP API in Hashicorp products too, for example, in &lt;a href=&quot;https://developer.hashicorp.com/consul/api-docs/kv&quot;&gt;Consul&lt;/a&gt;. The ability to have a full-featured HTTP API to perform KV operations, resolve conflicts and even perform a &lt;a href=&quot;https://developer.hashicorp.com/consul/tutorials/developer-configuration/application-leader-elections&quot;&gt;leader election&lt;/a&gt; without needing to write a lot of Java clients (I look at you, ZooKeeper) is fascinating.&lt;/p&gt;

&lt;p&gt;Another available protocol is Protobuf. Curiously, it is not used over gRPC transport but instead with a very simple &lt;a href=&quot;https://docs.riak.com/riak/kv/2.2.3/developing/api/protocol-buffers/index.html&quot;&gt;TCP transport&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Of course, many client libraries and drivers are written for Riak to interact with the store. For example, here is some Ruby code to create and put a value:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Riak&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:pb_port&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10017&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;bucket&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;bucket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bucket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;mykey&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;answer&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bucket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;mykey&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;fail&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;answer&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And an excerpt of quering the key by HTTP:&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;http http://localhost:10018/buckets/test/keys/mykey
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Link: &amp;lt;/buckets/test&amp;gt;; rel=&quot;up&quot;
X-Riak-Vclock: a85hYGBgzGDKBVI8R4M2cgds+X+VgUFdMIMpkTGPlSFaZ8tdviwA
{
    &quot;answer&quot;: 42
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Libraries lack maintenance too and show their age. For example, it’s not possible to use Python newer than 3.7.&lt;/p&gt;

&lt;h2 id=&quot;tunable-consistency&quot;&gt;Tunable consistency&lt;/h2&gt;

&lt;p&gt;It’s well known that Dynamo databases are eventually-consistent. Usually, they are put to “AP” class, but &lt;a href=&quot;https://martin.kleppmann.com/2015/05/11/please-stop-calling-databases-cp-or-ap.html&quot;&gt;Kleppman disagrees&lt;/a&gt; and instists on “P”. Let’s check why.&lt;/p&gt;

&lt;p&gt;To handle conflicts, Riak provides a variety of options. I will describe four of them.&lt;/p&gt;

&lt;p&gt;The simplest conflict resolution strategy is “last write wins” (Cassandra uses it too) based on the latest timestamp. Client does not need to address conflicts.&lt;/p&gt;

&lt;p&gt;The recommended option is to use multi-version values. For this option, Riak uses &lt;a href=&quot;https://riak.com/posts/technical/vector-clocks-revisited-part-2-dotted-version-vectors/index.html&quot;&gt;dotted version vectors&lt;/a&gt;. This structure is an improvement of vector clocks which can handle many clients with conflicting writes. Two conflicting writes create &lt;em&gt;siblings&lt;/em&gt; – different values identified by their vector clock values. So you need to handle siblings at the client application.&lt;/p&gt;

&lt;p&gt;Let’s create a bucket type with version vectors.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;riak-admin bucket-type create vv &lt;span class=&quot;s1&quot;&gt;&apos;{&quot;props&quot;:{&quot;allow_mult&quot;:true}}&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Create conflict:&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;http PUT http://localhost:10018/types/vv/buckets/cats/keys/favourite &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Puffy
http PUT http://localhost:10018/types/vv/buckets/cats/keys/favourite &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Pirate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When we try to get value, Riak returns both siblings&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;http http://localhost:10018/types/vv/buckets/cats/keys/favourite
HTTP/1.1 300 Multiple Choices
...
Siblings:
4IgpOUc3rR7wPOKbgJTGfJ
5UX8mBVbx3Y9Vno6ncicuO
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So to resolve the conflict, the next update must include a context (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X-Riak-Vclock&lt;/code&gt; value from PUT responses) and therefore resolve a conflict. If no context is given, Riak cannot reconstruct lineage.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;http http://localhost:10018/types/vv/buckets/cats/keys/favourite &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Pirate X-Riak-Vclock:&lt;span class=&quot;s2&quot;&gt;&quot;hexademical-clock-value-from-response&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Libraries encapsulate sibling handling with a fetch-modify-write loop.
For example, the equivalent code in Ruby:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;bucket&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;bucket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;cats&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# read from Riak&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bucket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get_or_new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;favourite&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;type: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;vv&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# deal with siblings&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;fail&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;siblings&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# update object locally&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;content_type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;application/json&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Pirate&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# write to Riak&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;store&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;So to write a key, you always need to read a key before and therefore create a causal context which will be included in write operation.&lt;/p&gt;

&lt;p&gt;The third option is a &lt;a href=&quot;https://docs.riak.com/riak/kv/2.2.3/configuring/strong-consistency/index.html&quot;&gt;strong consistency mode&lt;/a&gt;. Strong consistency is specified at a per-bucket level. There Riak guarantees that no siblings will be returned during read operations. Since the causal context is used for write operations, no siblings will be produced too. Client code is free from handling conflicts (a line &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fail if obj.siblings&lt;/code&gt; can be dropped).&lt;/p&gt;

&lt;p&gt;The fourth option appeared in 2014 in version 2.0 with the introduction of CRDT types (&lt;a href=&quot;https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type&quot;&gt;Conflict-free replicated data types&lt;/a&gt;) like flags, registers, counters, sets, and maps. It is a rare and complex feature for databases. CRDT types allow the developer to ignore all eventual consistency quirks, vector clocks, and handling sibling versions because the conflict resolution is fused into data types, not operations. Of course, data modelling using CRDTs is more challenging than just dropping blobs into the KV store. Full-fledged sequence CRDTs were out of scope too.&lt;/p&gt;

&lt;p&gt;With this broad choice of consistency modes and options, it’s hard to call Riak a “simply AP” system.&lt;/p&gt;

&lt;h2 id=&quot;mapreduce&quot;&gt;MapReduce&lt;/h2&gt;

&lt;p&gt;As with every good SQL and NoSQL database, Riak provides server-side computational primitives.&lt;/p&gt;

&lt;p&gt;In past years, we were able to run MapReduce queries with JavaScript functions which are registered to the Riak via HTTP API:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;function(v) {
var parsed_data = JSON.parse(v.values[0].data); var data = {};
data[parsed_data.style] = parsed_data.capacity; return [data];
}&apos;&lt;/span&gt; | http PUT http://localhost:10018/riak/my_functions/map_capacity
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And then call the MapReduce with some inputs:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{
  &quot;inputs&quot;:[[&quot;rooms&quot;,&quot;101&quot;],[&quot;rooms&quot;,&quot;102&quot;],[&quot;rooms&quot;,&quot;103&quot;] ],
  &quot;query&quot;:[
    {&quot;map&quot;:{
      &quot;language&quot;:&quot;javascript&quot;,
      &quot;bucket&quot;:&quot;my_functions&quot;,
      &quot;key&quot;:&quot;map_capacity&quot;
		}}
	]
}&apos;&lt;/span&gt; | http POST http://localhost:10018/mapred
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The function can be inlined in this &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mapred&lt;/code&gt; call. Unfortunately, the JavaScript interface is deprecated and doesn’t work reliably anymore.&lt;/p&gt;

&lt;p&gt;You should write your functions in Erlang, and it could be a little tricky. The &lt;a href=&quot;https://docs.riak.com/riak/kv/latest/developing/app-guide/advanced-mapreduce/index.html&quot;&gt;official documentation on MapReduce&lt;/a&gt; is very good but still lacks a complete example. The general approach is similar to Hadoop and HBase ecosystem techniques. You build JARs, push them to the cluster, register with runtime and then execute. Let me show how to do it.&lt;/p&gt;

&lt;h2 id=&quot;colouring-all-the-cats&quot;&gt;Colouring all the cats&lt;/h2&gt;

&lt;p&gt;Let’s analyse cat’s colours by their names:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;White Stripes
Black Ash
Grey Hound
Small White Dot
Orange Pirate
Snow White
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As we know from the internet, the number of cats is enormous. We should do it in a massively parallel fashion!&lt;/p&gt;

&lt;p&gt;First, fire up the cluster. Official documentation described the process well enough. Let nodes live locally at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CLUSTERDIR/{dev1,dev2,dev3}&lt;/code&gt;. And the Riak libraries needed to build our extension are located at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$SDKDIR&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To check cluster, run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;riak-admin member-status&lt;/code&gt; for the cluster membership and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;riak-admin ring-status&lt;/code&gt; for the state of the ring.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;riak-admin member-status
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;================================= Membership ========
Status     Ring    Pending    Node
-----------------------------------------------------
valid      34.4%      --      dev1@127.0.0.1
valid      32.8%      --      dev2@127.0.0.1
valid      32.8%      --      dev3@127.0.0.1
-----------------------------------------------------
Valid:3 / Leaving:0 / Exiting:0 / Joining:0 / Down:0
ok
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The interesting thing is that each VNode is implemented as an Erlang process, specifically a finite state machine with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gen_fsm&lt;/code&gt; behaviour. For example, my three-node cluster has 64 partitions and 64 vnodes. Each physical node will get 21 or 22 vnodes (approximately 33% of all partitions). More than that, a lot of operations for node (storage maintenance, handoff) are also implemented as processes. Since they are really lightweight, running such an amount of processes is not a problem.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;% riak-admin ring-status
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;================================== Claimant ========
Claimant:  &apos;dev1@127.0.0.1&apos;
Status:     up
Ring Ready: true

============================== Ownership Handoff ===
No pending changes.

============================== Unreachable Nodes ===
All nodes are up and reachable

ok
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Write some code in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;colorer.erl&lt;/code&gt; for loading cats to the database (function &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;load/1&lt;/code&gt;), for map phase (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;colormap/3&lt;/code&gt;) and for reduce phase (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;coloreduce/2&lt;/code&gt;).&lt;/p&gt;

&lt;div class=&quot;language-erlang highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;ni&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;colorer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;ni&quot;&gt;export&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]).&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;ni&quot;&gt;export&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;colormap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]).&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;ni&quot;&gt;export&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coloreduce&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]).&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;ni&quot;&gt;export&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]).&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;%% Determine a cat color from its name
&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get_color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;NameTokens&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tokens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;lists&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;White&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;NameTokens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;White&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;lists&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;member&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Black&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;NameTokens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;
                    &lt;span class=&quot;s&quot;&gt;&quot;Black&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;
                    &lt;span class=&quot;s&quot;&gt;&quot;Mixed&quot;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;%% map phase
&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;colormap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Keydata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;ObjKey&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;riak_object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;KeyStr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;binary_to_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ObjKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;Color&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;get_color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;KeyStr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;nn&quot;&gt;error_logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;info_msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;colorer mapped key &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;~p&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; to &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;~p&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;KeyStr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}].&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;%% map reduce
&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;coloreduce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nn&quot;&gt;error_logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;info_msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;colorer reduce list &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;~p&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;ListOfDicts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;I&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;Merged&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
        &lt;span class=&quot;nn&quot;&gt;lists&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;foldl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;Acc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;merge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(_,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;X&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;Y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;X&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;Y&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;Item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;Acc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;nn&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
                    &lt;span class=&quot;nv&quot;&gt;ListOfDicts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;% convert dict to list
&lt;/span&gt;    &lt;span class=&quot;nn&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Merged&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;%% End of list
&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(_&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[])&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;%% Put a cat from head of list to the Riak
&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Head&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;Tail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;% All cats are adorable, so it goes to the Riak value as a JSON
&lt;/span&gt;    &lt;span class=&quot;nv&quot;&gt;Body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;list_to_binary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;mochijson2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;adorable&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})),&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;% Riak key is a cat name
&lt;/span&gt;    &lt;span class=&quot;nv&quot;&gt;Key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;Head&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;RawObj&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;riakc_obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cats&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;Key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;Body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;Obj&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;riakc_obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update_content_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;RawObj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;riakc_pb_socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;Obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ok&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nn&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Inserted key &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;~p~n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Head&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nn&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Error inserting key &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;~p&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;Tail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;%% Load cats from text file
&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;Client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;riakc_pb_socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start_link&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;127.0.0.1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10017&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;Lines&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;binary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;Lines&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;put-cats-in-a-bucket&quot;&gt;Put cats in a bucket&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/cats-in-bucket.jpg&quot; alt=&quot;Cats in bucket&quot; /&gt;&lt;/p&gt;

&lt;p&gt;First things first, load all our cats into Riak.&lt;/p&gt;

&lt;p&gt;Compile the file with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;erlc colorer.erl&lt;/code&gt;. It produces a BEAM file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;colorer.beam&lt;/code&gt; in the current directory. Then run the loader:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;erl &lt;span class=&quot;nt&quot;&gt;-pa&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$SDKDIR&lt;/span&gt;/lib/riakc/ebin &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;$SDKDIR&lt;/span&gt;/lib/riak_pb/ebin &lt;span class=&quot;nv&quot;&gt;$SDKDIR&lt;/span&gt;/lib/mochiweb/ebin &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-noshell&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-eval&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;colorer:load(&quot;cats.txt&quot;), halt().&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Since we connect to the Riak cluster from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;load/1&lt;/code&gt; function by Protobuf protocol, we can start the standalone virtual machine, not the one which Riak uses.&lt;/p&gt;

&lt;p&gt;Now check the bucket &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cats&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;http http://localhost:10018/buckets/cats/keys &lt;span class=&quot;nv&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;HTTP/1.1 200 OK
Content-Type: application/json
Server: MochiWeb/3.0.0 WebMachine/1.11.1 (greased slide to failure)
Vary: Accept-Encoding

{
    &quot;keys&quot;: [
        &quot;Snow White&quot;,
        &quot;White Stripes&quot;,
        &quot;Grey Hound&quot;,
        &quot;Small White Dot&quot;,
        &quot;Black Ash&quot;,
        &quot;Orange Pirate&quot;
    ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Check a random cat:&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;http http://localhost:10018/buckets/cats/keys/Snow%20White
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;pre&gt;&lt;code class=&quot;language-plain&quot;&gt;HTTP/1.1 200 OK
Link: &amp;lt;/buckets/cats&amp;gt;; rel=&quot;up&quot;
Server: MochiWeb/3.0.0 WebMachine/1.11.1 (greased slide to failure)
Vary: Accept-Encoding
X-Riak-Vclock: a85hYGBgzGDKBVI8R4M2ckekvudgYIyfmMGUyJnHyjDl29a7fFkA

{
    &quot;adorable&quot;: true
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Great, all our cats are in a bucket.&lt;/p&gt;

&lt;h2 id=&quot;running-mapreduce-job&quot;&gt;Running MapReduce job&lt;/h2&gt;

&lt;p&gt;All Riak nodes must be able to load the code &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;colorer.beam&lt;/code&gt;. Add the path to this directory to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;riak/etc/advanced.config&lt;/code&gt; for each node and restart the cluster:&lt;/p&gt;

&lt;div class=&quot;language-erlang highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;riak_kv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_paths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;YOURDIR&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]}&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;]},&lt;/span&gt;

 &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;riak_core&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Each time you recompile the file, the cluster must reload the BEAM file. Since Erlang uses hot reloading, it could be achieved without restarting the cluster!&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;erlc colorer.erl &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;N &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;1 2 3 &lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CLUSTERDIR&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;/dev&lt;span class=&quot;nv&quot;&gt;$N&lt;/span&gt;/riak/bin/riak-admin erl-reload &lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we can ask Riak to launch a job like it was in the JavaScript example.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{&quot;inputs&quot;:&quot;cats&quot;, &quot;query&quot;:[
  {&quot;map&quot;:{&quot;language&quot;:&quot;erlang&quot;,&quot;module&quot;:&quot;colorer&quot;,&quot;function&quot;:&quot;colormap&quot;}},
  {&quot;reduce&quot;:{&quot;language&quot;:&quot;erlang&quot;,&quot;module&quot;:&quot;colorer&quot;,&quot;function&quot;:&quot;coloreduce&quot;}}]}&apos;&lt;/span&gt; | http POST localhost:10018/mapred
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And we got the answer&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Black&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Mixed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;White&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So we just run a distributed colorer job on our cluster!&lt;/p&gt;

&lt;p&gt;Let’s explore what is going on inside. Actually, you can test map and reduce functions from REPL with manually specified inputs. But for actual remote invocations let’s log inputs&lt;/p&gt;

&lt;div class=&quot;language-erlang highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;error_logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;info_msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;colorer mapped key &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;~p&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; to &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;~p&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;KeyStr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]).&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Output can be seen in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CLUSTER_DIR/dev*/riak/log/console.log&lt;/code&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;colorer mapped key &quot;Black Ash&quot; to &quot;Black&quot;&lt;/code&gt;. So the mapper produces a list of tuples with the cat’s colour. We produce a string with a colour name (actually it is an Erlang binary) for each cat and initial count as one. Value is not analysed for simplicity but can be easily extracted with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;riak_object:get_value(Value)&lt;/code&gt; and converted to dict using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mochijson2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then all the outputs of the map phase are passed to reduce phase (remember, we specified the function and module name in HTTP invocation). The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;coloreduce/2&lt;/code&gt; gets the list of all tuples: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;colorer reduce list [{&quot;Black&quot;,1},{&quot;Mixed&quot;,1},{&quot;White&quot;,1},{&quot;White&quot;,1},{&quot;White&quot;,1},{&quot;Mixed&quot;,1}]&lt;/code&gt;. Then it converts them to dicts and folds in a resulting dict:&lt;/p&gt;
&lt;div class=&quot;language-erlang highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Black&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Mixed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;White&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So the function returns a list of dicts. Riak produces the MapReduce output as JSON. Outputs and inputs between stages are still binary-serialized Erlang objects.&lt;/p&gt;

&lt;p&gt;It isn’t difficult at all.&lt;/p&gt;

&lt;h2 id=&quot;bitcask&quot;&gt;Bitcask&lt;/h2&gt;

&lt;p&gt;Riak has pluggable backend architecture. The default storage engine is &lt;a href=&quot;https://docs.riak.com/riak/kv/2.2.3/setup/planning/backend/bitcask/index.html&quot;&gt;Bitcask&lt;/a&gt;. Another widely-used backend is a customised version of &lt;a href=&quot;https://github.com/google/leveldb&quot;&gt;LevelDB&lt;/a&gt; which is an LSM-based (log-structured merge tree) key-value store. The main reason to prefer LevelDB to Bitcask is having a lot of keys that could not fit into the node’s memory because Bitcask requires the entire keyspace to be in memory.&lt;/p&gt;

&lt;p&gt;A lot of literature is dedicated to LSM engines. I would like to describe Bitcask architecture a bit (pun intended).&lt;/p&gt;

&lt;p&gt;This famous storage engine is described in a really short &lt;a href=&quot;https://riak.com/assets/bitcask-intro.pdf&quot;&gt;paper&lt;/a&gt; that is worth reading. The Bitcask is a key-value store. The original design covers on-disk format, data flow and access protocols and the Erlang application. Some parts could remind the LSM but it is a much simpler engine.&lt;/p&gt;

&lt;p&gt;A database node handles a directory of data files exclusively. The directory has multiple datafiles, and the most recent one is called an active file. A node can only append to the active file. All other files are read-only. So all the writes are sequential and therefore blazing fast. When the file has grown over the limit, it is closed and another one is created as an active file.&lt;/p&gt;

&lt;p&gt;The structure of key/value entry is simple. It is a variable-sized record with sizes of key and value specified in a header. The whole record is checksummed.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;0           4           8            16           24
+-----------+-----------+------------+------------+--------+--------+
|   CRC     | TIMESTAMP | KEY_SIZE   | VALUE_SIZE |  KEY   | VALUE  |
+-----------+-----------+------------+------------+--------+--------+
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Bitcask requires maintaining an in-memory structure called “keydir”. It stores a mapping from each key to a location information: file path, offset in a file to the corresponding entry, and the most recent timestamp.&lt;/p&gt;

&lt;p&gt;Reads are simple. Node checks keydir in a constant time. Then it opens a file (if not opened yet) where the record is stored and seeks to the specified offset. Since size information is stored in an entry header, the node reads &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;value_size&lt;/code&gt; bytes containing a value. The total cost of a read operation is a single random disk seek.&lt;/p&gt;

&lt;p&gt;To perform a write, node just appends a new entry to the active file and updates information in keydir. If the key is already present, its previous apperance in data files became orphaned (no links to them). The cost of a write operation is a sequential write to disk without any seeks.&lt;/p&gt;

&lt;p&gt;To prune orphaned records, Bitcask uses merging. It is much simpler than LSM merges. Bitcask knows all the keys because they are stored in keydir. It needs to iterate over all directory files and get only the latest entry for each key. Then a new file is written with only the latest entries for each key. Old files can be removed. Merge is completed.&lt;/p&gt;

&lt;p&gt;The major advantage and both disadvantage of Bitcask is the keydir. It must store all the keys. If the keyspace is larger than available memory, it makes no sense to store keys in a hybrid manner (in memory and disk), and an LSM-based engine should be preferred. Also, when a node is started, it must populate a keydir with all keys from all files (not only an active file). It can be optimised by using hint files.&lt;/p&gt;

&lt;p&gt;The interesting thing in Bitcask is the lack of a commit log or WAL. Data files &lt;em&gt;are&lt;/em&gt; the log itself. Concurrency is not described in the paper and is addressed only in the implementation.&lt;/p&gt;

&lt;h2 id=&quot;my-implementation&quot;&gt;My implementation&lt;/h2&gt;

&lt;p&gt;Speaking of implementation, I stumbled upon a really fun project &lt;a href=&quot;https://github.com/avinassh/py-caskdb&quot;&gt;CaskDB&lt;/a&gt;. It is a skeleton for designing your own Bitcask store in Python. A lot of tests are already written.&lt;/p&gt;

&lt;p&gt;I have written an extremely simple storage engine. Concurrency and network protocol are out of scope. Surprisingly, it was a really fun project to code Bitcask in modern Python for a few evenings. If by any chance you are interested, it is &lt;a href=&quot;https://github.com/theirix/py-caskdb&quot;&gt;on my GitHub&lt;/a&gt;. I made a few additions compared to the original task, primarily in tests. I have used property-based testing with &lt;a href=&quot;https://hypothesis.readthedocs.io&quot;&gt;Hypothesis&lt;/a&gt; which helped to pinpoint hard-to-find bugs. I think it is an essential technique for data engines. Other useful techniques that should be used are fuzzing and secure coding with invariants. Of course, a whole class of problems can be avoided by using more safe languages with static typing like Rust, but it will be another project. Stay tuned!&lt;/p&gt;

&lt;h2 id=&quot;outcome&quot;&gt;Outcome&lt;/h2&gt;

&lt;p&gt;Implementing Bitcask storage engine helps to understand why the engine works, where it shines and what the downsides are. Definitely worth your Netflix weekend:)&lt;/p&gt;

&lt;p&gt;The Riak is a little outdated, and 15 years seems like an eternity in the database world. But it is a pure elixir (pun intended) of design approaches for leaderless replication, tunable consistency, causal contexts, CRDTs, effective storage engines and another important concept. Also, it is a beautiful example of a well-architected OTP application. Long live Riak!&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Learning by outdated books</title>
   <link href="https://omniverse.ru/blog/2023/02/22/outdated-books/"/>
   <updated>2023-02-22T00:00:00+00:00</updated>
   <id>https://omniverse.ru/blog/2023/02/22/outdated-books</id>
   <content type="html">&lt;p&gt;Learning new things is hard, and finding the right way to do that is even more complicated. Since academia, I prefer books to get a systematic introduction to a subject. The first chapters of a book, as well as per-chapter and book conclusions, are the most valuable parts to skim through. It usually works well with more theoretical material. For example, I dug into books about distributed systems and storage architectures. Usually, they are peer-reviewed, thick and stand a couple of editions. The theoretical foundations for those systems rarely change.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/bookshelf.jpg&quot; alt=&quot;bookshelf&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(not my actual bookshelf, but close enough, CC0)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There is a different story for more practical books. If you are going to read a book about a specific technology, you have a great chance to stumble upon an older book written a few years ago. The pace of technology is very high nowadays, and that book could be outdated soon after being published. I assume an attentive reader should follow the book and try doing the described topics by oneself, even if exercises and follow-ups are made-up. The first idea is to drop it and start learning from more up-to-date documentation. But wait! Learning from outdated books is a perfect chance to master technology. You would stop taking written as granted and begin to find out why the subsystems or approaches were deprecated or replaced with others. It helps to understand the rationale behind the outdated system design, its advantages and defects, and why the new system is superior to the former. You will dig into documentation more often rather than copy-pasting the code from the book.&lt;/p&gt;

&lt;p&gt;And to be honest, everyone has a bookshelf with books that were bought a long time ago just to read them someday. So outdated books are helpful. Use them to your advantage.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.linkedin.com/posts/eseliverstov_learning-books-activity-7033408027890192384-_0mu&quot;&gt;Crossposted from my LinkedIn&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Sad story about modems and routers</title>
   <link href="https://omniverse.ru/blog/2021/09/13/lte-modem/"/>
   <updated>2021-09-13T00:00:00+00:00</updated>
   <id>https://omniverse.ru/blog/2021/09/13/lte-modem</id>
   <content type="html">&lt;h2 id=&quot;need-for-broadband&quot;&gt;Need for broadband&lt;/h2&gt;

&lt;p&gt;We are so used to have broadband and mobile internet that we take it for granted. This year we escaped to the summer house from a city, heat and pandemic. One of the most important things for us is a good and fast connection.&lt;/p&gt;

&lt;p&gt;We have several internet usage scenarios:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;casual browsing&lt;/li&gt;
  &lt;li&gt;watching streaming services&lt;/li&gt;
  &lt;li&gt;listening to the home music library&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I was surprised how easily first the two scenarios can be accomplished even with a crappy internet connection. Browser caching and progressive loading work very nice. YouTube and streaming services can degrade video quality smoothly and buffer minutes of video without hiccups. But when you require a consistent and steady connection, problems arise.&lt;/p&gt;

&lt;p&gt;Our house is equipped with a Wi-Fi router connected via a LoRa radio link to the base station which is connected to the ISP by an optic or copper channel. The problem here is a very limited bandwidth of the ISP, the download rate rarely exceeds 3 Mbps.&lt;/p&gt;

&lt;p&gt;Okay, there are a lot of 3G/LTE USB dongles that can be plugged into a router and provide a steady LTE connection. Without hesitation, I got one from my mobile ISP with a prepaid plan. While it flawlessly works with a Windows laptop after plugging into a USB port, making it work with a Wi-Fi router took a while.&lt;/p&gt;

&lt;h2 id=&quot;zte-modem&quot;&gt;ZTE modem&lt;/h2&gt;

&lt;p&gt;The dongle appeared to be a branded ZTE 8810FT device that was sold by MTS ISP (VID 0x19d2, PID 0x1225). It can work as an usb-to-ethernet device or as a modem device. Internally this dongle works on an Android OS and has special drivers for radio and USB networking. Also, it has a simple web interface for configuring basic settings and working with SMS and USSD calls.&lt;/p&gt;

&lt;p&gt;What is the difference between those modes? In an ethernet mode, a modem device should work without drivers with any system supporting a generic RNDIS or CDC device. On Linux, you will have a CDC ethernet device (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cdc_ether&lt;/code&gt;), on Windows most likely an RNDIS device. In a modem mode, a dongle provides a modem device via a COM port that can be used to send AT commands for dialling to the mobile network. Configuring a modem connection is performed by NCM, QMI or MBIM protocols. You will have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/ttyUSB0&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/cdc-wdm0&lt;/code&gt; on successful configuration.&lt;/p&gt;

&lt;p&gt;Besides Windows, you are out of luck. On Mac, you need a special driver even for an ethernet mode. Because providing a driver for a USB device requires another USB device (it increases a package cost) or internet access (to download drivers for a modem), the dongle uses a trick. A dongle announces itself as a composite USB device including mass storage. So the first time the host system does not have network drivers for a dongle. A USB disk device appears. A user runs an installer from this disk and installs a driver for usb-to-ethernet. Next time a plugged-in device is detected as an Ethernet device and you get a DHCP-provided IP address. Unfortunately, this trick works extremely bad with headless hosts like a router or a server. It is extremely unreliable with Mac or Windows after touching any part of this fragile system (like removing an Ethernet adapter from the system).&lt;/p&gt;

&lt;p&gt;My experiments with ZTE dongle and Linux systems show that the dongle just does not work on Linux. I had tried a laptop with a Debian Buster as well as a TP-LINK TL-WR842N v5 router with an OpenWRT 17 and 21. The dongle can enter an Ethernet mode (ethernet interface is created) but it does not provide a connection to a host system. Entering a modem mode is much more complicated. The dongle should create COM ports only after switching to serial mode (i.e. debug mode) which is not persisted across device reboots. But even before reboot COM ports are non-functioning and do not accept any AT commands on any available OS. A special factory mode that exists for other dongles is not available for this device. So both modes do not work without a special driver that does not exist for Linux.&lt;/p&gt;

&lt;p&gt;Amusingly, switching between modes can be performed by nvram tweaks as well as by requesting a web server for a special URL. Hello, CSRF!&lt;/p&gt;

&lt;p&gt;Unfortunately, my experiments with ZTE were ended early because this device just bricked without any special efforts from me. Host systems cannot see the USB device. Looks like a read/write filesystem on the device can be damaged due to mode switching, or it is just a faulty device. ISP employee told me I am the third person in a day returning this modem. I doubt all customers are tinkering with firmware so much. So if you can, avoid ZTE 8800FT, especially if you need non-Windows support.&lt;/p&gt;

&lt;h2 id=&quot;huawei-modem&quot;&gt;Huawei modem&lt;/h2&gt;

&lt;p&gt;I purchased another well-known modem Huawei E3372h-320 (VID 0x12d1, PID 0x1f01). It has a usb-to-ethernet mode known as HiLink-mode after the firmware name. There was a more versatile 153 model that has two modes (modem and usb-to-ethernet) but it is out of production.&lt;/p&gt;

&lt;p&gt;This new modem was recognized in macOS and Windows after installing vendor-provided drivers. A stock router firmware did not recognize the modem. OpenWRT 21.02 works fine after installing drivers:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;   opkg install kmod-usb-net-cdc-ether kmod-usb-net-huawei-cdc-ncm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then I just configured a bunch of OpenWRT settings and got a new ethernet device with a DHCP-provided IP address.&lt;/p&gt;

&lt;p&gt;It would be a happy ending to the story but connections were extremely unstable.
A connection to the router via Wi-Fi and Ethernet so the LAN is ok. Let’s check the WAN. Sadly, I can use only the web interface to the router which can only display basic instant metrics. It is awkward. So I decided to monitor metrics from the modem continuously.&lt;/p&gt;

&lt;h2 id=&quot;building-a-monitoring-system-on-influxdb&quot;&gt;Building a monitoring system on InfluxDB&lt;/h2&gt;

&lt;p&gt;To get metrics programmatically I found a few HTTP endpoints on the modem’s web server that provide data for displaying in a HiLink web interface. It was trivial to get a session cookie because Hilink does not require any authorization. Then I can fetch all data, normalize and store them into a time-series database. Because I gather data periodically, I need to push metrics into a database.&lt;/p&gt;

&lt;p&gt;A push-based approach dismissed Prometheus and Grafana which are well-known instruments for visualizing metrics. So I tried InfluxDB.
A version 2.0 provides a nice dashboard (ex-Telegraph, I think) which is more than enough to draw plots. Versatile queries allow me to calculate statistics over sliding windows.&lt;/p&gt;

&lt;p&gt;I prefer SQL-based query languages like InfluxQL in older versions of InfluxDB. The second version provides a new javascript-based language Flux. It is easy to start with and fun to use but sometimes plain old SQL could be enough.&lt;/p&gt;

&lt;p&gt;What I dislike in InfluxDB 2.0 is a very glitchy web interface for editing queries which sometimes lost updates from the query editor. I gave up, fired a Vim and started sending queries via API:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl -X POST &apos;http://localhost:8086/api/v2/query?org=hilinkmon-org&apos; \
  --header &quot;Authorization: Token $(sed &apos;/INFLUXDB/!d;s/.*TOKEN=//&apos; .env)&quot; \
  --header &apos;Accept: application/csv&apos; \
  --header &apos;Content-type: application/vnd.flux&apos; \
  --data &quot;$(cat test.flux)&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;One more disturbing issue is the inequality of table and scalar values in UI. One cannot simply display a scalar value in UI without &lt;a href=&quot;https://docs.influxdata.com/influxdb/cloud/query-data/flux/scalar-values/#current-limitations&quot;&gt;wrapping it into table&lt;/a&gt;. Hope it will be addressed in future versions of UI but for now you are out for using API calls.&lt;/p&gt;

&lt;p&gt;The monitoring application &lt;a href=&quot;https://github.com/theirix/hilinkmon&quot;&gt;hilinkmon&lt;/a&gt; I wrote is available on GitHub. It is easy to set up via provided Docker compose. Hope it helps somebody with debugging LTE issues.&lt;/p&gt;

&lt;p&gt;The repository includes a predefined dashboard for monitoring with key metrics for LTE connections: RSSI, RSRP, SINR and CQI.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/hilinkmon-dashboard.png&quot; alt=&quot;hilinkmon dashboard&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;analyzing-metrics&quot;&gt;Analyzing metrics&lt;/h2&gt;

&lt;p&gt;Okay, I can gather a lot of metrics now. Let’s describe what those acronyms stay for:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;RSSI (Received Signal Strength Indicator) is signal strength in dBm. It is used in a bar indicator.&lt;/li&gt;
  &lt;li&gt;RSRP (Reference Signal Received Power) has a close meaning to RSSI.&lt;/li&gt;
  &lt;li&gt;SINR (Signal Interference Noise Ratio) is a signal to noise ratio. Simple and easy to understand.&lt;/li&gt;
  &lt;li&gt;CQI (Channel Quality Indication) is a discrete class of quality that specify a modulation scheme and code rate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Different network generations like 3G and LTE use those metrics but physical interpretation differs. All metrics listed above are better with higher values. Consult Internets for proper ranges of each metric. What is more important is to check metric dynamics as you tune your system.&lt;/p&gt;

&lt;p&gt;Why do you need to look for all those metrics? Maximal RSSI does not always mean the best connection. You will get a bad connection if the modem is too optimistic and prefer to pick a higher channel (large CQI is chosen by user equipment by checking SINR) while the signal (SINR and RSSI) is unstable. Looks like Huawei modems do exactly this. By moving the modem around I tend to maximize RSSI while keeping SINR stable to avoid rapid channel jumps.&lt;/p&gt;

&lt;p&gt;With a help of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hilinkmon&lt;/code&gt;, I figured out that my router makes a lot of radio noise to the plugged-in modem. So I wrapped a Wi-Fi router with a foil to make a shield between an LTE modem and router. Only Wi-Fi antennas and vent holes stay open. Things got much better. And hot because it was July. So I switched from a foil shield to an air shield.&lt;/p&gt;

&lt;p&gt;A simple USB cable could not work because my modem has a weak PSU and a cable is long so the signal fades. So I built a weird construction with an additional power brick and USB cable splitter. The connection was the best when a dongle was at a precise position hold by rubber bands. No photo of a foil shield and the optimized  position, sorry. Check a photo of a generic setup.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/lte-cables.jpg&quot; alt=&quot;lte cables&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When I noticed a frequent switching between 3G and LTE on a graph, I quickly disabled 3G in the modem settings. Then metrics stabilized.&lt;/p&gt;

&lt;h2 id=&quot;problems-with-a-router&quot;&gt;Problems with a router&lt;/h2&gt;

&lt;p&gt;After all those tweaks I noticed frequent freezes in the Internet connection. Connection from the router to the Internet was ok, connection to the router was ok but the Internet connection from the Wi-Fi client interrupted up to a minute. Nothing in logs, temperature sensors or metrics.&lt;/p&gt;

&lt;p&gt;It can be reproduced on OpenWRT 21.02 (last release candidate) by a permanent load like copying files over a VPN connection. No multiple connections, additional Wi-Fi clients, even a simple client can trigger a freeze.&lt;/p&gt;

&lt;p&gt;When I almost lost hope, I flashed a beta firmware for TL-WR842N which added support for a Huawei E3372h modem. And it helps!&lt;/p&gt;

&lt;p&gt;Of course, I lost a lot of cool OpenWRT features but all freezes disappeared at all. A lot of threads about freezes can be found on OpenWRT forums without proper solutions. Looks like there is a problem in the firmware itself. Maybe this problem is somehow caused by a weak CPU. I did not expect that a super-stable OpenWRT can freeze in a strange unpredictable way. Based on my experience, it is much more likely to see it in DD-WRT firmware betas.&lt;/p&gt;

&lt;p&gt;Regarding the stock firmware, I don’t know why adding support for a simple ethernet-based modem from 2014 took so much time for TP-Link (remember, it is still beta). As I understand from OpenWRT, there is no need to use a modem mode. Just add a few lines with USB PIDs/VIDs so an USB device is properly detected.&lt;/p&gt;

&lt;h2 id=&quot;outcomes&quot;&gt;Outcomes&lt;/h2&gt;

&lt;p&gt;It was a long journey to a stable and fast connection, and I achieved it only at the end of my vacation. It is a sad story but what did I learn from it?&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Never buy hardware that is not confirmed to work on your operating system. Confirmed means a reported success story in a forum or an explicit statement from a vendor.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Sometimes even a super-stable open-source firmware like OpenWRT can experience a strange bug.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Observability is a key engineering principle. Always measure system output before you change something.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Turn problems into a chance to learn something new. Like digging into USB mode-switching and drivers, reverse-engineering a strange android box, learning about cellular networks and trying a new time-series database. At least make it fun.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>Talk about Conan and Python at Russian Python Week 2020</title>
   <link href="https://omniverse.ru/blog/2020/09/11/rpw-pyconan/"/>
   <updated>2020-09-11T00:00:00+00:00</updated>
   <id>https://omniverse.ru/blog/2020/09/11/rpw-pyconan</id>
   <content type="html">&lt;p&gt;I will be giving a &lt;a href=&quot;https://conf.python.ru/moscow/2020/abstracts/7113&quot;&gt;talk about Conan&lt;/a&gt; next Tuesday at Russian Python Week 2020 conference.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/rpw2020-logo.png&quot; alt=&quot;russian python week 2025&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We will discuss C++, Conan and the role of Python in Conan package manager development
and open-source infrastructure.&lt;/p&gt;

&lt;p&gt;This year the conference goes online so the talk format is a cosy Zoom room.
Please join us if you are interested in those topics!&lt;/p&gt;

&lt;p&gt;The talk will be in Russian.&lt;/p&gt;

&lt;p&gt;UPDATED:
Slides &lt;a href=&quot;/assets/pyconan-rpw2020-v1.2.pdf&quot;&gt;&lt;img src=&quot;/assets/pdf-icon.gif&quot; alt=&quot;pdf&quot; /&gt; pdf&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Fix apt-get networking problem on MIPS</title>
   <link href="https://omniverse.ru/blog/2016/08/25/apt-get-mips/"/>
   <updated>2016-08-25T00:00:00+00:00</updated>
   <id>https://omniverse.ru/blog/2016/08/25/apt-get-mips</id>
   <content type="html">&lt;p&gt;If you are building a chroot with Debian MIPS (sid, Aug 2016) or other non-primary architectures you can get a strange message during a chroot creating:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Err:1 http://httpredir.debian.org/debian sid InRelease
  Temporary failure resolving &apos;httpredir.debian.org&apos;
Reading package lists... Done
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It happens because IPv6 works bad or does not work on MIPS so just add a hook &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/cache/pbuilder/hooks/G90forceIPv4&lt;/code&gt; with the following content:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#!/bin/sh
echo &apos;Acquire::ForceIPv4 &quot;True&quot;;&apos; &amp;gt; /etc/apt/apt.conf.d/80forceIPv4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Optimize Local Guides</title>
   <link href="https://omniverse.ru/blog/2016/03/16/optimize-local-guide/"/>
   <updated>2016-03-16T00:00:00+00:00</updated>
   <id>https://omniverse.ru/blog/2016/03/16/optimize-local-guide</id>
   <content type="html">&lt;h2 id=&quot;what-is-local-guides&quot;&gt;What is Local Guides?&lt;/h2&gt;

&lt;p&gt;Usually I contribute to openstreetmap.org with non-commercial objects, transportation and fixing vector maps.
They use a wonderful new &lt;a href=&quot;https://www.mapbox.com/blog/new-map-editor-launches-openstreetmap/&quot;&gt;iD Editor&lt;/a&gt; that allows you to quickly edit Maps online without offline editor such as Josm.
I tried to do something useful with Google Maps because they were broken at Moscow at many aspects.
Recently Google provided an excellent instrument to boost contributing to Google Maps
called &lt;a href=&quot;https://www.google.com/local/guides/&quot;&gt;Local Guides&lt;/a&gt;. Gamification and awarding contributors seems
to be a vital idea.&lt;/p&gt;

&lt;p&gt;There are points given to you for adding a review, photo, rating to existing place, editing missing information or adding a missing place. There are five levels with a few benefits given to contributor at each level. The most attractive one is a one terabyte of Drive storage for two years (4th level). An award for the 5th level is not so useful for non-US citizens. So why not?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/drive-storage.png&quot; alt=&quot;drive storage&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;automate-everything&quot;&gt;Automate everything&lt;/h2&gt;

&lt;p&gt;I decided to automate tedious tasks of finding places with incomplete information. So I wrote a little Python script using &lt;a href=&quot;https://github.com/googlemaps/google-maps-services-python&quot;&gt;Google Maps API Web Services&lt;/a&gt; python library and published it to the GitHub - &lt;a href=&quot;https://github.com/theirix/google-incomplete-places&quot;&gt;google-incomplete-places&lt;/a&gt;. The script uses API for server applications. Provide a server key to specific location, replace a query type in source code and launch it. Free API is limited for several thousands of requests per day. Do not expect to run your app all day long. I had became tired long before a daily API quota had exceeded.&lt;/p&gt;

&lt;p&gt;A good workflow is to open two web browsers and a console at two monitors, click for the next place and search for a place while the edit page is loading (it is not very fast).&lt;/p&gt;

&lt;p&gt;Hope it helps you to accelerate a search for incomplete places and gain new rewards.
This approach helped to find almost two thousands of broken places such as vet clinics, bike stores, banks, drugstores and hospitals. Now the problem is what to do with one terabyte of data!&lt;/p&gt;

&lt;h2 id=&quot;how-the-script-works&quot;&gt;How the script works?&lt;/h2&gt;

&lt;p&gt;The script searches a given area for a specific place type using
&lt;a href=&quot;https://developers.google.com/places/web-service/search#PlaceSearchRequests&quot;&gt;Places Search&lt;/a&gt;. 
Search requires a &lt;a href=&quot;https://developers.google.com/places/supported_types&quot;&gt;place type&lt;/a&gt;
and a query string (I used a place type here).
Then we get a list of Place ids. Unfortunately they are not fully populated with details so it is needed to query &lt;a href=&quot;https://developers.google.com/places/web-service/details#PlaceDetailsRequests&quot;&gt;Place Details&lt;/a&gt; for each found place. Usually a website or opening hours are missing and the script looks for them. Then it composes a handy URL that leads you directly to the edit page. Places API supports paging but limits number of pages so total amount of results is capped at 60. Be creative to formulate a query to get distinct sets of places!&lt;/p&gt;

&lt;p&gt;Moving parts in the script are query text and place type. Script can be adapted for your place by changing location and language variables.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Spring clean all your git repos</title>
   <link href="https://omniverse.ru/blog/2016/03/03/git-spring-clean/"/>
   <updated>2016-03-03T00:00:00+00:00</updated>
   <id>https://omniverse.ru/blog/2016/03/03/git-spring-clean</id>
   <content type="html">&lt;p&gt;Often we have a lot of git working copies at the machine. They can be old and with a lot unneeded git objects. They could be mass-cleaned to save inodes, optimize speed and lighten backups.&lt;/p&gt;

&lt;p&gt;To ease cleaning let’s write a simple script with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gc&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pack&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prune&lt;/code&gt;. Remember, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gc --aggressive&lt;/code&gt; is &lt;a href=&quot;http://stackoverflow.com/a/28721047&quot;&gt;misleading&lt;/a&gt;.&lt;/p&gt;

&lt;noscript&gt;&lt;pre&gt;400: Invalid request&lt;/pre&gt;&lt;/noscript&gt;
&lt;script src=&quot;https://gist.github.com/65d8fb4566a2dd8d41d2.js&quot;&gt; &lt;/script&gt;

&lt;p&gt;Let’s count how much objects are in all our git repositories:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;(find . -name .git -type d -not -path &apos;*exclude*&apos; | xargs -L1 find ) | wc -l
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then use the script to mass-clean all repositories:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;find . -name .git -not -path &apos;*exclude*&apos; | xargs -L1 -t gitcleanup.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And finally count again with the first one-liner.&lt;/p&gt;

&lt;p&gt;For me this simple spring clean reduces git objects from 38631 to 7050.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>How to make a network quarantine with firewalld</title>
   <link href="https://omniverse.ru/blog/2015/12/17/firewalld-quarantine/"/>
   <updated>2015-12-17T00:00:00+00:00</updated>
   <id>https://omniverse.ru/blog/2015/12/17/firewalld-quarantine</id>
   <content type="html">&lt;p&gt;Surely it is not about reinventing a wheel but a short how-to about commands I cannot remember every time.&lt;/p&gt;

&lt;p&gt;Sometimes it is needed to make a fully quarantined machine without incoming &lt;em&gt;and&lt;/em&gt; outgoing network access, just with
SSH connection from the local network. It can be achieved with a few firewalld / iptables commands.&lt;/p&gt;

&lt;p&gt;RHEL 7 and CentOS 7 switched from a lot of well-known command line tools such as sysvinit, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netstat&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ipconfig&lt;/code&gt; to newer technologies (literally - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;systemctl&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;journalctl&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ss&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ip&lt;/code&gt;). Using iptables directly is not recommended too due to introducing the &lt;a href=&quot;https://fedoraproject.org/wiki/FirewallD&quot;&gt;firewalld&lt;/a&gt;. Best tutorial I found is found at &lt;a href=&quot;https://www.digitalocean.com/community/tutorials/how-to-set-up-a-firewall-using-firewalld-on-centos-7&quot;&gt;Digital Ocean&lt;/a&gt;.
Native firewalld zones do no allow outgoing traffic filtering so it is needed to add “direct” rules which are clearly iptables rules. There are also &lt;a href=&quot;https://fedoraproject.org/wiki/Features/FirewalldRichLanguage&quot;&gt;Rich rules&lt;/a&gt; but I have not tried them yet.&lt;/p&gt;

&lt;p&gt;So let’s assume you are in network &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;192.168.1.0/255&lt;/code&gt;. To filter limit outgoing traffic only to IPv4 SSH connections to local network run as root:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;firewall-cmd --permanent --direct --add-rule ipv4 filter OUTPUT 0 -p tcp -m tcp --sport=22 -s 192.168.1.0/16 -d 192.168.1.0/16 -j ACCEPT
firewall-cmd --permanent --direct --add-rule ipv4 filter OUTPUT 1 -p tcp -m tcp --dport=22 -s 192.168.1.0/16 -d 192.168.1.0/16 -j ACCEPT
firewall-cmd --permanent --direct --add-rule ipv6 filter OUTPUT 98 -j DROP
firewall-cmd --permanent --direct --add-rule ipv4 filter OUTPUT 99 -j DROP
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then reload the firewalld:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;firewall-cmd --reload
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Rules are saved permanently at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/firewalld/direct.xml&lt;/code&gt;. Check the list of loaded rules:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;firewall-cmd --direct --get-all-rules
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To remove all direct rules run:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;firewall-cmd --direct --remove-rules ipv4 filter OUTPUT
firewall-cmd --direct --remove-rules ipv6 filter OUTPUT
firewall-cmd --reload
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>UNIX way to live preview AsciiDoc</title>
   <link href="https://omniverse.ru/blog/2015/06/21/asciidoc-reload/"/>
   <updated>2015-06-21T00:00:00+00:00</updated>
   <id>https://omniverse.ru/blog/2015/06/21/asciidoc-reload</id>
   <content type="html">&lt;p&gt;AsciiDoc is a nice markup language and I am slowly migrating my documents from various flavours
of Markdown to the AsciiDoc.&lt;/p&gt;

&lt;p&gt;For converting I prefer &lt;a href=&quot;http://asciidoctor.org&quot;&gt;AsciiDoctor&lt;/a&gt;. Ruby AsciiDoctor implementation seems more robust and
modern than original Perl implementation. I edit AsciiDoc documents in TextMate with a
&lt;a href=&quot;https://github.com/theirix/AsciiDoc-TextMate-2.tmbundle&quot;&gt;AsciiDoc-TextMate-2&lt;/a&gt; plugin. It is a fork of great plugin by
mattneub and it is slightly modified for my demands (syntax, AsciiDoctor support).&lt;/p&gt;

&lt;p&gt;There are three ways to get a HTML webpage from the document:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Launch a command to get a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;notes.html&lt;/code&gt; file:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; asciidoctor notes.adoc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Use &lt;em&gt;Ctrl+Shift+H&lt;/em&gt; shortcut to regenerate html from the current document.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Use a live reload preview feature.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I love an idea of a live preview and I am using it at my LaTeX workflow.&lt;/p&gt;

&lt;p&gt;Official &lt;a href=&quot;http://asciidoctor.org/docs/editing-asciidoc-with-live-preview/&quot;&gt;documentation&lt;/a&gt; suggests several ways to
track changes in editing document and reload a browser. They seems a little complex and insecure for me (browser
extensions) so I used a simple shell command:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;fswatch -o notes.adoc | xargs -L1 sh -c &quot;asciidoctor notes.adoc &amp;amp;&amp;amp; chrome-cli reload&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Utility &lt;a href=&quot;https://github.com/emcrisostomo/fswatch&quot;&gt;fswatch&lt;/a&gt; is a cross-platform wrapper for file notifications. Each
file change is propagated to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;asciidoctor&lt;/code&gt; command and then to the
&lt;a href=&quot;https://github.com/prasmussen/chrome-cli&quot;&gt;chrome-cli&lt;/a&gt; utility (AppleScript chrome wrapper) that reloads a current tab
emits characters for each file change. That is it. You launch a command in a shell only when you need it and then
terminate with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+C&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The best way may be an active browser reloading from TextMate plugin using chrome-cli command but it is not very portable between browsers. Unfortunately built-in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;open&lt;/code&gt; command (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xdg-open&lt;/code&gt; analogue) does not know about browser internals and cannot reload a tab or even open document in a current tab.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>The tale of automating BibDesk</title>
   <link href="https://omniverse.ru/blog/2015/05/31/bibtex/"/>
   <updated>2015-05-31T00:00:00+00:00</updated>
   <id>https://omniverse.ru/blog/2015/05/31/bibtex</id>
   <content type="html">&lt;p&gt;For organising scientific publications I use a standard LaTeX tool bibtex.&lt;/p&gt;

&lt;h2 id=&quot;bibtex&quot;&gt;bibtex&lt;/h2&gt;

&lt;p&gt;It is wise to switch later to the biblatex which handles UTF-8 better but it is a different story. Bibtex specifies a file format for publications with a lot of standard and custom fields where each field actually is a text key-value pair. For example (wikipedia):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-tex&quot; data-lang=&quot;tex&quot;&gt;@Book&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;hicks2001,
 author    = &quot;von Hicks, III, Michael&quot;,
 title     = &quot;Design of a Carbon Fiber Composite Grid Structure for the GLAST
              Spacecraft Using a Novel Manufacturing Technique&quot;,
 publisher = &quot;Stanford Press&quot;,
 year      =  2001,
 address   = &quot;Palo Alto&quot;,
 edition   = &quot;1st&quot;,
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can edit bibtex file by hands but there are some good programs to present and edit publications such as &lt;a href=&quot;http://jabref.sourceforge.net/&quot;&gt;JabRef&lt;/a&gt;, &lt;a href=&quot;http://mendeley.com&quot;&gt;Mendeley&lt;/a&gt; and &lt;a href=&quot;http://bibdesk.sourceforge.net/&quot;&gt;BibDesk&lt;/a&gt;. I use BibDesk because of its good user interface and integration to OS X.&lt;/p&gt;

&lt;p&gt;I am using a lot of specific bibtex tasks for my researches. For example, I often need to find bibtex publication id or title by filename and vice versa, grep citations, set PDF title and author fields from a publication and publishing missing publications to my Kindle via Calibre - a lot of small tasks that require reasonable amount of time and need to be automated. And I prefer a command line utility for these tasks. It accepts a command verb and optional argument and provides a list of strings as an output. Unix way rocks.&lt;/p&gt;

&lt;h2 id=&quot;scripting-bibdesk&quot;&gt;Scripting BibDesk&lt;/h2&gt;

&lt;p&gt;A few years ago I found an exhausted AppleScript support for BibDesk. I could write a script for each of these tasks. Anyone who ever wrote an apple script could understand a complexity of writing a complex data processing applescript. I wanted to wrote a script in a more friendly language, effectively any other language, preferable Ruby or Python or plain C or Java.&lt;/p&gt;

&lt;h3 id=&quot;scripting-bridge-version&quot;&gt;Scripting Bridge version&lt;/h3&gt;
&lt;p&gt;First version of script was written in “Scripting Bridge”. But it stopped working when required MacRuby died. MacRuby was needed because Scripting Bridge is based on Cocoa and the only good way to use Cocoa from Ruby is using MacRuby. MacRuby development halted in 2012.&lt;/p&gt;

&lt;h3 id=&quot;appscript-version&quot;&gt;Appscript version&lt;/h3&gt;
&lt;p&gt;Second version was rewritten in (ruby appscript)[http://appscript.sourceforge.net/rb-appscript/]. Script was nice except the part where each domain object needs to be manually extracted from the scripting object using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.get&lt;/code&gt; call. Here is the example for task providing files by citation string:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;files_for_cite&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cite_str&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;BibDesk&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;documents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;publications&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pub&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cite_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cite_str&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;linked_files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;compact&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;It uses a typical pseudo-functional Ruby chained call that can filter, map and zip things. Then in 2014 I realised that script does not work in Mavericks and Yosemite. Appscript simply does not builds here due to missing symbols. &lt;a href=&quot;http://appscript.sourceforge.net/&quot;&gt;Official page&lt;/a&gt; says it is dead too.&lt;/p&gt;

&lt;h3 id=&quot;swift-version&quot;&gt;Swift version&lt;/h3&gt;
&lt;p&gt;A week ago I made a third version of the script. Currently Apple Script supports binding to Objective C, JavaScript using Scripting Bridge. I do not know JavaScript enough to master a script and do not like it at all. Writing a script in Objective C is possible but very verbose. So I got a new Apple language called &lt;a href=&quot;https://developer.apple.com/swift/&quot;&gt;Swift&lt;/a&gt;. Apple positioned Swift as a replacement for Objective C that could work with existing codebase and improve a lot syntax and safety of Objective C. It is good for my purpose!&lt;/p&gt;

&lt;p&gt;First of all it’s needed to generate a binding from Scripting Bridge to Swift using an experimental project &lt;a href=&quot;github.com/tingraldi/SwiftScripting&quot;&gt;SwiftScripting&lt;/a&gt;. Objective C bindings are supported out of box. Then you need to fix bindings by hands because (see below) it is experimental. Then just write a Swift script.&lt;/p&gt;

&lt;p&gt;The same script in Swift looks like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot; data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;files_for_cite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;BibDeskApplication&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;SBApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;bundleIdentifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;edu.ucsd.cs.mmccrack.bibdesk&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pubs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;documents&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as!&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;BibDeskDocument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;publications&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as!&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;BibDeskPublication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pubs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pub&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;citeKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cite&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pub&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;linkedFiles&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;AnyObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as!&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;BibDeskLinkedFile&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filter&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Huh! I even cannot make a single chained call because amount of braces became astronomical. Technically Swift specifically encouraged chained calls but they seems very cumbersome because of static type system of Swift where we need to cast proxy chain objects to specific types. Swift functional capabilities are limited to the very weak Foundation and Cocoa library support while Ruby has a lot of useful functions in &lt;a href=&quot;http://apidock.com/ruby/Enumerable&quot;&gt;Enumerable&lt;/a&gt; and &lt;a href=&quot;http://apidock.com/ruby/Array&quot;&gt;Array&lt;/a&gt;. Sometimes I just wrote a matching replacement for Ruby function for more direct porting.&lt;/p&gt;

&lt;h2 id=&quot;swift-impressions&quot;&gt;Swift impressions&lt;/h2&gt;

&lt;p&gt;It took one or two hours for reading Swift manual and Stack Overflow questions and a few hours to rewrite and debug a dozen of tasks to Swift. Major problems in porting were unwrapping values and type casting.&lt;/p&gt;

&lt;p&gt;Optional types are pretty good and could protect you from raw pointers usage and NPEs. It is a little similar to Rust optional enums but with added syntax sugar (question and exclamation marks).&lt;/p&gt;

&lt;p&gt;Swift could automatically deduce type from right-hand expressions so variable declaration does not need a type. If type-deduction is not possible you need to manually cast type using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;as&lt;/code&gt; operator. Casting became a nightmare because Scripting Bridge provides only untyped pointers that required casting from/to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnyObject&lt;/code&gt;. And it looks like sad programming in Java 1.4 with non-generic containers.&lt;/p&gt;

&lt;p&gt;So Swift is a pretty language that is objectively better than Objective C :) It has nice features that simplify existing code and improve its safety and readability. Programming Scripting Bridge in Swift is not very comfortable but entirely possible. Seems like it is the only sane way to script OS X applications without dealing with Apple Script or Objective C syntax.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>omnifiles</title>
   <link href="https://omniverse.ru/blog/2015/04/24/omnifiles/"/>
   <updated>2015-04-24T00:00:00+00:00</updated>
   <id>https://omniverse.ru/blog/2015/04/24/omnifiles</id>
   <content type="html">&lt;p&gt;Recently I wrote a small web application for storing temporary files and providing short links to them. It is like a shortener service integrated with file storage. I had found a few simple services for screenshots but it was needed to store pdfs and archives sometimes. Another requirement was to allow easy access by curl both for accessing (downloading) and for storing (uploading). And of course I wanted to store sensitive files at my own server.&lt;/p&gt;

&lt;p&gt;This application called omnifiles is open-source and can be found at &lt;a href=&quot;https://github.com/theirix/omnifiles&quot;&gt;https://github.com/theirix/omnifiles&lt;/a&gt;. omnifiles is a hobby project and a playground to tighten my web skills. I am using it for my own needs but you are welcome to provide feedback or patches!&lt;/p&gt;

&lt;p&gt;Let’s talk a little about it’s architecture. It is a simple web app built with Ruby Sinatra framework, HAML for markup, MongoDB for metadata storage and filesystem for file storage.&lt;/p&gt;

&lt;h2 id=&quot;storage&quot;&gt;Storage&lt;/h2&gt;

&lt;p&gt;Files are stored in filesystem with their unique shortened names. MongoDB contains documents for each shortened link containing shortened link itself (acts as a key), original filename and MIME type, access statistics. Initially metadata was stored in sqlite. Certainly there is no need in scaling, sharding and other CAP stuff but hey, it is 2015! It is reasonable to use NoSQL if there is no strict need in structured data.&lt;/p&gt;

&lt;p&gt;I used MongoDB 2.6 and stable ruby driver. Ruby driver was recently &lt;a href=&quot;https://www.mongodb.com/blog/post/announcing-ruby-driver-20-rewrite&quot;&gt;rewritten to version 2.0&lt;/a&gt; to support Mongo 3.0 but for now a stable version is sufficient and easy to use.&lt;/p&gt;

&lt;h2 id=&quot;api&quot;&gt;API&lt;/h2&gt;

&lt;p&gt;omnifiles should be simple and easily accessible.
Requests to store files are sent using POST requests. POST request emits a shortened URL as a response body. I use a curl utility for command-line posting.&lt;/p&gt;

&lt;p&gt;There are two ways to store a file (omitting auth and url).&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Send a POST form with a single file field:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; % curl -F &quot;file=@file.jpg&quot; ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Send a file using POST binary stream:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; % curl -H &quot;Content-Type: application/octet-stream&quot; --data-binary &quot;@file.jpg&quot; ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both variants are not very concise. I do not like an artificial form field at the first variant. Second variant just streams a file as a request body. Another vote against first variant is about intermediate form file saving to the temporary directory by the Rack middleware. You can be more efficient with a stream.&lt;/p&gt;

&lt;p&gt;File is downloaded by GET request with a shortened link:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    % curl http://localhost/sge36a
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Omnifiles provides an original filename as an additional header if a client wants to rename a downloaded file after downloading. Another nice feature is to return a saved MIME type to the response so browser can show images or pdfs directly inside browser window.&lt;/p&gt;

&lt;p&gt;I added control panel to omnifiles to view a single file statistics (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost/stat/sge36a&lt;/code&gt;), a whole storage statistics (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost/stat&lt;/code&gt;) or to delete unneeded files.&lt;/p&gt;

&lt;h2 id=&quot;web&quot;&gt;Web&lt;/h2&gt;

&lt;p&gt;Web frontend and backend are written in Sinatra. In omnifiles API and presentation are not clearly separated for the sake of simplicity. For example, routes are not classic REST because it was necessary for me to minimize possible URLs and to group certain URLs for auth in web server. So it is not a proper way to build a REST API service.&lt;/p&gt;

&lt;p&gt;I consider Sinatra as a good solution for simple REST services and simple apps without unneeded Rails complexity. There a lot of API, auth, model plugins and Rack middleware to Sinatra so you could build your app from the ground. Sinatra uses Rack middleware to work with requests/responses so with some Rack magic we could distinguish stream against form (see two POST approaches) and store a file in Mongo and filesystem.&lt;/p&gt;

&lt;p&gt;Omnifiles separates logic into different apps - public (for GET requests) and protected (POST and control panel). Protected app is using digest auth using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Rack::Auth&lt;/code&gt;. Routing between two apps is performed by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Rack::URLMap&lt;/code&gt;. It is definitely less flexible than Rails routes and unfortunately cannot route by HTTP methods.&lt;/p&gt;

&lt;p&gt;For presentation I am using HAML. HAML is an another markup language above HTML and it is a lot more concise than ERB. It’s required to learn it for a while it because it seems strange and awkward at first. A common problem with HAML is strict indent and string policy. Resulting markup is compact and beautiful and I like it.&lt;/p&gt;

&lt;p&gt;App can be served as a thin app (there is a launcher script &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/omnifiles&lt;/code&gt;) or as a Rack app (using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config.ru&lt;/code&gt;). Command line usage of thin or launcher can be cumbersome as you can see in &lt;a href=&quot;https://github.com/theirix/omnifiles/blob/master/README.md&quot;&gt;README.md&lt;/a&gt;. Sometimes it is simpler to run Rack app inside Passenger.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>pstack for OS X</title>
   <link href="https://omniverse.ru/blog/2015/04/07/pstack-osx/"/>
   <updated>2015-04-07T00:00:00+00:00</updated>
   <id>https://omniverse.ru/blog/2015/04/07/pstack-osx</id>
   <content type="html">&lt;p&gt;Unfortunately OS X does not have a wonderful Linux-world utility &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pstack&lt;/code&gt; that helps to inspect a running process with a shell one-liner:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;pstack &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;pgrep myprogram&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;So I wrote a replacement based on system gdb. Please use it if you find it useful.&lt;/p&gt;

&lt;noscript&gt;&lt;pre&gt;400: Invalid request&lt;/pre&gt;&lt;/noscript&gt;
&lt;script src=&quot;https://gist.github.com/ceacb52b1e5cf1a9594a.js&quot;&gt; &lt;/script&gt;

&lt;p&gt;I discovered a few interesting things while writing this utility.
Homebrew &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gdb&lt;/code&gt; sometimes leaves a process in a sleep state after attaching and detaching. Okay, let’s use system &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gdb&lt;/code&gt;. But it is very old (version 6), non-standard and compiled without python support so we cannot use nice python scripting (initially I found a python script to run inside gdb). Okay, there is a nice native `gdb command&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;thread apply all bt&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;that does everything we need. The next problem with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gdb&lt;/code&gt; was a lack of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--ex&lt;/code&gt; command-line argument that allows to specify a gdb script inline. So we need to create a temporary file with a gdb script. And finally OS X lacks &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/proc&lt;/code&gt; filesystem so to determine a process binary it’s needed to consult with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ps&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;The moral of this story. OS X and Linux are POSIX-compatible but differences environments, different flavors of developer tools and well-known bugs became nightmare to port even so simple utilities.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Amazon AWS SDK v2</title>
   <link href="https://omniverse.ru/blog/2015/02/24/aws2/"/>
   <updated>2015-02-24T00:00:00+00:00</updated>
   <id>https://omniverse.ru/blog/2015/02/24/aws2</id>
   <content type="html">&lt;p&gt;Recently I noticed that my hobby project &lt;a href=&quot;https://github.com/theirix/usbunfreeze/&quot;&gt;usbunfreeze&lt;/a&gt; uses AWS SDK version 1.
It was fresh vesion when I wrote it at New Year holidays. But a few weeks later the &lt;a href=&quot;http://ruby.awsblog.com/post/Tx2NJE86FP0HHXX/Announcing-V2-of-the-AWS-SDK-for-Ruby&quot;&gt;Version 2&lt;/a&gt; became available.
It was a challenge to explore a new API without any good up-to-date examples and tutorials. Older SDK contains a lot of official working examples inside. All Ruby service mappings were standard classes with well-defined methods without metaprogramming magic. Then Amazon moved to description of their services via JSON manifests (i.e. &lt;a href=&quot;https://github.com/aws/aws-sdk-core-ruby/blob/master/aws-sdk-core/apis/EC2.api.json&quot;&gt;EC2.api.json&lt;/a&gt;). It is definitely more generic approach but there is no way to look inside the code and check an implementation. Only rubydocs. It looks like YARD docs are also automatically generated from JSON manifests…&lt;/p&gt;

&lt;p&gt;All things became clearer when you understand the logic behind the mappings for each service. API is more flexible than v1, allows pagination, advanced async mechanics, provides more structured responses with automatic deserialization. The best reference to hack the new API is an official AWS SDK Ruby blog with a few examples and service descriptions in SDK. Very few projects moved to the new API so GitHub is not the best helper. Hope my project will be usefuly for someone who hacking new SDK right now :)&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Tests and color</title>
   <link href="https://omniverse.ru/blog/2015/02/13/tests-and-color/"/>
   <updated>2015-02-13T00:00:00+00:00</updated>
   <id>https://omniverse.ru/blog/2015/02/13/tests-and-color</id>
   <content type="html">&lt;p&gt;It is a hard choice to pick a good test frameworks for C++.
Generally I’m using &lt;a href=&quot;https://code.google.com/p/googletest/&quot;&gt;Google Test&lt;/a&gt;
and &lt;a href=&quot;http://www.boost.org/doc/libs/1_57_0/libs/test/doc/html/index.html&quot;&gt;Boost Test&lt;/a&gt;.
Both frameworks are mature, support fixtures, complex test cases, expects and asserts (Google Test suited me a little better
because of non-fatal assertion support)&lt;/p&gt;

&lt;p&gt;But there is only one feature that stops me from using Boost Test. It can’t produce colored output. Seriously, it’s 2015
and you must squeeze into raw output to find which test had failed. Do they remember the concept of green bar for unit tests?&lt;/p&gt;

&lt;p&gt;It is especially sad that Boost Test has a wonderful extendable output generator (supports both xml and human-readable output) and
there is no sign of any activity for adding colored output. People on boost mailing list advised to write generator on your own.&lt;/p&gt;

&lt;p&gt;So I have chosen my testing framework by the coloring support…&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Welcome</title>
   <link href="https://omniverse.ru/blog/2015/01/05/welcome/"/>
   <updated>2015-01-05T00:00:00+00:00</updated>
   <id>https://omniverse.ru/blog/2015/01/05/welcome</id>
   <content type="html">&lt;p&gt;It is just a welcome page.&lt;/p&gt;
</content>
 </entry>
 

</feed>
