Recent Posts (all)

Recruiters: recruit!

Since having changed my main business title on LinkedIn away from Shoe Designer, I got my good share of recruiters contacting me whether I’d be interested in the best data scientists and engineers I’ve ever encountered.

At GoDataDriven we’re always hiring so my standard answer was — initially — “Yes of course, send the profile over”.

However what happened next was always more or less the following:

This means that every time a single recruiter would send me 20 CVs, I would lose 4-5 hours, scattered among multiple days. If I include a generous context-switching time lost of 2-3 hours, that meant that every recruiter interaction meant a whole day would be wasted.

I should add to this the frustration of not winning: I did not hire a single person in months. And it wasn’t for lack of clarity on my part. I onboarded each recruiter with a 30’ call explaining in details what kind of colleagues we were looking for.

Certainly something had to change. I introduced a recruiter “policy”. The policy was very simple and I would send it as soon as someone contacted me:

  • You can send a maximum of 4 CVs, all at the same time;
  • If we don’t hire anyone from this first batch, we terminate our relationship.

My inspiration was something that I believe Atlassian published1. I loved this policy because it shifted most of the work from my back to the recruiters’ back2.

The full day of work was reduced to 2 hours in total3 — if I didn’t hire anyone: otherwise they could send me all the CVs they wanted.

Most interestingly, lots of recruiters stopped before sending me a single CV: a strong indicator that they didn’t want to do their job and rather wanted to continue their volume game with some other fool.

How many recruiter agency did we end up working with? Just one, comprised of a single person. We love him, and kept using his service until we hired our first internal recruiter.

So next time you think you’re overwhelmed by the amount of CVs recruiters send your way, try shifting the work


  1. I cannot find the page anymore, so I am not 100% sure anymore that it was Atlassian. ↩︎

  2. You’d expect this to be obvious, but believe me it’s not. ↩︎

  3. 30’ recruiter onboarding, 20’ CV scanning (as they were of higher quality), 45’ to talk to usually a single person, and some 30’ of overhead. ↩︎

Posted on 18 Jul 2019

Google can be creepy

On Wednesday July the 10th, the Dutch website NOS broke the news that people employed by or through Google, listen to thousands of conversations Dutch people are holding in the proximity of Google Home devices (that’s the reason I keep the microphone of my Sonos turned off).

In our company Slack, I reacted stealing John Gruber’s words about a very different topic:

Get me to the fainting couch. What a shocker.

Posted on 12 Jul 2019

Africa writer

I love the job ad for the new Africa writer for the the Economist.

They don’t care if you’re a journalist, what’s your experience, background, skin color, etc.

They only care about:

original thinking, good analytical skills and, above all, good writing.

👌🏼👍🏼👏🏼

Posted on 07 Jul 2019

Causes of burnout

Today HBR published an article about some causes of burnout1. One struck a cord with me, and, as a physicist that went more into the managerial path, I’m sure I’m not the only one:

Workload […]: assess how well you’re doing in these key areas: planning your workload, prioritizing your work, delegating tasks, saying no, and letting go of perfectionism.

I think they’re all tightly coupled: if you’re good at planning, you must have prioritized properly by knowing what you can and cannot accomplish with your time, and if you have prioritized you must say no and you must have delegated tasks. If you’re good at planning, you also can’t be a perfectionist, because perfection is difficult to plan.

I struggle with three of them mostly: delegating tasks, letting go of perfection, and saying no.

Delegating tasks is hard because I can’t let go of perfection, and because I am usually not good at communicating the end result. And I am not good at communicating the end result because I delegate too little: if I were to delegate more, I would learn — from all the times it went wrong — what things are important to communicate.

Since I know that, I also know that the first times I delegate, the end result will not be what I want: again, I can’t let go of perfection.

Luckily I’m learning the hard way that I need to let go quickly in these key areas:

So, right before the summer, I tricked myself into start delegating. Two things helped me out:

So here I am now, with time in my hands to write this post :)


  1. Six according to the Areas of Worklife model, but I’m sure there’s more, depending who you ask. ↩︎

  2. Though this is frequently the case. ↩︎

Posted on 05 Jul 2019

Algorithms to drive engagement

Brent Simmons doesn’t mince words when he talks about algorithms to drive engagement, honed and “abused” by companies such as Facebook and Twitter:

My hypothesis: these algorithms — driven by the all-consuming need for engagement in order to sell ads — are part of what’s destroying western liberal democracy, and my app will not contribute to that.

