<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.3">Jekyll</generator><link href="https://chris.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="https://chris.dev/" rel="alternate" type="text/html" /><updated>2024-02-09T17:51:06+00:00</updated><id>https://chris.dev/feed.xml</id><title type="html">Its Chris!</title><subtitle>Solving problems I created.</subtitle><entry><title type="html">100 Ubuntu Zfs At Rest</title><link href="https://chris.dev/2024/02/07/100-ubuntu-zfs-at-rest.html" rel="alternate" type="text/html" title="100 Ubuntu Zfs At Rest" /><published>2024-02-07T00:00:00+00:00</published><updated>2024-02-07T00:00:00+00:00</updated><id>https://chris.dev/2024/02/07/100-ubuntu-zfs-at-rest</id><content type="html" xml:base="https://chris.dev/2024/02/07/100-ubuntu-zfs-at-rest.html">&lt;p&gt;I have a home storage server with 6 drives in a raidz2 zfs pool, and a seperate boot drive. In case it gets stolen, I want to have all of the data encrypted at rest, but since it’s down in the garage I also need to be able to boot into it remotely after a restart without having to hook up a mouse and keyboard.&lt;/p&gt;

&lt;p&gt;To do this, I used a fresh install of Ubuntu on the boot drive, selecting ‘Encrypt the new Ubuntu installation for security’. This enables LUKS encryption on the boot drive, and every time you restart the system you’ll be presented with a splash screen to decrypt the drive before booting into the OS.
The next step is to set up the system to allow remote unlocking via SSH. This is done by installing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dropbear&lt;/code&gt; package, which is a small SSH server that can be used to unlock the LUKS encrypted drive. The package will install a script that will automatically start the dropbear server when the system boots, and then shut it down after the drive is unlocked.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo apt install dropbear-initramfs&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;add your ssh key to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/dropbear/initramfs/authorized_keys&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now after a restart you can ssh in as root, and run ‘cryptroot-unlock’ to unlock the drive. This will then shutdown the ssh connection, and after a few seconds you will be able to ssh in again using your regular user account.&lt;/p&gt;

&lt;p&gt;Next, I encrypted the zfs pool using native zfs encryption:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo zfs create -o encryption=aes-256-gcm -o keyformat=passphrase storage/photos&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now, when the system boots, the boot drive will be unlocked via ssh, and then the zfs pool will be unlocked using ‘zfs load-key storage/photos’ with the passphrase. This means that the entire system is encrypted at rest, and the system can be booted into remotely.&lt;/p&gt;

&lt;p&gt;Issues:
As the dropbear server runs its own ssh server, it doesnt know about your regular ssh server or the users on the system. This means that you can only unlock the drive as root, and then you have to log in again as your regular user. This is a bit of a pain, but I haven’t found a good way round it yet.&lt;/p&gt;</content><author><name></name></author><summary type="html">I have a home storage server with 6 drives in a raidz2 zfs pool, and a seperate boot drive. In case it gets stolen, I want to have all of the data encrypted at rest, but since it’s down in the garage I also need to be able to boot into it remotely after a restart without having to hook up a mouse and keyboard.</summary></entry><entry><title type="html">100% Coverage: Too much and not enough</title><link href="https://chris.dev/2019/02/20/100-coverage-too-much-and-not-enough.html" rel="alternate" type="text/html" title="100% Coverage: Too much and not enough" /><published>2019-02-20T00:00:00+00:00</published><updated>2019-02-20T00:00:00+00:00</updated><id>https://chris.dev/2019/02/20/100-coverage-too-much-and-not-enough</id><content type="html" xml:base="https://chris.dev/2019/02/20/100-coverage-too-much-and-not-enough.html">&lt;p&gt;Code coverage percentage is a controversial subject: some will tell you that you get diminishing returns after 70-80% and it’s not worth bothering, others will say that 100% is not enough. The underlying problem is often the approach taken to testing:&lt;/p&gt;

&lt;h2 id=&quot;100-coverage-as-a-goal-is-counterproductive&quot;&gt;100% coverage as a goal is counterproductive&lt;/h2&gt;
&lt;p&gt;Code coverage tools tell you how much of your code has been tested, not how well that code has been tested.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Code coverage only applies to the code you have written, and doesn’t highlight the features you’ve forgotten to implement.&lt;/li&gt;
  &lt;li&gt;Achieving high code coverage with low quality is relatively simple, but only results in the code being run, not being tested. It’s possible to hit 100% code coverage with 0 assertions!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No assertions:
&lt;img src=&quot;/assetts/img/no-assertions.png&quot; alt=&quot;A test that doesnt test anything&quot; /&gt;&lt;/p&gt;

