A place to cache linked articles (think custom and personal wayback machine)
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

index.html 54KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang="en">
  4. <!-- Has to be within the first 1024 bytes, hence before the `title` element
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset="utf-8">
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name="viewport" content="width=device-width,initial-scale=1">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>Engineering for Slow Internet (archive) — David Larlet</title>
  13. <meta name="description" content="Publication mise en cache pour en conserver une trace.">
  14. <!-- That good ol' feed, subscribe :). -->
  15. <link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
  16. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  17. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
  18. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
  19. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
  20. <link rel="manifest" href="/static/david/icons2/site.webmanifest">
  21. <link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
  22. <link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
  23. <meta name="msapplication-TileColor" content="#f7f7f7">
  24. <meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
  25. <meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
  26. <meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
  27. <!-- Is that even respected? Retrospectively? What a shAItshow…
  28. https://neil-clarke.com/block-the-bots-that-feed-ai-models-by-scraping-your-website/ -->
  29. <meta name="robots" content="noai, noimageai">
  30. <!-- Documented, feel free to shoot an email. -->
  31. <link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
  32. <!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
  33. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  34. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  35. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  36. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  37. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  38. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  39. <script>
  40. function toggleTheme(themeName) {
  41. document.documentElement.classList.toggle(
  42. 'forced-dark',
  43. themeName === 'dark'
  44. )
  45. document.documentElement.classList.toggle(
  46. 'forced-light',
  47. themeName === 'light'
  48. )
  49. }
  50. const selectedTheme = localStorage.getItem('theme')
  51. if (selectedTheme !== 'undefined') {
  52. toggleTheme(selectedTheme)
  53. }
  54. </script>
  55. <meta name="robots" content="noindex, nofollow">
  56. <meta content="origin-when-cross-origin" name="referrer">
  57. <!-- Canonical URL for SEO purposes -->
  58. <link rel="canonical" href="https://brr.fyi/posts/engineering-for-slow-internet">
  59. <body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">
  60. <article>
  61. <header>
  62. <h1>Engineering for Slow Internet</h1>
  63. </header>
  64. <nav>
  65. <p class="center">
  66. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  67. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  68. </svg> Accueil</a> •
  69. <a href="https://brr.fyi/posts/engineering-for-slow-internet" title="Lien vers le contenu original">Source originale</a>
  70. <br>
  71. Mis en cache le 2024-05-31
  72. </p>
  73. </nav>
  74. <hr>
  75. <p><em>Hello everyone! I got partway through writing this post while I was still in Antarctica, but I departed
  76. before finishing it.</em></p>
  77. <p><em>I’m going through my old draft posts, and I found that this one was nearly complete.</em></p>
  78. <p><em>It’s a bit of a departure from the normal content you’d find on brr.fyi, but it reflects my
  79. software / IT engineering background.</em></p>
  80. <p><em>I hope folks find this to be an interesting glimpse into the on-the-ground reality of using the Internet
  81. in bandwidth-constrained environments.</em></p>
  82. <p><em>Please keep in mind that I wrote the majority of this post ~7 months ago, so it’s likely that the
  83. IT landscape has shifted since then.</em></p>
  84. <hr>
  85. <p>Welcome back for a <strong>~~bonus post~~</strong> about Engineering for Slow Internet!</p>
  86. <p>For a 14-month period, while working in Antarctica, I had access to the Internet only through an
  87. extremely limited series of satellite links provided by the United States Antarctic Program.</p>
  88. <p>Before I go further, this post requires a special caveat, above and beyond my standard disclaimer:</p>
  89. <hr>
  90. <p><em>Even though I was an IT worker within the United States Antarctic Program,</em> <strong><em>everything</em></strong> <em>I am going to
  91. discuss in this post is based on either publicly-available information, or based on my own observations as a
  92. regular participant living on ice.</em></p>
  93. <p><em>I have not used any internal access or non-public information in writing this post.</em></p>
  94. <p><em>As a condition of my employment, I agreed to a set of restrictions regarding public disclosure
  95. of non-public Information Technology material. I fully intend to honor these restrictions.
  96. These restrictions are ordinary and typical of US government contract work.</em></p>
  97. <p><em>It is
  98. unlikely that I will be able to answer additional questions about matters I discuss in this post.
  99. I’ve taken great care to write as much as I am able to, without disclosing non-public information regarding
  100. government IT systems.</em></p>
  101. <hr>
  102. <p>Good? Ok, here we go.</p>
  103. <p><em>… actually wait, sorry, one more disclaimer.</em></p>
  104. <hr>
  105. <p><em>This information reflects my own personal experience in Antarctica, from August 2022 through December
  106. 2022 at McMurdo, and then from December 2022 through November 2023 at the South Pole.</em></p>
  107. <p><em>Technology moves quickly, and I make no claims that the circumstances of my own specific experience
  108. will hold up over time. In future years, once I’ve long-since forgotten about this post,
  109. please do not get mad at me when the on-the-ground IT experience in Antarctica evolves away from the snapshot
  110. presented here.</em></p>
  111. <hr>
  112. <p>Ok, phew. Here we go for real.</p>
  113. <p>It’s a non-trivial feat of engineering to get <strong>any</strong> Internet at the South Pole! If you’re bored,
  114. check out the <a href="https://www.usap.gov/technology/sctnsouthpolesats.cfm">South Pole Satellite Communications</a>
  115. page on the public USAP.gov website, for an overview of the limited selection of satellites available for
  116. Polar use.</p>
  117. <p>
  118. <a href="/media/engineering-for-slow-internet/south-pole-radomes-01.jpg">
  119. <picture>
  120. <source srcset="/media/engineering-for-slow-internet/south-pole-radomes-01-small.webp" type="image/webp"></source>
  121. <source srcset="/media/engineering-for-slow-internet/south-pole-radomes-01-small.jpg" type="image/jpg"></source>
  122. <img src="/media/engineering-for-slow-internet/south-pole-radomes-01-small.jpg" alt="Radomes 01">
  123. </picture>
  124. <em>South Pole's radomes, out in the RF sector. These radomes contain the equipment
  125. necessary to communicate with the outside world using our primary satellites.</em>
  126. </a>
  127. </p>
  128. <p>If you’re interested, perhaps also look into the
  129. <a href="https://www.usap.gov/news/4685/">2021 Antarctic Subsea Cable Workshop</a>
  130. for an overview of some hurdles associated with running traditional fiber to the continent.</p>
  131. <p><strong><em>I am absolutely not in a position of authority to speculate on the future of Antarctic connectivity!</em></strong>
  132. Seriously. I was a low-level, seasonal IT worker in a large, complex organization.
  133. Do not email me your
  134. ideas for improving Internet access in Antarctica – I am not in a position to do anything with them.</p>
  135. <p>I do agree with the widespread consensus on the matter: There is <strong>tremendous interest</strong> in
  136. improving connectivity to US research stations in Antarctica. I would timidly conjecture that, at some point,
  137. there will be engineering solutions to these problems.
  138. Improved connectivity will eventually arrive in Antarctica,
  139. either through enhanced satellite technologies or through the arrival of fiber to the continent.</p>
  140. <p>But – that world will only exist at some point in the future. Currently, Antarctic connectivity is
  141. <em>extremely limited</em>. What do I mean by that?</p>
  142. <p>Until very recently, at McMurdo, nearly <strong>a thousand people</strong>, plus numerous scientific
  143. projects and operational workloads, all relied on a series of links
  144. that provided max, aggregate speeds of a few dozen megabits
  145. per second to the <strong>entire station</strong>.
  146. For comparison, that’s less bandwidth shared by everyone <strong>combined</strong> than what
  147. everyone <strong>individually</strong> can get on a typical 4g cellular network in an American suburb.</p>
  148. <p>Things <strong>are</strong> looking up! The NSF recently
  149. <a href="https://www.nsf.gov/news/news_summ.jsp?cntn_id=307974&amp;org=OPP">announced</a> some important developments
  150. regarding Starlink at McMurdo and Palmer.</p>
  151. <p>I’m aware that the on-the-ground experience in McMurdo and Palmer is better now than it was even just a year ago.</p>
  152. <p>But – as of October 2023, the situation was still pretty dire at the South Pole.
  153. As far as I’m aware, similar developments regarding Starlink have <strong>not</strong> yet been announced for South Pole Station.</p>
  154. <p>As of October 2023, South Pole had the limitations described above,
  155. <strong>plus</strong> there was only connectivity for a few hours a day, when the satellites rose above the horizon
  156. and the station was authorized to use them.
  157. The satellite schedule generally shifts forward (earlier) by about 4 minutes per day, due to
  158. the <a href="https://en.wikipedia.org/wiki/Sidereal_time">difference between Sidereal time and Solar (Civil) time</a>.</p>
  159. <p>The current satellite schedule can be found online, on the
  160. <a href="https://www.usap.gov/technology/sctnsouthpolesats.cfm">South Pole Satellite Communications</a> page
  161. of the public USAP.gov website. Here’s an example of the schedule from October 2023:</p>
  162. <p>
  163. <a href="/media/engineering-for-slow-internet/satellite-schedule-01.png">
  164. <picture>
  165. <source srcset="/media/engineering-for-slow-internet/satellite-schedule-01.png" type="image/png"></source>
  166. <img src="/media/engineering-for-slow-internet/satellite-schedule-01.png" alt="Satellite Schedule 01">
  167. </picture>
  168. <em>South Pole satellite schedule, for two weeks in October 2023.</em>
  169. </a>
  170. </p>
  171. <p>These small intermittent links to the outside world are shared by <strong>everyone at Pole</strong>, for operational,
  172. science, and community / morale usage.</p>
  173. <p>Complicating matters further is the unavoidable physics of this connectivity.
  174. These satellites are in a high orbit, thousands of miles up. This means high latency. If you’ve used
  175. a consumer satellite product such as HughesNet or ViaSat, you’ll understand.</p>
  176. <p>From my berthing room at the South Pole, it was about <strong>750 milliseconds</strong>, round trip,
  177. for a packet to get to and from a terrestrial US destination.
  178. This is about <strong>ten times</strong> the latency of a round trip
  179. between the US East and West coasts (up to 75 ms).
  180. And it’s about <strong>thirty times</strong> the expected
  181. latency of a healthy connection from your home, on a terrestrial cable or fiber connection,
  182. to most major content delivery networks (up to 25 ms).</p>
  183. <p>Seriously, I can’t emphasize how jarring this is. At my apartment back home, on GPON fiber,
  184. it’s about 3 ms roundtrip to Fastly, Cloudflare, CloudFront, Akamai, and Google.
  185. At the South Pole, the latency was over <strong>two hundred and fifty times greater</strong>.</p>
  186. <p>I can’t go into more depth about how USAP does prioritization, shaping,
  187. etc, because I’m not authorized to share these details. Suffice to say, if you’re an enterprise network
  188. engineer used to working in a bandwidth-constrained environment, you’ll feel right at home with the
  189. equipment, tools, and techniques used to manage Antarctic connectivity.</p>
  190. <p>Any individual trying to use the Internet for community use at the South Pole, as of October 2023,
  191. likely faced:</p>
  192. <ul>
  193. <li>Round-trip latency averaging around 750 milliseconds, with jitter between
  194. packets sometimes exceeding several seconds.</li>
  195. <li>Available speeds, to the end-user device, that range from a couple kbps (yes, you read that right),
  196. up to 2 mbps on a <strong>really good</strong> day.</li>
  197. <li>Extreme congestion, queueing, and dropped packets, far in excess of even the worst oversaturated ISP links
  198. or bufferbloat-infested routers back home.</li>
  199. <li>Limited availability, frequent dropouts, and occasional service preemptions.</li>
  200. </ul>
  201. <p>These constraints <em>drastically</em> impact the modern web experience! Some of it is unavoidable. The link characteristics
  202. described above are truly bleak. But – a lot of the end-user
  203. impact is caused by web and app engineering which fails to take slow/intermittent links
  204. into consideration.</p>
  205. <p>If you’re an app developer reading this, can you tell me, off the top of your head, how your app behaves
  206. on a link with 40 kbps available bandwidth, 1,000 ms latency, occasional jitter of up to 2,000 ms,
  207. packet loss of 10%, and a complete 15-second connectivity dropout every few minutes?</p>
  208. <p>It’s probably not great! And yet – these are real-world performance parameters that I encountered,
  209. under certain conditions, at the South Pole.
  210. It’s normally better than this, but this does occur, and it occurs often enough
  211. that it’s worth taking seriously.</p>
  212. <p>This is what happens when you have a tiny pipe to share among high-priority
  213. operational needs, plus dozens of community users. Operational needs are aggressively prioritized,
  214. and the community soaks up whatever is left.</p>
  215. <p>I’m not expecting miracles here! Obviously no amount of client engineering can make, say, real-time video
  216. conferencing work under these conditions. But – getting a few bytes of text in and out <strong>should</strong> still be possible!
  217. I know it is possible, because some apps are still able to do it. Others are not.</p>
  218. <h2 id="detailed-real-world-example">Detailed, Real-world Example</h2>
  219. <p>One day at the South Pole, I was trying to load the website of <strong><em>&lt;$enterprise_collaboration_platform&gt;</em></strong>
  220. in my browser. It’s <em>huge</em>! It needed to load nearly 20 MB of Javascript, <em>just</em> to render the main screen!
  221. And of course, the app had been updated since last time I loaded it, so all of my browser’s cached assets were
  222. stale and had to be re-downloaded.</p>
  223. <p>Fine! It’s slow, but at least it will work… eventually, right? Browsers do a decent job of handling
  224. slow Internet. Under the hood, the underlying protocols do a decent job at congestion control.
  225. I should get a steady trickle of data. This will be
  226. subject to the negotiated send and receive windows between client and server,
  227. which are based on the current level of congestion on the link, and which are further influenced by any
  228. shaping done by middleware along the way.</p>
  229. <p>It’s a complex webapp, so the app developer would also need to implement some
  230. of their own retry logic. This allows for recovery in the event that individual assets fail,
  231. especially for those long, multi-second total connectivity dropouts.
  232. But eventually, given enough time, the transfers should complete.</p>
  233. <p>Unfortunately, this is where things broke down and got really annoying. <em>The developers implemented
  234. a global failure trigger somewhere in the app.</em>
  235. If the app didn’t fully load within the parameters specified by the developer
  236. (time? number of retries? I’m not sure.), then the app
  237. <strong>stopped, gave up, redirected you to an error page, dropped all the loading progress you’d made, and
  238. implemented aggressive cache-busting countermeasures for next time you retried.</strong></p>
  239. <p>
  240. <a href="/media/engineering-for-slow-internet/load-error-01.png">
  241. <picture>
  242. <source srcset="/media/engineering-for-slow-internet/load-error-01.png" type="image/png"></source>
  243. <img src="/media/engineering-for-slow-internet/load-error-01.png" alt="Load Success">
  244. </picture>
  245. <em>The app wasn't loading fast enough, and the developers decided that the app should give up instead
  246. of continuing to load slowly.</em>
  247. </a>
  248. </p>
  249. <p>I cannot tell you how frustrating this was! Connectivity at the South Pole was never going to meet the
  250. performance expectations set by engineers using a robust terrestrial Internet connection.
  251. It’s not a good idea to hardcode a single, static, global expectation for how long
  252. 20 MB of Javascript should take to download.
  253. Why not let me load it at my own pace? I’ll get there
  254. when I get there. <em>As long as data is still moving, however slow, just let it run.</em></p>
  255. <p>But – the developers decided that if the app didn’t load within the parameters they set,
  256. I couldn’t use it at all.
  257. And to be clear – this was primarily a <strong>messaging</strong> app. The actual content payload here, when
  258. the app is running and I’m chatting with my friends, is measured in <em>bytes</em>.</p>
  259. <p>As it turns out, our Internet performance at the South Pole was <em>right on the edge</em> of what the app
  260. developers considered “acceptable”. So, if I kept reloading the page, and if I kept
  261. letting it re-download the same 20 MB of Javascript, and if I kept putting up with the
  262. developer’s cache-busting shenanigans, <em>eventually</em> it finished before the artificial failure criteria.</p>
  263. <p>What this means is that I wasted <em>extra</em> bandwidth doing all these useless reloads, and it took sometimes
  264. <strong>hours</strong> before I was able to use the app. All of this hassle, even though, if left alone,
  265. I could complete the necessary data transfer in 15 minutes.
  266. Several hours (and a shameful amount of retried Javascript) later, I was finally able to send a short,
  267. text-based message to my friends.</p>
  268. <p>
  269. <a href="/media/engineering-for-slow-internet/load-success-01.png">
  270. <picture>
  271. <source srcset="/media/engineering-for-slow-internet/load-success-01.png" type="image/png"></source>
  272. <img src="/media/engineering-for-slow-internet/load-success-01.png" alt="Load Success">
  273. </picture>
  274. <em>A successful webapp load, after lots of retrying. 809 HTTP requests, 51.4 MB of data transfer,
  275. and 26.5 minutes of loading... </em>
  276. </a>
  277. </p>
  278. <p>
  279. <a href="/media/engineering-for-slow-internet/chat-success-01.png">
  280. <picture>
  281. <source srcset="/media/engineering-for-slow-internet/chat-success-01.png" type="image/png"></source>
  282. <img src="/media/engineering-for-slow-internet/chat-success-01.png" alt="Chat Success">
  283. </picture>
  284. <em>...all so that I could send a 1.8 KB HTTPS POST...</em>
  285. </a>
  286. </p>
  287. <p>
  288. <a href="/media/engineering-for-slow-internet/chat-content-01.png">
  289. <picture>
  290. <source srcset="/media/engineering-for-slow-internet/chat-content-01.png" type="image/png"></source>
  291. <img src="/media/engineering-for-slow-internet/chat-content-01.png" alt="Chat Content">
  292. </picture>
  293. <em>...containing a 6-byte message.</em>
  294. </a>
  295. </p>
  296. <p>Does this webapp <strong>really need</strong> to be 20 MB? What all is
  297. being loaded that could be deferred until it is needed, or included in an “optional” add-on bundle?
  298. Is there a possibility of a “lite” version, for bandwidth-constrained users?</p>
  299. <p>In my 14 months in Antarctica, I collected <strong>dozens</strong> of examples of apps like this, with artificial
  300. constraints built in that rendered them unusable or borderline-unusable.</p>
  301. <p>For the rest of this post, I’ll outline some of my major frustrations, and what I would have liked
  302. to see instead that would mitigate the issues.</p>
  303. <p>I understand that not every app is in a position to implement all of these! If you’re
  304. a tiny app, just getting off the ground, I don’t expect you to spend all of your development time optimizing
  305. for weirdos in Antarctica.</p>
  306. <p>Yes, Antarctica is an edge case! Yes, 750 ms / 10% packet loss / 40 kbps <strong>is</strong> rather extreme.
  307. But the South Pole was not <strong>uniquely</strong> bad. There are entire commercial marine vessels that rely on older
  308. <a href="https://www.inmarsat.com/">Inmarsat</a> solutions for a few hundred precious kbps of data while at sea.
  309. There’s someone at a remote research site deep in the mountains right now, trying to load your app on
  310. a <a href="https://www.iridium.com/products/thales-missionlink-700/">Thales MissionLink</a> using the Iridium
  311. Certus network at a few dozen kbps.
  312. There are folks behind misconfigured routers, folks with flaky
  313. wifi, folks stuck with fly-by-night WISPs delivering sub-par service. Folks who still use dial-up
  314. Internet connections over degraded copper phone lines.</p>
  315. <p>These folks are worthy of your consideration. At the very least, you should make an effort to avoid
  316. <strong>actively interfering</strong> with their ability to use your products.</p>
  317. <p>So, without further ado, here are some examples of development patterns that routinely caused me grief at
  318. the South Pole.</p>
  319. <h2 id="hardcoded-timeouts-hardcoded-chunk-size">Hardcoded Timeouts, Hardcoded Chunk Size</h2>
  320. <p>As per the above example, <strong>do not hardcode your assumptions about how long a given payload will take to
  321. transfer, or how much you can transfer in a single request.</strong></p>
  322. <ol>
  323. <li>If you have the ability to measure whether bytes are flowing, and they are, <strong>leave them alone</strong>, no
  324. matter how slow. Perhaps show some UI indicating what is happening.</li>
  325. <li>If you are doing an HTTPS call,
  326. fall back to a longer timeout if the call fails. Maybe it just needs more time under current network
  327. conditions.</li>
  328. <li>If you’re having trouble moving large amounts of data in a single HTTPS call, break it up. Divide the
  329. content into chunks, transfer small chunks at a time, and <strong>diligently keep track of the progress</strong>, to
  330. allow resuming and retrying small bits without losing all progress so far. Slow, steady,
  331. incremental progress is better than a one-shot attempt to transfer a huge amount of data.</li>
  332. <li>If you can’t get an HTTPS call done successfully, do some troubleshooting. Try DNS, ICMP,
  333. HTTP (without TLS), HTTPS to a known good status endpoint, etc. This information might be helpful
  334. for troubleshooting, and it’s better than blindly retrying the same end-to-end HTTPS call.
  335. This HTTPS call requires a bunch of under-the-hood stuff to be working properly. Clearly it’s not,
  336. so you should make an effort to figure out why and let your user know.</li>
  337. </ol>
  338. <p>A popular desktop application tries to download some configuration information from the vendor’s website
  339. at startup. There is a hardcoded timeout for the HTTPS call. <strong>If it fails, the app will not load.</strong> It’ll
  340. just keep retrying the same call, with the same parameters, forever. It’ll sit on the loading page,
  341. without telling you what’s wrong. I’ve confirmed this is what’s happening by reading the logs.</p>
  342. <p>
  343. <a href="/media/engineering-for-slow-internet/hardcoded-timeout-02.png">
  344. <picture>
  345. <source srcset="/media/engineering-for-slow-internet/hardcoded-timeout-02.png" type="image/png"></source>
  346. <img src="/media/engineering-for-slow-internet/hardcoded-timeout-02.png" alt="Hardcoded Timeout 02">
  347. </picture>
  348. <em>Excerpt from debug log for a commercial desktop application, showing a request timing out
  349. after 15 seconds.</em>
  350. </a>
  351. </p>
  352. <p>Luckily, if you kept trying, the call would eventually make it through under network conditions I
  353. experienced at the South Pole.</p>
  354. <p>It’s frustrating
  355. that just a single hardcoded timeout value, in an otherwise perfectly-functional and enterprise-grade
  356. application, can render it almost unusable. The developers could have:</p>
  357. <ol>
  358. <li>Fallen back to increasingly-long timeouts to try and get a successful result.</li>
  359. <li>Done some connection troubleshooting to infer more about the current network environment, and
  360. responded accordingly.</li>
  361. <li>Shown UX explaining what was going on.</li>
  362. <li>Used a cached or default-value configuration, if it couldn’t get the live one, instead of simply
  363. refusing to load.</li>
  364. <li>Provided a mechanism for the user to manually download and install the required data, bypassing the
  365. app’s built-in (and naive) download logic.</li>
  366. </ol>
  367. <h3 id="example-2---chat-apps">Example 2 - Chat Apps</h3>
  368. <p>A popular chat app (“app #1”) maintains a websocket for sending and receiving data.
  369. The initialization process for that websocket uses a <strong>hardcoded 10-second timeout</strong>.
  370. Upon cold boot, when network conditions are especially congested, that websocket setup can sometimes take
  371. more than 10 seconds! We have to do a full TCP handshake, then set up a TLS session, then set up the
  372. websocket, then do initial signaling over the websocket.
  373. Remember – under some conditions, each individual roundtrip at the South Pole took multiple seconds!</p>
  374. <p>If the 10-second timeout elapses, the app simply does not work. It enters a very long backoff state
  375. before retrying. The UX does not clearly show what is happening.</p>
  376. <p>
  377. <a href="/media/engineering-for-slow-internet/hardcoded-timeout-01.png">
  378. <picture>
  379. <source srcset="/media/engineering-for-slow-internet/hardcoded-timeout-01.png" type="image/png"></source>
  380. <img src="/media/engineering-for-slow-internet/hardcoded-timeout-01.png" alt="Hardcoded Timeout 01">
  381. </picture>
  382. <em>Excerpt from debug log for chat app #1, showing the hardcoded 10-second timeout. We did indeed
  383. have Internet access at this time -- it was just too congested to complete an entire TCP handshake +
  384. TLS negotiation + websocket setup + request within this timeframe.
  385. With a few more seconds, it may have finished.</em>
  386. </a>
  387. </p>
  388. <p>On the other hand, a competitor’s chat app (“app #2”) does <em>very well</em> in extremely degraded network
  389. conditions! It has multiple strategies for sending network requests, for resilience against
  390. certain types of degradation.
  391. It aggressively re-uses open connections. It dynamically adjusts timeouts.
  392. In the event of a failure, it intelligently chooses a retry cadence. And, throughout all of this,
  393. it has clear UX explaining current network state.</p>
  394. <p>The end result is that I could often use app #2 in network conditions when I could not use app #1. Both of
  395. them were just transmitting plain text! Just a few actual bytes of
  396. content! And even when I could not use app #2, it was at least telling
  397. me what it was trying to do. App #1 is written naively, with baked-in assumptions
  398. about connectivity that simply did not hold true at the South Pole.
  399. App #2 is written well, and it responds gracefully to the conditions it encounters in the wild.</p>
  400. <h3 id="example-3---incremental-transfer">Example 3 - Incremental Transfer</h3>
  401. <p>A chance to talk about my own blog publishing toolchain!</p>
  402. <p>The site you’re reading right now is a static Jekyll blog.
  403. Assets are stored on S3 and served through CloudFront. I build the static files locally here on my laptop,
  404. and I upload them directly to S3. Nothing fancy. No servers, no QA environment, no build system,
  405. no automated hooks, nothing dynamic.</p>
  406. <p>Given the extreme connectivity constraints at the South Pole,
  407. I wrote a Python script for publishing to S3 that
  408. worked well in the challenging environment. It uses the S3 API to upload assets in small chunks.
  409. It detects and resumes failed uploads without losing progress. It waits until everything is safely uploaded
  410. before publishing the new version.</p>
  411. <p>If I can do it, unpaid, working alone, for my silly little hobby blog, in 200 lines of Python…
  412. surely your team of engineers can do so for your flagship webapp.</p>
  413. <p>It’s amazing the usability improvements that come along with some proactive engineering. I had friends at Pole
  414. with blogs on commercial platforms, and who routinely shared large files to social
  415. media sites. They had
  416. to carefully time their day to maximize the likelihood of a successful “one-shot” upload, during a
  417. satellite window, using their platform’s poorly-engineered publishing tools.
  418. Often it took several retries, and it’s not always clear what was happening at every step of the process
  419. (Is the content live? Did the upload finish? Is it safe / should I hit “Post” again?).</p>
  420. <p>Meanwhile, I was able to harvest whatever connectivity I could find.
  421. I got a few kilobytes uploaded here and there,
  422. whenever it was convenient. If a particular chunked POST failed, no worries!
  423. I could retry or resume, with minimal lost progress, at a later time.
  424. Once it was all done and staged, I could safely publish the new version.</p>
  425. <p>
  426. <a href="/media/engineering-for-slow-internet/upload-process-01.png">
  427. <picture>
  428. <source srcset="/media/engineering-for-slow-internet/upload-process-01.png" type="image/png"></source>
  429. <img src="/media/engineering-for-slow-internet/upload-process-01.png" alt="Upload Process">
  430. </picture>
  431. <em>My custom publishing script for this blog, to handle intermittent and unreliable Internet.</em>
  432. </a>
  433. </p>
  434. <h2 id="bring-your-own-download">Bring-Your-Own-Download</h2>
  435. <p><strong>If you’re going to build in a downloader into your app, you have a high bar for quality that you have
  436. to meet.</strong> Otherwise, it’s going to fail in profoundly annoying or catastrophic ways.</p>
  437. <p>If I had to give one piece of advice:
  438. <strong><em>Let the user break out of your in-app downloader and use their own, if at all possible.</em></strong></p>
  439. <p>Provide a manual download link, ideally one that leads to whatever differential patch file the app was going
  440. to download. Don’t punish the user by making them download the full installer, just because your in-app patch
  441. downloader doesn’t meet their needs.</p>
  442. <p>This has the following benefits:</p>
  443. <ol>
  444. <li>If your downloader fails, the user can still get the file manually, using a more robust downloader
  445. of their choice, such as a web browser.</li>
  446. <li>The user can download the file one time, and share it with multiple devices.</li>
  447. <li>The user can download the file on a different computer than the one running the application.</li>
  448. <li>The user has the flexibility to schedule or manage the download based on whatever constraints they face.</li>
  449. </ol>
  450. <p><strong>Why is this all so important when considering users at the South Pole?</strong></p>
  451. <p>Downloads <strong>are</strong> possible at the South Pole, but they are subject to unique constraints. The biggest
  452. constraint is the lack of 24x7 Internet. While I was there, I <em>knew</em> we would lose Internet access at a
  453. certain time!</p>
  454. <p>It’s a frustrating reality: with most apps that do their own downloads, we were powerless to do
  455. anything about this known break in connectivity. We just had to sit there and watch it fail, and
  456. often watch all our progress be lost.</p>
  457. <p>Let’s say I had a 4-hour window, every day, during which I could do (very slow!!) downloads. If the total
  458. amount of data I could download in those 4 hours was <strong>less</strong> than the total size of the payload
  459. I was downloading, then there is <em>no way</em> I could complete the download in one shot!
  460. I’d <em>have</em> to split it over multiple Internet windows. Often the app wouldn’t let me do so.</p>
  461. <p>And that’s not even considering the fact that access might be unreliable during that time! What if the
  462. underlying connection dropped and I had to resume the download? What if my plans changed and I needed
  463. to pause? I didn’t want to waste whatever precious little progress I’d made so far.</p>
  464. <p>A lot of modern apps include their own homegrown, artisanal, in-app downloaders for large payloads.
  465. By “in-app downloader”, I’m referring to the system that obtains the content for automatic updates,
  466. patches, content database updates, etc. The common theme here is
  467. that the app transparently downloads content for you, without you being exposed to the underlying
  468. details such as the URL or raw file. This includes UI patterns such as <em>Check for updates</em>,
  469. <em>Click here to download new version</em>, etc.</p>
  470. <p>
  471. <a href="/media/engineering-for-slow-internet/in-app-downloader-01.png">
  472. <picture>
  473. <source srcset="/media/engineering-for-slow-internet/in-app-downloader-01.png" type="image/png"></source>
  474. <img src="/media/engineering-for-slow-internet/in-app-downloader-01.png" alt="In-App Downloader 01">
  475. </picture>
  476. <em>An in-app download notification for a popular chat application. This app apparently wants to
  477. download 83 MB of data in the background! This is tough at the South Pole. Will the UI be
  478. accommodating of the unique constraints at Pole?</em>
  479. </a>
  480. </p>
  481. <p>Unfortunately, most of these in-app downloaders are woefully ill-equipped for the task! Many of them lack
  482. pause/resume functionality, state notifications, retry logic, and progress tracking. Many of them have
  483. frustrating restrictions, such as time limits for downloading the payload. While most of these issues
  484. are mere annoyances in the land of fast Internet, at the South Pole, they can make or break
  485. the app entirely.</p>
  486. <p>
  487. <a href="/media/engineering-for-slow-internet/in-app-downloader-progress-01.png">
  488. <picture>
  489. <source srcset="/media/engineering-for-slow-internet/in-app-downloader-progress-01.png" type="image/png"></source>
  490. <img src="/media/engineering-for-slow-internet/in-app-downloader-progress-01.png" alt="In-App Downloader Progress 01">
  491. </picture>
  492. <em>Unfortunately, that's a resounding "no". There's no speed indication, no ETA, no pause button,
  493. no cancel button, no URL indication (so we can download manually), and no way to get at the underlying file.</em>
  494. </a>
  495. </p>
  496. <p>It was always frustrating to face down one of these interfaces, because I knew how much time,
  497. and data transfer, was going to be wasted.</p>
  498. <p>
  499. <a href="/media/engineering-for-slow-internet/in-app-downloader-failure-01.png">
  500. <picture>
  501. <source srcset="/media/engineering-for-slow-internet/in-app-downloader-failure-01.png" type="image/png"></source>
  502. <img src="/media/engineering-for-slow-internet/in-app-downloader-failure-01.png" alt="In-App Downloader Failure 01">
  503. </picture>
  504. <em>Darn, it failed! This was expected -- an uninterrupted 83 MB download is tough.
  505. Unfortunately, all progress has been lost, and now it's not even offering a patch on the
  506. retry -- the download size has ballooned to 133 MB, the size of the full installer.</em>
  507. </a>
  508. </p>
  509. <p>Every app that includes an in-app downloader has to compete with an extraordinarily high bar for usability:
  510. <strong>web browsers</strong>.</p>
  511. <p>Think about it! Every modern web browser includes a download manager that contains
  512. Abort, Pause, and Resume functionality. It allows you to retry failed
  513. downloads (assuming the content URL doesn’t include an expiring token).
  514. It clearly shows you current status, download speed, and estimated time remaining. It allows you to choose
  515. where you save the underlying file, so you can copy it around if needed. And – it doesn’t include arbitrary
  516. performance cutoffs! If you really want to download a multi-gigabyte file at 60 kbps, go for it!</p>
  517. <p>
  518. <a href="/media/engineering-for-slow-internet/browser-download-progress-01.png">
  519. <picture>
  520. <source srcset="/media/engineering-for-slow-internet/browser-download-progress-01.png" type="image/png"></source>
  521. <img src="/media/engineering-for-slow-internet/browser-download-progress-01.png" alt="Browser Download Progress 01">
  522. </picture>
  523. <em>A fully-featured download experience, from a major web browser. Downloading an app installer from
  524. the vendor's website. Note the status, speed, estimated
  525. time remaining, full URL, and pause / cancel buttons.</em>
  526. </a>
  527. </p>
  528. <p>
  529. <a href="/media/engineering-for-slow-internet/browser-partial-file-01.png">
  530. <picture>
  531. <source srcset="/media/engineering-for-slow-internet/browser-partial-file-01.png" type="image/png"></source>
  532. <img src="/media/engineering-for-slow-internet/browser-partial-file-01.png" alt="Browser Partial File 01">
  533. </picture>
  534. <em>A partially-downloaded file, for the above-mentioned download.</em>
  535. </a>
  536. </p>
  537. <p>Here are a few more examples of where in-app downloaders caused us grief.</p>
  538. <h3 id="example-1---macos-updates">Example 1 - macOS Updates</h3>
  539. <p>It’s no secret that macOS updates are huge. This is sometimes even annoying back home, and it was
  540. much worse at the South Pole.</p>
  541. <p>The patch size for minor OS updates is usually between 0.5 and 1.5 gigabytes. Major OS upgrade patches are
  542. sometimes 6+ gigabytes. Additional tools, such as Xcode, are often multiple gigabytes.</p>
  543. <p>
  544. <a href="/media/engineering-for-slow-internet/macos-update-prompt-01.png">
  545. <picture>
  546. <source srcset="/media/engineering-for-slow-internet/macos-update-prompt-01.png" type="image/png"></source>
  547. <img src="/media/engineering-for-slow-internet/macos-update-prompt-01.png" alt="macOS Update Prompt 01">
  548. </picture>
  549. <em>Sigh, yet another 1 GB patch for my personal macOS device at the South Pole.</em>
  550. </a>
  551. </p>
  552. <p>If every single macOS device at the South Pole downloaded these updates, directly from Apple, we would
  553. have wasted a tremendous amount of bandwidth. And the built-in macOS downloader certainly wanted
  554. us to do this! Look at this interface – few controls, no way to break out and easily get
  555. the underlying patch files. If I canceled the download, or if it failed for some reason, it didn’t always
  556. intelligently resume. Sometimes, I lost all my progress.</p>
  557. <p>
  558. <a href="/media/engineering-for-slow-internet/apple-download-progress-01.png">
  559. <picture>
  560. <source srcset="/media/engineering-for-slow-internet/apple-download-progress-01.png" type="image/png"></source>
  561. <img src="/media/engineering-for-slow-internet/apple-download-progress-01.png" alt="Apple Download Progress 01">
  562. </picture>
  563. <em>The macOS updater. No pause button! Was I expected to leave my laptop on, connected to the Internet,
  564. and untouched, for 15 days??</em>
  565. </a>
  566. </p>
  567. <p>Now – Apple <em>does</em> have a caching server feature built into macOS. In theory, this should alleviate some
  568. of the burden! We should be able to leverage this feature to ensure each patch is only downloaded to
  569. the South Pole one time, and then client Macs will hit the cache.</p>
  570. <p>I experimented with this feature in my spare time, with a handful of my own Apple devices.
  571. In practice, this feature still
  572. required each client Macbook to make a successful HTTPS call directly to Apple, to negotiate cache parameters.
  573. If this call failed, which it often did (<em>because of hardcoded short timeouts!!!</em>), then
  574. the client Mac just fetched the patch from public Apple servers. No retry, no notification. The client
  575. Mac just made a unilateral decision to bypass the cache, without any recourse or even a notification for
  576. the user. In practice, this initial cache negotiation call failed often enough
  577. at the South Pole that the caching feature wasn’t useful.</p>
  578. <p>What we <em>could</em> do was to fetch the full installer (12 gigabytes!) from Apple. Links to the full installer
  579. packages are conveniently aggregated on the
  580. <a href="https://mrmacintosh.com/macos-ventura-13-full-installer-database-download-directly-from-apple/">Mr. Macintosh</a>
  581. blog. We could pull the full installer down to the South Pole slowly
  582. and conscientiously: throttled, at low,
  583. background priority, using robust, interrupt-tolerant
  584. tooling, with support for caching and resumption of paused or failed transfers.
  585. Once we had the file, we could distribute it on station.
  586. This process could take several days, but it was reliable.</p>
  587. <p>
  588. <a href="/media/engineering-for-slow-internet/macos-installer-01.png">
  589. <picture>
  590. <source srcset="/media/engineering-for-slow-internet/macos-installer-01.png" type="image/png"></source>
  591. <img src="/media/engineering-for-slow-internet/macos-installer-01.png" alt="macOS Installer 01">
  592. </picture>
  593. <em>The macOS full installer, painstakingly and conscientiously downloaded to the South Pole.</em>
  594. </a>
  595. </p>
  596. <p>But <em>even this</em> didn’t solve the problem! If the client Mac is Apple Silicon, it <em>still insisted</em> on
  597. downloading additional content directly from Apple, <em>even if</em> you ran the update using the full, 12 GB
  598. installer.
  599. There is no way to bypass or cache this. If the OS update required certain types of
  600. firmware updates or Rosetta updates, then <em>every Apple Silicon client Mac</em> would <em>still</em>
  601. download 1-2 GB of data directly from Apple during the install process.</p>
  602. <p>Even worse, the download process was sometimes farmed out to a separate component in
  603. macOS, which didn’t even report progress to the installer! Installing a macOS update at
  604. the South Pole meant staring at a window that said “installing, 32 minutes remaining”,
  605. for <em>several hours</em>, while a subcomponent of macOS downloaded a gigabyte of un-cacheable
  606. data in the background.</p>
  607. <p>Apple naively assumed that the 1 GB download would be so fast that they didn’t bother
  608. incorporating download speed feedback into the updater’s time estimate.
  609. They did not anticipate people installing
  610. macOS updates from a location where a gigabyte of downloads can take <strong>several hours</strong>, if not <strong>days</strong>.</p>
  611. <p>You can’t cache it, and you can’t download it directly using a web browser (or other mechanism). You have
  612. to let Apple’s downloader do it directly. And, of course, there’s no pause button. It is a major
  613. inconvenience to users, and a major waste of bandwidth, for each individual client Mac to
  614. download 1-2 GB of data in a single, uninterrupted shot.</p>
  615. <p><strong>Ways that Apple could make this significantly better for users with slow or otherwise-weird Internet:</strong></p>
  616. <ol>
  617. <li>Compute the required patch, and then give us a download link, so we can download it outside of
  618. Apple’s downloader.</li>
  619. <li>Improve the built-in update download tool with pause/resume functionality and intelligent state
  620. management, to ensure progress isn’t lost.</li>
  621. <li>Fix the full installer, so it includes <em>everything</em>, including all the currently-excluded items
  622. such as firmware and Rosetta updates for Apple Silicon Macs.
  623. It would be much more useful if it included everything. I
  624. could download it once, and then distribute it, without worrying about each Mac <em>still</em> needing to fetch
  625. additional data from Apple.</li>
  626. <li>Improve the Apple Caching Server feature, so it’s more reliable in situations where direct Internet
  627. access is unreliable. Give us more controls so that we can force a Mac to use it, and so that we can force
  628. the caching server to proactively download an item that we know will be needed in the future.</li>
  629. </ol>
  630. <p>As it stands, it was a huge hassle for me to help people with macOS updates at the South Pole.</p>
  631. <h2 id="example-2---samsung-android-phone-os-updates">Example 2 - Samsung Android Phone OS Updates</h2>
  632. <p>My Samsung Android phone receives periodic OS updates. These updates include relevant Android
  633. patches, as well as updates to the Samsung UI and other OS components.</p>
  634. <p>The updater is a particularly bad example of an app that fails to consider slow / intermittent Internet
  635. use cases.</p>
  636. <p>
  637. <a href="/media/engineering-for-slow-internet/phone-download-progress-01.png">
  638. <picture>
  639. <source srcset="/media/engineering-for-slow-internet/phone-download-progress-01.png" type="image/png"></source>
  640. <img src="/media/engineering-for-slow-internet/phone-download-progress-01.png" alt="Phone Download Progress 01">
  641. </picture>
  642. <em>Downloading an OS update for my Samsung Android phone at the South Pole.</em>
  643. </a>
  644. </p>
  645. <p>First, the basics. There is no speed indicator, no numeric progress indicator (good luck counting pixels on
  646. the moving bar), no pause button, no cancel button, no indicator of the file size, and no way to get at
  647. the underlying file to download separately.</p>
  648. <p>Second – if the download fails, it cannot be resumed. It will restart from the beginning.</p>
  649. <p>In practice, at the South Pole, the phone could not download an entire OS update on a single satellite
  650. pass. So – it inevitably failed as soon as connectivity dropped, and I had to restart it from the
  651. beginning.</p>
  652. <p>The <strong>only way</strong> I was able to get this done was by <strong>turning off the phone entirely</strong>, right before
  653. Internet access dropped, and then turning it back on when Internet access resumed at the next satellite pass.
  654. This tricked the phone into not giving up on the download, because it was totally off during the period
  655. without Internet. It never had a chance to fail.
  656. By doing this, I was able to spread out the download across multiple satellite passes, and I could complete
  657. the download.</p>
  658. <p>This is an absurd workaround! I should not have had to do this.</p>
  659. <p>My US carrier (Verizon) does offer a downloadable application for macOS and Windows which should, in theory,
  660. allow me to flash the OS updates from my computer, instead of relying on the phone to download the patches.
  661. In practice, the Verizon app is even worse. Buggy, unreliable, and also insists on using its own in-app
  662. downloader to fetch the update files (sigh…).</p>
  663. <p>I’m sure there’s a way I could have gotten the images and flashed them manually.
  664. This is not an invitation for a bunch
  665. of Android enthusiasts to email me and explain bootloaders and APKs and ROMs and sideloading and whatever else is
  666. involved here.
  667. That’s not the point. The point is – the mainstream tools that vendors ship are <em>hopelessly deficient</em>
  668. for users on slow Internet, and that’s a bummer.</p>
  669. <h2 id="example-3---small-app-auto-updater">Example 3 - Small App Auto-Updater</h2>
  670. <p>A small desktop app has an in-app downloader for updates. Can you spot the issues?</p>
  671. <p>
  672. <a href="/media/engineering-for-slow-internet/in-app-downloader-02.png">
  673. <picture>
  674. <source srcset="/media/engineering-for-slow-internet/in-app-downloader-02.png" type="image/png"></source>
  675. <img src="/media/engineering-for-slow-internet/in-app-downloader-02.png" alt="In-App Downloader 02">
  676. </picture>
  677. <em>Downloading an in-app update.</em>
  678. </a>
  679. </p>
  680. <p>Let’s count them:</p>
  681. <ol>
  682. <li>No pause button.</li>
  683. <li>No cancel button.</li>
  684. <li>No progress indicator of any kind.</li>
  685. <li>No speed / time remaining indicator.</li>
  686. <li>No way to get at the underlying URL, so I can use my own downloader.</li>
  687. <li>No progress tracking and no graceful resumption of an interrupted download.</li>
  688. </ol>
  689. <p>This is actually one of my favorite desktop apps! It’s a shame to call them out like this.
  690. A quick, easy way to make this MUCH better for users at the South Pole would be to provide a manual download
  691. link. Then, the developers wouldn’t need to reimplement all the nice download features that my
  692. browser provides. I could just use my browser.</p>
  693. <h2 id="example-4---yet-another-app-auto-updater">Example 4 - Yet Another App Auto-Updater</h2>
  694. <p>Here’s another one!</p>
  695. <p>
  696. <a href="/media/engineering-for-slow-internet/in-app-downloader-03.png">
  697. <picture>
  698. <source srcset="/media/engineering-for-slow-internet/in-app-downloader-03.png" type="image/png"></source>
  699. <img src="/media/engineering-for-slow-internet/in-app-downloader-03.png" alt="In-App Downloader 03">
  700. </picture>
  701. <em>Downloading another in-app update.</em>
  702. </a>
  703. </p>
  704. <p>Let’s count the issues:</p>
  705. <ol>
  706. <li>No pause button.</li>
  707. <li>No numeric progress / speed indicator.</li>
  708. <li>No way to get at the underlying URL, so I can use my own downloader.</li>
  709. <li>No progress tracking and no graceful resumption of an interrupted download.</li>
  710. </ol>
  711. <p>It does have a few things going for it:</p>
  712. <ol>
  713. <li>Cancel button.</li>
  714. <li>Visual progress indicator.</li>
  715. </ol>
  716. <p>But – overall, still a frustrating user experience for users with slow or intermittent Internet access.</p>
  717. <h2 id="example-5---microsoft-office-for-mac">Example 5 - Microsoft Office for Mac</h2>
  718. <p>Credit where credit is due – Microsoft has a GREAT auto-updater built into Office for Mac! Check it out:</p>
  719. <p>
  720. <a href="/media/engineering-for-slow-internet/microsoft-autoupdate-01.png">
  721. <picture>
  722. <source srcset="/media/engineering-for-slow-internet/microsoft-autoupdate-01.png" type="image/png"></source>
  723. <img src="/media/engineering-for-slow-internet/microsoft-autoupdate-01.png" alt="Microsoft Autoupdate 01">
  724. </picture>
  725. <em>Downloading Office for macOS updates at the South Pole.</em>
  726. </a>
  727. </p>
  728. <p>Look at all these nice features!</p>
  729. <ol>
  730. <li>Pause button!</li>
  731. <li>Cancel buttons!</li>
  732. <li>Progress indicator!</li>
  733. <li>Speed and time remaining indicators!</li>
  734. <li>Graceful resumption of interrupted downloads!</li>
  735. </ol>
  736. <p>The only thing that could have made this better is a link to get at the underlying URL, so I could use
  737. my own downloader. But, given how good this interface is, I didn’t mind using it, even at
  738. the South Pole.</p>
  739. <h1 id="conclusion">Conclusion</h1>
  740. <p>I hope the examples I’ve shown in this post have been a helpful illustration of how minor oversights
  741. or under-developed features back home can become <strong>major issues</strong> in a place with slow Internet.</p>
  742. <p>Again, I’m not asking that every app developer spend a huge amount of time optimizing for edge cases like
  743. the South Pole.</p>
  744. <p>And I’m also definitely not asking for people to work miracles. Internet access at the South Pole, as of
  745. October 2023, was <strong><em>slow</em></strong>. I don’t expect immersive interactive streaming media to work
  746. under the conditions I described here, but it would be nice if apps were resilient enough to
  747. get a few bytes of text up or down. Unfortunately, what
  748. often ended up happening is that apps got stuck in a loop because of an ill-advised hardcoded timeout.</p>
  749. <p>I hope everyone found this helpful, or at least interesting.</p>
  750. <p>And thank you again to everyone who followed along with me on my Antarctic journey! I’ve been off-ice for about
  751. six months now, and going through my old posts here have brought back fond memories.</p>
  752. <p>I hope the current winter-over
  753. crew is doing well, and that everyone is enjoying the <a href="/posts/polar-night">Polar Night</a>. If the egg supply and
  754. consumption rate is the same as it was during Winter 2023, they should soon be
  755. finishing up <a href="/posts/the-last-egg">The Last Egg</a>.</p>
  756. <p>I won’t promise any more content, but I do have a handful of other half-finished posts sitting in my
  757. drafts. We’ll see!</p>
  758. </article>
  759. <hr>
  760. <footer>
  761. <p>
  762. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  763. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  764. </svg> Accueil</a> •
  765. <a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
  766. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
  767. </svg> Suivre</a> •
  768. <a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
  769. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
  770. </svg> Pro</a> •
  771. <a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
  772. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
  773. </svg> Email</a> •
  774. <abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
  775. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
  776. </svg> Légal</abbr>
  777. </p>
  778. <template id="theme-selector">
  779. <form>
  780. <fieldset>
  781. <legend><svg class="icon icon-brightness-contrast">
  782. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
  783. </svg> Thème</legend>
  784. <label>
  785. <input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
  786. </label>
  787. <label>
  788. <input type="radio" value="dark" name="chosen-color-scheme"> Foncé
  789. </label>
  790. <label>
  791. <input type="radio" value="light" name="chosen-color-scheme"> Clair
  792. </label>
  793. </fieldset>
  794. </form>
  795. </template>
  796. </footer>
  797. <script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
  798. <script>
  799. function loadThemeForm(templateName) {
  800. const themeSelectorTemplate = document.querySelector(templateName)
  801. const form = themeSelectorTemplate.content.firstElementChild
  802. themeSelectorTemplate.replaceWith(form)
  803. form.addEventListener('change', (e) => {
  804. const chosenColorScheme = e.target.value
  805. localStorage.setItem('theme', chosenColorScheme)
  806. toggleTheme(chosenColorScheme)
  807. })
  808. const selectedTheme = localStorage.getItem('theme')
  809. if (selectedTheme && selectedTheme !== 'undefined') {
  810. form.querySelector(`[value="${selectedTheme}"]`).checked = true
  811. }
  812. }
  813. const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
  814. window.addEventListener('load', () => {
  815. let hasDarkRules = false
  816. for (const styleSheet of Array.from(document.styleSheets)) {
  817. let mediaRules = []
  818. for (const cssRule of styleSheet.cssRules) {
  819. if (cssRule.type !== CSSRule.MEDIA_RULE) {
  820. continue
  821. }
  822. // WARNING: Safari does not have/supports `conditionText`.
  823. if (cssRule.conditionText) {
  824. if (cssRule.conditionText !== prefersColorSchemeDark) {
  825. continue
  826. }
  827. } else {
  828. if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
  829. continue
  830. }
  831. }
  832. mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
  833. }
  834. // WARNING: do not try to insert a Rule to a styleSheet you are
  835. // currently iterating on, otherwise the browser will be stuck
  836. // in a infinite loop…
  837. for (const mediaRule of mediaRules) {
  838. styleSheet.insertRule(mediaRule.cssText)
  839. hasDarkRules = true
  840. }
  841. }
  842. if (hasDarkRules) {
  843. loadThemeForm('#theme-selector')
  844. }
  845. })
  846. </script>
  847. </body>
  848. </html>