Posted on 03 Jul 2019

Open-plan office

I forgot to link to this very good article from David. Having almost 6 kids, I am usually not bothered by noise outside my head, but by noise inside my head.

Noise inside my head comes mostly from not having a long or well defined task. These are the kind of tasks that tends to come in through Slack1.

To fix it I offload most of these tasks — before they reach me — to people who are better at handling them.

Companies that are business-savvy about the hidden costs of interruptions should know that they should be penny foolish and pound wise on this one.


  1. Instant messaging in general: I have nothing against the company besides creating a Mac application that loves to eat all my resources and that doesn’t feel Mac-like. ↩︎

Posted on 02 Jul 2019

Netlified

A couple of days ago I moved the blog and the website over to Netlify.

The reasons are simple:

Every time I’ve read comments on Netlify the message was the same: it’s easy to set up, and once you’ve set it up, you can forget about it.

I gave myself 5’ to try: if I could do it, good, otherwise I would stay on the current setup.

Well, not only I could do it, but the whole project was undistinguishable from magic. They took care of everything for blog.lanzani.nl and lanzani.nl, including serving the naked domain (previously it would be forwarded to www.lanzani.nl, something that always bothered me).

As they integrate with GitHub and hugo, I don’t even need to build the website anymore, they do it for me every time I push the repo!

So the end result is that you can read this blog without fearing that someone has tampered with the content!

Posted on 30 Jun 2019

Get started with miniflux

This is another post that is totally a note for my future self.

I don’t write on this blog often. But what I do, a lot, is read what other people write on their blog. I do that through the wonderful capabilities of RSS.

Doing so in a sane manner involves a few moving parts:

Up until a couple of weeks ago I was using a simple pair: Stringer, hosted on a spare GCP machine, and Unread on iOS. Stringer offers a nice reading experience on the web, so I didn’t need an app for my Mac.

However, as the spare machine wasn’t spare anymore I started looking for something else as I did not like the fact that Stringer was an unmaintained Ruby app anymore. I have nothing against Ruby, but the fact that the app was unmaintained meant running a potentially insecure application.

There are many RSS readers as a service since Google Reader shut down:

The only “problem” is that these services cost from approximately $2 to $5 a month. Can I do something for free?

At first I thought about running stringer on one of my Raspberry Pis. They are pretty powerful and I don’t have that many feeds I need to read.

But if I do that, I possibly want to have everything working in a semi-automatic fashion, so that there’s little to no manual work if the SD is my Raspberry Pi goes south.

The easiest solution — for single machine scenarios and where seconds of downtime are OK — is to use Docker with docker-compose.

This is where, however, Ruby and the Ruby version stringer uses (2.3.3) are painful:

If you’re a bit like me, the above feels like a chore and change of many headaches (that’s probably why all those RSS as a service services exist in the first place).

So I turned to Reddit to see what others are doing. While searching here and there, I came across a thread where they mention miniflux.

When I looked at the website, I couldn’t believe it: it has everything I need and then some more:

Requirements

Now that I have settled down on the server, what else do I need?

The solution

After a bit of googling, I’ve come up with the following folder structure and files to serve my needs:

.
├── data
│   └── nginx
│       └── app.conf
├── docker-compose.yml
└── init-letsencrypt.sh

Let’s see the content of each file.

version: '3'
services:
  database.postgres:
    image: postgres:9.6-alpine
    container_name: postgres
    ports:
      - 5432:5432
    environment:
      - POSTGRES_PASSWORD=<insert_pg_password>
      - POSTGRES_USER=miniflux
      - POSTGRES_DB=miniflux
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    restart: always

  service.rss:
    image: miniflux/miniflux:latest
    container_name: miniflux
    ports:
      - 8080:8080
    depends_on:
      - database.postgres
    environment:
      - DATABASE_URL=postgres://miniflux:<insert_pg_password>@database.postgres:5432/miniflux?sslmode=disable
      - RUN_MIGRATIONS=1
      - CREATE_ADMIN=1
      - ADMIN_USERNAME=admin
      - ADMIN_PASSWORD=<insert_miniflux_password>
    restart: always

  nginx:
    image: nginx
    restart: unless-stopped
    volumes:
      - ./data/nginx:/etc/nginx/conf.d
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    ports:
      - "80:80"
      - "443:443"
    command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"

  certbot:
    image: tobi312/rpi-certbot
    restart: unless-stopped
    volumes:
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

  watchtower:
    image: v2tec/watchtower:armhf-latest
    restart: always
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /root/.docker/config.json:/config.json
    command: --interval 604800