&lt;p&gt;But our tool is happy!
&lt;img src=&quot;/assetts/img/full-coverage.png&quot; alt=&quot;100% coverage&quot; /&gt;
&lt;a href=&quot;https://github.com/kraftman/coverage-example/tree/100-percent-coverage&quot;&gt;(code on github)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If features drive your testing, you are more likely to find missing code paths and to write assertions for the features. If coverage drives your testing, your are likely to write low quality, brittle tests that satisfy the coverage tool - missing assertions and missing omissions in your logic. This is a problem because:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Your customer doesn’t care about your coverage, hitting 100% doesn’t help them if your code is still full of bugs. &lt;strong&gt;Tests should focus on surfacing bugs, not satisfying tool output.&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;You lose insight into the areas that need to be more thoroughly tested: &lt;strong&gt;when 100% of your code is covered by low quality tests, your code coverage tool becomes useless.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;100-unit-test-coverage--100-test-coverage&quot;&gt;100% unit test coverage != 100% test coverage&lt;/h2&gt;

&lt;p&gt;Unit testing glue-code with tonnes of mocks is possible, but the effort-to-value ratio is pretty high: you’ll end up duplicating the same tests at a higher level anyway, and then have more tests to fix when you change that code later.&lt;/p&gt;

&lt;p&gt;Let’s say we’re testing an API, and we aim for 100% unit test coverage. We’ll use this very professional test pyramid to indicate each testing type:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assetts/img/test-pyramid.png&quot; alt=&quot;100% coverage&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We take our codebase, and test all the business logic using unit tests. Then we notice there’s a bunch of code that isn’t business logic, but does other things like orchestrate other code, access the database, make external calls, etc. So we spend some time stubbing out our code so we can unit test it, and we get to 100% unit test coverage (this is the point where people give up on 100% coverage due to the efort):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assetts/img/100-coverage.png&quot; alt=&quot;100% coverage&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Since we also need to test that each unit of code works with each other, we start writing component tests on top of our unit tests, and then some end to end tests that tests a few happy paths:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assetts/img/fragmented-code1.png&quot; alt=&quot;layered tests&quot; /&gt;&lt;/p&gt;

&lt;p&gt;At this point, since we have 100% unit test coverage, the component and e2e tests overlap the unit tests and we end up directly testing the same chunks of code more than once. The difference is that the component tests (for non business-logic code) are easier to write, do a better job of testing since they test against real code instead of mocks, and are less likely to break when the code changes.&lt;/p&gt;

&lt;p&gt;If we reduce the scope of our unit tests down to just code that can easily be tested (the business logic), the other layers of testing can fill in the gaps and help us reach 100% &lt;strong&gt;total coverage&lt;/strong&gt; with better confidence in our tests, less overlap, and less effort (since we wrote fewer tests with fewer stub):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assetts/img/defragged.png&quot; alt=&quot;defragged&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;not-all-code-is-equal&quot;&gt;Not all code is equal&lt;/h2&gt;
&lt;p&gt;Some code is much more important than other code. Your focus should be on testing the important code thoroughly, not in wasting effort setting up mocks for the less important code.&lt;/p&gt;

&lt;p&gt;Some projects have more risk than others. Are you writing banking software for millions of paying users, or a side project for fun? Scale your testing time appropriately.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Use your product requirements to drive testing. Use the risk factors of your product to determine how thorough your tests need to be. Use your code coverage tool as a tool, to tell you where best to spend your limited amount of time, not as a goal to give you a false sense of security.&lt;/p&gt;

