When it comes to software maintenance tradeoffs, most of what I laid out in “The why & when of software releases” remains accurate. An ‘iron triangle’ exists between speed, stability, and ease of maintenance – it’s impossible to prioritize all three.

I historically prioritize stability, specifically via bugfix-oriented release branches – users can stick to a given minor version even after subsequent feature releases appear, with the implied contract that they can install updates from those branches without anything breaking.

But as I’ve burnt out, I lean more towards ease of maintenance (at least when push comes to shove) though I still do my best to backport fixes anytime it’s easy enough.

Today’s post is about a specific conflict between stability and ease of maintenace, as seen in my efforts to drop Python 2.6 and 3.3 support from Paramiko.

That is not dead which can be eternally supported

Paramiko 2.0 came out in spring 2016, heralding a switch of crypto backend from PyCrypto to Cryptography (though no API incompatibilities were introduced). At the time, my commitment to Python 2.6 was still firm, despite slowly decreasing support elsewhere.

By late 2017, the combination of a vastly shrunken 2.6 user pool and similarly decreased ecosystem support (in Cryptography especially) finally prompted me to drop it too, in Paramiko 2.4. However, because of the stability promise, I couldn’t extend this cut to 2.0-2.3.

At the time, this only required minor tweaks to those branches’ copies of our (Travis-)CI config. Code backported to old branches would still be tested under Python 2.6; modern code was freed up; everybody was happy.

But with enough pain, even stability may die

2018 only saw a few bugfix releases, spaced a number of months apart, but each one brought with it more dead leaves in the dependency tree (both runtime and development). I now have a very different .travis.yml between the 2.0-2.3 branches and 2.4-master, and seemingly every backport turns up new, exciting frustrations.

I’m clearly at the implicit inflection point where stability is no longer worth prioritizing over ease of maintenance, for this specific backwards compatibility ‘barrier’ – so the question becomes not whether to make further cuts to Paramiko 2.0-2.3 support, but how.

The devil’s choice

There are two obvious options, both with downsides:

  • Continue backporting bugfixes, but remove Python 2.6 from the CI configuration so we can use Python 2.7-only test/lint/etc tools across branches; or
  • Stop supporting Paramiko 2.0-2.3 entirely, requiring users to upgrade to 2.4+ for any level of support.

Continue backporting, but drop testing for Python 2.6

This is the more-stable option: continue backporting everything to 2.0+, but modernize the dev requirements and drop the 2.6 test-matrix cell. Ideally, even if this is done, we’ll continue to patch accidental 2.6 breakages (e.g. introduction of set literals) but rely on affected users to report such.

Unfortunately, it’s a near guarantee that that sort of bug will appear, especially as the average contributor rarely thinks about anything but their own interpreter version. And bugfixes which cause syntax errors may as well not exist.

On the other hand, Python 2.7 users will still value getting such backports – and there are many more users in this camp than the other. Is it OK to violate that backwards compatibility contract, as long as it’s only for a small minority? That’s (partly) the question.

Support only Paramiko 2.4 and up

This is the “clean” option: declare 2.0-2.3 end-of-lifed, and if you want patches, get onto Paramiko 2.4 or newer.

This is less work generally (temporarily smaller support window, and a clear backstop for future backports) and specifically (don’t have to do more work on the old branches). Plus it means Python 2.6 users pinned to e.g paramiko<2.4 are at no risk of receiving invalid bugfixes. (Or any other fixes…)

The downside: stable users – even those on Python 2.7 or 3.x – are left out in the cold. That said, as much as I default to providing stable backports, the reality is that upgrading from (say) Paramiko 2.0 to 2.4 should be minimally disruptive since I practice semantic versioning. And the support window will grow again as 2.5, etc come out.

What to do?

I don’t know yet, though I don’t have to decide ‘til the next time a backport causes 2.6 issues. But I kept wanting to write multiple long, boring tweets about this (due to some faves showing up for this original tweet), so you got a long, boring blog post instead.

I’m also curious to see if anyone’s got alternative approaches I’ve plumb forgot. Let me know on Twitter, IRC or in comments!