The docker-compose.yml contains quite some images:

For nginx the app.conf file is needed. Its content is

server {
    listen 80;
    server_name <my_domain>;
    server_tokens off;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    server_name <my_domain>;
    server_tokens off;
    resolver 127.0.0.11;
    set $upstream service.rss:8080;

    ssl_certificate /etc/letsencrypt/live/<my_domain>/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/<my_domain>/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location / {
        proxy_pass  http://$upstream;
        proxy_set_header    Host                $http_host;
        proxy_set_header    X-Real-IP           $remote_addr;
        proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
        proxy_set_header  X-Forwarded-Ssl     on;
        proxy_set_header  X-Forwarded-Proto   $scheme;
        proxy_set_header  X-Frame-Options     SAMEORIGIN;

        client_max_body_size        100m;
        client_body_buffer_size     128k;

        proxy_buffer_size           4k;
        proxy_buffers               4 32k;
        proxy_busy_buffers_size     64k;
        proxy_temp_file_write_size  64k;
    }
}

There’s not much to explain here. The last snippet is the init-letsencrypt.sh. The script “bootstraps” nginx for the first time: since we want https, but we cannot have it without certificates, but we cannot ask certificates without a running nginx, this script creates fake certificates, start nginx, removes the certificates, and then request real ones through letsencrypt. The content is quite long, but here you go:

#!/bin/bash

if ! [ -x "$(command -v docker-compose)" ]; then
  echo 'Error: docker-compose is not installed.' >&2
  exit 1
fi

domains=(<my_domain>)
rsa_key_size=4096
data_path="./data/certbot"
email="" # Adding a valid address is strongly recommended
staging=0 # Set to 1 if you're testing your setup to avoid hitting request limits

if [ -d "$data_path" ]; then
  read -p "Existing data found for $domains. Continue and replace existing certificate? (y/N) " decision
  if [ "$decision" != "Y" ] && [ "$decision" != "y" ]; then
    exit
  fi
fi


if [ ! -e "$data_path/conf/options-ssl-nginx.conf" ] || [ ! -e "$data_path/conf/ssl-dhparams.pem" ]; then
  echo "### Downloading recommended TLS parameters ..."
  mkdir -p "$data_path/conf"
  curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/options-ssl-nginx.conf > "$data_path/conf/options-ssl-nginx.conf"
  curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/ssl-dhparams.pem > "$data_path/conf/ssl-dhparams.pem"
  echo
fi

echo "### Creating dummy certificate for $domains ..."
path="$data_path/conf/live/$domains"
mkdir -p "$data_path/conf/live/$domains"
openssl req -x509 -nodes -newkey rsa:1024 -days 1 \
  -keyout $path/privkey.pem \
  -out $path/fullchain.pem \
  -subj '/CN=localhost'
echo


echo "### Starting nginx ..."
docker-compose up --force-recreate -d nginx
echo

echo "### Deleting dummy certificate for $domains ..."
docker-compose run --rm --entrypoint "\
  rm -Rf /etc/letsencrypt/live/$domains && \
  rm -Rf /etc/letsencrypt/archive/$domains && \
  rm -Rf /etc/letsencrypt/renewal/$domains.conf" certbot
echo


echo "### Requesting Let's Encrypt certificate for $domains ..."
#Join $domains to -d args
domain_args=""
for domain in "${domains[@]}"; do
  domain_args="$domain_args -d $domain"
done

# Select appropriate email arg
case "$email" in
  "") email_arg="--register-unsafely-without-email" ;;
  *) email_arg="--email $email" ;;
esac

# Enable staging mode if needed
if [ $staging != "0" ]; then staging_arg="--staging"; fi

docker-compose run --rm --entrypoint "\
  certbot certonly --webroot -w /var/www/certbot \
    $staging_arg \
    $email_arg \
    $domain_args \
    --rsa-key-size $rsa_key_size \
    --agree-tos \
    --force-renewal" certbot
echo

echo "### Reloading nginx ..."
docker-compose exec nginx nginx -s reload

For this and for app.conf file I took inspiration from the nginx-certbot repository with some modification: I’m using rpi-certbot instead of certbot and the openssl utility that comes with the Raspberry (if it doesn’t, use sudo apt-get install openssl to get it).

Outside the Raspberry