&lt;p&gt;Related reading:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://martinfowler.com/bliki/TestCoverage.html&quot;&gt;https://martinfowler.com/bliki/TestCoverage.html&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://martinfowler.com/bliki/TestCoverage.html&quot;&gt;http://www.exampler.com/testing-com/writings/coverage.pdf&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name></name></author><summary type="html">Code coverage percentage is a controversial subject: some will tell you that you get diminishing returns after 70-80% and it’s not worth bothering, others will say that 100% is not enough. The underlying problem is often the approach taken to testing:</summary></entry><entry><title type="html">Controllers are the gatekeepers of your API</title><link href="https://chris.dev/uncategorized/2018/02/06/controllers-are-the-gatekeepers-of-your-api.html" rel="alternate" type="text/html" title="Controllers are the gatekeepers of your API" /><published>2018-02-06T08:07:03+00:00</published><updated>2018-02-06T08:07:03+00:00</updated><id>https://chris.dev/uncategorized/2018/02/06/controllers-are-the-gatekeepers-of-your-api</id><content type="html" xml:base="https://chris.dev/uncategorized/2018/02/06/controllers-are-the-gatekeepers-of-your-api.html">&lt;p&gt;Controllers safely separate your API from the outside world; they parse and sanitise data that comes in, and they filter the data you return.&lt;br /&gt;
For this reason, you shouldn't do any input validation past your controllers: if it doesn't exist by that point then something has gone wrong, and you should fix the code instead of adding hundreds of validations.&lt;/p&gt;
&lt;p&gt;If your endpoint requires an account ID with your request and the user doesn't pass it in, that's a user error and you should handle it by validating the input and rejecting the request.&lt;br /&gt;
If your internal code requires an account ID and gets called without it however, that's an error with your code, and your code should fail, rather than specifically checking for it and handling it in every single function it may occur.&lt;/p&gt;
&lt;p&gt;For the same reason, controllers should abstract the request information from the rest of the code, and should be the last point in your code that you ever see the request object. Internal code beyond the controllers should have no concept of a request, only the values parsed in.&lt;/p&gt;</content><author><name>chris</name></author><category term="Uncategorized" /><summary type="html">Controllers safely separate your API from the outside world; they parse and sanitise data that comes in, and they filter the data you return. For this reason, you shouldn't do any input validation past your controllers: if it doesn't exist by that point then something has gone wrong, and you should fix the code instead of adding hundreds of validations. If your endpoint requires an account ID with your request and the user doesn't pass it in, that's a user error and you should handle it by validating the input and rejecting the request. If your internal code requires an account ID and gets called without it however, that's an error with your code, and your code should fail, rather than specifically checking for it and handling it in every single function it may occur. For the same reason, controllers should abstract the request information from the rest of the code, and should be the last point in your code that you ever see the request object. Internal code beyond the controllers should have no concept of a request, only the values parsed in.</summary></entry><entry><title type="html">Express parameter callbacks</title><link href="https://chris.dev/uncategorized/coding/javascript/node/2017/10/04/express-parameter-callbacks.html" rel="alternate" type="text/html" title="Express parameter callbacks" /><published>2017-10-04T04:15:42+00:00</published><updated>2017-10-04T04:15:42+00:00</updated><id>https://chris.dev/uncategorized/coding/javascript/node/2017/10/04/express-parameter-callbacks</id><content type="html" xml:base="https://chris.dev/uncategorized/coding/javascript/node/2017/10/04/express-parameter-callbacks.html">&lt;p&gt;Handy little feature I didn't know about in Express:&lt;br /&gt;
Using &lt;a href=&quot;https://expressjs.com/en/4x/api.html#app.param&quot;&gt;app.param([name], callback)&lt;/a&gt; you can bind callbacks directly to route parameters, allowing you to move common preprocessing/validation out of each function that uses the parameter, and into a single function (without having to call it explicitly each time.)&lt;br /&gt;
You can pass in an array of names, using next() to jump to the next parameter, and the callback is only called once regardless of how many times the parameter appears in route handlers.&lt;br /&gt;
The callbacks are local to the router they are defined on, so you can handle things (or not) differently based on the context.&lt;br /&gt;
Neat!&lt;/p&gt;</content><author><name>chris</name></author><category term="Uncategorized" /><category term="Coding" /><category term="Javascript" /><category term="node" /><summary type="html">Handy little feature I didn't know about in Express: Using app.param([name], callback) you can bind callbacks directly to route parameters, allowing you to move common preprocessing/validation out of each function that uses the parameter, and into a single function (without having to call it explicitly each time.) You can pass in an array of names, using next() to jump to the next parameter, and the callback is only called once regardless of how many times the parameter appears in route handlers. The callbacks are local to the router they are defined on, so you can handle things (or not) differently based on the context. Neat!</summary></entry><entry><title type="html">Developing a node app in docker</title><link href="https://chris.dev/coding/node/docker/2017/09/28/developing-a-node-app-in-docker.html" rel="alternate" type="text/html" title="Developing a node app in docker" /><published>2017-09-28T21:57:14+00:00</published><updated>2017-09-28T21:57:14+00:00</updated><id>https://chris.dev/coding/node/docker/2017/09/28/developing-a-node-app-in-docker</id><content type="html" xml:base="https://chris.dev/coding/node/docker/2017/09/28/developing-a-node-app-in-docker.html">&lt;h3&gt;Problem&lt;/h3&gt;
&lt;p&gt;We want to rapidly develop our node app inside a docker container, being able to install modules, make code changes, and see instant results. The problem is that while the &lt;a href=&quot;https://nodejs.org/en/docs/guides/nodejs-docker-webapp&quot;&gt;official node image&lt;/a&gt; supports a handy onbuild feature which will grab the package.json and install everything we need, this also means having to rebuild the image every time a dependency changes.&lt;/p&gt;
&lt;h3&gt;Solution&lt;/h3&gt;
&lt;p&gt;&lt;del&gt;Use &lt;a href=&quot;https://github.com/grahamgilchrist/docker-node-onbuild&quot;&gt;this image&lt;/a&gt;, which places the node_modules folder one level higher, meaning it isn't overwritten by the docker mount, and you can still mount and install your own node modules on the fly.&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;Edit:&lt;/p&gt;
&lt;p&gt;An easier but less obvious way to solve the problem is to specify your /usr/src/app/node_modules folder as a volume with no mapping to the host. This preserves the container copy and allows you to keep your local copy.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;p&gt;volumes: - /usr/src/app/node_modules&lt;/p&gt;
&lt;p&gt;When you're deploying the image and need to copy the entire app in, you can use the .dockerignore file to prevent your host node_modules from being loaded into the build context, improving build time&lt;/p&gt;</content><author><name>chris</name></author><category term="Coding" /><category term="node" /><category term="docker" /><summary type="html">Problem We want to rapidly develop our node app inside a docker container, being able to install modules, make code changes, and see instant results. The problem is that while the official node image supports a handy onbuild feature which will grab the package.json and install everything we need, this also means having to rebuild the image every time a dependency changes. Solution Use this image, which places the node_modules folder one level higher, meaning it isn't overwritten by the docker mount, and you can still mount and install your own node modules on the fly. Edit: An easier but less obvious way to solve the problem is to specify your /usr/src/app/node_modules folder as a volume with no mapping to the host. This preserves the container copy and allows you to keep your local copy. Example: volumes: - /usr/src/app/node_modules When you're deploying the image and need to copy the entire app in, you can use the .dockerignore file to prevent your host node_modules from being loaded into the build context, improving build time</summary></entry><entry><title type="html">NGINX Timer Resolution</title><link href="https://chris.dev/coding/nginx/lua/2017/06/16/nginx-timer-resolution.html" rel="alternate" type="text/html" title="NGINX Timer Resolution" /><published>2017-06-16T11:01:55+00:00</published><updated>2017-06-16T11:01:55+00:00</updated><id>https://chris.dev/coding/nginx/lua/2017/06/16/nginx-timer-resolution</id><content type="html" xml:base="https://chris.dev/coding/nginx/lua/2017/06/16/nginx-timer-resolution.html">&lt;p&gt;Using &lt;a href=&quot;https://github.com/openresty/lua-nginx-module#ngxtime&quot;&gt;ngx.time&lt;/a&gt;&amp;nbsp;or &lt;a href=&quot;https://github.com/openresty/lua-nginx-module#ngxnow&quot;&gt;ngx.now&amp;nbsp;&lt;/a&gt;is encouraged over using Lua's built in functions because they use the cached time rather than performing a syscall, but how often is the cache updated?&lt;/p&gt;
&lt;p&gt;After a bit of a dig, it turns out there's no absolute answer, because the cache is actually updated when a kernel event fires:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;It can be set manually using nginx's timer_resolution&lt;br /&gt;
http://nginx.org/en/docs/ngx_core_module.html#timer_resolution&lt;br /&gt;
but this is not recommended because it either causes too many syscalls if set too low, or necessary time lag if set too high.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://groups.google.com/d/msg/openresty-en/OdNzfh8rlzk/EQHF-hlhlQ0J&quot;&gt;https://groups.google.com/d/msg/openresty-en/OdNzfh8rlzk/EQHF-hlhlQ0J&lt;/a&gt;&lt;/p&gt;</content><author><name>chris</name></author><category term="Coding" /><category term="nginx" /><category term="Lua" /><summary type="html">Using ngx.time&amp;nbsp;or ngx.now&amp;nbsp;is encouraged over using Lua's built in functions because they use the cached time rather than performing a syscall, but how often is the cache updated? After a bit of a dig, it turns out there's no absolute answer, because the cache is actually updated when a kernel event fires: It can be set manually using nginx's timer_resolution http://nginx.org/en/docs/ngx_core_module.html#timer_resolution but this is not recommended because it either causes too many syscalls if set too low, or necessary time lag if set too high. https://groups.google.com/d/msg/openresty-en/OdNzfh8rlzk/EQHF-hlhlQ0J</summary></entry><entry><title type="html">Openresty Redis ZUNIONSTORE gotcha</title><link href="https://chris.dev/coding/redis/lua/2017/06/16/openresty-redis-zunionstore-gotcha.html" rel="alternate" type="text/html" title="Openresty Redis ZUNIONSTORE gotcha" /><published>2017-06-16T10:49:52+00:00</published><updated>2017-06-16T10:49:52+00:00</updated><id>https://chris.dev/coding/redis/lua/2017/06/16/openresty-redis-zunionstore-gotcha</id><content type="html" xml:base="https://chris.dev/coding/redis/lua/2017/06/16/openresty-redis-zunionstore-gotcha.html">&lt;h3&gt;Problem&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://redis.io/commands/zunionstore&quot;&gt;ZUNIONSTORE&lt;/a&gt;&amp;nbsp;merges multiple sorted sets into one, and stores the result under the key specified. Since the number of keys can vary and there are more parameters after the keys, it require the numkeys parameter to be specified before the keys.&lt;/p&gt;
&lt;p&gt;If using the default aggregate function (SUM) this is fine, as you can simply store the sorted set names to be merged in a table, use the size of the table as numkeys, and unpack() the table to pass all of the keys names to redis.&lt;/p&gt;
&lt;p&gt;The problem occurs when you want to change the aggregate function, adding more parameters after unpack(), which changes its behaviour:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.lua.org/pil/5.1.html&quot;&gt;http://www.lua.org/pil/5.1.html&lt;/a&gt;&lt;br /&gt;
'Lua always adjusts the number of results from a function to the circumstances of the call. When we call a function as a statement, Lua discards all of its results. When we use a call as an expression, Lua keeps only the first result. We get all results only when the call is the last (or the only) expression in a list of expressions.'&lt;/p&gt;
&lt;p&gt;So unpack will only pass the first sorted set key name.&lt;/p&gt;
&lt;h3&gt;Solution&lt;/h3&gt;
&lt;p&gt;The workaround is to add the aggregate command to the list of sorted set key names, and deduct the number of keys passed:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;table.insert(setNames, 'AGGREGATE')&lt;br /&gt;
table.insert(setNames, 'MAX')&lt;br /&gt;
local ok, err = red:zunionstore('destinationKey',#setNames-2,unpack(setNames))&lt;/p&gt;&lt;/blockquote&gt;</content><author><name>chris</name></author><category term="Coding" /><category term="redis" /><category term="Lua" /><summary type="html">Problem ZUNIONSTORE&amp;nbsp;merges multiple sorted sets into one, and stores the result under the key specified. Since the number of keys can vary and there are more parameters after the keys, it require the numkeys parameter to be specified before the keys. If using the default aggregate function (SUM) this is fine, as you can simply store the sorted set names to be merged in a table, use the size of the table as numkeys, and unpack() the table to pass all of the keys names to redis. The problem occurs when you want to change the aggregate function, adding more parameters after unpack(), which changes its behaviour: http://www.lua.org/pil/5.1.html 'Lua always adjusts the number of results from a function to the circumstances of the call. When we call a function as a statement, Lua discards all of its results. When we use a call as an expression, Lua keeps only the first result. We get all results only when the call is the last (or the only) expression in a list of expressions.' So unpack will only pass the first sorted set key name. Solution The workaround is to add the aggregate command to the list of sorted set key names, and deduct the number of keys passed: table.insert(setNames, 'AGGREGATE') table.insert(setNames, 'MAX') local ok, err = red:zunionstore('destinationKey',#setNames-2,unpack(setNames))</summary></entry><entry><title type="html">Remote Redis: Spiped vs Stunnel</title><link href="https://chris.dev/coding/linux/redis/2017/05/19/remote-redis-spiped-vs-stunnel.html" rel="alternate" type="text/html" title="Remote Redis: Spiped vs Stunnel" /><published>2017-05-19T10:39:42+00:00</published><updated>2017-05-19T10:39:42+00:00</updated><id>https://chris.dev/coding/linux/redis/2017/05/19/remote-redis-spiped-vs-stunnel</id><content type="html" xml:base="https://chris.dev/coding/linux/redis/2017/05/19/remote-redis-spiped-vs-stunnel.html">&lt;p&gt;Redis is fast, there's no doubt about that. Unfortunately for us, connecting to Redis has an overhead, and the method you connect with can have a huge impact.&lt;/p&gt;
&lt;h2&gt;Connecting locally&lt;/h2&gt;
&lt;p&gt;Our options for connecting locally are Unix sockets or TCP sockets, so let's start by comparing them directly:&lt;/p&gt;
&lt;p&gt;Socket vs TCP:&lt;br /&gt;
&lt;iframe width=&quot;600&quot; height=&quot;371&quot; seamless frameborder=&quot;0&quot; scrolling=&quot;no&quot; src=&quot;https://docs.google.com/spreadsheets/d/1BUTXAvtaZ3OgMua-GQHfVO2_xG_QpiOASDAIj3xAzGo/pubchart?oid=1804726560&amp;amp;format=interactive&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p&gt;As we can see, the higher overhead of TCP connections limits the throughput. By &lt;a href=&quot;https://redis.io/topics/pipelining&quot;&gt;pipelining&lt;/a&gt; multiple requests through single connections, we can reduce the TCP setup overhead and get performance approaching that of sockets:&lt;/p&gt;
&lt;p&gt;Socket vs tcp with pipeline of 1000:&lt;br /&gt;
&lt;iframe width=&quot;600&quot; height=&quot;371&quot; seamless frameborder=&quot;0&quot; scrolling=&quot;no&quot; src=&quot;https://docs.google.com/spreadsheets/d/1BUTXAvtaZ3OgMua-GQHfVO2_xG_QpiOASDAIj3xAzGo/pubchart?oid=1629826158&amp;amp;format=interactive&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;h2&gt;Connecting over the network:&lt;/h2&gt;
&lt;p&gt;When we connect over the network, we have no choice but to use TCP sockets, and since redis has &lt;a href=&quot;https://redis.io/topics/security#data-encryption-support&quot;&gt;no network security&lt;/a&gt;, we need to secure our connections.&lt;/p&gt;
&lt;p&gt;Our options for secure connections are &lt;a href=&quot;https://www.stunnel.org/index.html&quot;&gt;stunnel&lt;/a&gt; and &lt;a href=&quot;https://www.tarsnap.com/spiped.html&quot;&gt;spiped&lt;/a&gt;, let's test them both out.&lt;/p&gt;
&lt;p&gt;Spiped vs stunnel:&lt;br /&gt;
&lt;iframe width=&quot;600&quot; height=&quot;371&quot; seamless frameborder=&quot;0&quot; scrolling=&quot;no&quot; src=&quot;https://docs.google.com/spreadsheets/d/1BUTXAvtaZ3OgMua-GQHfVO2_xG_QpiOASDAIj3xAzGo/pubchart?oid=1428434209&amp;amp;format=interactive&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p&gt;As we can see, spiped seems to be hitting some kind of bottleneck, limiting the numbers regardless of the tests performed. The problem here appears to be that &lt;a href=&quot;https://github.com/Tarsnap/spiped&quot;&gt;spiped&lt;/a&gt; pads messages:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;
[spiped] can significantly increase bandwidth usage for interactive sessions: It sends data in packets of 1024 bytes, and pads smaller messages up to this length, so a 1 byte write could be expanded to 1024 bytes if it cannot be coalesced with adjacent bytes.
&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;So when we're doing a large number of small requests with redis-benchmark, each small request is padded out to make it much larger, maxing out our bandwidth:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://i.imgur.com/hnNf7A2.png&quot; width=&quot;610&quot; height=&quot;361&quot; class=&quot;alignnone&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Like with unix sockets vs tcp, this improves when we use pipelining, as less bandwidth is wasted to padding:&lt;/p&gt;
&lt;p&gt;Spiped vs stunnel, pipeline 1000:&lt;br /&gt;
&lt;iframe width=&quot;600&quot; height=&quot;371&quot; seamless frameborder=&quot;0&quot; scrolling=&quot;no&quot; src=&quot;https://docs.google.com/spreadsheets/d/1BUTXAvtaZ3OgMua-GQHfVO2_xG_QpiOASDAIj3xAzGo/pubchart?oid=1633790963&amp;amp;format=interactive&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p&gt;There's still a gap, but it's much narrower now.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;So what's the solution? If you can, have your application on the same server as Redis, so that you can use Unix sockets for performance.&lt;br /&gt;
If you have to run over the network, bear in mind the overhead of spiped when sending large numbers of small requests.&lt;br /&gt;
Pipelining can have a huge impact, performing better over the network than none-pipelined locally. The issue is that not every application can neatly bundle all requests into pipelined chunks, so your result may vary depending on use case.&lt;/p&gt;
&lt;p&gt;All tests were performed between two Kimsufi ks-5 dedicated servers, with a 100mb link.&lt;/p&gt;</content><author><name>chris</name></author><category term="Coding" /><category term="Linux" /><category term="redis" /><summary type="html">Redis is fast, there's no doubt about that. Unfortunately for us, connecting to Redis has an overhead, and the method you connect with can have a huge impact. Connecting locally Our options for connecting locally are Unix sockets or TCP sockets, so let's start by comparing them directly: Socket vs TCP: As we can see, the higher overhead of TCP connections limits the throughput. By pipelining multiple requests through single connections, we can reduce the TCP setup overhead and get performance approaching that of sockets: Socket vs tcp with pipeline of 1000: Connecting over the network: When we connect over the network, we have no choice but to use TCP sockets, and since redis has no network security, we need to secure our connections. Our options for secure connections are stunnel and spiped, let's test them both out. Spiped vs stunnel: As we can see, spiped seems to be hitting some kind of bottleneck, limiting the numbers regardless of the tests performed. The problem here appears to be that spiped pads messages: [spiped] can significantly increase bandwidth usage for interactive sessions: It sends data in packets of 1024 bytes, and pads smaller messages up to this length, so a 1 byte write could be expanded to 1024 bytes if it cannot be coalesced with adjacent bytes. So when we're doing a large number of small requests with redis-benchmark, each small request is padded out to make it much larger, maxing out our bandwidth: Like with unix sockets vs tcp, this improves when we use pipelining, as less bandwidth is wasted to padding: Spiped vs stunnel, pipeline 1000: There's still a gap, but it's much narrower now. Conclusion So what's the solution? If you can, have your application on the same server as Redis, so that you can use Unix sockets for performance. If you have to run over the network, bear in mind the overhead of spiped when sending large numbers of small requests. Pipelining can have a huge impact, performing better over the network than none-pipelined locally. The issue is that not every application can neatly bundle all requests into pipelined chunks, so your result may vary depending on use case. All tests were performed between two Kimsufi ks-5 dedicated servers, with a 100mb link.</summary></entry><entry><title type="html">Redis notes</title><link href="https://chris.dev/coding/linux/redis/2017/04/03/redis-notes.html" rel="alternate" type="text/html" title="Redis notes" /><published>2017-04-03T10:44:20+00:00</published><updated>2017-04-03T10:44:20+00:00</updated><id>https://chris.dev/coding/linux/redis/2017/04/03/redis-notes</id><content type="html" xml:base="https://chris.dev/coding/linux/redis/2017/04/03/redis-notes.html">&lt;p&gt;I decided the&amp;nbsp;&lt;a href=&quot;https://github.com/antirez/redis-doc/pull/813/files&quot;&gt;tidy up the redis docs&lt;/a&gt;&amp;nbsp;and I wrote some notes for myself on the way:&lt;/p&gt;
&lt;h4&gt;Redis as pure cache:&lt;/h4&gt;
&lt;p&gt;Setting maxmemory and maxmemory-policy to 'allkeys-lru' will make redis auto expire all keys, starting with the oldest first, without any need for manually setting EXPIRE. Perfect when used just for caching.&lt;/p&gt;
&lt;h4&gt;Lexicographical sorted sets:&lt;/h4&gt;
&lt;p&gt;Elements stored under the same key in a sorted set can be retrieved lexicographicaly, powerful for string searching. If you need to normalise a string while retaining the original, you can store them together. E.g. 'banana:Banana' to ignore case while searching but preserve the case of the result.&lt;/p&gt;
&lt;h4&gt;Distributed Locks&lt;/h4&gt;
&lt;p&gt;Getting distributed locks safely is more complicated than it first appears, with a few edge cases that may cause locks to not be released, etc.&amp;nbsp;&lt;a href=&quot;https://redis.io/topics/distlock&quot;&gt;Redlock&lt;/a&gt; has been written as a general solution, and has a large number of implementations in different languages.&lt;/p&gt;
&lt;h4&gt;Redis-cli&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Only returns extra info for tty, raw for all others&lt;/li&gt;
&lt;li&gt;Can be set to repeat commands using -r &lt;count&gt; -i &lt;delay&gt;&lt;/li&gt;
&lt;li&gt;&amp;nbsp;'--stat' produces continuous stats&lt;/li&gt;
&lt;li&gt;&amp;nbsp;Can scan for big keys with --big-keys (can be used in production)&lt;/li&gt;
&lt;li&gt;&amp;nbsp;Supports pub/sub directly&lt;/li&gt;
&lt;li&gt;&amp;nbsp;Can echo all redis commands using MONITOR&lt;/li&gt;
&lt;li&gt;&amp;nbsp;Can show redis latency and intrinsic latency&lt;/li&gt;
&lt;li&gt;&amp;nbsp;Can grab RDB from server&lt;/li&gt;
&lt;li&gt;&amp;nbsp;Can simulate LRU load with 80/20 access rates&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Replication&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Slaves can chain (slave -&gt; slave replication, doesn't replicate local slave writes)&lt;/li&gt;
&lt;li&gt;Master can use diskless replication, sends rdb directly to slave from mem.&lt;/li&gt;
&lt;li&gt;Master can be set to reject writes unless a certain number of slaves are available.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Sentinel&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&amp;nbsp;Clients can subscribe to sentinel pub/sub for events.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;Sentinels never forget seen sentinels&lt;/li&gt;
&lt;li&gt;&amp;nbsp;Slaves can be given promotion priority to avoid or prefer them becoming masters.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Transactions&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;DISCARD cancels the current queue&lt;/li&gt;
&lt;li&gt;WATCH will cancel&amp;nbsp;EXEC if the watched key has changed since the WATCH command was issued.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Other&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;SUNION can take a long time for large/many sets.&lt;/li&gt;
&lt;li&gt;The Lua debugger can be used to step through lua scripts line by line.&lt;/li&gt;
&lt;li&gt;Total memory used can exceed maxmemory briefly, could be by a large amount but only if setting a large key.&lt;/li&gt;
&lt;li&gt;If you are storing a lot of objects in a set, split the key apart and use the first part as a hash key instead -&gt; more memory efficient. ('test1234' -&gt; 'test1' '234' &lt;value&gt;)&lt;/li&gt;
&lt;li&gt;Publishing ignores database members&lt;/li&gt;
&lt;li&gt;Subscribing supports pattern matching&lt;/li&gt;
&lt;li&gt;Clients may receive&amp;nbsp;duplicated messages if they have multiple subscriptions&lt;/li&gt;
&lt;li&gt;Keyspace notifications can report all commands affecting a key, all keys receiving lpush, and all keys expiring in db 0.&lt;/li&gt;
&lt;li&gt;Expired keys only fire when they are actually expired by redis, not the exact time they should expire.&lt;/li&gt;
&lt;/ul&gt;</content><author><name>chris</name></author><category term="Coding" /><category term="Linux" /><category term="redis" /><summary type="html">I decided the&amp;nbsp;tidy up the redis docs&amp;nbsp;and I wrote some notes for myself on the way: Redis as pure cache: Setting maxmemory and maxmemory-policy to 'allkeys-lru' will make redis auto expire all keys, starting with the oldest first, without any need for manually setting EXPIRE. Perfect when used just for caching. Lexicographical sorted sets: Elements stored under the same key in a sorted set can be retrieved lexicographicaly, powerful for string searching. If you need to normalise a string while retaining the original, you can store them together. E.g. 'banana:Banana' to ignore case while searching but preserve the case of the result. Distributed Locks Getting distributed locks safely is more complicated than it first appears, with a few edge cases that may cause locks to not be released, etc.&amp;nbsp;Redlock has been written as a general solution, and has a large number of implementations in different languages. Redis-cli Only returns extra info for tty, raw for all others Can be set to repeat commands using -r -i &amp;nbsp;'--stat' produces continuous stats &amp;nbsp;Can scan for big keys with --big-keys (can be used in production) &amp;nbsp;Supports pub/sub directly &amp;nbsp;Can echo all redis commands using MONITOR &amp;nbsp;Can show redis latency and intrinsic latency &amp;nbsp;Can grab RDB from server &amp;nbsp;Can simulate LRU load with 80/20 access rates Replication Slaves can chain (slave -&gt; slave replication, doesn't replicate local slave writes) Master can use diskless replication, sends rdb directly to slave from mem. Master can be set to reject writes unless a certain number of slaves are available. Sentinel &amp;nbsp;Clients can subscribe to sentinel pub/sub for events. &amp;nbsp;Sentinels never forget seen sentinels &amp;nbsp;Slaves can be given promotion priority to avoid or prefer them becoming masters. Transactions DISCARD cancels the current queue WATCH will cancel&amp;nbsp;EXEC if the watched key has changed since the WATCH command was issued. Other SUNION can take a long time for large/many sets. The Lua debugger can be used to step through lua scripts line by line. Total memory used can exceed maxmemory briefly, could be by a large amount but only if setting a large key. If you are storing a lot of objects in a set, split the key apart and use the first part as a hash key instead -&gt; more memory efficient. ('test1234' -&gt; 'test1' '234' ) Publishing ignores database members Subscribing supports pattern matching Clients may receive&amp;nbsp;duplicated messages if they have multiple subscriptions Keyspace notifications can report all commands affecting a key, all keys receiving lpush, and all keys expiring in db 0. Expired keys only fire when they are actually expired by redis, not the exact time they should expire.</summary></entry><entry><title type="html">Redis Cluster vs Redis Replication</title><link href="https://chris.dev/uncategorized/coding/linux/redis/2017/03/21/redis-cluster-vs-redis-replication.html" rel="alternate" type="text/html" title="Redis Cluster vs Redis Replication" /><published>2017-03-21T11:51:54+00:00</published><updated>2017-03-21T11:51:54+00:00</updated><id>https://chris.dev/uncategorized/coding/linux/redis/2017/03/21/redis-cluster-vs-redis-replication</id><content type="html" xml:base="https://chris.dev/uncategorized/coding/linux/redis/2017/03/21/redis-cluster-vs-redis-replication.html">&lt;p&gt;While researching Redis Cluster I found a large number of tutorials on the subject that confused Replication and Cluster, with people setting up 'replication' using cluster but no slaves, or building a 'cluster' only consisting of master-slave databases with no cluster config.&lt;/p&gt;
&lt;p&gt;So to clear things up:&lt;/p&gt;
&lt;h4&gt;Replication&lt;/h4&gt;
&lt;p&gt;Replication involves a master server which serves&amp;nbsp;reads and writes, and duplicates all data to one or more slave servers (which serves reads but not writes). Slaves can be used to replace a master in case of failure, spread read request load, or to perform backups of the database to reduce load on the master.&lt;/p&gt;
&lt;h4&gt;Cluster&lt;/h4&gt;
&lt;p&gt;Clusters are used when you have more data than RAM in a single machine: the data is automatically split (based on the key) across multiple databases, increasing the amount of data you can store. Clients requesting a key from any cluster node will be redirected to the node holding the key, and are expected to learn the locations of keys to reduce the number of redirects.&lt;/p&gt;
&lt;h4&gt;Replicaton + Cluster&lt;/h4&gt;
&lt;p&gt;Redis Cluster supports replication by adding slaves to existing nodes, if a master becomes unreachable then its slave will be promoted to master.&lt;/p&gt;
&lt;h4&gt;Sentinel&lt;/h4&gt;
&lt;p&gt;Last but not least, Redis Sentinel can be used to manage replicated servers (not clustered, see below.) Clients connect to a Sentinel and request a master or slave to communicate with, the sentinels handle health checks of the masters/slaves, and will automatically promote a slave if a master is unreachable. You need to have at least 3 sentinels running so that they can agree on reachability of nodes, and to ensure the sentinels aren't a single point of failure.&lt;/p&gt;
&lt;p&gt;Cluster handles its own promotion and does not need Sentinel in front of it.&lt;/p&gt;</content><author><name>chris</name></author><category term="Uncategorized" /><category term="Coding" /><category term="Linux" /><category term="redis" /><summary type="html">While researching Redis Cluster I found a large number of tutorials on the subject that confused Replication and Cluster, with people setting up 'replication' using cluster but no slaves, or building a 'cluster' only consisting of master-slave databases with no cluster config. So to clear things up: Replication Replication involves a master server which serves&amp;nbsp;reads and writes, and duplicates all data to one or more slave servers (which serves reads but not writes). Slaves can be used to replace a master in case of failure, spread read request load, or to perform backups of the database to reduce load on the master. Cluster Clusters are used when you have more data than RAM in a single machine: the data is automatically split (based on the key) across multiple databases, increasing the amount of data you can store. Clients requesting a key from any cluster node will be redirected to the node holding the key, and are expected to learn the locations of keys to reduce the number of redirects. Replicaton + Cluster Redis Cluster supports replication by adding slaves to existing nodes, if a master becomes unreachable then its slave will be promoted to master. Sentinel Last but not least, Redis Sentinel can be used to manage replicated servers (not clustered, see below.) Clients connect to a Sentinel and request a master or slave to communicate with, the sentinels handle health checks of the masters/slaves, and will automatically promote a slave if a master is unreachable. You need to have at least 3 sentinels running so that they can agree on reachability of nodes, and to ensure the sentinels aren't a single point of failure. Cluster handles its own promotion and does not need Sentinel in front of it.</summary></entry></feed>