The outside world need to know where to find your Raspberry and should be able to get there. Doing so is outside the scope of this post, but in general

Start everything

Once all these files are in place, you are in the right folder, and you have updated the various variables marked with <> (passwords and domain name) in the files above you can get rolling with

curl -sSL https://get.docker.com | sh
pip install --user docker-compose
bash init-letsencrypt.sh
docker-compose up

Now visit your (sub)domain, use admin as the user and the password you have chosen to log in. Enjoy the rest!

Posted on 28 Jun 2019

Git patch workflow

This is totally a note for my future self.

Sometimes when working with git I find myself having to create a patch, because I had to merge in my feature branch more than once, but I want to have a single commit when doing a PR.

Assuming I want to merge against master, and my branch is called feature, I can do the following

git checkout feature
git merge master
git diff master..feature > patch.diff
git checkout master  # the new branch should stem from master
git checkout -b feature-patch  # need a different name
git apply --ignore-space-change --ignore-whitespace patch.diff
git add .  # assuming it's a clear working directory, besides the branch
git commit -m "Add reassuring commit message here"
git push -f origin feature-patch:feature  # this will push feature-patch on the feature branch

It seems involved, but once you get the hang of it, it’s pretty fast.

Posted on 21 Jun 2018

A biased view of the whole Mac vs PC discussion

The Mac vs PC debate is practically as old as the youngest of the two platform. I’ve tried to take a biased look at the whole thing again.

My first machine was an IBM x286. I was 6 years old and our neighbor was working for IBM and he thought my brother and I would be interested in playing with a computer. Boy, was he right!

For practically 15 years I’ve use DOS, Windows 3.1, 95, 98, and Windows XP.

When I started university, I got myself an Acer laptop. What a piece of junk that was. After a couple of months (in 2005) I’ve wiped it and installed the second Ubuntu version ever released1. At the university we were using Scientific Linux: Ubuntu felt like fresh air.

When I decided to move to the Netherlands in 2006, I’ve bought my first Mac. What convinced me was the iPod: it was so more intuitive than any other electronic device used up to that moment that I thought that if Macs were half that good, I was missing out.

I was right. My first Mac was an iBook G4. The battery would last 7 hours and after replacing the hard drive with an 80GB 7200rpm variant and upgrading the RAM to 1GB (IIRC) it was flying compared to the Acer.

That wasn’t the only great thing about the Mac. Everything felt as good as the iPod.

I’ve kept using Macs during my PhD, with various iMacs, Macbook Pros, Macbook Airs, and what not. When I first got into industry I had to use a Dell with Windows 7 Enterprise Edition. It was a piece of junk, commodity enterprise laptop.

Once I joined GoDataDriven I immediately got a Macbook Pro.

In the meantime, however, something happened. Microsoft was changing course, developing an open source friendly attitude and people were kind of discontent of the hit Apple software quality was taking, reportedly due to the huge success that the iPhone is.

After two year I decided to get a Dell XPS 15" for work. I wanted to challenge myself and see how far could I go using Windows (10). After two years of use, I went back to a Mac. Why?

Where the Dell shined

There are a number of area’s where the Dell shined for me. The Dell:

On the other hand the Dell runs Windows, and this has also a number of advantages:

Given all the above, I could work on my laptop just fine for two years. I installed all the various Python packages, virtualenvs and whatnot (without Anaconda), Scala, Spark, Docker, and databases such as Postgres and MySQL. I even got PyCuda working with the NVidia GPU I had.

Verdict: If you want to use a Windows machine to use the above, you will be fine. Don’t drink too much Apple kool aid.

Where the Mac shines

That said: why did I come back? The single, biggest factor, is applications. I think macOS has much better frameworks to develop applications.

It is also true that third party apps usually cost more, but they give me a much better experience. In particular I love:

Besides apps there are a lot of other touches that I really enjoy about the Mac:

Where the Mac falls short

Not all is good in macOS land however:

As for work: I could install all the stuff I wanted, excluding PyCuda because, guess what, these things don’t ship with a NVidia card, no matter how much money you have.

Conclusion

Well, I already gave it away: I’m back to Mac, apps being the biggest factor, but I gained a lot of nice touches in the switch!


  1. Feeling old now. ↩︎

  2. Some things are worse, such as the trackpad, some are better, such as the 4K screen. ↩︎

  3. There are third party applications in Windows that offer the same behavior, but having it built in the OS is always more stable. ↩︎

Posted on 03 Nov 2017
4/10