<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Paul Osman</title>
    <link>https://paulosman.me/categories/</link>
    <description>Paul Osman</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <copyright>Paul Osman</copyright>
    
    
    <atom:link href="https://paulosman.me/categories/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>Building The Web: A History of Web Architecture</title>
      <link>https://paulosman.me/2026/03/05/building-the-web-a-history-of-web-architecture/</link>
      <pubDate>Thu, 05 Mar 2026 00:00:00 +0000</pubDate>
      <guid>https://paulosman.me/2026/03/05/building-the-web-a-history-of-web-architecture/</guid>
      <description>&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt; I&amp;rsquo;m writing a book about the history of web architecture. The audience will be engineers and anyone who wants to deep dive on how architectures have evolved over time. It will focus on the practitioners who made significant contributions to foundational ideas and technologies in web architecture, whether they intended to or not. Updates will be published at &lt;a href=&#34;https://buildingtheweb.dev&#34;&gt;buildingtheweb.dev&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The years spanning from 1993 to 2015 were extraordinary. Linux and FreeBSD became mainstream server operating systems, the NCSA httpd project was released, it begat the Apache HTTP server, which in turn inspired projects like nginx. The world saw the birth of a global network for information exchange and its evolution into the substrate of our everyday lives. We went from editing files and placing them in directories on a server to shipping fully containerized applications through complex pipelines. Client-server architecture became three-tiered architecture, eventually morphing into the microservice death star diagrams shared in conference talks as humble brags about the amount of infrastructure required to run large sites. Most of this innovation was done in the open, often by people collaborating across organizational and geographic boundaries. I owe my career to this time period and to the people who shaped it. The amount of knowledge sharing and learning, between practitioners figuring things out the hard way, that has happened during this time period is staggering.&lt;/p&gt;
&lt;p&gt;I’m writing a book detailing the major shifts in web architecture that happened during this time period. Things continued to happen, of course, and 2015 is admittedly a bit arbitrary - but I chose this timeline because we had containerization and orchestration cemented. Much work that has happened since has been focused on ML / AI, which is out of scope for this project. I’m writing the book because it’s important to illustrate the connective tissue between these developments. It&amp;rsquo;s also important to appreciate how these contributions were made by practitioners working in specific contexts under specific constraints, and how their innovations compounded - sometimes unexpectedly. I want to dive into how the industry evolved from stateless web servers serving static documents through the C10K problem, through developments in caching reverse proxies, improvements to OS kernels, experiments with new programming models, infrastructure like CDNs, and beyond. I’ll treat &lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc3552&#34;&gt;security as a common thread that runs through each chapter&lt;/a&gt;, with each innovation addressing some challenges and introducing new attack surface area for others.&lt;/p&gt;
&lt;p&gt;I’m going to publish incrementally, online at &lt;a href=&#34;https://buildingtheweb.dev&#34;&gt;buildingtheweb.dev&lt;/a&gt;. I’ll make each chapter available as a PDF as well as on the website. I want this to be an open process, so I may look into ways to make early versions available (probably a public git repo).&lt;/p&gt;
&lt;p&gt;I am grateful for the support of a few trusted people in my network for reviews and connecting me to people to talk to for this project. If you or anyone you know was involved in any part of this story and would be open to speaking with me, I’d love to chat and get your perspective - or if you want to support this project in any way, don’t hesitate to reach out!&lt;/p&gt;
&lt;h2 id=&#34;whats-covered&#34;&gt;What’s Covered&lt;/h2&gt;
&lt;p&gt;The exact table of contents is a work in progress, but the progression will be roughly chronological, with each chapter covering a major architectural shift. It’s important to note that work was going on in parallel in many of these areas. I will try to draw connections where possible. Here’s the current layout:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prologue - The Network: Then and Now&lt;/strong&gt; - Networking was very different in 1993. This short prologue will go into some detail about the physical constraints that would have shaped architectural decisions. The evolution from dial-up to broadband to always-on mobile devices had a huge impact on the industry. The evolution of networking infrastructure, from the routing protocols that held the internet together, to the physical links that determined what was possible paved the way for many of the innovations discussed in later chapters.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Early Web&lt;/strong&gt; - Starting with the design of HTTP and the initial RFCs. The development of the first web server and what the early web looked like as a network of servers hosting static HTML documents. The story of the early web is well told, but I want to go deeper into the specific architectural trade-offs that were baked in at this stage.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Dynamic Web&lt;/strong&gt; - At some point, static web documents were joined by dynamic applications with the development of protocols like CGI. Languages like Perl and PHP became ubiquitous on the internet, sometimes by accident. This was when the web stopped being a library and became a platform for application development. I want to dive into how HTTP servers handled this, from preforking and worker models, to protocols like FastCGI, to evented frameworks like Twisted, EventMachine, and eventually node.js. In parallel, browsers became a platform in their own right. JavaScript support shipped in Netscape Navigator in 1995. Microsoft shipped an at-the-time obscure ActiveX object called XMLHttpRequest in 1999. It took a while to catch on, but by 2005 the term AJAX (Asynchronous JavaScript and XML) entered the lexicon and applications like Google Maps and GMail pushed the envelope in terms of what web applications could do in the browser.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Load Balancers and Proxies&lt;/strong&gt; - Eventually it all became too much for one server to handle. The earliest scaling challenges required changes in thinking that would have been radical at the time. The first applications (that sometimes ran on specialized hardware) to intercept the requests and either reroute them or serve them from a cache. I also want to dive into early scaling stories - for example, the first time teams had to deal with melting servers or the need to serve large amounts of content quickly to a global audience.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;State and Sessions&lt;/strong&gt; - Clients and servers needed to establish how to use the primitives provided by HTTP to manage more than just individual requests and responses. We needed to build caching, session data, and other forms of state into the protocol itself. Browsers started being much more active participants in the platform. Support for cookies was shipped in Netscape Navigator in 1994. They were formalized in various RFCs authored between 1997 and 2011.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SSL/TLS and HTTPS&lt;/strong&gt; - Netscape recognized that the web needed a mechanism for secure transport. After some revisions of SSL, the IETF took over the initiative, creating TLS 1.0. Systems administrators now had to figure out ways to obtain, and maintain certificates. Pushing adoption became a huge priority for the broader web community, driven by initiatives like Let’s Encrypt. Google also helped tremendously by giving ranking boosts to sites that used https with a valid certificate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scaling Databases&lt;/strong&gt; - I remember the first time I installed MySQL on my laptop and wrote a web application that persisted data to a database. It felt magical. Relational databases predate the web by decades, but by the late nineties and early aughts it was common to use MySQL and PostgreSQL as the persistence layer for web applications. Scaling became a concern and read-replicas were often added to clusters with libraries that could pin reads to the primary after writes to provide read-after-write consistency. Contributions from various early practitioners as well as companies like Percona turned databases like MySQL and PostgreSQL into battle tested web software.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Caching&lt;/strong&gt; - Caching will be discussed in the load balancer and proxies chapter, but this chapter will go into more detail about how tools like squid and varnish were used as “accelerators”. It will also discuss the origins of memcached, a distributed key value store that commonly sits in between web applications and databases, saving databases from having to serve unnecessary reads. Redis served a similar role, but also acted as a broader data structure server used for caching of chunks of data, sets, counters, etc.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CDNs and The Edge&lt;/strong&gt; - There are a few techniques that have been published over the years that gave birth to whole industries. Consistent hashing was one of those. Akamai was first, but the category grew to include cloud providers and other companies like Fastly and Cloudflare. Serving content quickly from edge servers geographically close to a consumer became a significant differentiator for sites with a global audience. Interest quickly grew and companies started experimenting with how much compute could reasonably be pushed to the edge.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The API Era&lt;/strong&gt; - SaaS hit a certain critical mass and APIs started becoming an expectation. If you ran a web business, chances were you had some kind of API offering. After the dust settled from attempts like SOAP and WS-* specifications, REST APIs accepting and serving XML or JSON became ubiquitous. Specifications such as OAuth and OpenID attempted to ease interoperability. Large companies jumped in and authentication and authorization became huge economic moats with enormous privacy implications. This era also saw the proliferation of microservice based architectures and aggregation patterns like BFF and GraphQL.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NoSQL (including Search)&lt;/strong&gt; - Early web applications almost exclusively used relational databases for durable persistence. In 2006, Google published the BigTable paper. In 2007, Amazon realized that a large percentage of database reads were primary key lookups, and published the Dynamo paper. This period saw a huge influx in activity in specialized NoSQL databases. Search also started to become common in web applications. Apache Lucene had its origins as far back as 1999, but it wasn’t until Solr and later ElasticSearch were released that it became an almost drop-in piece of architecture.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Queues and Event Streams&lt;/strong&gt; - Queues and job systems go back to the early nineties, but few blog posts have had as big an impact in how I think about software as Jay Kreps&amp;rsquo; 2013 manifesto, “The Log”. Kafka was open sourced in 2010 and has become ubiquitous in mid-sized and large distributed systems. A parallel work stream on job queues saw the development of ActiveMQ, RabbitMQ and lightweight solutions like sidekiq. Asynchronous processing became a foundational way to scale out backend systems, solving a whole bunch of problems and introducing quite a few as well.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Virtual Machines and The Cloud&lt;/strong&gt; - It was common in the late nineties to use tools like VMWare Workstation to run virtual machines on development workstations, often to test portability of software. In 2003, the Xen hypervisor was born, which modified the operating system so that a guest OS and a host OS could cooperate using a technique called paravirtualization. Xen was chosen by Amazon as the virtualization solution that formed the basis of AWS. KVM built virtualization directly into the Linux kernel. This simplified virtualization significantly which along with advances in hardware virtualization, made it the eventual de facto choice. Later work on microVMs made starting up virtual machines even faster, making concepts like serverless architecture feasible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Configuration Management and IaaC&lt;/strong&gt; - Scaling would have had very strict limits if we didn’t have some kind of automated, version controlled way to provision and configure new compute and storage resources. Configuration management solutions such as CFEngine, Puppet and Chef provided a way to describe in code how to set up a machine based on its role as a web server, database server, or something else. Infrastructure as Code went a step above and provided ways to describe, in code, what virtual machines were required. IaaC made it possible to spin up not just a database server, but an actual hosted database run by a cloud provider, or a load balancer and cluster of application servers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Containers and Orchestration&lt;/strong&gt; - Virtualization allowed a single physical computer to run multiple operating systems, but with a certain amount of overhead. It wasn’t feasible to ship around a configured virtual machine containing your application, so it was necessary to provision and configure the virtual machine before deploying your application to it. Containers changed this, making it possible to ship around a fully contained environment for your application. Orchestration inverted the relationship that cloud computing started - instead of treating a single machine as a bunch of different computers, you could treat an entire data center as one big virtual machine. Containers have their origin as far back as the late seventies with chroot in UNIX environments as well as FreeBSD jails in the early aughts, but it was when namespaces and cgroups hit the Linux kernel and projects like Docker started building on them that containers became a real thing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Continuous Delivery&lt;/strong&gt; - A key goal in web architectures has been to minimize feedback loops. The faster we can provision and deploy an application and its environment, the faster we can deliver and iterate on value. It has also been observed that shipping smaller increments can be a lot safer than shipping big batches of changes at once. Eric Ries at IMVU started talking about how they shipped to production multiple times per day. John Allspaw and Paul Hammond gave a talk called “10+ Deploys Per Day” about how Flickr practiced continuous delivery. The practice of continuously shipping to production, once a highly polarizing idea, has now become table stakes for anyone running a 24/7 web product.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Monitoring and Observability&lt;/strong&gt; - &amp;ldquo;Is it working?&amp;rdquo; is a fundamental question when running a web application. Monitoring goes back to the nineties, starting with tools that checked to see if a server was up and running, and eventually what metrics could be recorded, such as CPU, memory, and disk space utilization. Tools like MRTG, RRDTool, and Nagios popularized time-series data and graphs for web application developers and operators. When Chris Davis wrote Graphite in 2006 and then Erik Kastner wrote statsd in 2010, things really exploded. Engineers started emitting custom metrics from their applications and building dashboards to visualize an innumerable amount of operationally relevant information. As systems became more complicated, Observability, an idea with its roots in Control Theory and popularized by Charity Majors as well as the engineering team at Twitter, started to gain popularity. It should now be possible to ask a huge range of dynamic questions about a system running in any environment and get answers quickly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SRE and DevOps&lt;/strong&gt; - In order to be able to provision, configure and deploy frequently, while monitoring and being able to observe your production environments adequately, it became a huge advantage to reduce or eliminate cultural and organizational barriers between the people building applications and those tasked with operating them. The DevOps movement and Google’s popularization of the Site Reliability Engineering role changed the way many organizations architected and built web applications. Primarily a cultural movement, these ideas nevertheless had profound implications for how we architect web applications.&lt;/p&gt;
&lt;h2 id=&#34;timelines-and-updates&#34;&gt;Timelines and Updates&lt;/h2&gt;
&lt;p&gt;I have no idea how long this is going to take - it’s a big undertaking, and I’m a parent with only weekends and evenings to work on it. At the moment, Chapter 1 is under way and I’m shooting for momentum, not strict timelines. If you’re interested in the content, you can get updates here or you can subscribe via email or &lt;a href=&#34;https://buildingtheweb.dev/chapters/feed.xml&#34;&gt;RSS&lt;/a&gt; at &lt;a href=&#34;https://buildingtheweb.dev&#34;&gt;buildingtheweb.dev&lt;/a&gt;. I appreciate your interest!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Thank you to John Allspaw, Niall Murphy, and Meg Osman for reviewing drafts of this blog post.&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>SFP&#43; Support on the Honeycomb LX2</title>
      <link>https://paulosman.me/2026/02/07/sfp-support-on-the-honeycomb-lx2/</link>
      <pubDate>Sat, 07 Feb 2026 08:14:23 -0500</pubDate>
      <guid>https://paulosman.me/2026/02/07/sfp-support-on-the-honeycomb-lx2/</guid>
      <description>&lt;p&gt;A few years ago, I bought a &lt;a href=&#34;https://www.solid-run.com/arm-servers-networking-platforms/honeycomb-lx2/&#34;&gt;SolidRun Honeycomb LX2&lt;/a&gt;. The LX2 is a 16-core ARM board (NXP LX2160A) with four SFP+ ports and a 1GbE RJ45. Paired with the extremely nice looking Lone Industries L5 mini itx case, it&amp;rsquo;s a very cool small form factor machine that requires very little power. When I first got it, I followed &lt;a href=&#34;https://dev.to/lizthegrey/first-experiences-with-honeycomb-lx2k-26be&#34;&gt;Liz Fong-Jones&amp;rsquo; tips for getting Ubuntu 20.04 running&lt;/a&gt; and since then it&amp;rsquo;s been happily existing as a node in a heterogeneous kubernetes cluster in my homelab.&lt;/p&gt;
&lt;p&gt;Recently, I wanted to repurpose it as a router and take advantage of the SFP+ ports to build a 10Gbit backbone. I also was using a USB ethernet adapter because I had problems getting the onboard 1GbE NIC to work back when I first set it up, so I wanted to see if I could fix that. I wanted to start with a fresh install of Ubuntu 24.04 LTS. The onboard NIC uses NXP&amp;rsquo;s ENETC driver, &lt;a href=&#34;https://community.solid-run.com/t/debian-bookworm-rj45-ethernet-interface-not-working/410&#34;&gt;which was merged in kernel 5.14&lt;/a&gt;. I figured by running 6.8 which ships with Ubuntu 24.04, I should be good to go there.&lt;/p&gt;
&lt;h2 id=&#34;the-problem&#34;&gt;The Problem&lt;/h2&gt;
&lt;p&gt;NXP&amp;rsquo;s LX2160A uses a subsystem called &lt;a href=&#34;https://www.kernel.org/doc/html/v4.17/networking/dpaa2/overview.html&#34;&gt;DPAA2 (Data Path Acceleration Architecture)&lt;/a&gt; for its high-speed network ports. DPAA2 uses a hardware component called the Management Complex (or MC) for managing the hardware resources. Unlike normal NICs that show up as &lt;code&gt;eth0&lt;/code&gt;, the MC is used to create and wire up network objects before Linux can see them. The tool used to do this is called &lt;a href=&#34;https://github.com/nxp-qoriq/restool&#34;&gt;restool&lt;/a&gt; and it has a convenience wrapper called &lt;code&gt;ls-addni&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo ls-addni dpmac.7   &lt;span style=&#34;color:#75715e&#34;&gt;# should create an eth interface for SFP+ port 7&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;On a fresh Ubuntu 24.04 install with the prebuilt UEFI firmware from SolidRun&amp;rsquo;s image server, this fails:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;MC error: Unsupported operation &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;status 0xb&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Error: dpmcp object was not created!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The MC firmware bundled in SolidRun&amp;rsquo;s prebuilt UEFI images was version 10.28.1, which does not support dynamic object creation. This has been a &lt;a href=&#34;https://github.com/SolidRun/lx2160a_uefi/issues/13&#34;&gt;known issue&lt;/a&gt; and has been fixed, but I don&amp;rsquo;t think the prebuilt images have been updated yet. Time to build a new version of the firmware!&lt;/p&gt;
&lt;p&gt;I performed all of the following steps using minicom over the micro-USB serial console port from my laptop. Follow Liz&amp;rsquo;s instructions linked above if you have trouble getting that working. I downloaded Ubuntu 24.04 and put it on a USB thumb drive, then selected the device from the BIOS when booting the Honeycomb LX2.&lt;/p&gt;
&lt;h2 id=&#34;step-0-install-ubuntu-2404-with-iommu-workaround&#34;&gt;Step 0: Install Ubuntu 24.04 with IOMMU Workaround&lt;/h2&gt;
&lt;p&gt;Ubuntu 24.04 (kernel 6.8) won&amp;rsquo;t install cleanly on the LX2. The installer crashes with ARM SMMU context (&lt;code&gt;arm-smmu arm-smmu.0.auto: Unhandled context fault&lt;/code&gt;). At the GRUB menu during install, press e to edit the boot entry and add to the kernel command line:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;iommu.passthrough&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; arm-smmu.disable_bypass&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After install, make it permanent:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo sed -i &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;s/GRUB_CMDLINE_LINUX_DEFAULT=&amp;#34;.*&amp;#34;/GRUB_CMDLINE_LINUX_DEFAULT=&amp;#34;console=ttyAMA0,115200 iommu.passthrough=1 arm-smmu.disable_bypass=0&amp;#34;/&amp;#39;&lt;/span&gt; /etc/default/grub
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo update-grub&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&#34;step-1-verify-the-1gbe-works&#34;&gt;Step 1: Verify the 1GbE Works&lt;/h2&gt;
&lt;p&gt;The onboard RJ45 uses the ENETC driver, which works out of the box on kernel 6.8. Hooray, first success!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo ip link set eth0 up
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo ethtool eth0   &lt;span style=&#34;color:#75715e&#34;&gt;# Look for &amp;#34;Link detected: yes&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now I could get rid of the annoying USB ethernet adapter.&lt;/p&gt;
&lt;h2 id=&#34;step-2-build-and-install-restool&#34;&gt;Step 2: Build and install restool&lt;/h2&gt;
&lt;p&gt;You need &lt;code&gt;restool&lt;/code&gt; to interact with the DPAA2 subsystem. It&amp;rsquo;s not packaged for Ubuntu, so clone the repo and download it from source. Building the man pages required pandoc as a dependency, and I didn&amp;rsquo;t want to screw around with that, so I just disabled it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git clone https://github.com/nxp-qoriq/restool.git
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd restool
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;make MANPAGE&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; install&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now time to verify the MC firmware version and confirm the SFP+ MAC objects exist:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo restool --mc-version          &lt;span style=&#34;color:#75715e&#34;&gt;# shows 10.28.1 on prebuilt firmware&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ls /sys/bus/fsl-mc/devices/        &lt;span style=&#34;color:#75715e&#34;&gt;# should show dpmac.7, dpmac.8, dpmac.9, dpmac.10&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The MAC objects are there. The MC firmware just refuses to do anything with them:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo ls-addni dpmac.7   &lt;span style=&#34;color:#75715e&#34;&gt;# should create an eth interface for SFP+ port 7&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;MC error: Unsupported operation &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;status 0xb&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Error: dpmcp object was not created!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&#34;step-3-fix-the-uefi-build-environment&#34;&gt;Step 3: Fix the UEFI Build Environment&lt;/h2&gt;
&lt;p&gt;SolidRun&amp;rsquo;s &lt;a href=&#34;https://github.com/SolidRun/lx2160a_uefi&#34;&gt;UEFI build repo&lt;/a&gt; uses Docker. Unfortunately, it seems to be broken as of 2025. Here&amp;rsquo;s what I did to fix it:&lt;/p&gt;
&lt;h3 id=&#34;problem-1---debian-buster-is-dead&#34;&gt;Problem 1 - Debian Buster is Dead&lt;/h3&gt;
&lt;p&gt;The Dockerfile uses &lt;code&gt;debian:buster-slim&lt;/code&gt;. Buster reached end-of-life and its repos return 404. Change to Bookworm:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd lx2160a_uefi/docker
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sed -i &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;s/debian:buster-slim/debian:bookworm-slim/&amp;#39;&lt;/span&gt; Dockerfile
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sed -i &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;s|/usr/bin/python3.7|/usr/bin/python3|&amp;#39;&lt;/span&gt; Dockerfile
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd ..
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker build -t lx2160a_uefi docker/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&#34;problem-2---the-entrypoint-fails-silently&#34;&gt;Problem 2 - The Entrypoint Fails Silently&lt;/h3&gt;
&lt;p&gt;Docker entrypoint (&lt;code&gt;entry.sh&lt;/code&gt;) uses &lt;code&gt;#!/bin/sh -e&lt;/code&gt; and runs &lt;code&gt;git rev-parse&lt;/code&gt; early. Because Docker volume mounts trigger git&amp;rsquo;s safe directory check, this command fails with exit code 128, and &lt;code&gt;set -e&lt;/code&gt; kills the script before it produces any output. You get no error message, no log, nothing, just an instant return.&lt;/p&gt;
&lt;p&gt;Bypass the entrypoint entirely:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker run ... --entrypoint /bin/bash lx2160a_uefi -c &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; git config --global --add safe.directory /work
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; cd /work
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; INITIALIZE=1 ./runme.sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; ./runme.sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&#34;problem-3---gcc-12-breaks-edk2-basetools&#34;&gt;Problem 3 - GCC 12 Breaks edk2 BaseTools&lt;/h3&gt;
&lt;p&gt;BookWorm ships GCC 12, which enables new warnings under &lt;code&gt;-Wall&lt;/code&gt; that edk2&amp;rsquo;s &lt;code&gt;-Werror&lt;/code&gt; promotes to build failures. Four suppresions were needed:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Flag&lt;/th&gt;
          &lt;th&gt;File&lt;/th&gt;
          &lt;th&gt;Issue&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;-Wno-vla-parameter&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;BrotliCompress/brotli/c/dec/decode.c&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;VLA parameter declaration mismatch&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;-Wno-use-after-free&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;GenFfs/GenFfs.c&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Upstream bug: closed FILE* passed to printf&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;-Wno-dangling-pointer&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;LzmaCompress/Sdk/C/LzmaEnc.c&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;False positive on local struct address&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;-Wno-stringop-overflow&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;DevicePath/DevicePathUtilities.c&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;False positive on memcpy into struct&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The fix uses edk2&amp;rsquo;s existing &lt;code&gt;EXTRA_OPTFLAGS&lt;/code&gt; hook in &lt;code&gt;header.makefile&lt;/code&gt;. In &lt;code&gt;runme.sh&lt;/code&gt;, change the BaseTools make invocation to:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;EXTRA_OPTFLAGS&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;-Wno-vla-parameter -Wno-use-after-free -Wno-dangling-pointer -Wno-stringop-overflow&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; PYTHON_COMMAND&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/usr/bin/python3 make -C BaseTools&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This survives &lt;code&gt;INITIALIZE=1&lt;/code&gt; because it lives in &lt;code&gt;runme.sh&lt;/code&gt; (parent repo), not in the edk2 submodule which gets reset.&lt;/p&gt;
&lt;h2 id=&#34;step-4---get-a-modern-mc-firmware&#34;&gt;Step 4 - Get a Modern MC Firmware&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s the actual fix. Everything above is just getting the build to compile.&lt;/p&gt;
&lt;p&gt;The Management Complex is a separate microcontroller embedded in the LX2160A SoC that runs its own firmware, independent of Linux. It manages all DPAA2 network objects: the MACs, buffer pools, channels, and network interfaces that make the SFP+ ports work. When you run &lt;code&gt;ls-addni dpmac.7&lt;/code&gt;, the restool utility sends commands to the MC over an ioctl, asking it to dynamically allocate these objects and wire them together into a usable network interface.&lt;/p&gt;
&lt;p&gt;MC firmware 10.28.1 (bundled in SolidRun&amp;rsquo;s UEFI images) has all these objects pre-allocated at boot in a static layout, but it does not support creating new ones at runtime. When &lt;code&gt;ls-addni&lt;/code&gt; asks it to create a &lt;code&gt;dpmcp&lt;/code&gt; (management command portal), the MC responds with &amp;ldquo;Unsupported operation.&amp;rdquo; The objects you need are technically there - you can see them in &lt;code&gt;/sys/bus/fsl-mc/devices/&lt;/code&gt; - but without the ability to create new &lt;code&gt;dpni&lt;/code&gt; (network interface) objects and connect them to the existing &lt;code&gt;dpmac&lt;/code&gt; objects, they&amp;rsquo;re useless from Linux.&lt;/p&gt;
&lt;p&gt;Newer MC firmware versions support dynamic object creation. NXP publishes these binaries on GitHub:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git clone --depth &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; https://github.com/NXP/qoriq-mc-binary.git
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ls qoriq-mc-binary/lx216xa/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# mc_lx2160a_10.40.0.itb&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The MC firmware is embedded in the UEFI image at a specific offset on the SD card - it&amp;rsquo;s loaded by the SoC before Linux boots. There&amp;rsquo;s no way to update it independently; you have to rebuild the entire UEFI firmware image with the new binary included.&lt;/p&gt;
&lt;p&gt;I added an &lt;code&gt;MC_FW&lt;/code&gt; environment variable hook to &lt;code&gt;runme.sh&lt;/code&gt; that copies a user-supplied &lt;code&gt;.itb&lt;/code&gt; into the edk2-non-osi tree and patches the FDF filename before building. This is opt-in - without &lt;code&gt;MC_FW&lt;/code&gt;, you get the old bundled version.&lt;/p&gt;
&lt;h2 id=&#34;step-5-build-and-flash&#34;&gt;Step 5: Build and Flash&lt;/h2&gt;
&lt;p&gt;Putting it all together:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git clone https://github.com/SolidRun/lx2160a_uefi.git
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd lx2160a_uefi
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git submodule update --init --recursive
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Apply the Dockerfile and runme.sh fixes described above&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker build -t lx2160a_uefi docker/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker run &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -e DDR_SPEED&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2600&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -e MC_FW&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/work/mc_fw.itb &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -v &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$PWD&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;:/work:Z &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -v /path/to/mc_lx2160a_10.40.0.itb:/work/mc_fw.itb:ro &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  --rm -t --entrypoint /bin/bash lx2160a_uefi -c &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    git config --global --add safe.directory /work
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    cd /work
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    INITIALIZE=1 ./runme.sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    ./runme.sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  &amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Output lands in &lt;code&gt;images/lx2160acex7_2000_700_2600_8_5_2_sd_&amp;lt;hash&amp;gt;.img&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DDR speed matters.&lt;/strong&gt; My RAM is DDR4-2666, so I use &lt;code&gt;DDR_SPEED=2600&lt;/code&gt;. My first build attempt used 3200, which produced a firmware that failed at boot with &amp;ldquo;DDR Clk is faster than DIMM can support.&amp;rdquo; The prebuilt images report 3200 in &lt;code&gt;dmidecode&lt;/code&gt; because the &lt;em&gt;firmware&lt;/em&gt; attempted that speed, not because the RAM supports it.&lt;/p&gt;
&lt;p&gt;Flash to microSD on another machine (early HoneyComb revisions can&amp;rsquo;t write to the microSD from Linux):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo dd &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;images/lx2160acex7_2000_700_2600_8_5_2_sd_*.img of&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/dev/sdX bs&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;4M status&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;progress&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&#34;step-6-activate-sfp-ports&#34;&gt;Step 6: Activate SFP+ Ports&lt;/h2&gt;
&lt;p&gt;Boot with the new firmware and verify:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ restool --mc-version
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;MC firmware version: 10.40.0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo ls-addni dpmac.7
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;created eth1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Hooray! We&amp;rsquo;ve got functioning SFP+ Ports now.&lt;/p&gt;
&lt;h2 id=&#34;wrapup&#34;&gt;Wrapup&lt;/h2&gt;
&lt;p&gt;The HoneyComb LX2 is a genuinely interesting piece of hardware, but the toolchain can be a little tough to work with sometimes.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re running a HoneyComb LX2 with UEFI and your SFP+ ports don&amp;rsquo;t work, the answer is almost certainly that your MC firmware is too old. Build your own UEFI image with a newer one from NXP&amp;rsquo;s qoriq-mc-binary repo.&lt;/p&gt;
&lt;h2 id=&#34;whats-next&#34;&gt;What&amp;rsquo;s Next&lt;/h2&gt;
&lt;p&gt;I still need to actually configure this as a router - nftables NAT, DHCP, the whole deal. That&amp;rsquo;s for a future post once my switch and SFP modules arrive. But at least now, hard part is done!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Shipping When You&#39;re Not Sure</title>
      <link>https://paulosman.me/2026/01/26/shipping-when-youre-not-sure/</link>
      <pubDate>Mon, 26 Jan 2026 08:14:23 -0500</pubDate>
      <guid>https://paulosman.me/2026/01/26/shipping-when-youre-not-sure/</guid>
      <description>&lt;p&gt;I recently had to write a query planner for a graph store. The query planner wasn&amp;rsquo;t terribly complicated - it used some simple heuristics and estimates to decide whether it would be better to execute a query term against the underlying data store or perform in-memory filtering. The hope was that this would improve the vast majority of queries without introducing any regressions.&lt;/p&gt;
&lt;p&gt;Normally in a situation like this, I would ship the query planner behind a feature flag and instrument the code with some metric or trace that I could use to compare query performance from before and after the query planner was enabled. The trouble is, our product was not yet generally available, so query traffic was sporadic and highly variable. Some queries would take 200 ms without the planner, and some would timeout at 30 seconds. This was not an ideal dataset to test performance improvements.&lt;/p&gt;
&lt;p&gt;What I wanted was to compare the query planners performance against a baseline using the same queries under similar conditions. This required a different approach. Luckily, I had a couple of advantages: load wasn&amp;rsquo;t a concern, and all of the queries were read-only.&lt;/p&gt;
&lt;h2 id=&#34;the-pattern&#34;&gt;The pattern&lt;/h2&gt;
&lt;p&gt;I figured it would be pretty easy to execute both code paths in parallel: the new query planner and the baseline. Go makes this especially easy - I wrapped the actual query logic in a method that took a flag as a parameter indicating whether to use the new planner or not:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;execQuery&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;query&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;usePlanner&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;queryResult&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;result&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;u&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;execQueryVariant&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;query&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;usePlanner&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;go&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;primary&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;queryResult&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;primaryIsPlanner&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;cancel&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;WithTimeout&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Background&lt;/span&gt;(), &lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Second&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;defer&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cancel&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;baseline&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;u&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;execQueryVariant&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;query&lt;/span&gt;, !&lt;span style=&#34;color:#a6e22e&#34;&gt;usePlanner&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// log results, compare primary vs baseline&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }(&lt;span style=&#34;color:#a6e22e&#34;&gt;result&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;usePlanner&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;result&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The primary path returns immediately. The comparison runs in a goroutine, logs both results, and records the delta. I also set up an email alert so that I would be notified in the case that results diverged - latency differences could be inspected manually, but I wanted to know immediately if there were correctness issues.&lt;/p&gt;
&lt;p&gt;Load wasn&amp;rsquo;t a concern in this case. If it were, this approach wouldn&amp;rsquo;t work. Also, all of the queries are read-only and therefore free from side effects. When these constraints hold, this approach is very helpful for comparing apples to apples.&lt;/p&gt;
&lt;h2 id=&#34;the-payoff&#34;&gt;The payoff&lt;/h2&gt;
&lt;p&gt;I deployed this with the feature flag turned off at first. User traffic was processed using the old query execution path and the query planner was executed asynchronously. This paid off immediately - I spotted several cases where the planner was making suboptimal decisions. Because I was comparing against baseline in real time, I could tune the planner incrementally, deploy, and watch the metrics improve.&lt;/p&gt;
&lt;p&gt;This isn&amp;rsquo;t shadow testing in the traditional sense. Shadow testing typically routes traffic to a secondary system to validate it can handle load. This is closer to a continuous correctness check: same input, both paths, compared outputs.&lt;/p&gt;
&lt;h2 id=&#34;cleaning-up&#34;&gt;Cleaning up&lt;/h2&gt;
&lt;p&gt;After a couple of weeks running the system with the query planner enabled and seeing good results, I removed the dual execution path so nobody tripped over it. These experiments have a tendency to become permanent complexity so it&amp;rsquo;s important to remember to remove the experiment code.&lt;/p&gt;
&lt;h2 id=&#34;a-complementary-technique&#34;&gt;A Complementary Technique&lt;/h2&gt;
&lt;p&gt;As I was doing this work, I remembered a similar situation. I&amp;rsquo;ve written before about &lt;a href=&#34;https://www.honeycomb.io/blog/shipping-on-spent-error-budget&#34;&gt;shipping on a spent error budget&lt;/a&gt; &amp;ndash; using traffic isolation to keep iterating when you can&amp;rsquo;t afford to break things. Swimlanes and parallel runs solve similar problems differently:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Swimlanes&lt;/em&gt; work when you can route traffic separately and tolerate the new path being broken (no real users yet)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Parallel runs&lt;/em&gt; work when you need to validate against real production queries but can afford the extra compute and ensure no side effects.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both are tools for building confidence, hopefully others find them helpful!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Where I&#39;m at with AI</title>
      <link>https://paulosman.me/2026/01/18/where-im-at-with-ai/</link>
      <pubDate>Sun, 18 Jan 2026 08:14:23 -0500</pubDate>
      <guid>https://paulosman.me/2026/01/18/where-im-at-with-ai/</guid>
      <description>&lt;p&gt;We’re in new paradigm territory with generative AI. A lot of commentary falls into the skeptic, evangelist, or doomsdayer categories, or are practical but narrow takes. In this article, I&amp;rsquo;ll discuss my current use of generative AI tools and outline areas that concern me - areas that raise questions about how our industry and others will evolve. I am certain that generative AI is a productivity amplifier, but its economic, environmental, and cultural externalities are not being discussed enough.&lt;/p&gt;
&lt;p&gt;A quick note on terminology: in this article, I’ll use the term &amp;ldquo;generative AI&amp;rdquo; to describe the current wave of generative AI systems - large language models like Claude and ChatGPT and similar tools. I want to be clear that I’m talking about LLMs and not other areas of AI such as autonomous vehicles or medical diagnostic algorithms.&lt;/p&gt;
&lt;h2 id=&#34;were-moving-quickly&#34;&gt;We&amp;rsquo;re Moving Quickly&lt;/h2&gt;
&lt;p&gt;If you asked me six months ago what I thought of generative AI, I would have said that we’re seeing a lot of interesting movement, but the jury is out on whether it will be useful in my day to day work. It’s remarkable how quickly my position has changed - fast forward just a few months and I am using Claude daily at work and at home. I use it for routine coding tasks like generating scaffolding or writing tests, and for ideation on new projects. I treat Claude like another software engineer and give it specific instructions. I spend a lot of time reading code it generates and making corrections before submitting a PR for my coworkers to review. I often have a generative AI tool do a pass on the PR before I ask a human to have a look, which has saved me many iterations. Coworkers of mine have used generative AI tools to build some truly mind blowing things in a short period of time. I’ve become a convert.&lt;/p&gt;
&lt;p&gt;Coding with generative AI absolutely increases velocity. Absent the concerns I outline in this article, the role of software engineers has changed. I am probably most closely aligned with the view &lt;a href=&#34;https://brooker.co.za/blog/2025/12/16/natural-language.html&#34;&gt;Mark Brooker puts forward&lt;/a&gt; - that most software will be built very quickly, and more complicated software should be developed by writing the specification, and then generating the code. We may still need to drop down to a programming language from time to time, but I believe that almost all development will be done with generative AI tools.&lt;/p&gt;
&lt;p&gt;On the surface, my position here shouldn’t be surprising or controversial. I’ve long held the belief that our job as software engineers is not to write code, but rather to solve problems. If code is the most efficient way to solve a problem, then great. Generative AI makes code much cheaper to generate. That comes with some huge wins, and some very real concerns that I’ll outline here. My purpose is not to express skepticism, or cast doubt, but rather to shine a light on questions that to my knowledge, are still open.&lt;/p&gt;
&lt;h2 id=&#34;ironies-of-automation&#34;&gt;Ironies of Automation&lt;/h2&gt;
&lt;p&gt;Lisanne Bainbridge&amp;rsquo;s 1983 paper &lt;a href=&#34;https://ckrybus.com/static/papers/Bainbridge_1983_Automatica.pdf&#34;&gt;&amp;ldquo;Ironies of Automation&amp;rdquo;&lt;/a&gt; posits that automation can relegate humans to exception handling tasks (think of this whenever you hear someone say that it’s important to always “have a human in the loop”) and that when humans are relegated to such tasks, they become less effective than if they had a more active role.&lt;/p&gt;
&lt;p&gt;The best example of this that I can think of relates to roadway design and safety. &lt;a href=&#34;https://en.wikipedia.org/wiki/Stroad&#34;&gt;&amp;ldquo;Stroads&amp;rdquo;&lt;/a&gt; (hybrid throughfares that combine qualities of high traffic streets and roads), for example, are dangerous because they encourage driving at high speeds and reduce the amount of friction encountered on a route. This causes inattentive driving which leads to more crashes and fatalities. Replacing stroads with more obstacles results in fewer crashes. One of the more striking examples is &lt;a href=&#34;https://www.cnu.org/publicsquare/2018/01/10/road-diet-bridges-barrier-boosts-safety&#34;&gt;redesign of the La Jolla Boulevard in San Diego&lt;/a&gt;, where crashes were reduced by 90% after going from 5 lanes and 70ft pedestrian crossings to 2 lanes and 12-14 foot crossings with islands. Traffic volume stayed the same, and crashes plummeted. &lt;a href=&#34;https://www.youtube.com/watch?v=Ra_0DgnJ1uQ&#34;&gt;This video documents similar phenomenon&lt;/a&gt; contrasting urban design in Toronto and cities in the Netherlands.&lt;/p&gt;
&lt;p&gt;I’m certainly not the first to draw similarities between Bainbridge’s paper and the current use of generative AI tools. &lt;a href=&#34;https://en.wikipedia.org/wiki/Mica_Endsley&#34;&gt;Mica R Endsley&lt;/a&gt;, former Chief Scientist of the U.S. Air Force published a paper called &lt;a href=&#34;https://pubmed.ncbi.nlm.nih.gov/37534468/&#34;&gt;&amp;ldquo;Ironies of artificial intelligence&amp;rdquo;&lt;/a&gt; in 2023 which directly builds on Bainbridge in this context.&lt;/p&gt;
&lt;p&gt;The principle applies beyond roads. In software and operations, we have long accepted that a certain amount of friction is necessary or beneficial. Very few companies would release software without doing some kind of security evaluation, and software teams frequently debate how many gates need to be included to operate safely. Anyone who has found themselves in a vibe coding loop with their role reduced to periodically saying &amp;ldquo;yes&amp;rdquo; or “no” to a coding tool knows how this principle could easily apply to generative AI and coding.&lt;/p&gt;
&lt;p&gt;Certain kinds of friction, such as code review, also have secondary benefits – they are a tool for vicarious learning and reducing the bus factor for parts of a system. By reviewing each others code, we are more able to safely modify and operate that code. Just as drivers lose attentiveness on over-automated roads, software teams risk losing deep system understanding if they offload too much judgment to AI.&lt;/p&gt;
&lt;h2 id=&#34;open-source-is-behind&#34;&gt;Open Source is Behind&lt;/h2&gt;
&lt;p&gt;People sometimes compare the current wave of generative AI coding tools with other shifts in how we build software. It is true that in my career, I’ve seen us move further and further away from bare metal thanks to the introduction of new tools. Higher level languages, frameworks, and tools that allow us to develop at a higher level of abstraction, such as virtualization, containers, and orchestrators. These comparisons are fair, in my opinion.&lt;/p&gt;
&lt;p&gt;Most of the previous waves were dominated by Open Source technology. Docker, Kubernetes, Linux, Xen, GCC, Ruby, Python, Rails, Numpy, JQuery, React, and countless other technologies have made software engineers more productive. They were also, however, open source and available to anyone with an internet connection. I am deeply concerned that the current wave of generative AI is highly dependent on a small set of vendors (OpenAI, Anthropic, Google, etc). I do not know what implications that will have, but I can say that before the great mainstreaming of open source in the late nineties and early aughts, we were far worse off as an industry – accessibility was a real concern (it was harder for newcomers to the field to get started) and innovation was nowhere near as plentiful.&lt;/p&gt;
&lt;p&gt;Vendor dependency concentrates control over core development infrastructure. That centralization risks slower innovation, higher pricing, and reduced accessibility - the opposite of what open source has historically delivered.&lt;/p&gt;
&lt;p&gt;The reliance on vendors brings me to my next concern.&lt;/p&gt;
&lt;h2 id=&#34;we-arent-paying-the-real-cost&#34;&gt;We Aren’t Paying the Real Cost&lt;/h2&gt;
&lt;p&gt;The current landscape is a battle between loss-leaders. &lt;a href=&#34;https://futurism.com/openai-trouble-subprime&#34;&gt;OpenAI is burning through billions of dollars per year&lt;/a&gt; and is expected to hit tens of billions in losses per year soon. Your $20 per month subscription to ChatGPT is nowhere near keeping them afloat. Anthropic’s figures are more moderate, &lt;a href=&#34;https://ceoworld.biz/2025/11/25/the-20-billion-ai-duopoly-inside-openai-and-anthropics-unprecedented-revenue-trajectory/&#34;&gt;but it is still currently lighting money on fire&lt;/a&gt; in order to compete and gain or protect market share.&lt;/p&gt;
&lt;p&gt;We’ve seen loss-leader strategies before, most notably with Uber, which I’ll return to later. The danger in these strategies is that dependencies are being created while the product is cheap, meaning alternatives – even open source tools, can’t compete. This has the potential of creating lock-in where companies integrate these tools into their products, developers become dependent on them in their workflows, and even students learn using them.&lt;/p&gt;
&lt;p&gt;What will this mean for generative AI? I have no idea - obviously there is a chance that some breakthrough will make LLMs far cheaper to build and operate, or companies will have to start forking out a much higher price to use these tools, which could make our industry even harder to break into for people who can’t afford the premium. For now, the question isn’t whether this is sustainable - the companies themselves admit it isn’t. The question is what happens when the subsidy phase ends.&lt;/p&gt;
&lt;h2 id=&#34;environmental-impact&#34;&gt;Environmental Impact&lt;/h2&gt;
&lt;p&gt;Even if the markets appetite for losing billions of dollars annually continues, the use of generative AI is not at all free. LLMs require enormous compute. That compute generates heat. Cooling that heat consumes water - massive amounts of it. A &lt;a href=&#34;https://www.nature.com/articles/s41893-025-01681-y&#34;&gt;recently published study in Nature Sustainability&lt;/a&gt; concluded that AI could have a footprint of 731-1,125 million m³ of water and 24-44 Mt CO2-equivalent emissions annually from 2024 to 2030. That’s equivalent to 200 - 500 bottles of water per person on Earth annually. A &lt;a href=&#34;https://www.cell.com/patterns/fulltext/S2666-3899(25)00278-8&#34;&gt;similar study published in Patterns&lt;/a&gt; concludes that AI systems could produce the same amount of CO2 as the entire city of New York in 2025.&lt;/p&gt;
&lt;p&gt;I agree that generative AI is exciting, but the speed with which a lot of industry leaders went from being apparently concerned with the environment to being happy emitting a NYC worth of CO2 in a year is dizzying.&lt;/p&gt;
&lt;h2 id=&#34;marketing-is-off---not-really-ai&#34;&gt;Marketing is Off - Not Really AI&lt;/h2&gt;
&lt;p&gt;This might be comparatively trivial, but it’s always bothered me how loose we are with the term &amp;ldquo;AI&amp;rdquo;. Artificial Intelligence has the humorous distinction of being a field that is named for what it hopes to achieve, not what it actually does. We have not yet created anything that can be called intelligent, instead we have created impressive technology that looks like thinking without being it.&lt;/p&gt;
&lt;p&gt;Noam Chomsky, Ian Roberts, and Jeffrey Watumull made this point sharply in their New York Times piece &lt;a href=&#34;https://www.nytimes.com/2023/03/08/opinion/noam-chomsky-chatgpt-ai.html&#34;&gt;&amp;ldquo;The False Promise of ChatGPT,&amp;rdquo;&lt;/a&gt; characterizing these systems as statistical pattern-matchers rather than thinking machines. They note these tools are useful for tasks like computer programming, but we shouldn&amp;rsquo;t mistake that utility for understanding.&lt;/p&gt;
&lt;p&gt;This framing matters because it affects how we talk about these tools and what we can expect from them. Calling them &amp;ldquo;AI&amp;rdquo; rather than &amp;ldquo;LLMs&amp;rdquo; or “generative systems” inflates expectations and enables hype bubbles. Setting realistic expectations helps us figure out where these tools actually fit into our workflows and what their genuine limitations are.&lt;/p&gt;
&lt;p&gt;Beyond terminology, there&amp;rsquo;s a more fundamental question about who benefits from this shift.&lt;/p&gt;
&lt;h2 id=&#34;wheres-the-wealth&#34;&gt;Where’s the Wealth?&lt;/h2&gt;
&lt;p&gt;Generative AI will certainly be economically disruptive. Jobs will change and in some cases, certain types of work may be eliminated. Many similar technological shifts have resulted in increased demands for certain kinds of work – meaning that overall, economic opportunity grows, but the disruption is still there.&lt;/p&gt;
&lt;p&gt;I’ve heard more optimistic people suggest that generative AI may lead to more leisure time for more people, without negatively impacting them economically. I disagree with this. You don’t have to look very far into the history of technological shifts to see that increases in worker productivity at best increase demand for labor, and at worst result in massive disruption - they never result in the same pay for less manual work.&lt;/p&gt;
&lt;p&gt;Because these technologies are vendor specific, I actually predict that generative AI will make a relatively small number of people massively wealthy, and a large number of people moderately wealthier, but I fear that an even larger number of people could be left out in the cold as certain types of work become possible without human labor.&lt;/p&gt;
&lt;p&gt;When a technological shift is centralized to one or two vendors, there is a real possibility of a massive wealth transfer. &lt;a href=&#34;https://americanaffairsjournal.org/2019/05/ubers-path-of-destruction/&#34;&gt;Uber famously benefited from massive investor subsidies while following a loss-leader strategy&lt;/a&gt;. This enabled them to drive out competition, both from established players such as taxis and from future projects such as public transportation that would have belonged to the public. Once competitors were eliminated or reduced in size and Uber’s user base was established, prices were raised and consumers were left with few alternatives. During the subsidy phase of Uber’s growth, passengers paid only 41% of costs, leading to a loss of $20 Billion dollars from 2015-2019. Once the subsidy phase ended, fares increased by 65% and the rate that Uber took from drivers was increased.&lt;/p&gt;
&lt;p&gt;The end result was that competitors were eliminated, cities became dependent on ride sharing, and users were locked into higher prices. I fear we could end up in a similar situation with generative AI where productivity is gained through use of generative AI tools, companies become dependent on those tools, prices increase once the land-grab has been established, and workers are left either paying higher premiums for AI tools, or seeing reduced compensation because of the productivity gains seen by using generative AI.&lt;/p&gt;
&lt;h2 id=&#34;where-ai-doesnt-belong&#34;&gt;Where AI Doesn’t Belong&lt;/h2&gt;
&lt;p&gt;I’ve focused so far on technical and economic concerns, but there’s something that troubles me more fundamentally.&lt;/p&gt;
&lt;p&gt;Generative AI is being used for a variety of non-technical tasks such as writing and creating art, including visual art and music. This disturbs me greatly. Art does not have a single purpose, and debating the purpose of art is another subject entirely, but for me, art is a way to communicate a variety of ideas and emotions across time and geography. Experiencing art as a viewer or listener provides a connection with the artist in a very real and human way. Replacing the artist with a computer, in my strong opinion, strips away something essential. Even if the consumer can’t tell that the art was created by generative AI, we’ve lost something real and unquantifiable in the exchange and I fear for what that means for our culture and civilization.&lt;/p&gt;
&lt;h2 id=&#34;so-what-now&#34;&gt;So, What Now?&lt;/h2&gt;
&lt;p&gt;The productivity gains experienced by using generative AI tools are not small, but neither are these concerns. As a professional, I am obligated to use the most effective tools that help me do my job well. I am also obligated to consider the trade-offs between doing something fast and doing something safely. Generative AI applies a thumb on this scale in a very real way, but does not completely eradicate the need for some friction. This will be something that we in the software industry figure out over the next few years. I predict a bit of a roller coaster.&lt;/p&gt;
&lt;p&gt;As a human, I am concerned about my impact on the environment and our collective experiences on this planet. As I&amp;rsquo;ve mentioned, there’s no putting the genie back in the bottle, so it’s imperative that we continue to focus research on making LLMs more environmentally sustainable and I implore those subsidizing the unrestrained growth to take a very real look at the economic realities staring at us all. I do not know what the future holds, and I would never venture to guess what impact this will all have on the environment, our collective sense of purpose, and our careers, but it is up to us all to try and make it as positive as possible.&lt;/p&gt;
&lt;p&gt;The challenge isn’t choosing &amp;ldquo;AI or not AI&amp;rdquo; - that ship has sailed. It’s navigating the shift thoughtfully and considering the trade offs of how and when we use it in our day to day lives.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Solving a Murder Mystery</title>
      <link>https://paulosman.me/2022/11/29/solving-a-murder-mystery/</link>
      <pubDate>Tue, 29 Nov 2022 08:30:00 +0000</pubDate>
      <guid>https://paulosman.me/2022/11/29/solving-a-murder-mystery/</guid>
      <description>&lt;p&gt;A coworker asked me if I ever write about difficult bugs I&amp;rsquo;ve encountered. I hadn&amp;rsquo;t thought about doing that before, but I like the idea. The conversation made me think about a bug I ran into at Honeycomb that I want to document before I forget the details. Solving this one was a team effort, requiring different insights from different people. It was also the kind of bug I could only imagine solving with good observability and teamwork.&lt;/p&gt;
&lt;p&gt;I will detail how some of Honeycomb&amp;rsquo;s systems worked when I worked there. This, and the details of the bug, are from memory so something is bound to be incorrect. Also, &lt;a href=&#34;https://www.honeycomb.io/blog/a-better-environment-for-observability-at-your-service&#34;&gt;a lot has changed since then&lt;/a&gt;, so this isn&amp;rsquo;t intended to be current. Finally, because I no longer work at Honeycomb, I don&amp;rsquo;t have access to the queries we ran while debugging this issue, so you&amp;rsquo;ll have to settle for my hand-drawn approximations! (did you know that queries in Honeycomb last forever so you can always refer back to them? Seriously underrated feature if you ask me).&lt;/p&gt;
&lt;h2 id=&#34;context&#34;&gt;Context&lt;/h2&gt;
&lt;p&gt;Honeycomb is an observability tool. It is a service that accepts ultra-wide events (as JSON or OpenTelemetry Protocol) and stores them in datasets. Users run queries against their datasets, slicing and dicing them by multiple dimensions.&lt;/p&gt;
&lt;p&gt;Honeycomb can serve most queries in a second or two, and queries can be filtered on or grouped by high cardinality fields. Example queries that a user may run include &amp;ldquo;Show me the P95 and a heatmap of latency of requests to a particular endpoint in one of two provided availability zones, grouped by customer id over the last two days.&amp;rdquo;. Or &amp;ldquo;Show me counts of requests grouped by HTTP status codes over the last two weeks.&amp;rdquo;.&lt;/p&gt;
&lt;h2 id=&#34;the-symptom&#34;&gt;The Symptom&lt;/h2&gt;
&lt;p&gt;A customer complained that a specific query was consistently generating an error. The particular error message they were seeing usually indicated a timeout, which generally indicates an availability problem with our columnar datastore. However, this case was consistently reproducible, pointing to a problem with this specific query, not database availability. There was a problem with the data causing errors in the query processor—running the same query with different time windows produced no error, providing further evidence for the bad data theory.&lt;/p&gt;
&lt;h2 id=&#34;technical-details&#34;&gt;Technical Details&lt;/h2&gt;
&lt;p&gt;Honeycomb stores events in a custom columnar datastore called retriever. Retriever stores all customer events and processes queries for customers.&lt;/p&gt;
&lt;p&gt;All customer events flow through a distributed event store, Kafka. Honeycomb maintains a mapping of customer datasets to Kafka partitions (higher volume datasets get more partitions). When an event is received by Honeycomb&amp;rsquo;s ingest service, it is published to one of the Kafka partitions that the customer dataset is mapped to. At the other end of the Kafka partitions, two redundant retriever nodes consume events and persist them to their local disks. Event logs are append-only, and retriever nodes have fast nvme disks.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/solving-a-murder-mystery/ingest-service.png&#34; alt=&#34;Events received by the ingest service are published to one of several assigned kafka partitions and consumed by two retriever nodes doing redundant work.&#34;&gt;&lt;/p&gt;
&lt;p&gt;When a retriever node consumes an event, it writes the event to separate files per column. Retriever groups events into segments. Segments are routinely rolled out (when I worked at Honeycomb, they were marked read-only, and a new segment was created when the segment hit 1 million events, 1GB of data, or when 12 hours had passed).&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/solving-a-murder-mystery/segments.jpeg&#34; alt=&#34;Retriever writes columnar data to segments, new segments are routinely rolled out.&#34;&gt;&lt;/p&gt;
&lt;p&gt;An out-of-band process manages the lifecycle of segments. Segments go from being live and having data written to them, to being read-only but still stored on disk, to being read-only and stored in S3, and finally to being tombstoned and eventually deleted when the data ages out (it is no longer queryable).&lt;/p&gt;
&lt;p&gt;When a query is processed, each retriever node that could have data for the query processes the data it has on disk. Data from segments in S3 is retrieved and processed with lambda functions, which merge with the results from the retriever nodes. AWS Lambda functions are a &lt;a href=&#34;https://www.thestrangeloop.com/2021/how-we-used-serverless-to-speed-up-our-servers.html&#34;&gt;core part of how retriever processes queries&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;custom-instrumentation-to-the-rescue&#34;&gt;Custom Instrumentation to the Rescue&lt;/h2&gt;
&lt;p&gt;Okay, back to the problem. At this point, I&amp;rsquo;m assuming that there&amp;rsquo;s a piece of bad data causing the queries to fail. I narrowed it down to a one-minute range of data, so I was confident in this hypothesis. However, I assumed it was a problem with invalid data (e.g., a division by zero error somehow being thrown by Honeycomb&amp;rsquo;s derived column DSL).&lt;/p&gt;
&lt;p&gt;One of my coworkers started looking at traces and noticed that the lambda functions had an error message that read NoSuchKey in the error field of the span. The custom error field alerted us to the possibility that the segment was somehow missing from S3 &amp;ndash; we independently confirmed this by trying to fetch one of the bad segments using the AWS CLI tool and seeing it return a 404.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/solving-a-murder-mystery/trace.png&#34; alt=&#34;A trace with the culprit error message - NoSuchKey indicates that the lambda function could not retrieve the segment file.&#34;&gt;&lt;/p&gt;
&lt;p&gt;This discovery was a scary realization - data we thought we had stored was missing!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; While losing data is always scary, especially when the scope is unknown, a single segment represents a relatively tiny amount of data. If the query wasn’t failing, none of the aggregate values (SUM, AVG, P95, etc) in an average sized dataset would be noticeably skewed by a single missing segment when queried over a typical time window. While observability data is fundamentally lossy and data loss isn’t great, it doesn’t have the same criticality as say, a transactional database.&lt;/p&gt;
&lt;h2 id=&#34;s3-prefixes&#34;&gt;S3 Prefixes&lt;/h2&gt;
&lt;p&gt;Before continuing, it&amp;rsquo;s necessary to understand how Honeycomb stores segments in S3.&lt;/p&gt;
&lt;p&gt;S3 is a great storage system. It allows Honeycomb to store a massive volume of events, enabling customers to query their data over long time windows (The default is 60 days as of this writing).&lt;/p&gt;
&lt;p&gt;To optimize performance and avoid hotspots, it&amp;rsquo;s a good practice to prefix objects in a bucket with high variability. An engineer at Honeycomb had previously led a project to design a prefixing scheme for our segments. When uploading to S3, objects are given a fixed-length hash code as a prefix. We calculate the hash code from the dataset id, the partition id, and the segment id. For example, given dataset id 432, partition id 32, and segment id 1023:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;hash(432, 32, 1023) = 9d4&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;We then prepend this prefix to the S3 object name when uploading the segment, so the object name for files in segment 10253 on partition 32 and dataset 432 would have a prefix like this:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;9d4/432/32/10253&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Without the hashing, all objects in segments for dataset id 432 would have the same prefix. If 432 is a frequently queried, huge dataset, this could result in hotspots and performance degradations.&lt;/p&gt;
&lt;h2 id=&#34;finding-needles-in-haystacks&#34;&gt;Finding Needles in Haystacks&lt;/h2&gt;
&lt;p&gt;So we knew that specific segments did not exist in S3. Now what? Failing to store data is pretty bad. We started combing through our S3 logs dataset in Honeycomb to find out what happened. We saw that requests for one of the problem segments went from returning 200 (OK) to suddenly returning 404 (Not Found). Okay, so the segment existed at some point, and then it didn&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/solving-a-murder-mystery/missing-segments.png&#34; alt=&#34;A graph showing a series of requests for a particular S3 object returning 200, and then 404&#34;&gt;&lt;/p&gt;
&lt;p&gt;Because of the overall volume, the S3 logs dataset we stored in Honeycomb is heavily sampled. If an errant process deleted the problem segment, it&amp;rsquo;s unlikely we would have the specific log line in our dataset.&lt;/p&gt;
&lt;p&gt;As mentioned previously, retriever has an out-of-band process that manages the lifecycle of segments, including deleting the segments from S3. Still, it should only delete segments that have aged out. These segments that suddenly went missing were still supposed to be serving data.&lt;/p&gt;
&lt;p&gt;Another engineer downloaded a huge trove of S3 logs and started running parallel grep tasks to find entries related to the problem segment. This was challenging - Honeycomb has many terabytes of S3 logs. Nevertheless, we hit the jackpot. The log files showed that when the out-of-band process deleted an aged-out segment with a name like &lt;code&gt;9d4/432/32/102&lt;/code&gt;, it also deleted a newer segment with a name like &lt;code&gt;9d4/432/32/10253&lt;/code&gt;. This accidental deletion only happened to a few segments out of billions, but that&amp;rsquo;s enough to fail a query if the time window spans the data contained in the missing segment.&lt;/p&gt;
&lt;p&gt;Another engineer confirmed this by running a Honeycomb query showing that on sequential runs, our out-of-band lifecycle manager showed an unusually high count of objects deleted for a really old segment and then no objects deleted on the next run.&lt;/p&gt;
&lt;p&gt;Looking closer at the code, I confirmed we were not appending a trailing forward slash to S3 objects when deleting aged-out segments from S3. We were using the S3 API in such a way to emulate deleting all “files” in a “directory”, so the provided key was not an object name, but a prefix. When we deleted &lt;code&gt;9d4/432/32/102&lt;/code&gt;, a very old segment, we were also deleting &lt;code&gt;9d4/432/32/10253&lt;/code&gt; because the two segments were part of the same dataset, came in through the same partition, both had segment ids that began with 102, and happened to have a hash collision. We really should have been deleting &lt;code&gt;9d4/432/32/102/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Hash collisions are common at scale, but in this case, the hash was never supposed to guarantee the uniqueness of keys. It just needed to spread out the prefix scheme so segments from the same dataset wouldn&amp;rsquo;t get grouped together. Some collisions were acceptable and even expected because the other components that made up the S3 object name would be different (even if the dataset and partition are the same, the segment id wouldn’t be).&lt;/p&gt;
&lt;p&gt;The real issue here was a bug in our segment lifecycle management process. A simple missing trailing slash was inadvertently deleting more data than intended (Honeycomb employees love puns, so someone dubbed this murder mystery a “real slasher”). Funny enough, because hash collisions don&amp;rsquo;t happen all of the time, our prefixing scheme ended up hiding this bug from us for a long time. This bug would have been immediately apparent if we weren&amp;rsquo;t prepending segments with a fixed-width hash code.&lt;/p&gt;
&lt;p&gt;The fix ended up being about twenty lines of code, including comments. I think I had it ready and submitted a PR within 15 minutes of the team discovering the source of the bug.&lt;/p&gt;
&lt;h2 id=&#34;a-series-of-coincidences&#34;&gt;A Series of Coincidences&lt;/h2&gt;
&lt;p&gt;Someone pointed out that there are some fun statistical properties to this bug. For instance, while hash collisions are pretty common at scale, they only matter here if the two segments are on the same dataset, on the same partition, and have ids with the same first digit or digits. A hash collision wouldn’t matter if the paths end up being completely different anyway (e.g. &lt;code&gt;9d4/432/32/10253&lt;/code&gt; and &lt;code&gt;9d4/321/23/43&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;​​This bug skews heavily towards newer customers who generated a lot of events &amp;ndash; these customers create new datasets with a lot of segments (because segments get rolled out every 1M events or once they hit 1GB). Segment ids start at 1 for every new dataset. The distance between 10&lt;sup&gt;0&lt;/sup&gt;, 10&lt;sup&gt;1&lt;/sup&gt;, 10&lt;sup&gt;2&lt;/sup&gt;, &amp;hellip;, 10&lt;sup&gt;n&lt;/sup&gt; grows very quickly as n increases. Furthermore, the distance between 10&lt;sup&gt;n&lt;/sup&gt; and 10&lt;sup&gt;n+1&lt;/sup&gt; is much smaller than the distance between m · 10&lt;sup&gt;n&lt;/sup&gt; and m · 10&lt;sup&gt;n+1&lt;/sup&gt; for larger values of m where 0 &amp;lt; m &amp;lt; 10. This is a fun application of &lt;a href=&#34;https://en.wikipedia.org/wiki/Benford%27s_law&#34;&gt;Benford&amp;rsquo;s law&lt;/a&gt; which maintains that real-world data sets of numbers will include many more leading 1s and 2s than 3s and so on. So we can see that this bug becomes increasingly unlikely as segment ids increase. If we accept that hash collisions will have a uniform distribution, we can see that they&amp;rsquo;re less likely to result in an accidental deletion with segments that begin with a leading 8 than with segments that begin with a leading 1 or 2.&lt;/p&gt;
&lt;p&gt;So the hash collisions wouldn&amp;rsquo;t matter, except that all the rest of the data up until the segment prefix was the same. The rest of the data being the same wouldn&amp;rsquo;t matter if we had used a trailing slash in the object name when deleting objects from S3. This bug was noticed because we were growing fast and adding more and more teams, who were creating more and more high volume datasets, hence increasing the probability of one of these consequential collisions biting us.&lt;/p&gt;
&lt;h2 id=&#34;team-work-and-observability&#34;&gt;Team Work and Observability&lt;/h2&gt;
&lt;p&gt;Solving this bug was a lot of fun and took four engineers looking at things from slightly different angles. The solution was simple but figuring out what was happening involved stitching together a series of clues. I don&amp;rsquo;t know how we would have caught it without custom instrumentation. No auto-instrumentation tool will know that the s3 hash prefix is a &lt;em&gt;very&lt;/em&gt; important part of a segment, for example.&lt;/p&gt;
&lt;p&gt;Thankfully this system had pretty good instrumentation that gave us observability from a few different angles - all of which ended up being necessary to come to the solution we did in the time we did. We used a fair amount of Honeycomb querying and also had to look at raw access logs — that’s rare in my experience, but further proof that while sampling and aggregation can save on disk space and bandwidth, it’s always nice to be able to get all of the source data you need, even if you have to pull it from cheaper storage.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Thank you to &lt;a href=&#34;https://ferd.ca/&#34;&gt;Fred Hebert&lt;/a&gt; and other Honeycombers for reviewing this post and feedback. I hope it was a fun trip down memory lane!)&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Getting up to Speed</title>
      <link>https://paulosman.me/2022/11/05/getting-up-to-speed/</link>
      <pubDate>Sat, 05 Nov 2022 00:00:00 +0000</pubDate>
      <guid>https://paulosman.me/2022/11/05/getting-up-to-speed/</guid>
      <description>&lt;p&gt;I recently started a new role working on data infrastructure at LinkedIn. Working with data at a vast scale at an organization with a &lt;em&gt;lot&lt;/em&gt; of experience in the space is exciting.&lt;/p&gt;
&lt;p&gt;The area I work in is described internally as &amp;ldquo;Big Data.&amp;rdquo;. That term has been used in many marketing materials and can induce a lot of cringes. For this post, &amp;ldquo;Big Data&amp;rdquo; refers to working with large, semi-structured datasets. These can be hosted on distributed filesystems or streamed through a distributed event platform like Kafka. The datasets are typically not pre-loaded into an OTLP or OLAP database.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve worked for 22 years in this industry, but most of my experience applies to online or real-time systems. Working with offline data is new to me, so I&amp;rsquo;ve had to get up to speed quickly with the history, trends, and vernacular common to the domain. Before starting in this role, I had some ambient awareness of terms like data warehouse, data lake, MPP, DAG, and others. Still, I wasn&amp;rsquo;t comfortable discussing them in-depth, and I wasn&amp;rsquo;t up to date on developments in the data ecosystem.&lt;/p&gt;
&lt;p&gt;I focused my research on two categories: learning about the history of the space and learning about the customer profiles (data scientists and data engineers). In this post, I will focus on how I researched the former.&lt;/p&gt;
&lt;h2 id=&#34;reading-papers&#34;&gt;Reading Papers&lt;/h2&gt;
&lt;p&gt;Reading papers is something I started doing back when I worked in Bioinformatics. Most of the concepts I explored then came from academic research, and reading research papers was the only way to stay up to date. I picked the habit up again when I worked at PagerDuty.&lt;/p&gt;
&lt;p&gt;Reading papers can be daunting for someone like me without an academic background. I&amp;rsquo;ve found that the approach &lt;a href=&#34;https://caitiem.com/2017/09/07/getting-started-with-distributed-systems/&#34;&gt;Caitie McCaffrey describes in her blog&lt;/a&gt; works well for me (see &amp;ldquo;A Note on Reading Papers&amp;rdquo; in the linked article). Marc Brooker&amp;rsquo;s article &lt;a href=&#34;https://brooker.co.za/blog/2020/05/25/reading.html&#34;&gt;Reading Research: A Guide for Software Engineers&lt;/a&gt; is also an excellent resource. Watching &lt;a href=&#34;https://www.youtube.com/c/PapersWeLove&#34;&gt;PapersWeLove&lt;/a&gt; talks is also a great way to read some computer science papers. I like reading a paper, watching a talk on it, then returning to it to re-read sections that confused me the first time.&lt;/p&gt;
&lt;h2 id=&#34;creating-a-reading-list&#34;&gt;Creating a Reading List&lt;/h2&gt;
&lt;p&gt;In &lt;a href=&#34;https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1006467&#34;&gt;Ten simple rules for developing good reading habits for grad school and beyond&lt;/a&gt;, the author recommends that if you have to learn about a new topic, consider reading in chronological order. I decided to do this and compiled a list of papers, starting with the &lt;a href=&#34;https://static.googleusercontent.com/media/research.google.com/en//archive/gfs-sosp2003.pdf&#34;&gt;Google File System&lt;/a&gt; paper published in 2003, continuing with the &lt;a href=&#34;https://static.googleusercontent.com/media/research.google.com/en//archive/mapreduce-osdi04.pdf&#34;&gt;MapReduce&lt;/a&gt; paper in 2004, and so on.&lt;/p&gt;
&lt;p&gt;I used a straightforward process to build my reading list. I knew that the GFS and MapReduce papers were among the earlier publications in the domain, so I anchored my search with them. I searched for them on Google Scholar and then looked at lists of papers that cited them. Many familiar pieces turned up, including the original &lt;a href=&#34;https://www.usenix.org/legacy/event/hotcloud10/tech/full_papers/Zaharia.pdf&#34;&gt;Spark paper&lt;/a&gt;, but also many unfamiliar ones to me, such as the &lt;a href=&#34;https://www.usenix.org/conference/osdi16/technical-sessions/presentation/abadi&#34;&gt;TensorFlow&lt;/a&gt; paper, the &lt;a href=&#34;https://www.diva-portal.org/smash/get/diva2:1059537/FULLTEXT01.pdf&#34;&gt;Flink paper&lt;/a&gt;, and eventually, the &lt;a href=&#34;https://dl.acm.org/doi/abs/10.1145/3514221.3526054&#34;&gt;Photon paper&lt;/a&gt; and others that represent more recent contributions to the domain.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/paulosman/database-systems/blob/main/big-data.md&#34;&gt;Here&amp;rsquo;s my list so far&lt;/a&gt;. I&amp;rsquo;m working through it, but please comment if you have anything to add!&lt;/p&gt;
&lt;h2 id=&#34;connecting-to-experience&#34;&gt;Connecting to Experience&lt;/h2&gt;
&lt;p&gt;One exciting thing about this exercise was that it allowed me to compare what I was reading to what I was doing in the industry when the paper was published.&lt;/p&gt;
&lt;p&gt;As mentioned, I&amp;rsquo;ve never worked directly in the &amp;ldquo;Big Data&amp;rdquo; domain, but I have worked with data in some form or another in my career. When the GFS and MapReduce papers were published, I worked in Bioinformatics. Bioinformaticians often need to analyze large datasets and feed the output of their analysis into some other process. The team I worked with built a workflow automation language and runtime engine designed to run on OpenMosix clusters with datasets stored on large NFS volumes. NFS was a pain to maintain and susceptible to common failure modes, so reading about how GFS was designed to run on commodity hardware and treated component failures as the norm made a lot of sense to me. Our lives would have been much easier if we had GFS and MapReduce (or the Hadoop ecosystem, the open-source project primarily inspired by these projects). (In retrospect, what we built was similar to Azkaban or Airflow).&lt;/p&gt;
&lt;p&gt;Some of what I read doesn&amp;rsquo;t relate to my experience, but I still try to imagine how I&amp;rsquo;d solve the problem stated in the paper. It&amp;rsquo;s a valuable exercise that helps me get into the right headspace to read the rest of the paper.&lt;/p&gt;
&lt;h2 id=&#34;being-hands-on&#34;&gt;Being Hands On&lt;/h2&gt;
&lt;p&gt;Reading papers is a great way to generate a broad understanding of work done in a particular domain. I also like to combine it with hands-on operational work. Thankfully, most of the topics covered relate to some open-source work that I can use to gain hands-on experience, even if just with toy examples. For example, after reading the GFS and MapReduce papers, I set up an HDFS cluster on a small set of raspberry PIs I had lying around (with my &lt;a href=&#34;https://www.solid-run.com/arm-servers-networking-platforms/honeycomb-servers-workstation/&#34;&gt;Honeycomb LX2&lt;/a&gt; acting as a ResourceManager and NameNode). Maybe it&amp;rsquo;s my ops background, but I like seeing what&amp;rsquo;s happening at an OS level when running MapReduce jobs or Spark tasks. Some people get the same level of understanding while skipping this step, as always, YMMV.&lt;/p&gt;
&lt;h2 id=&#34;making-time&#34;&gt;Making Time&lt;/h2&gt;
&lt;p&gt;Researching and deep diving into a particular topic or domain takes a lot of time. I try to get through two papers per week. I&amp;rsquo;ve recently started working from the NYC office, about a 35-minute commute from my house in Brooklyn. I use this time to read through a paper on my &lt;a href=&#34;https://remarkable.com/&#34;&gt;Remarkable 2&lt;/a&gt; tablet and make notes for later. I&amp;rsquo;ll also use time in the evenings after my daughter has gone to bed. Whatever works for you, if you can etch out an extra hour or so per day, that&amp;rsquo;ll likely be enough!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Being on a Board</title>
      <link>https://paulosman.me/2022/10/17/being-on-a-board/</link>
      <pubDate>Mon, 17 Oct 2022 00:00:00 +0000</pubDate>
      <guid>https://paulosman.me/2022/10/17/being-on-a-board/</guid>
      <description>&lt;p&gt;I recently left my job at &lt;a href=&#34;https://www.honeycomb.io&#34;&gt;Honeycomb&lt;/a&gt;. They&amp;rsquo;re a fantastic company, and I think they will be wildly successful. Still, life circumstances are such that I wanted to explore an opportunity at a much larger technology organization. More on that in another post, perhaps. In this post, I want to reflect on a unique aspect of my employment at Honeycomb: I was the first employee to sit on their Board of Directors as a full voting member. While this is a &lt;a href=&#34;https://en.wikipedia.org/wiki/Codetermination_in_Germany&#34;&gt;common practice in some countries&lt;/a&gt;, as far as I know, I am the first non-founder/executive person at a venture-backed software company in North America to have had this opportunity (since first publishing, people have pointed out &lt;a href=&#34;https://o1labs.org/&#34;&gt;some other companies&lt;/a&gt; that have also done this, which is great to see!). Because I left my term early, I only got to participate in two meetings, so these reflections come from that partial sample.&lt;/p&gt;
&lt;h3 id=&#34;nominations-and-voting&#34;&gt;Nominations and Voting&lt;/h3&gt;
&lt;p&gt;When Honeycomb decided to add an employee board member, our founders, Charity and Christine, outlined a process in a wiki document and circulated it to everyone in the company. Employees who had been employed full-time at Honeycomb for two years would be eligible for board membership and could be nominated. Anyone who had been working for six months could vote. After the nomination period, each candidate wrote a 500-word pitch explaining why they wanted to be on the Board and how they would approach their responsibilities. Employees then voted using ranked-choice ballots. There were four nominees, three from engineering and one from the sales team. Voting continued for a fixed period, after which Charity announced that I would be Honeycomb&amp;rsquo;s first employee board member!&lt;/p&gt;
&lt;h3 id=&#34;what-is-a-board&#34;&gt;What is a board&lt;/h3&gt;
&lt;p&gt;A part of this role involved demystifying what a board is and isn&amp;rsquo;t, so I want to cover that before going into more detail.&lt;/p&gt;
&lt;p&gt;Every corporation has a board, and many other types of organizations do too. A board of directors is a group of people who jointly supervise the activities of an organization. For a venture-backed startup, the Board usually consists of the founders and representatives from venture capital firms that have led or participated in significant funding rounds.&lt;/p&gt;
&lt;p&gt;Board members have a fiduciary responsibility, which means they are required to serve the interests of shareholders in the organizations. That is open to interpretation, of course. Still, usually, it means they&amp;rsquo;re out to maintain the financial health of the company and maximize the value of the company in the long term. In other words, boards want measured bets and continued growth.&lt;/p&gt;
&lt;p&gt;Boards often have independent members who are there to either provide some outside perspective or to bring some specific experience to the group. These people have often worked in similar markets or have knowledge or skills that the company needs.&lt;/p&gt;
&lt;h3 id=&#34;meeting-format&#34;&gt;Meeting Format&lt;/h3&gt;
&lt;p&gt;The board meetings I attended had a pretty standard format. At Honeycomb, the &lt;a href=&#34;https://www.honeycomb.io/team#leadership&#34;&gt;executive team&lt;/a&gt; spends a few weeks in advance preparing the board deck. The deck outlines the meeting agenda, which usually includes a recap of various company health indicators for the last quarter and topics that might be of interest — strategy outlines or a deep dive into a particular area of the company.&lt;/p&gt;
&lt;p&gt;We go through the deck in the first part of each meeting. The Honeycomb executive team is present, and each member reviews updates relevant to their specific areas (e.g., Chad, the VP of Sales, will give an overview of the sales pipeline). The Board, legal counsel, board observers, additional representatives of investor firms, and people who have participated in angel and other investment rounds are welcome to join this part of the meeting. The deck is sent out ahead of time as pre-read for the meeting. People tend to ask clarifying questions and might ask to dive deeper into a specific area on one or two slides.&lt;/p&gt;
&lt;p&gt;The next part of the meeting is administrative. The board members discuss and vote on administrative issues such as certifying the minutes of the previous meeting and financial matters like compensation changes, stock grants, etc. Non-voting participants, except for the legal counsel, are asked to leave before this part of the meeting.&lt;/p&gt;
&lt;h3 id=&#34;summarizing-board-meetings-to-the-company&#34;&gt;Summarizing Board Meetings to the Company&lt;/h3&gt;
&lt;p&gt;A big part of my responsibility as an employee board member was to summarize board meetings to the rest of the company during an all-hands meeting. I had a 15 - 20 minute slot, during which I would recap what the Board&amp;rsquo;s role is and who are the other board members. Especially at a growing company, repetition never hurts the message, as many people would be hearing this for the first time. I&amp;rsquo;d then go through a recap of what I thought the highlights were. Everyone in the company has access to the board deck, so I don&amp;rsquo;t have to recap everything, and I usually kept it to what I thought were the big topics of discussion. I&amp;rsquo;d also try to give everyone a sense of the mood and how receptive investors were to our strategy. As an employee, I might pick up on different things in the meeting, and execs who were also present would often tell me that they were delightfully surprised at what I would choose to highlight.&lt;/p&gt;
&lt;h3 id=&#34;funnels-funnels-and-more-funnels&#34;&gt;Funnels, Funnels, and more Funnels&lt;/h3&gt;
&lt;p&gt;One personal takeaway from my participation on the Board is that, at a certain level, every organization has to get good at looking at and interpreting marketing and sales pipelines. A pipeline is just a value delivery chain, so looking at the shape and properties of the funnel can hopefully project roughly how successful an organization will be in the short and medium term. You can break a funnel into different pieces, commonly referred to as Top of the Funnel, or Tofu, Middle of the Funnel, or Mofu, and Bottom of the Funnel, or Bofu. So if you hear people talking about Tofu, Mofu, and Bofu, they&amp;rsquo;re talking about funnels.&lt;/p&gt;
&lt;p&gt;A marketing funnel feeds into the sales funnel. By looking at the percentage of movement from one part of a funnel to the other and the amount of time it takes to travel through a funnel, you can make educated guesses about how things will go for the next little while. Investors especially spend a lot of time looking at the health of various funnels; if these look healthy, everybody will be much more relaxed.&lt;/p&gt;
&lt;h3 id=&#34;trust&#34;&gt;Trust&lt;/h3&gt;
&lt;p&gt;Honeycomb is doing well as a company. Everything an investor or executive would measure is going in the right direction for them. As a result, I didn&amp;rsquo;t witness any tense discussions. The Board acted as an experienced audience to guide Honeycomb&amp;rsquo;s strategy. I understood that the effort to prepare and present the board deck was worthwhile for everyone, even if there were no significant disagreements. As the quote goes, &amp;ldquo;plans are useless, but planning is indispensable.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Of course, this level of trust isn&amp;rsquo;t going to be present on every Board or at all times. If a company is having significant difficulties, I&amp;rsquo;m sure the discussions in these meetings can be much more consequential.&lt;/p&gt;
&lt;h3 id=&#34;leaving-early&#34;&gt;Leaving Early&lt;/h3&gt;
&lt;p&gt;My original term was for a year, but I chose to leave Honeycomb and resign my board membership after six months (My family and I moved to NYC and I needed to bring home a higher salary than can be reasonably obtained at a startup). The process outlined specified that I would pick a successor if I resigned before my term was complete. I chose one of the original nominees who I thought would approach the responsibility similarly to me, providing as much consistency as possible to the Board.&lt;/p&gt;
&lt;p&gt;Admittedly, it was a bummer to leave early. I didn&amp;rsquo;t want it to harm the experiment. I thought about delaying my family&amp;rsquo;s move to NYC, but with a school-aged child, I didn&amp;rsquo;t want them to have to change schools mid-year. On the bright side, the Board gets to experience another employee perspective!&lt;/p&gt;
&lt;h3 id=&#34;being-a-representative&#34;&gt;Being a Representative&lt;/h3&gt;
&lt;p&gt;As an employee on the Board, people would sometimes approach me with concerns. These fell into two categories: 1) things that could be relevant to the Board and informed how I interpreted parts of the Board deck, and 2) things that the person should bring up with their manager. In the latter case, I tried to give that feedback directly to the person.&lt;/p&gt;
&lt;p&gt;I tried to solicit feedback frequently, especially after my presentations to the company during our All-Hands meetings. It&amp;rsquo;s hard to know what people want to hear or what information is worth conveying since you&amp;rsquo;re mostly demystifying something for many people. I received positive feedback, both from execs who were at the meeting and employees who weren&amp;rsquo;t.&lt;/p&gt;
&lt;h3 id=&#34;reflections&#34;&gt;Reflections&lt;/h3&gt;
&lt;p&gt;I appreciated the opportunity to serve on Honeycomb&amp;rsquo;s Board of Directors. My term was short, but the experience was valuable and changed how I see startups — I now have a behind-the-scenes perspective typically reserved for executives and founders. I&amp;rsquo;ll probably understand the motivations of executive teams a lot better from now on.&lt;/p&gt;
&lt;p&gt;I am aware that I served on the Board when Honeycomb was doing well as a company. Consequently, I didn&amp;rsquo;t participate in any discussions or decisions that had the potential for a profound impact on Honeycomb employees. I am also aware that this is &lt;em&gt;precisely&lt;/em&gt; the time when a company has the opportunity to experiment with having an employee sit on the Board &amp;ndash; when trust is high. When things are going well, the Board trusts the founders to steer the ship.&lt;/p&gt;
&lt;p&gt;When Honeycomb first &lt;a href=&#34;https://www.honeycomb.io/blog/employee-representative-board-of-directors&#34;&gt;announced that I was joining the Board&lt;/a&gt;, some people outside the company compared having an employee serve on the Board with having a union. I want to be clear that these two things are nothing alike. Unions exist to provide workers leverage in negotiations on issues that concern them. The structure accepts divergent interests between groups and provides countering influence to even the playing field. Having an employee on the Board of Directors provides that employee an opportunity to represent a point of view that could be helpful to the rest of the Board. Still, if interests are not aligned, they will not be very impactful.&lt;/p&gt;
&lt;p&gt;Having an employee Board member provides an extremely high level of transparency to the rest of the company&amp;rsquo;s employees. There&amp;rsquo;s an implicit trust that an employee can build that an executive would struggle to obtain. The Board benefits from an additional perspective- particularly one close to the knives- that can help it make more informed decisions.&lt;/p&gt;
&lt;p&gt;I hope to see more companies perform experiments like this, and I hope that Honeycomb, both the company and the Board, benefited as much as I did.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>On Programming Languages</title>
      <link>https://paulosman.me/2022/04/30/on-programming-languages/</link>
      <pubDate>Sat, 30 Apr 2022 00:00:00 +0000</pubDate>
      <guid>https://paulosman.me/2022/04/30/on-programming-languages/</guid>
      <description>&lt;p&gt;I was having a conversation with another software engineer the other day and they asked for my thoughts on various programming languages and paradigms. The topic came up because I mentioned that Python3 supported &lt;a href=&#34;https://docs.python.org/3/library/typing.html#typing.Optional&#34;&gt;Optional types&lt;/a&gt; which are a popular feature in many functional programming languages like &lt;a href=&#34;https://hackage.haskell.org/package/strict-data-0.2.0.2/docs/Data-Option.html&#34;&gt;Haskell&lt;/a&gt;, &lt;a href=&#34;https://v2.ocaml.org/api/Option.html&#34;&gt;OCaml&lt;/a&gt;, and &lt;a href=&#34;https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/options&#34;&gt;F#&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Conversations like these often turn into statements about the general merits of technology x or y. It can be very tempting to draw specific conclusions. I&amp;rsquo;ve heard many of these, and have held them myself in the past. Conclusions like &amp;ldquo;Functional programming makes parallelism easier&amp;rdquo; or &amp;ldquo;Static typing reduces bugs&amp;rdquo; are seductive in their simplicity, but they smooth over the massive surface area that Software Engineers find themselves navigating these days.&lt;/p&gt;
&lt;p&gt;Personally, I really enjoy functional programming. I enjoy that functional programming often allows you to express very complicated processes as simple chains of operations on immutable data. I learned Haskell many years ago by watching Erik Meijer&amp;rsquo;s &lt;a href=&#34;https://www.youtube.com/watch?v=UIUlFQH4Cvo&#34;&gt;Channel 9 Lecture Series&lt;/a&gt; and reading along with Graham Hutton&amp;rsquo;s &lt;a href=&#34;https://www.cs.nott.ac.uk/~pszgmh/pih.html&#34;&gt;Programming in Haskell&lt;/a&gt;. I worked at a few companies where I was paid to write Scala, and we used the &lt;a href=&#34;https://twitter.github.io/finagle/&#34;&gt;Finagle RPC System&lt;/a&gt; which uses a lot of functional programming concepts.&lt;/p&gt;
&lt;p&gt;These days, most of the code I write at work is in &lt;a href=&#34;https://go.dev/&#34;&gt;Go&lt;/a&gt; which is about as imperative as you can get. Functions are first class objects, and Go 1.18 has introduced &lt;a href=&#34;https://go.dev/blog/go1.18&#34;&gt;generics&lt;/a&gt;, but that&amp;rsquo;s about it as far as functional programming concepts go (sure, generics aren&amp;rsquo;t specifically a functional programming concept, but I&amp;rsquo;d argue that they&amp;rsquo;re kind of necessary in a statically typed language if you want to do any kind of functional programming). Go does, however, have a lot of power in its simplicity, in its treatment of errors as types, and the ability to &lt;a href=&#34;https://www.oreilly.com/library/view/concurrency-in-go/9781491941294/&#34;&gt;model concurrency through channels and goroutines&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m also really enjoying learning &lt;a href=&#34;https://www.rust-lang.org/&#34;&gt;Rust&lt;/a&gt; which is a multi-paradigm language particularly well suited for systems programming. For that matter, I&amp;rsquo;ve also been brushing up on the last 10 years of developments in C++ as part of doing the assignments in a &lt;a href=&#34;https://15445.courses.cs.cmu.edu/fall2021/&#34;&gt;CMU databases course&lt;/a&gt; that I&amp;rsquo;m following.&lt;/p&gt;
&lt;p&gt;So, what languages and paradigms do I prefer, and why? Here&amp;rsquo;s the kicker: &lt;strong&gt;I don&amp;rsquo;t care.&lt;/strong&gt; I really don&amp;rsquo;t. There are so many factors that go into whether a particular programming language is a &lt;em&gt;good&lt;/em&gt; choice for a team or project. Factors such as, what does the team have experience with? What problems are you solving? What support is there for various types of tooling and infrastructure at your organization? What conventions already exist and is it worth the friction of going against those conventions? Sometimes it is worth it, sometimes not. Only you and your team know enough context to make those sorts of decisions. What&amp;rsquo;s the ecosystem for a language like? Some languages have found niche status in certain domains, for instance with Python and ML.&lt;/p&gt;
&lt;p&gt;Changes have to be considered in context. There are merits, for instance, to using Optional types in Python3, that&amp;rsquo;s why they were added. But if a Python code base that you&amp;rsquo;re working with has a convention of throwing exceptions when a function cannot return a specific concrete type, which is going to cause more friction to use? A team can decide to adapt to changes in a language and start moving towards new conventions, but that&amp;rsquo;s a highly contextual decision that shouldn&amp;rsquo;t be made in isolation. Similarly, if an organization has really great infrastructure and tooling for developing services and applications in JVM based languages, there&amp;rsquo;s a huge benefit to sticking to those.&lt;/p&gt;
&lt;p&gt;Learning new programming languages can open you up to new areas for development. For instance, there&amp;rsquo;d be quite a lot of friction to doing systems programming in a language that doesn&amp;rsquo;t support low level memory management, whereas a language like Rust or C or C++ will make this easier. Most enjoyably, learning a new programming language can broaden how you think about programming. This is wonderful. I&amp;rsquo;m also so happy that we live in a time with such rich diversity of problems that need solving. We&amp;rsquo;re not all working on transaction processing systems at banks and business intelligence applications like we seemed to be when I started my career in the early 2000s (just after the dot com bubble burst). Just as in databases, &lt;a href=&#34;http://cs.brown.edu/~ugur/fits_all.pdf&#34;&gt;one size does not fit all&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I also think that cross-polination between language communities is a great thing. Features like list comprehensions, Optional types, Futures and Promises have found their way from one language to another. There&amp;rsquo;s an interview where Simon Peyton Jones talks about Haskell as a vessel for this kind of cross-polination that I find really refreshing, especially in a world where we can get caught up in language bigotry and holy wars. Have a watch:&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/iSmkqocn0oQ?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Sociotechnical Lenses into Software Systems</title>
      <link>https://paulosman.me/2021/10/02/sociotechnical-lenses-into-software-systems/</link>
      <pubDate>Sat, 02 Oct 2021 08:30:00 +0000</pubDate>
      <guid>https://paulosman.me/2021/10/02/sociotechnical-lenses-into-software-systems/</guid>
      <description>&lt;p&gt;Software systems are sociotechnical. I don&amp;rsquo;t think software professionals spend enough time discussing the challenges that span software and personal aspects. When we look at software through a sociotechnical lens, we begin to appreciate the complexity inherent in software development and operations. The systems we are building and operating are constantly being modified by different people, with different contexts, at different times, who may or may not speak to each other directly. This emergent collaboration can present unique challenges that are fun to navigate.&lt;/p&gt;
&lt;p&gt;Early in my career, I thought about software systems as static, unchanging entities. If you&amp;rsquo;ve ever thought of service maps or architectural diagrams as a panacea (instead of yet another lens), or if you&amp;rsquo;ve ever talked about software being &amp;ldquo;done,&amp;rdquo; you&amp;rsquo;ve probably done the same.&lt;/p&gt;
&lt;p&gt;When we think of software this way, though, we miss a fundamental property - people are constantly changing software, and those people almost certainly have different short and long-term goals, contexts, and pressures. Even software in &amp;ldquo;maintenance mode&amp;rdquo; has bug fixes, changes in its environment, and changes in the kind of usage patterns it supports.&lt;/p&gt;
&lt;p&gt;One way to visualize a software system&amp;rsquo;s lifecycle is with a simple timeline and markers that represent deploys or changes to the software code and configuration. Let&amp;rsquo;s use red markers to represent deploys:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/sociotechnical-lenses/timeline-1.jpeg&#34; alt=&#34;A simple timeline with deploy markers.&#34;&gt;&lt;/p&gt;
&lt;p&gt;This visualization makes it clear that the software is changing over time. The new detail is good, as it prepares us for a mental model that embraces that software is a dynamic, ever-changing system. It still leaves a lot out, though. &lt;/p&gt;
&lt;p&gt;The above visualization assumes that a software application lives in a perfect world without dependencies or changes in its environment. That&amp;rsquo;s not true. It&amp;rsquo;s impossible to separate operations from software development. So let&amp;rsquo;s add markers (in blue) to represent some of the changes we see in a software application&amp;rsquo;s environment:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/sociotechnical-lenses/timeline-2.jpeg&#34; alt=&#34;A timeline with deploy markers and changes to infrastructure.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Same system, but now we&amp;rsquo;ve added information about changes to the operational environment, like upgrading the version of MySQL that this fictional system uses, increasing the instance size of the AWS EC2 instances that this system runs on, or upgrading the OS kernel. These are all routine tasks when operating a web-based software system running in a public cloud environment, and they should impact the way we think about our software systems. The changes might have been made by the team responsible for deploying changes to code or a team responsible for purely operational work.&lt;/p&gt;
&lt;p&gt;Our visualization represents a continuously changing system, both in its software code and the operational environment hosting it. These are both in the team&amp;rsquo;s control or teams responsible for developing and operating this system, but there is still something missing. What about customers or users of this application? They&amp;rsquo;re constantly interacting with it in ways that will influence its development and operation. Let&amp;rsquo;s add more markers (in green this time) that represent significant shifts in how users interact with our application:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/sociotechnical-lenses/timeline-3.jpeg&#34; alt=&#34;A timeline with deploy markers, changes to infrastructure, and user behavior.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Now that we&amp;rsquo;ve added humans to our timeline, we can see more of the story. Through this lens, it might become apparent that specific changes in user behavior may have motivated some of the operational changes. The ten-fold increase in average requests per second was probably a motivator in increasing the instance sizes. Or was it? The only people who can answer that are the humans who did the work, and we haven&amp;rsquo;t added them yet. Let&amp;rsquo;s add staffing changes to the team or teams responsible for this system. We&amp;rsquo;ll add these as purple markers:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/sociotechnical-lenses/timeline-4.jpeg&#34; alt=&#34;A timeline with deploy markers, changes to infrastructure, user behavior, and team changes.&#34;&gt;&lt;/p&gt;
&lt;p&gt;This new dimension adds a fresh perspective to the system. We now know that we can ask Mary about the motivation for changing the instance size. Mary may not know anything about why the team upgraded MySQL when it did, though. In the world Mary lives in, the team has always been running MySQL 8. We know we can&amp;rsquo;t ask Aditya about the 10x spike in requests because he left the team before it happened. We also can&amp;rsquo;t ask the five engineers who joined later because that level of traffic has always been customary for them.&lt;/p&gt;
&lt;p&gt;When we add human beings to our visualization, it becomes evident that the system is sociotechnical. The humans are doing the work and have the context behind the changes they are making. But where did they get that context? Often it&amp;rsquo;s from participating in specific incidents. I&amp;rsquo;m a big fan of the idea that &lt;a href=&#34;https://www.adaptivecapacitylabs.com&#34;&gt;incidents are unplanned investments&lt;/a&gt;. The return on those investments is learning. Therefore, the lessons learned from analyzing incidents can reveal important information about your technical systems, organization, and how various parts interact. Let&amp;rsquo;s add yellow markers for significant incidents into our timeline:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/sociotechnical-lenses/timeline-5.jpeg&#34; alt=&#34;A timeline with deploy markers, changes to infrastructure, user behavior, team changes, and incidents.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Incidents provide context for the work we do. If we have questions about the MySQL 8 migration, the answers are probably informed by whatever happened in the &amp;ldquo;MySQL collation&amp;rdquo; incident. Visualizing our system this way also makes it clear that incidents can be powerful storytelling mechanisms. Investing in good quality incident write-ups can extend the value of incidents for engineers who join after the fact.&lt;/p&gt;
&lt;h2 id=&#34;lenses-into-a-system&#34;&gt;Lenses into a System&lt;/h2&gt;
&lt;p&gt;Each set of markers we added to our system provided new context to form assumptions and frame our thinking. Everything in the visualization existed whether we were looking or not. It becomes clear when looked at this way that each of these dimensions is inextricably linked. It&amp;rsquo;s impossible to think holistically about software without thinking about the operational environment, or the users of the system, or the people involved in building and maintaining it. These things come together to create another lens through which we can view the world.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s important to point out that the final image here is still incomplete. We&amp;rsquo;ll never fit all of the contexts into a single model. We could keep going, adding more and more context. A fascinating one, for example, would be marking the beginning of the COVID pandemic, when a team that perhaps was colocated started working remotely, and when stress and risk of burnout increased considerably. Otherwise, we&amp;rsquo;ll eventually include the whole world, but it&amp;rsquo;s interesting to continually zoom out and see how a new lens helps frame our perspectives.&lt;/p&gt;
&lt;h2 id=&#34;embrace-sociotechnical-perspectives&#34;&gt;Embrace Sociotechnical Perspectives&lt;/h2&gt;
&lt;p&gt;So our systems are sociotechnical. What do you do about this? I think it&amp;rsquo;s clear that you start by investing in the social parts of the system. If you want to understand why your systems perform the way they do, it&amp;rsquo;s necessary to know how expertise is created and distributed amongst the people in your organization. The single best way to do this is to invest in incident analysis.&lt;/p&gt;
&lt;p&gt;Many organizations have adopted the practice of doing &amp;ldquo;post-mortems&amp;rdquo; or &amp;ldquo;retrospectives&amp;rdquo; after incidents. Retrospectives are great! Unfortunately, I think a lot of learning is left on the table by the adoption of template-driven processes that produce shallow understandings of what transpired. I&amp;rsquo;ve &lt;a href=&#34;https://www.youtube.com/watch?v=AoWWZxrjPb8&#34;&gt;spoken about&lt;/a&gt; how I think we can improve this. There are also &lt;a href=&#34;https://www.adaptivecapacitylabs.com/&#34;&gt;experts in the field&lt;/a&gt; who provide training and consulting in incident analysis. There are also &lt;a href=&#34;https://www.learningfromincidents.io/&#34;&gt;communities&lt;/a&gt; and &lt;a href=&#34;https://www.jeli.io/&#34;&gt;companies&lt;/a&gt; dedicated to helping you improve this practice.&lt;/p&gt;
&lt;p&gt;Investing in incident analysis will empower teams to discover what they know, and almost as importantly, how they know what they know. It&amp;rsquo;s a necessary part of understanding the social aspects of your system, which hopefully this post demonstrates, are inextricable from any other part.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Questions I Keep Asking</title>
      <link>https://paulosman.me/2020/08/29/questions-i-keep-asking/</link>
      <pubDate>Sat, 29 Aug 2020 08:14:23 -0500</pubDate>
      <guid>https://paulosman.me/2020/08/29/questions-i-keep-asking/</guid>
      <description>&lt;p&gt;Software is a big space and in the span of a career, you can move in a bunch of
different directions, all interesting to different people. As I approach my
20th year as a professional software person, I thought it’d be interesting to
reflect on how I hope to spend the
next 20 years. I found it useful to write down questions I keep
coming back to and use them as a guide for my continued learning. I’ll be
interested to come back to this post and see how it compares to whatever
&amp;ldquo;future me&amp;rdquo; gets up to.&lt;/p&gt;
&lt;p&gt;Some of these questions have answers and I just need to learn more about them.
Others are active research areas with no “right” answer. Regardless, they’re
things I keep thinking about, reading about, and occasionally applying as a
practitioner.&lt;/p&gt;
&lt;p&gt;Of course, these aren’t the only things I’m interested in, they’re what I think
about the most. If anything, this exercise was a good way to make clear the
areas I’m not interested in enough to dedicate serious time to. For example, I
think that data visualization is an amazing topic. In a world with infinite
time, I’d love to dive into it, but probably not in this career. So, with that
said, here are my questions:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. How can we ensure that data flows through a system in ways that are performant, scalable, and reliable? How do we verify the integrity of data, especially in increasingly large-scale and complex data systems?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is a broad area, but it’s useful as a guide. My core technical interests
gravitate towards backend engineering and this is why. I think that most of
what we do when working on 24/7 internet services boils down to accepting
requests to retrieve or mutate data. Retrieving data necessitates that we do it
quickly and correctly for some definition of correct (I’m really interested in
when that definition becomes flexible, see Question 4 below). When mutating
data, we want to ensure that a write is either accepted or rejected, i.e., the
operation is reliable. It’s rarely acceptable to mysteriously drop writes, it&amp;rsquo;s
sometimes acceptable to repeat writes, and often it’s required that writes
happen in a specific order. This is all trivial with one process working at a
time but gets really hard when you have large numbers of concurrent operations
operating on more data than can possibly fit on one computer, and in a world
where you have to be able to tolerate the failure of one or more computers.&lt;/p&gt;
&lt;p&gt;The fun thing about this question is how broad it is. You can start at a really
high level, with systems design concepts, and deep dive in various directions.
For example, load distribution and queuing theory are complete topics in their
own right. You can also study the data structures used to store data in memory
and on disk, to the protocols used to make decisions about how and where to
store data, all the way down to lower-level concepts like filesystem and
network performance. At every path, there are tradeoffs to consider and I doubt
anyone could ever be considered an expert in all these areas, but it’s really
fun stuff to think about and has real implications.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. How can systems be built to maximize operability? What techniques can be followed to allow operators to observe the behavior of a system, make decisions about its operation, and act on those decisions?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I want to help make life better for those of us who keep things running, as
well as the people who depend on the services we build and operate. What are
the properties of a system that make it easier to operate compared to the
properties of a system that operators fear? Luckily, I &lt;a href=&#34;https://www.honeycomb.io/&#34;&gt;have a job&lt;/a&gt; that allows
me to work directly on one aspect of this problem: Observability. Observability
refers to the degree to which a system’s internal state can be inferred by
looking at external outputs. Doing this well requires a lot of thought and
context.&lt;/p&gt;
&lt;p&gt;This question also touches on complexity in systems. It’s intuitive, but often
wrong, to try and remove complexity — as system designers, we have this impulse
to try and hide complexity, but this can often create dangerous situations for
operators. Black boxes can have interesting, negative effects. This video,
about &lt;a href=&#34;https://www.youtube.com/watch?v=Ra_0DgnJ1uQ&#34;&gt;why cars rarely crash into buildings in the Netherlands&lt;/a&gt;, explains it
rather well, I think. In software systems, operators often need to be able to
respond to operational failures never considered by the designer. Systems can
fail in fundamentally surprising ways, and operators need to be able to act on
whatever they are seeing.&lt;/p&gt;
&lt;p&gt;I also think about the tunability of a system. Is it possible to affect the
internal state of software without deploying a change? For example, feature
flags are a useful way of toggling functionality and can be used effectively to
handle unexpected failures or conditions that degrade functionality. Traffic
control is another area where good complexity can be introduced. The ability to
direct small amounts of traffic to alternate versions of running software can
be tremendously helpful to operators responsible for rolling out changes
safely. Being able to make quick decisions about configuration changes, for
example, the version of a piece of software that a part of a system should run
is crucial in my experience. Chaos engineering is another area where I think
introducing complexity can be helpful.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. How do operators learn about the systems they are responsible for? How does that knowledge help or hinder them in their duties? How do they teach others who are less familiar with the system? How do teams coordinate and make decisions during high-stress incidents?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I believe these are classic &lt;a href=&#34;https://www.sciencedirect.com/science/article/pii/B9780444705365500063&#34;&gt;Cognitive Systems Engineering
questions&lt;/a&gt;.
Our software systems don’t run themselves, &lt;a href=&#34;https://en.wikipedia.org/wiki/Artificial_Intelligence_for_IT_Operations&#34;&gt;no matter what certain vendors
might want you to
believe&lt;/a&gt;.
People keep systems running, and people are constantly doing things, constantly
responding to things, and constantly changing things. Yes, change fail rates
are a real thing, but mostly, these the actions that people take are what
prevent outages or prevent outages from being worse than they are.&lt;/p&gt;
&lt;p&gt;Have you ever worked with someone or a group of people who seemed to have an
intuitive sense of what needed to happen when a certain alert fires or a graph
doesn’t look right, or when a customer complains about a bad experience? Have
you noticed how much you can learn by just being around that person or on their
team? I’m fascinated by how operators obtain, continue to shape, and share that
knowledge. I’ve heard this process referred to as sense-making and I think it’s
an under-appreciated aspect of how we do our jobs.&lt;/p&gt;
&lt;p&gt;I also know that we generally have different mental models of systems, and it’s
usually true that none of them are correct, so the ways in which we use those
different mental models to build shared understanding is fascinating to me.&lt;/p&gt;
&lt;p&gt;People have &lt;a href=&#34;http://lup.lub.lu.se/luur/download?func=downloadFile&amp;amp;recordOId=8084520&amp;amp;fileOId=8084521&#34;&gt;written
extensively&lt;/a&gt;
on this subject, specifically in the context of
software operations and outages and I want to dig in a lot more.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. What are the tradeoffs when considering the needs of consistency, availability, and scalability? In order to guarantee coordination, is it necessary to introduce limits that interfere with a system&amp;rsquo;s ability to scale? When is coordination unnecessary?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;These are classic distributed systems questions and I find them really fun to
think about. They come up in practical applications from time to time. If you
think about data storage or any part of a distributed system that has to make
decisions about how to handle a certain kind of operation, we run into some
interesting constraints when considering needs of scale and availability. By
scale, I mean handling volumes of data that can’t possibly be stored on a
single disk, and by availability, I mean that the system will need to tolerate
individual node failures, so data will have to be replicated. In such a system,
how many nodes have to agree that a write has been accepted before a client can
consider the operation successful? When adding nodes to a system, how is the
performance impacted? Is it possible to create a system that requires no
coordination, but can make safe guarantees about consistency? This is an active
research area, and it’s fun to read papers from companies like Google,
Facebook, and Amazon to see how practitioners are thinking about the various
tradeoffs or fun ways to cheat. There seems to be a lot of back and forth
between academia and industry here. &lt;a href=&#34;https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type&#34;&gt;CRDTs&lt;/a&gt;, for instance, is an attempt to get
around the need for coordination while guaranteeing strong, eventual
consistency. The Google Spanner team is no doubt doing some really interesting
work here. It’s fascinating and I feel like you could spend a lifetime learning
about all the work being done in this area and still not be caught up.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. How do organizations learn? How do organizations internalize information necessary to guarantee their future success?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So you have a large scale data-driven system that incorporates a number of
design decisions intended to prevent or isolate failures. You have groups of
people who are constantly learning about and changing the system and
coordinating their efforts in surprising and remarkable ways. How does an
organization internalize this knowledge and avoid or make less likely certain
organization failure modes? How do new members of a team learn about all the
folk knowledge held within an organization? I’ve worked in organizations that
were dangerously dependent on individuals, and not able to withstand
commonplace things like vacation or attrition. These kinds of conditions are
hell and lead to burnout for individuals, deteriorating quality for a product,
and less reliability for users. I’m fascinated by the differences between teams
who disseminate this knowledge well and teams that do not.&lt;/p&gt;
&lt;h2 id=&#34;the-next-20-years&#34;&gt;The next 20 years&lt;/h2&gt;
&lt;p&gt;Reading all of this, I could probably summarize my interests as: “building
large scale distributed data systems that are safe to operate and discovering
ways that teams internalize and share their operational knowledge.”. It’s fun
to break it down further though, and useful as a guide for learning. As I
described earlier, software engineering is a huge space with a seemingly
infinite number of paths to explore. It was a useful exercise for me to write
down the questions that seem to guide my learning and my career. I hope the
next 20 years of my career afford me more opportunities to study the areas I’ve
outlined, learn from my peers, mentors, and coworkers, and generally improve
the software I work on. I feel incredibly lucky to work in a space with so many
intellectually challenging problems, while also getting to help build products
that people find useful.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Production Oriented Development</title>
      <link>https://paulosman.me/2019/12/30/production-oriented-development/</link>
      <pubDate>Mon, 30 Dec 2019 08:14:23 -0500</pubDate>
      <guid>https://paulosman.me/2019/12/30/production-oriented-development/</guid>
      <description>&lt;p&gt;Throughout my career, I&amp;rsquo;ve developed some &lt;em&gt;opinions&lt;/em&gt;. Some have worn particularly deep ruts,
reinforced by years of experience. I tried to figure out what these
had in common, and it&amp;rsquo;s
the idea that &lt;strong&gt;code in production is the only code that matters&lt;/strong&gt;. Staging doesn&amp;rsquo;t
matter, code on your laptop doesn&amp;rsquo;t matter, QA doesn&amp;rsquo;t matter,
only production matters. Everything else is debt.&lt;/p&gt;
&lt;p&gt;This perspective probably comes from years sitting in between
operations and product development. I strongly
believe that teams should optimize for getting code to production as quickly as
possible as well as responding to incidents in production.&lt;/p&gt;
&lt;p&gt;This idea, and a lot of the practices it implies, can be
counter-intuitive or controversial, so I want to dive into them a little
further. What follows is a set of practices and principles I believe are true,
considering my underlying belief that code working in production is the only
code that matters.&lt;/p&gt;
&lt;h2 id=&#34;1-engineers-should-operate-their-code&#34;&gt;1. Engineers should operate their code.&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Engineers are the subject matter experts for the code they write and should be
responsible for operating it in production&lt;/strong&gt;. In this context, &amp;ldquo;operating&amp;rdquo; means
deploying, instrumenting, and monitoring code as well as helping to resolve
incidents related to or impacting that code. The responsibility of operating
code aligns incentives - it encourages engineers to write code that is
observable and easy to debug, and connects them to what customers really care
about. It encourages them to be curious about how their code is performing in
production. Importantly, engineers should be on-call for their code - being
on-call creates a positive feedback loop and makes it easier to know if their
efforts in writing production-ready code are paying off. I&amp;rsquo;ve heard people
complain about the prospect of being on-call, so I&amp;rsquo;ll just ask this: if you&amp;rsquo;re
not on-call for your code, who is?&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re not currently on-call for your code but want to be, and can help
influence this decision, there are some things you can do. Set up PagerDuty (or
similar) schedules for each group of engineers responsible for specific
services or parts of your code. A good schedule has 6–8 engineers. There are
plenty of variations, but a typical template is to have one-week rotations,
where you&amp;rsquo;ll be on-call for secondary for a week and then primary for a week.
Configuring alerts is a separate topic, which probably deserves it&amp;rsquo;s own blog
post entirely, but focus on things that impact your customers (see:
Symptom-based alerting) and remember that you&amp;rsquo;re ultimately responsible for how
you respond to alerts, which means you can change them.&lt;/p&gt;
&lt;p&gt;There are two talks I&amp;rsquo;d recommend watching that touch on the topic of
configuring alerts: Liz Fong-Jones talks about SLOs in &lt;a href=&#34;https://www.youtube.com/watch?v=MT4jbUzEVL0&#34;&gt;Cultivating Production Excellence&lt;/a&gt; and Aditya Mukerjee does a great job talking about techniques for
managing alerts in &lt;a href=&#34;https://vimeo.com/274820572&#34;&gt;Warning: This Talk Contains Content Known to the State of California to Reduce Alert Fatigue&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;2-buy-almost-always-beatsbuild&#34;&gt;2. Buy Almost Always Beats Build&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;If you can avoid building something, you should.&lt;/strong&gt; Code is the most expensive
way to solve a problem that isn&amp;rsquo;t addressing a core area of your business. For
most small to mid-sized companies, there are open source or better yet, hosted
solutions that solve a wide range of common problems. I mean things like git
repository hosting (Github, Gitlab, Bitbucket, etc), observability tooling
(Honeycomb, Lightstep, etc), managed databases (Amazon RDS, Confluent Kafka,
etc), alerting (PagerDuty, OpsGenie, etc) and a whole host of other commodity
technologies. This even applies to your infrastructure - if you can help
it, don&amp;rsquo;t roll your own Kubernetes clusters (side note: do you even need to use
Kubernetes?), don&amp;rsquo;t roll your own load balancers if you can use Amazon ELB or
ALBs.&lt;/p&gt;
&lt;p&gt;Unfortunately, &lt;a href=&#34;https://en.wikipedia.org/wiki/Not_invented_here&#34;&gt;NIH&lt;/a&gt; syndrome is very real and some companies get burned badly by
this. I&amp;rsquo;ve seen teams light time and money on fire reinventing components when
better, more battle-tested alternatives exist in the market. Those same teams
almost always end up spending years contending with the resulting technical
debt. If you&amp;rsquo;re on such a team and have the will and ability to impact change,
start rolling back these decisions one by one. Migrate your databases to a
managed provider, migrate your feature flagging system to a SaaS tool (i.e.
LaunchDarkly). Keep going until the only software you maintain yourselves is
the software that delivers value to your customers. You&amp;rsquo;ll be much, much better
off for it.&lt;/p&gt;
&lt;h2 id=&#34;3-make-deployseasy&#34;&gt;3. Make Deploys Easy&lt;/h2&gt;
&lt;p&gt;Deploying should be a frequent and unexciting activity. &lt;strong&gt;Engineers should be
able to deploy with minimal manual steps and it should be easy to see if the
deploy is successful&lt;/strong&gt; (this requires instrumenting your code for observability,
which - tada - is covered above), and it should be easy to roll back a deploy
if something doesn&amp;rsquo;t go well. Deploying frequently implies that
deploys are smaller, and smaller deploys are generally easier, faster and
safer.&lt;/p&gt;
&lt;p&gt;Many teams implement periods where deploys are forbidden - these can be
referred to as code freezes, or deploy policies like &amp;ldquo;Don&amp;rsquo;t deploy on Fridays&amp;rdquo;.
Having such blackout periods can lead to a pile-up of changes, which increases
the overall risk of something going very wrong.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re on a team that fears deploys, dedicate a percentage of your
engineering time to improvements in your deployment pipeline until the fear is
gone. On a recent team I worked with, we were able to improve deploy times from
3 hours to 30 minutes, which drastically improved the teams&amp;rsquo; confidence in the
deploy process. A natural side effect of this was that engineers started
deploying much more frequently instead of waiting for changes to pile up enough
to warrant a &amp;ldquo;release&amp;rdquo; (which was synonymous with a deploy).&lt;/p&gt;
&lt;p&gt;The book
&lt;a href=&#34;https://www.amazon.com/Accelerate-Software-Performing-Technology-Organizations/dp/1942788339&#34;&gt;Accelerate&lt;/a&gt;
has been getting a lot of attention. If you haven&amp;rsquo;t read it, I&amp;rsquo;d recommend it.
The team behind it also publishes the &lt;a href=&#34;https://cloud.google.com/devops/&#34;&gt;State of DevOps&lt;/a&gt;
reports, which are full of well-researched information about what various
companies in the industry are doing. It&amp;rsquo;s not a coincidence that two of the
four key metrics that the book focuses on are directly related to this (Deploy
Frequency, Change Lead Time). Shipping is &lt;a href=&#34;https://www.heavybit.com/library/podcasts/o11ycast/ep-12-speed-of-deployment-with-rich-archbold-of-intercom/&#34;&gt;your company&amp;rsquo;s heartbeat&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;4-trust-the-people-closest-to-theknives&#34;&gt;4. Trust the People Closest to the Knives&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;The people who work with a system are the ones who understand it best.&lt;/strong&gt; This
applies to any part of the socio-technical systems within which we all work. In
the case of software systems, the engineers who deploy every day and are
on-call for critical services understand the level of risk they operate in. A
sad trend is that managers tend to overestimate their teams&amp;rsquo; progress on
certain transitions - i.e. cloud-native, DevOps, etc. The higher up the
management chain, the larger this overestimation tends to be. Engineers who
deploy and get paged when things break know where the bodies are buried and
they know what needs the most work. They should, therefore, be the primary
stakeholders responsible for prioritizing technical work.&lt;/p&gt;
&lt;p&gt;Another manifestation of this principle applies to platform or services teams.
If you&amp;rsquo;re responsible for building some shared component that&amp;rsquo;s used within
your organization (i.e. a messaging system, ci/cd infrastructure, shared
libraries or services) there&amp;rsquo;s an uncomfortable truth lurking for you: the
people who use your work know more about it than you do in many cases. They
understand implicitly how it serves customers and they know what contortions or
hoops they have to jump through to get it to work. Listen to them for
clues on how to improve the UX of your services and tools.&lt;/p&gt;
&lt;h2 id=&#34;5-qa-gates-make-qualityworse&#34;&gt;5. QA Gates Make Quality Worse&lt;/h2&gt;
&lt;p&gt;Many teams have a manual QA step that gets performed before deploys. The idea,
I guess, is to have someone run automated or manual tests to verify that a set
of changes are ready to be released. This sounds like a comforting
idea - having a human being (or team of human beings) &amp;ldquo;verify&amp;rdquo; a release before
it goes out - &lt;strong&gt;but it falls victim to several false assumptions and creates
some misalignments that do more harm than good.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;First of all, if there&amp;rsquo;s manual work that needs to be done before a deploy can
go out, that creates a bottleneck - if you&amp;rsquo;re making deploys easy, and
deploying small changes frequently, no QA team is going to be able to keep up
testing every deploy, and will inevitably block teams from deploying. That&amp;rsquo;s no
good. If you have manual tests, automate them and build them into your CI
pipeline (if they do deliver value).&lt;/p&gt;
&lt;p&gt;Secondly, the teams doing QA often lack context and are under time pressure.
They may end up testing &amp;ldquo;effects&amp;rdquo; instead of &amp;ldquo;intents&amp;rdquo;. For example, I&amp;rsquo;ve seen
QA teams burn time testing that when something happens in a UI, something
related happens in a database. What happens when an engineer refactors that UI
component and changes the underlying data model? The functionality works, but
the test breaks. Because two teams are involved, this takes coordination and
time to fix. Similarly, I&amp;rsquo;ve seen QA teams block deploys because of failing
tests when caching was introduced at the CDN layer - a TTL of 5 seconds on an
activity feed may not ever be noticed by a user but it might break QA tests
causing unnecessary conflicts between product and QA engineers.&lt;/p&gt;
&lt;p&gt;Luckily, solving this one is easy. Instead of having a dedicated QA team work
on creating manual and automated test cases that run in a fictitious QA
environment, reassign that team to work on continuous testing in production.
Instead of being a gate for deploys, a QA team could continuously verify that
production is working as expected. QA teams are also well situated to lead
Chaos Engineering initiatives, where faults are intentionally injected in
production. QA engineers could also work on making the CI/CD pipeline more
reliable, so that deploys are no longer a nightmare.&lt;/p&gt;
&lt;h2 id=&#34;6-boring-technology-isgreat&#34;&gt;6. Boring Technology is Great.&lt;/h2&gt;
&lt;p&gt;With thanks to &lt;a href=&#34;https://mcfunley.com/choose-boring-technology&#34;&gt;Dan McKinley&lt;/a&gt;,
always &lt;strong&gt;strive for boring tech when possible&lt;/strong&gt;. Systems are inherently
unpredictable, and you want a wide area of expertise to fall back on when shit
goes sideways. There are also routine operations that you&amp;rsquo;ll have to do
(deploys, database migrations, etc) and it&amp;rsquo;s Very Nice to have widely used and
tested tooling for this stuff. I think of databases most often when I think
about this belief. MySQL is a database with many, many quirks, but it is so
widely used, that you should still just use it most of the time.&lt;/p&gt;
&lt;p&gt;Very few organizations have the bandwidth to debug unique problems. You
don&amp;rsquo;t want unique problems, especially when performing routine
operations - i.e. storing bytes on disk, choosing a new leader in a cluster,
garbage collecting objects, querying time-series data, etc. Having unique
problems will kill a small to medium size team. It will sap you of your
creative energy, which is better used creating value for customers who want to
pay you monies for your software. Use your innovation tokens wisely!&lt;/p&gt;
&lt;p&gt;Using boring technology means you can lean on a large community of users. Shit
on it all you want, but there are very few PHP issues that someone else hasn&amp;rsquo;t
already encountered. Nowadays, the same is probably true for sufficiently
widely used versions of Ruby on Rails. I often say that I like to be in the 3rd
cohort of technology adoption. The 1st cohort is the bleeding edge
organization. The 2nd cohort is the people who feel like they can take some
risks. Let those two groups go before you, run into all the big problems, and
then you can go, benefiting from all of their hard-won experience.&lt;/p&gt;
&lt;h2 id=&#34;7-simple-alwayswins&#34;&gt;7. Simple Always Wins&lt;/h2&gt;
&lt;p&gt;I don&amp;rsquo;t have much to say about this, but we&amp;rsquo;re all writing YAML and JSON
instead of XML and we&amp;rsquo;re all using HTTP instead of CORBA, RMI, DCOM, XPCOM,
etc. Right? In that same spirit, I&amp;rsquo;d rather debug problems in a LAMP stack than
a Microservices architecture any day.&lt;/p&gt;
&lt;p&gt;Quick sidebar on Microservices: as with so many trends in tech, they are often
sold as a panacea. Let me be clear: &lt;strong&gt;Microservices, designed well, solve some
specific problems and as with most solutions to complex problems, involve several trade-offs&lt;/strong&gt;. If you are going in this direction, I do have &lt;a href=&#34;https://www.amazon.com/Microservices-Development-Cookbook-independently-deployable/dp/1788479505&#34;&gt;opinions&lt;/a&gt; on
how you should do it, but I also think you should hold off for as long as you
can.&lt;/p&gt;
&lt;h2 id=&#34;8-non-production-environments-have-diminishing-returns&#34;&gt;8. Non-Production Environments Have Diminishing Returns&lt;/h2&gt;
&lt;p&gt;A more direct heading for this section would be &lt;strong&gt;&amp;ldquo;Non-Production Environments
are Bullshit&amp;rdquo;&lt;/strong&gt;. Environments like staging or pre-prod are a fucking lie. When
you&amp;rsquo;re starting, they make a little sense, but as you grow, changes happen
more frequently and you experience drift. Also, by definition, your non-prod
environments aren&amp;rsquo;t getting traffic, which makes them fundamentally different.
The amount of effort required to maintain non-prod environments grows very
quickly. You&amp;rsquo;ll never prioritize work on non-prod like you will on prod,
because customers don&amp;rsquo;t directly touch non-prod. Eventually, you&amp;rsquo;ll be
scrambling to keep this popsicle sticks and duct tape environment up and
running so you can test changes in it, lying to yourself, pretending it bears
any resemblance to production.&lt;/p&gt;
&lt;h2 id=&#34;9-things-will-alwaysbreak&#34;&gt;9. Things Will Always Break&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s impossible, even undesirable, to avoid failure. &lt;strong&gt;Lean into the fact
that failure is inevitable, and focus on how you respond to it&lt;/strong&gt;. This means
investing in a continuously improving incident response process. There&amp;rsquo;s no
one-size-fits-all for every company and team, but you should have a good idea
of what to do when things go wrong, and you should have mechanisms in place to
learn from those situations and improve your processes. Invest in &lt;a href=&#34;https://www.learningfromincidents.io/&#34;&gt;Incident Analysis&lt;/a&gt;. It&amp;rsquo;s a huge field with lots of valuable tools and resources for
maximizing the return on investment when incidents occur (or don&amp;rsquo;t!).&lt;/p&gt;
&lt;p&gt;This is an area where Chaos Engineering can be helpful. &lt;a href=&#34;https://queue.acm.org/detail.cfm?id=2353017&#34;&gt;Injecting failures into production&lt;/a&gt; can improve confidence in how to respond when a system
starts behaving in unexpected ways. &lt;a href=&#34;https://www.gremlin.com/community/tutorials/how-to-run-a-gameday/&#34;&gt;Game Days&lt;/a&gt; can be a particularly effective
way to allow a team of engineers to practice various outage scenarios.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;A lot of the beliefs outlined in this post are at least counter-intuitive, if
not somewhat controversial, but I&amp;rsquo;m nevertheless convinced that they&amp;rsquo;re true.
That doesn&amp;rsquo;t mean my mind cannot be changed, but it is unlikely. If you
strongly agree or disagree, &lt;a href=&#34;https://twitter.com/paulosman&#34;&gt;I&amp;rsquo;m on the internets&lt;/a&gt;. I&amp;rsquo;d be very curious to hear
about your experiences.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Newbie&#39;s Guide to Cyclocross</title>
      <link>https://paulosman.me/2016/11/26/newbies-guide-to-cyclocross/</link>
      <pubDate>Sat, 26 Nov 2016 08:14:23 -0500</pubDate>
      <guid>https://paulosman.me/2016/11/26/newbies-guide-to-cyclocross/</guid>
      <description>&lt;p&gt;For most of us in Central Texas, the cyclocross season ended with the Texas State Age &amp;amp; Skill Based Championships in Georgetown, TX. This was my first season racing cross, so I thought I’d take a crack at writing a guide for people thinking about getting into it. Cyclocross is an incredibly fun, completely insane sport. If you’re thinking about racing hopefully this will help.&lt;/p&gt;
&lt;p&gt;I can only speak to my own experiences, so the following advice pertains to local USAC races in Texas. Other races may be similar, they may not be. The usual disclaimers apply - I am not an expert, take all of this with a grain of salt. Also keep in mind that I placed in the bottom 50% in every race I entered this year - I&amp;rsquo;m here to tell you how to survive and have a lot of fun, I cannot give you any advice on how to win!&lt;/p&gt;
&lt;h3 id=&#34;skills-clinics&#34;&gt;Skills Clinics&lt;/h3&gt;
&lt;p&gt;Before your first cyclocross race, try going to a skills clinic or two. As the season nears, keep an eye out for clinics organized by your local bike shop or cycling club. This isn’t strictly necessary (A lot of people who regularly beat me never attended a clinic) but I found it helpful.&lt;/p&gt;
&lt;p&gt;I was lucky enough to catch one put on by none other than &lt;a href=&#34;https://en.wikipedia.org/wiki/Katie_Compton&#34;&gt;Katie F’n Compton&lt;/a&gt; organized by the &lt;a href=&#34;http://texascxproject.org/&#34;&gt;Texas Cyclocross Project&lt;/a&gt;. Katie discussed topics like choosing tires, tire pressure, race strategies as well as various cornering scenarios. We practiced a lot of cornering, riding off-camber and did a bit of riding on sand. I fell a few times, learned that riding off-camber is really f’in hard, and left feeling a lot more confident on my bike.&lt;/p&gt;
&lt;p&gt;If you can’t attend a clinic in your area, go to a park and practice mounting, dismounting, run-ups, starts and riding off-camber. You’ll want to see people do this stuff properly - &lt;a href=&#34;https://www.youtube.com/watch?v=28LnQcMuk7o&amp;amp;list=PLcp8c4S74ciHSd9lQHiNHvuTOPMp-z3F6&#34;&gt;YouTube is your friend&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;local-practice&#34;&gt;Local Practice&lt;/h3&gt;
&lt;p&gt;After you’ve attended a skills clinic you’ll probably want to ride as often as you can. Most local cycling clubs that cater to CX will host a weekly practice. Talk to folks, look at club Facebook pages, event listings, etc and find one that’s open for anyone to join. In Austin, &lt;a href=&#34;http://bicyclesportshop.com/&#34;&gt;Bicycle Sports Shop&lt;/a&gt; have practices on Wednesday evenings and &lt;a href=&#34;https://www.facebook.com/EmbrosBicycleClub/&#34;&gt;Embros Bicycle Club&lt;/a&gt; have one on most Sunday mornings. Double check those days / times - they might change next year.&lt;/p&gt;
&lt;p&gt;Practices, like shop rides, are a great way to learn from more experienced riders and meet people in your local cycling community.&lt;/p&gt;
&lt;h3 id=&#34;youtube--gopro&#34;&gt;YouTube / GoPro&lt;/h3&gt;
&lt;p&gt;GoPros are a blessing for those of us who are too nervous, lazy, busy or poor to experience something firsthand. I found it really helpful to watch GoPro videos on YouTube from people who have raced certain courses in previous years. My first race of the season was the Cyclocross Scuffle in Elgin, TX and &lt;a href=&#34;https://www.youtube.com/watch?v=VR4mgbS8uXo&amp;amp;t=1s&#34;&gt;searching YouTube&lt;/a&gt; paid off.&lt;/p&gt;
&lt;p&gt;If you want to see what winning a race looks like check out &lt;a href=&#34;https://www.youtube.com/watch?v=auaIUnjku5M&#34;&gt;Brandon’s video of the Cat 4/5 race at BSS Six Shooter&lt;/a&gt; this year.&lt;/p&gt;
&lt;h3 id=&#34;what-to-wear&#34;&gt;What to wear&lt;/h3&gt;
&lt;p&gt;People talk about skinsuits for cyclocross. I wouldn&amp;rsquo;t bother. Just some bib shorts and a jersey will do. Do invest in some decent full fingered gloves with some padding though - you&amp;rsquo;ll be grabbing lots of things (beer, your bikes downtube, your top tube, beer) and you&amp;rsquo;ll want some padding when you&amp;rsquo;re on your brake hoods. Other than that, regular cycling attire will do - sunglasses included. You probably won&amp;rsquo;t need a water cage - if it&amp;rsquo;s hot and you need water, just stick a bottle in your jersey pocket. Water cages make throwing your bike on your shoulder a little harder anyway.&lt;/p&gt;
&lt;h3 id=&#34;registering-for-your-first-race&#34;&gt;Registering for your First Race&lt;/h3&gt;
&lt;p&gt;Okay, so you’re ready to race, now what? In Texas, local races can be found on &lt;a href=&#34;http://www.bikereg.com/&#34;&gt;bikereg.com&lt;/a&gt;, &lt;a href=&#34;http://www.txbra.org/events/&#34;&gt;txbra.org/events&lt;/a&gt; and a few other places. You can pre-register or you can usually show up the day of the race and register onsite. I’d go ahead and pre-register — it’ll be cheaper and some races use pre-registration dates to help determine your starting position.&lt;/p&gt;
&lt;p&gt;To participate in a USAC sanctioned race you’ll need a USAC license. You can either buy a one-day license for this specific race or get yourself an annual one. If you know you’ll be doing more than one race in a season you might as well get the annual license. You’ll save money and there are other perks, like being able to use the USAC app and track your race history online. Also, you&amp;rsquo;ll never get bumped up in categories if you don&amp;rsquo;t have a regular license.&lt;/p&gt;
&lt;p&gt;You might wonder what all these categories mean. All you need to know is that men start at Category 5 and women start at Category 4. You can read more about Cyclocross Categories on the &lt;a href=&#34;https://www.usacycling.org/news/user/story.php?id=2627&#34;&gt;USAC&lt;/a&gt; website.&lt;/p&gt;
&lt;p&gt;Once you have your license, go ahead and register for your first Men’s Cat 4/5 or Women’s Cat 3/4 race.&lt;/p&gt;
&lt;h4 id=&#34;get-your-race-number&#34;&gt;Get your Race Number&lt;/h4&gt;
&lt;p&gt;I’ve participated in running races and triathlons in the past, so was used to showing up just 10-15 minutes before my race begins. Cyclocross isn’t like that. You want to show up early enough to be able to pre-ride the course and pick up your race number. Before you do anything else, find the registration area and show them your USAC license number and some id. You’ll get your race number and some safety pins. Your race number goes on your right with the bottom of the number closer to your stomach (so race officials can easily read it).&lt;/p&gt;
&lt;h4 id=&#34;watch-a-race--pre-ride&#34;&gt;Watch a Race &amp;amp; Pre-Ride&lt;/h4&gt;
&lt;p&gt;I like to show up a few hours before my race starts. You get a chance to chat with people, scout the course, pre-ride and watch another race. A lot of the races will have Men’s Cat 4/5 racing later in the day so if you’re lucky you can catch the pro races to see how it’s supposed to be done.&lt;/p&gt;
&lt;p&gt;There’s usually 10-15 minutes between races. If you time it right, you can get on the course and do a pre-ride lap. This is super useful especially on technical courses. You can get a feel for the more technical parts and see if you need to make any adjustments to your tire pressure. I also use the pre-ride to decide if I’m going to ride or run some parts of the course.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/newbies-guide-cyclocross/racing1.jpg&#34; alt=&#34;Racing at TX Age &amp;amp; Skill based championships&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;falling&#34;&gt;Falling&lt;/h3&gt;
&lt;p&gt;You’ll fall. Don&amp;rsquo;t worry about it.&lt;/p&gt;
&lt;h3 id=&#34;heckling&#34;&gt;Heckling&lt;/h3&gt;
&lt;p&gt;Heckling is a big part of cyclocross. Consider it mental training - you&amp;rsquo;ll nail that 20th place finish despite their attempts to phase you!&lt;/p&gt;
&lt;h3 id=&#34;mechanical-trouble&#34;&gt;Mechanical Trouble&lt;/h3&gt;
&lt;p&gt;You&amp;rsquo;ll probably have some mechanical trouble during your season. I had a rear derailleur malfunction that resulted in me shifting my chain into my wheel at Six Shooter and I had some trouble with my crank after a crash at Webberville. I&amp;rsquo;m not looking to podium, so whatever &amp;ndash; but some people will keep a spare set of wheels or even a spare bike in the pit for things like this. In that event, you&amp;rsquo;ll get your broken bike to the pit (you may have to run for a good bit of a lap) and swap out what you need.&lt;/p&gt;
&lt;p&gt;This is also where I&amp;rsquo;ll suggest you get to know the mechanics at your local bike shop. Having a good local with friendly people who care about their customers is really important when you&amp;rsquo;re gonna mess up your bike on the regular. It&amp;rsquo;s also a good idea to get your bike checked out a few times during the season just to make sure things like spoke tension are good - cross takes a toll on your bike. I&amp;rsquo;m lucky enough to live close to &lt;a href=&#34;http://cycleast.com/&#34;&gt;Cycleast&lt;/a&gt; and Russell, Jacob, Blake and Jarod all helped me out in some way this season. They also built me some amazing wheels for my cross bike. If you&amp;rsquo;re in Austin, consider giving them your business.&lt;/p&gt;
&lt;h3 id=&#34;surviving-30-minutes-of-hell&#34;&gt;Surviving 30 Minutes of Hell&lt;/h3&gt;
&lt;p&gt;Cyclocross races are 30-60 minutes of hell. This isn’t like triathlon or a stage race where you pace yourself, it’s going to be full on for the duration of the race if your fitness allows it.&lt;/p&gt;
&lt;p&gt;Prepare for this if you can. Do interval work and threshold training during training rides. In Texas most races will be hot (this is supposed to be a winter sport, right?!) so acclimate to the heat through the summer. Running helped me out here. My first race had 11 people DNF (did not finish), so it’s not uncommon for people to get caught off guard by the heat. It’s rough, for sure.&lt;/p&gt;
&lt;p&gt;To drive the point in, here’s my heart rate on a regular 25 mile training ride:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/newbies-guide-cyclocross/bpm-training-ride.png&#34; alt=&#34;Heart Rate during training ride&#34;&gt;&lt;/p&gt;
&lt;p&gt;And here’s my heart rate at this years Cross of Ages Cat 4/5 race:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/newbies-guide-cyclocross/bpm-race.png&#34; alt=&#34;Heart Rate during race&#34;&gt;&lt;/p&gt;
&lt;p&gt;So yeah, expect to floor it for the duration of the race.&lt;/p&gt;
&lt;h4 id=&#34;the-start&#34;&gt;The Start&lt;/h4&gt;
&lt;p&gt;About 10-15 minutes before the race begins, you’ll want to get to the staging area. Just look for where the crowd of cyclists are gathering near the start. The race director may or may not go over a few things before the race (confirm the time, number of laps, etc). A few minutes before the race they’ll start doing call-ups. These are usually based on USAC rankings and pre-registration time. After the last call-up, they’ll say “everyone else”. If this is your first race, this will probably be you. One of the hardest things about this sport is getting over your starting position - just focus on picking people off one at a time if you can.&lt;/p&gt;
&lt;h4 id=&#34;barriers&#34;&gt;Barriers&lt;/h4&gt;
&lt;p&gt;Barriers are one of the most common obstacles on a cross course. These are usually roughly 30 - 40cm wood stands. Some rare creatures are capable of bunny hopping these - if you’re able to get this down, it’ll definitely help shave some seconds and get some oohs and aahs from the crowd. For the rest of us mere mortals, you’ll want to swing your leg over your bike, dismount and jump over the barriers carrying your bike like a suitcase, then remount. As with most things, &lt;a href=&#34;https://www.youtube.com/watch?v=fIlfszCJADU&#34;&gt;YouTube is your friend here&lt;/a&gt;. Also practice. Lots of practice. My worst crash of the season was the result of coming in too hot to some barriers at Webberville CX. I couldn’t find my balance when my feet hit the ground and I went crashing bike first into the ground, bending my crank. That sucked.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/newbies-guide-cyclocross/barriers.jpg&#34; alt=&#34;Here&amp;rsquo;s a bunch of people who know what they&amp;rsquo;re doing&#34;&gt;&lt;/p&gt;
&lt;h4 id=&#34;run-ups&#34;&gt;Run ups&lt;/h4&gt;
&lt;p&gt;Most courses will have one or two steep hills you have to get up. The general rule of cyclocross is: do whatever is fastest. If you can ride the hill and get traction and this is the quickest way, do that. If you can’t, you’ll want to dismount, throw the bike on your shoulder and run up the hill. There’s a specific way to hold your bike that involves putting your arm under the down tube and grabbing your handlebars. &lt;a href=&#34;https://www.youtube.com/watch?v=CBAbZzFeT34&#34;&gt;Obligatory YouTube link&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here’s me running up a hill at Six Shooter this year, holding my bike the wrong way:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/newbies-guide-cyclocross/runup.jpg&#34; alt=&#34;That&amp;rsquo;s not how you do it, Paul&#34;&gt;&lt;/p&gt;
&lt;p&gt;Other skills you’ll want to practice are cornering, riding off camber and dealing with things like ditch crossings. Cornering especially is an extremely important skill - the main advice I can give is keep your head up, look where you want to go and use your bodyweight. You can shave a second off every turn if you get good at this. It’s the thing I struggle with the most and I often find myself stumbling or riding into the tape on sharper turns.&lt;/p&gt;
&lt;h4 id=&#34;handups&#34;&gt;Handups&lt;/h4&gt;
&lt;p&gt;Handups were a surprise to me. As I mentioned, my first race was the Cyclocross Scuffle in Elgin, TX and it was 100F. I’d raced triathlons before where outside help is a big no-no, but I noticed all these people holding out cups along the course. After crashing in a ditch, I got my bike up and decided to take one of the cups thinking it was water - chugged it and to my surprise discovered that it was actually beer! At first I thought this was a terrible idea (beer dehydrates you!) but it was actually lovely and I think it gave me a bit of a boost. Handups are your friend, especially when you’re losing anyway.&lt;/p&gt;
&lt;p&gt;Beware though - at Georgetown I grabbed a cup of something, chugged it and realized it wasn’t beer at all, but pickle juice with some kind of alcohol. That was gross - I was so happy when someone handed me beer later on the course.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/newbies-guide-cyclocross/racing2.jpg&#34; alt=&#34;About to remount at Cyclocross Scuffle, Elgin, TX&#34;&gt;&lt;/p&gt;
&lt;h4 id=&#34;have-fun&#34;&gt;Have fun&lt;/h4&gt;
&lt;p&gt;Cyclocross can be a bit intimidating at first, but hopefully some of what I’ve put here will help you out. It’s an amazingly fun and crazy sport and I’d heartily recommend it to anyone who likes riding a bike and is at least a little crazy.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Photo Credits: &lt;a href=&#34;https://hardcorvtm.smugmug.com/&#34;&gt;Corvin Alstot&lt;/a&gt; &amp;amp; &lt;a href=&#34;http://jahicks.zenfolio.com/&#34;&gt;Jim Hicks&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Race Report - Jack&#39;s Generic Triathlon 2016</title>
      <link>https://paulosman.me/2016/08/09/race-report-jacks-generic-triathlon-2016/</link>
      <pubDate>Tue, 09 Aug 2016 08:14:23 -0500</pubDate>
      <guid>https://paulosman.me/2016/08/09/race-report-jacks-generic-triathlon-2016/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/race-report-rookie-triathlon/swim.jpg&#34; alt=&#34;Swim&#34;&gt;&lt;/p&gt;
&lt;p&gt;Early in the year, when I decided to sign up for &lt;a href=&#34;https://paulosman.me/2016/05/19/race-report-rookie-triathlon.html&#34;&gt;my first triathlon&lt;/a&gt;, I remember thinking that I would do a Sprint and Olympic distance and that’d be it - mission accomplished. That was before I was addicted - a week after that first race, I went looking for more triathlons to sign up for and found Jack’s Generic Triathlon. Jack’s celebrated it’s 14th anniversary this year, so it’s been a staple in the Austin triathlon scene for a long time. The name comes from the race’s emphasis on athletes and the race itself rather than sponsors, big name participants, etc.&lt;/p&gt;
&lt;p&gt;JGT is held at Lake Pflugerville, a venue I’m pretty familiar with having raced in the Lake Pflugerville Sprint Distance Triathlon in June. It’s also become a favorite spot of mine for swim / bike / run workouts. Lake Pflugerville is well suited as a venue for multi-sport races. The beach has a good swimming area, most of the roads in the area are low traffic and the lake has a trail that goes around it for about 3 miles. The only difficulty is that the lake is plagued by fast growing hydrilla that can make swimming&amp;hellip; interesting.&lt;/p&gt;
&lt;h3 id=&#34;race-day&#34;&gt;Race Day&lt;/h3&gt;
&lt;p&gt;I arrived at Lake Pflugerville at 6:20am and was directed into the parking area (basically the grass beside the parking lot). I got my bike and my bag and headed towards the transition area where volunteers were ready with markers to do body marking. I got my number and headed into transition - volunteers were checking wrist bands to make sure that only athletes were entering the transition area.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/race-report-jacks-generic/transition.jpg&#34; alt=&#34;Transition Area&#34;&gt;&lt;/p&gt;
&lt;p&gt;Transition closed at 7:00am and athletes started congregating on the beach. After some brief stretching we were able to go for a very quick warmup swim. High Five Events had been at the venue in the days before clearing out the hydrilla and they managed to clear out a nice out and back course. Thank god because that stuff was annoying during practice swims in the lake.&lt;/p&gt;
&lt;p&gt;The warmup was a bit hilarious. I swam out to the first buoy, then turned around and saw the horde coming right at me — uh oh, I didn’t think this through! Luckily I managed to get to shore without getting hit in the face by an oncoming swimmer.&lt;/p&gt;
&lt;h3 id=&#34;the-race&#34;&gt;The Race&lt;/h3&gt;
&lt;p&gt;The race started promptly at 7:30am with the open wave, followed by all intermediate women, followed by intermediate men 39 and under, which is my swim start group. Swimmers started one at a time, so once again I avoided the dreaded mass start. The swim course involved two 500m loops with a short (and I mean like 10 feet short) run on the beach between the first and second lap. I managed to stay on course for the most part (only swimming an additional 80 meters according to my Suunto watch) and only got punched in the head a few times. I even managed to pass a few people without incident. I wanted to finish the swim in 20 minutes — I based this on what it would take to make a top 10 finish in my AG last year. &lt;a href=&#34;http://www.mapmyrun.com/workout/1661756753&#34;&gt;I ended up doing it in 21:16&lt;/a&gt;, which was 10th amongst M 35-39. So far, so good.&lt;/p&gt;
&lt;p&gt;My T1 got off to a rough start - I remembered a specific curb that my bike was close to, unaware that it was just like another curb nearby, so I ran right past my bike and got a little lost - probably wasted about 30 seconds or so. Once I got to my transition area things went smoothly. This was my first race going sockless in my new Giro Inciter tri shoes, which saved me some time. Anyway, off I went on the bike. 2:40, also 9th in my age group.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/race-report-jacks-generic/giro.jpg&#34; alt=&#34;Giro Inciter Triathlon Shoes&#34;&gt;&lt;/p&gt;
&lt;p&gt;The bike portion went alright. The course is really great. There are some really nice curving turns on Jesse Bohls Rd and Engelmann Ln that almost feel banked so you can pick up speed as you lean into them. I managed to pass quite a few people on those turns. Overall I only got passed by a few people on expensive tri bikes and huge calf muscles, so I feel like I did alright. My &lt;a href=&#34;http://www.mapmyrun.com/workout/1661759570&#34;&gt;average speed was 19mph, with a time of 1:22:11&lt;/a&gt;, 12th in my age group. I&amp;rsquo;ve got a bit of work to do on the bike, so I&amp;rsquo;m going to do more interval workouts to try and bring my speed up. Also, the new shoes have one small annoyance - the velcro strap on my left shoe kept rubbing against my bikes crank. Not a huge deal, but I had to reach down and fix it a few times, which probably took a few seconds off.&lt;/p&gt;
&lt;p&gt;My T2 went better than the T1. 1:25, 6th in my age group. Going sockless and having elastic laces really helped here - all I had to do was pull off my Giros and snap on my Mizunos and I was off.&lt;/p&gt;
&lt;p&gt;I started the run course with a quick stop at the aid station to down some water and gatorade and then I took off, trying to set a sustainable pace. I&amp;rsquo;ve run this course a bunch of times before and while it&amp;rsquo;s a great trail around the lake, it&amp;rsquo;s mentally challenging. There are parts of the course that weave away from the lake and make it seem longer than you might otherwise expect and there&amp;rsquo;s almost no shade. My plan was to try and ignore how far I had to go and just look down and straight ahead. My brain worked against me a few times here and I ended up thinking a bit too much about the distance and heat. According to the chip time, &lt;a href=&#34;http://www.mapmyrun.com/workout/1661760527&#34;&gt;I finished the run in 53:04 or 8:51/mile&lt;/a&gt;. Again, 9th in my age group, but still much slower than I&amp;rsquo;d like. I feel like I should be able to manage something closer to 8:00/mile based on my training paces. Something to strive for next year. Going sockless in my Mizunos took a toll - in the second lap I felt my feet a lot and started noticing friction burns and blisters. I didn&amp;rsquo;t let it slow me down but I&amp;rsquo;m now sold on tri-specific run shoes and will probably shop for some before my next race. I walked a few times during the run, but tried to keep it to aid stations and ran solidly for the last mile. I started to feel nauseous a few times which might have meant I wasn&amp;rsquo;t hydrating properly. The laps are a little less than the advertised 3 miles - my watch clocked them in at 2.8 or so, so this was actually something like a 5.7 mile run, not a 6 mile run.&lt;/p&gt;
&lt;p&gt;Crossing the finish line felt great - I find courses with laps mentally challenging but there&amp;rsquo;s something great about that final lap when you get to run towards the &amp;ldquo;Finish&amp;rdquo; sign instead of the &amp;ldquo;2nd lap&amp;rdquo; sign. I crossed the finish line and was handed my finishers medal and water bottle. I saw some familiar faces at the finish line - my training buddy Holland had finished a minute ahead of me and I ran into Shane from &lt;a href=&#34;http://runlabaustin.com/&#34;&gt;RunLab&lt;/a&gt; who&amp;rsquo;s helped me overcome nagging shin splints this season. If you&amp;rsquo;re in Austin and have any running related injury or performance concerns, check out RunLab, they&amp;rsquo;re awesome folks. After collecting myself, I headed to the beer tent for the best tasting &lt;a href=&#34;http://www.newbelgium.com/beer/fat-tire&#34;&gt;Fat Tire&lt;/a&gt; I&amp;rsquo;ve ever had.&lt;/p&gt;
&lt;p&gt;My total finishing time was 2:40:38, which placed me 10th in my age group - I&amp;rsquo;m really happy with a top 10 finish and while I&amp;rsquo;ve got areas that I really want to improve on next year, I&amp;rsquo;m proud of this time especially for my first year doing tris.&lt;/p&gt;
&lt;p&gt;Overall, had a blast and highly recommend this race. It&amp;rsquo;s really well organized - kudos to the good people at &lt;a href=&#34;http://highfiveevents.com/&#34;&gt;High Five Events&lt;/a&gt;, all the other participants in the race and especially to all the volunteers who must&amp;rsquo;ve woken up at an ungodly hour to help make it all happen. I&amp;rsquo;ll be back next year looking to nudge closer to the podium.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/race-report-jacks-generic/finishersmedal.jpg&#34; alt=&#34;Finishers Medal&#34;&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Race Report - Rookie Tri 2016</title>
      <link>https://paulosman.me/2016/05/19/race-report-rookie-tri-2016/</link>
      <pubDate>Thu, 19 May 2016 08:14:23 -0500</pubDate>
      <guid>https://paulosman.me/2016/05/19/race-report-rookie-tri-2016/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/race-report-rookie-triathlon/swim.jpg&#34; alt=&#34;Swimming&#34;&gt;&lt;/p&gt;
&lt;p&gt;A few weeks ago I competed in my first triathlon - a super sprint distance race
aptly named the &lt;a href=&#34;http://therookietri.com/&#34;&gt;Rookie Tri&lt;/a&gt;. It&amp;rsquo;s a 300 meter swim,
followed by an 11.2 mile bike ending with a 2 mile run. I&amp;rsquo;m registered to
race an Olympic Distance (1500m swim, 40km bike, 10km run) at the &lt;a href=&#34;http://www.captextri.com/&#34;&gt;CapTex
Triathlon&lt;/a&gt; at the end of the month, so I figured this
would be good practice. I&amp;rsquo;m so glad I did it, I had a great time and learned a
ton.&lt;/p&gt;
&lt;p&gt;Before getting into the specific lessons I took away, I&amp;rsquo;d like to heartily
recommend this event. It&amp;rsquo;s a lot of fun and the organizers go to great lengths
to make it accessible to newcomers. Triathlons are intimidating but this one
really isn&amp;rsquo;t - mostly because so many other people are also first timers, so you
shouldn&amp;rsquo;t feel alone. I never once felt like I should have known something I
didn&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;The swim is the most intimidating part of a triathlon for a lot of people. For
most newer triathletes I talk to, this is their weakest discipline and the idea
of a mass start seems insane to them. The Rookie Tri gets around this by
breaking people into divisions when they register and then having staggered
starts. Competitive triathletes can register in the &amp;ldquo;Open&amp;rdquo; division meaning
they&amp;rsquo;d like to compete with others regardless of age group. This is the only
group that actually does a mass swim. After that there are &amp;ldquo;Veteran&amp;rdquo; (meaning
you&amp;rsquo;ve done 2 or more triathlons in the past) and &amp;ldquo;Rookie&amp;rdquo; (meaning this is your
first or second triathlon) divisions who each start the swim 2 people at a time
in their age group. It was a very relaxed way to start the race and made the
swim portion a lot less terrifying for me.&lt;/p&gt;
&lt;p&gt;People had told me ahead of the time that the bike portion would be hilly. I
guess I take a certain amount of hills for granted, because I didn&amp;rsquo;t find it so
bad and used the hills to pass people. Cycling is my strongest discipline, so
your mileage may vary. The run portion was mostly on grass and involved doing an
out and back, so you would pass the people in front of you. There was a short
uphill section of the run, which just made it more interesting.&lt;/p&gt;
&lt;p&gt;Another way that the Rookie Tri is particularly beginner friendly is that awards
are given out separately for rookies and veterans. This makes it a lot easier to
win a spot on the podium. I was only competing with other rookies in my age
group, and so I managed to come away with a 3rd place finish! That was a great
feeling.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/race-report-rookie-triathlon/podium.jpg&#34; alt=&#34;Podium for 35-39 age group&#34;&gt;&lt;/p&gt;
&lt;p&gt;All in all I was really happy with how things went. Here&amp;rsquo;s a list of take aways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Race belts are a life saver&lt;/em&gt;. When you pick up your race packet, you get
your bib number and some safety pins. USAT rules state that you must wear
your race bib number for the duration of the bike and run, but clearly it
wouldn&amp;rsquo;t be possible to wear it during the swim (it would either get soaked
and soggy, or get torn up if you wear a wetsuit). The idea of fiddling with
safety pins during a T1 seemed insane to me, so I took some advice from a
more experienced triathlete and opted for a race belt that I left in the
transition area with the bib number already attached. All I had to do was
clip it around my waist when going from the swim to the bike. I bought &lt;a href=&#34;http://www.amazon.com/VeloChampion-Triathlon-Race-Number-Belt/dp/B00AQ0TEL0?ie=UTF8&amp;amp;psc=1&amp;amp;redirect=true&amp;amp;ref_=oh_aui_detailpage_o00_s00&#34;&gt;one
made by VeloChampion&lt;/a&gt;
and it worked great for me.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Pace yourself&lt;/em&gt;. I made two pacing mistakes. I underestimated how hard the
swim would be and I killed my legs on the bike. Full of adrenaline, I went
out at full force when the swim started. Swimming is my weakest discipline
and I remember being surprised when I was passing all these much more
confident looking people! Then at about the 150 meter mark, holy hell I
started to feel it and needed to tread water and take a break for a bit. The
realities of open water swimming hit me (the waves!) as well. The second half
of the swim was a lot harder than it needed to be as a result. Once on the
bike, I pushed pretty hard which felt good at the time. I passed dozens of
people and only got passed by one. I paid for it on the run though, so
careful here. My run splits were about 8:20/mile which is slow for me on a 2
mile run, I should have been able to pull off 7:00/mile or faster. The lesson
I&amp;rsquo;m taking away from this is that I need to do more brick workouts so I can
better understand what I&amp;rsquo;m capable of on the bike without killing my legs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Watch your step&lt;/em&gt;. The transition area is going to be hectic. I expected
that. What I didn&amp;rsquo;t expect was that I would step on a bunch of burrs with my
bare feet and have tiny barbs in my foot for the rest of the race - that
sucked! When you&amp;rsquo;re setting up your transition area in the morning, take a
moment to have look around and make sure it&amp;rsquo;s not in an area with lots of
sharp brush, rocks, etc. You&amp;rsquo;ll be running there straight out of the swim and
you&amp;rsquo;ll be a bit disoriented, so make sure you pick a spot with a clear path.
Additionally, practice making your way to your transition area from both
directions depending on the course map. I had it down coming from the swim,
but got a bit lost in T2 which probably cost me a minute or two.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Finish strong&lt;/em&gt;. It&amp;rsquo;s easy to start to ease off when you see the finish line.
You&amp;rsquo;re exhausted, you&amp;rsquo;ve been pushing yourself hard, but now isn&amp;rsquo;t the time
to slow down! With just a few hundred feet to go, I noticed too late that
a guy behind me was starting to up his pace and now my &amp;ldquo;finishing photo&amp;rdquo;
is a shot of that other guy crossing the finish line right in front of me.
It&amp;rsquo;s a race, and all in good fun - we even high fived each other right
afterwards, but I do wish that I&amp;rsquo;d crossed the finish line ahead of him.
Lesson learned - never again!&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All in all it was a really great race and experience. Multisport is intimidating
but I&amp;rsquo;d heartily recommend the Rookie Tri to any newcomers as a perfect way to
get your feet wet (literally!). I&amp;rsquo;ll be there next year, competing in the
Veteran category and I can&amp;rsquo;t wait.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Looking back at 2014</title>
      <link>https://paulosman.me/2015/01/24/looking-back-at-2014/</link>
      <pubDate>Sat, 24 Jan 2015 11:40:20 +0000</pubDate>
      <guid>https://paulosman.me/2015/01/24/looking-back-at-2014/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/wedding.jpg&#34; alt=&#34;Getting married&#34;&gt;&lt;/p&gt;
&lt;p&gt;I was in New Orleans, LA at the beginning of 2014. Fitting, as the Crescent City played a huge role
in my life this past year. Meg and I were in town planning our wedding and decided to stay and
celebrate New Years in our favourite city.&lt;/p&gt;
&lt;p&gt;Our normal abode (and future wedding venue), the &lt;a href=&#34;http://www.maisonmacarty.com/&#34;&gt;Maison de Macarty&lt;/a&gt;
in the Bywater, was fully booked that night so we were temporarily relocated to the Hilton Riverside
hotel. After a nice evening out, we decided to get a bottle of wine and go back to our hotel room
before the countdown began.  This turned out to be a wise move as when the clock struct midnight, we
were treated to a spectacular fireworks display over the Mississippi river. It was the perfect way to
ring in what would become one hell of an eventful year.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s still January, so I reserve the right to pretend that it&amp;rsquo;s not unsually late for a reflective
New Years Day kind of blog post. 2014 was a big year for me so I want to take some time and properly
reflect on it. What follows is a list of highlights recorded mostly for my own sake.&lt;/p&gt;
&lt;h2 id=&#34;the-wedding&#34;&gt;The Wedding&lt;/h2&gt;
&lt;p&gt;On March 8th, shortly after one of the &lt;a href=&#34;http://www.accuweather.com/en/weather-news/new-orleans-may-sneak-away-wit/23867494&#34;&gt;coldest Mardi
Gras&lt;/a&gt; on record,
Meghann and I got married in the backyard of the Maison de Macarty. We were surrounded by people we
love in a city we treat as our second home. It was perfect. Seeing so many of our friends and family
in New Orleans was a great feeling. We had people travel from Boston, New York, Toronto, Dublin and
beyond.  It was truly remarkable and I&amp;rsquo;ll never forget how I felt that week.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/wedding1.jpg&#34; alt=&#34;Photos taken by Kelli Summerford&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/wedding2.jpg&#34; alt=&#34;Photos taken by Kelli Summerford&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/wedding3.jpg&#34; alt=&#34;Photos taken by Kelli Summerford&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/wedding4.jpg&#34; alt=&#34;Photos taken by Kelli Summerford&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/wedding5.jpg&#34; alt=&#34;Photos taken by Kelli Summerford&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/wedding6.jpg&#34; alt=&#34;Photos taken by Kelli Summerford&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/wedding7.jpg&#34; alt=&#34;Photos taken by Kelli Summerford&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/wedding8.jpg&#34; alt=&#34;Photos taken by Kelli Summerford&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;travel&#34;&gt;Travel&lt;/h2&gt;
&lt;p&gt;I got a lot of travel in during 2014. As mentioned, I started the year in New Orleans. Meg and
I ended up fitting in 4 trips to New Orleans in total. We also spent a long weekend in Austin.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/bywatercat.jpg&#34; alt=&#34;Meghann befriending a Bywater cat&#34;&gt;&lt;/p&gt;
&lt;p&gt;Work sent me to Tokyo in February. My trip overlapped with Music Hack Day, so I got to
catch up with old friends from that scene and I managed to see the Crocodiles who were playing
their first show in Japan.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/crocodiles.jpg&#34; alt=&#34;Crocodiles supported by Miila and the Geeks, O-Nest, Shibuya, Feb 2014&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/miila.jpg&#34; alt=&#34;Crocodiles supported by Miila and the Geeks, O-Nest, Shibuya, Feb 2014&#34;&gt;&lt;/p&gt;
&lt;p&gt;In August, our great friends Erin and Andrew hosted us in Maine. We met them at Boston Logan
airport, spent a few days meeting their families and seeing where they grew up, then drove back,
stopping in New Hampshire and Vermont on the way.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/ne1.jpg&#34; alt=&#34;Meg and I in New England&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/ne2.jpg&#34; alt=&#34;Andrew and Erin in New England&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/ne3.jpg&#34; alt=&#34;Meg getting ready to eat Lobster&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/ne4.jpg&#34; alt=&#34;Andrew on the beach&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/ne5.jpg&#34; alt=&#34;The beach&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/ne6.jpg&#34; alt=&#34;New England Scenery&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/ne7.jpg&#34; alt=&#34;New England Scenery&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/ne8.jpg&#34; alt=&#34;New England Scenery&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/ne9.jpg&#34; alt=&#34;New England Scenery&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/ne10.jpg&#34; alt=&#34;New England Scenery&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/ne11.jpg&#34; alt=&#34;New England Scenery&#34;&gt;&lt;/p&gt;
&lt;p&gt;Meg found ridiculously inexpensive flights to Seoul. We spent a week and a half in total with a few
days in Tokyo. It was great going to Tokyo for the 2nd time in less than a year. Seoul was great as
well. We enjoyed wonderful hospitality from Meg&amp;rsquo;s cousin Derek, his wife Jill and their new baby Logan
who&amp;rsquo;ve been living there for a while now. Oh and we ran into Kevin who was also taking advantage of
ridiculously cheap airfare.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/korea1.jpg&#34; alt=&#34;Korea&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/korea2.jpg&#34; alt=&#34;Korea&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/korea3.jpg&#34; alt=&#34;Korea&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/korea4.jpg&#34; alt=&#34;Korea&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/korea5.jpg&#34; alt=&#34;Korea&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/korea6.jpg&#34; alt=&#34;Korea&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/japan1.jpg&#34; alt=&#34;Japan&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/japan2.jpg&#34; alt=&#34;Japan&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/japan3.jpg&#34; alt=&#34;Japan&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/japan4.jpg&#34; alt=&#34;Japan&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/japan5.jpg&#34; alt=&#34;Japan&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/japan6.jpg&#34; alt=&#34;Japan&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;fitness&#34;&gt;Fitness&lt;/h2&gt;
&lt;p&gt;In terms of fitness, I&amp;rsquo;m calling 2014 &amp;ldquo;pretty good&amp;rdquo;. I started cycling again (more than just to and
from work) and got into running. According to Strava I managed just over 2000km on the bike and
about 170km running.&lt;/p&gt;
&lt;p&gt;I went for runs in Toronto, San Francisco, New Orleans, Tokyo and Seoul. I participated in the Ride
to Conquer Cancer (200km over 2 days) and a 110km organized ride.&lt;/p&gt;
&lt;p&gt;The Ride to Conquer Cancer was especially meaningful for me as I rode with my brother, father and
brother in law (and 7 other teammates). I&amp;rsquo;m riding again this year, hoping to raise $2500 for cancer
research.  Proceeds go to the Princess Margaret Hospital. &lt;a href=&#34;http://conquercancer.ca/goto/paulosman2015&#34;&gt;Ya&amp;rsquo;ll should
donate&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For 2015 I&amp;rsquo;m looking to double my total ride distance (4000km) and participate in my first 10k organized
run.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/rtcc1.jpg&#34; alt=&#34;Ride to Conquer Cancer&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/rtcc2.jpg&#34; alt=&#34;Ride to Conquer Cancer&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/rtcc3.jpg&#34; alt=&#34;Ride to Conquer Cancer&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/rtcc4.jpg&#34; alt=&#34;Ride to Conquer Cancer&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/rtcc5.jpg&#34; alt=&#34;Ride to Conquer Cancer&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/rtcc6.jpg&#34; alt=&#34;Ride to Conquer Cancer&#34;&gt;
&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/rtcc7.jpg&#34; alt=&#34;Ride to Conquer Cancer&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;work&#34;&gt;Work&lt;/h2&gt;
&lt;p&gt;Professionally, 2014 was hit and miss. I left SoundCloud at the end of 2013 to start a new position
at 500px. I managed to do a lot of great work and I&amp;rsquo;m proud of what my team and I were able to
accomplish. We evolved the architecture of the application, introduced Go as a language for backend
service development, moved the organization further towards a DevOps culture, and improved response
time while reducing infrastructure costs.&lt;/p&gt;
&lt;p&gt;At the end of the year though, I decided that 500px was not the right company for me and that it was
time to move on.  Nothing dramatic, I still love the people and the product and wish them the
best. I started 2015 with a new challenge that I&amp;rsquo;m very excited about, but that&amp;rsquo;s for different blog
post.&lt;/p&gt;
&lt;p&gt;I did two conference talks in 2014.  &lt;a href=&#34;http://vimeo.com/107499529&#34;&gt;One at DevOpsDays Toronto&lt;/a&gt; and
another at &lt;a href=&#34;https://qconsf.com/presentation/organizing-your-company-embrace-microservices&#34;&gt;QCon San Francisco&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/looking-back-2014/qcon.jpg&#34; alt=&#34;Speaking at QConn&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;in-closing&#34;&gt;In Closing&lt;/h2&gt;
&lt;p&gt;2014 was a great year for me. I married the love of my life, traveled to great places, accomplished
some personal goals and generally had a blast. I was surrounded by wonderful friends, family and
colleagues. I feel like I totally lucked out.&lt;/p&gt;
&lt;p&gt;Reflecting on the past year may seem like naval gazing, and maybe it is a little, but it also helps
me appreciate the things that have happened, and focuses my thinking as I make goals for the next
year. Here&amp;rsquo;s to a great 2015.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Cycling for Cancer Research</title>
      <link>https://paulosman.me/2014/04/22/cycling-for-cancer-research/</link>
      <pubDate>Tue, 22 Apr 2014 08:14:23 -0500</pubDate>
      <guid>https://paulosman.me/2014/04/22/cycling-for-cancer-research/</guid>
      <description>&lt;p&gt;On June 7th and 8th, I&amp;rsquo;ll be joining the
&lt;a href=&#34;http://www.conquercancer.ca/index.html&#34;&gt;Ride to Conquer Cancer&lt;/a&gt; — a
two day ride from Toronto, Ontario to Niagara Falls (that&amp;rsquo;s just over
200km). The ride raises money for the
&lt;a href=&#34;http://www.uhn.ca/corporate/AboutUHN/OurHospitals/Pages/pmh.aspx&#34;&gt;Princess Margaret Cancer Centre&lt;/a&gt;,
one of the top 5 cancer research centres in the world. I&amp;rsquo;m doing this
because the work done at centres like Princess Margaret has a direct
impact on cancer patients and their loved ones. The ride funds are
directed towards research initiatives that are focused on Personalized
Cancer Medicine improving detection, diagnosis, targetting and
support.&lt;/p&gt;
&lt;h1 id=&#34;fundraising&#34;&gt;Fundraising&lt;/h1&gt;
&lt;p&gt;The ride has a $2500 fundraising minimum, and thanks to the
generosity of friends and family, I&amp;rsquo;m sitting at $340 with a mere
$2160 to go.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Here&amp;rsquo;s the thing:&lt;/strong&gt; I really hate asking people for money, even when
it is going to such a worthy institution. Instead I&amp;rsquo;ve decided to give
things away to people who donate certain amounts. I&amp;rsquo;ve been quite busy
as of late, and came to the conclusion that one of the most valuable
things I can give is my time.&lt;/p&gt;
&lt;h1 id=&#34;about-me&#34;&gt;About Me&lt;/h1&gt;
&lt;p&gt;I have 15 years of experience working in the tech industry. In that
time I&amp;rsquo;ve worked for companies like SoundCloud and Mozilla, and I&amp;rsquo;m
now the Director of Platform Engineering at 500px (you can see my
&lt;a href=&#34;http://www.linkedin.com/in/paulosman&#34;&gt;LinkedIn Profile&lt;/a&gt; for details).
I have a huge amount of experience writing software and managing teams
of Software Developers with an emphasis on platforms (both internal
and external APIs).&lt;/p&gt;
&lt;p&gt;You can use this time to pick my brain about anything related to
technology. If you&amp;rsquo;re new to software or considering getting into the
industry, I&amp;rsquo;d love to answer your questions or go over your code with
you. If you&amp;rsquo;re starting a product company, I can give you my honest
feedback on your idea. Maybe you just want to bounce ideas off me or
just chat about tech, I&amp;rsquo;m happy to do that too.&lt;/p&gt;
&lt;h1 id=&#34;the-perks&#34;&gt;The Perks&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;$30 or more&lt;/strong&gt; $30 will get you a thank you card from me - I know this is paltry, but I appreciate your support so much.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$100 or more&lt;/strong&gt; For $100 you&amp;rsquo;ll get the above plus an hour over Skype / Google Hangouts or in person if you&amp;rsquo;re in the Toronto area.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$500 or more&lt;/strong&gt; If you want to go nuts, you get a whopping 8 hours of my time for general tech consulting. I&amp;rsquo;m limiting this to 3 donations and I&amp;rsquo;ll update here once they&amp;rsquo;re gone.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;donate-now&#34;&gt;Donate Now&lt;/h1&gt;
&lt;p&gt;Any amount makes a difference. Cancer is a terrible disease that
effects so many of us. If you want to take advantage of any of the
perks above, simply include details in the Personal Note section when
donating. &lt;a href=&#34;http://bndfr.com/Ctp8&#34;&gt;Donate Here&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Great Engineering Teams</title>
      <link>https://paulosman.me/2012/01/31/great-engineering-teams/</link>
      <pubDate>Tue, 31 Jan 2012 08:14:23 -0500</pubDate>
      <guid>https://paulosman.me/2012/01/31/great-engineering-teams/</guid>
      <description>&lt;p&gt;Building great teams is hard. We all know this. Nobody has the perfect
recipe for creating a great engineering team, but I&amp;rsquo;m fairly certain
that some things are obvious and should be heeded. What follows is a
list of observations I&amp;rsquo;ve made throughout my career about what seems
to work and what doesn&amp;rsquo;t. Perfect excuse for a good old &amp;ldquo;Do&amp;rdquo; and
&amp;ldquo;Don&amp;rsquo;t&amp;rdquo; list:&lt;/p&gt;
&lt;h2 id=&#34;do-encourage-side-projects&#34;&gt;Do: Encourage Side Projects&lt;/h2&gt;
&lt;p&gt;All good developers run into things that piss them off from time to
time. Great developers build tools that help them fix the shit that
pisses them off. Give developers space and time to work on things not
on the roadmap and they&amp;rsquo;ll probably end up building tools that solve
unseen business problems. Intentionally under-load developers (or go
one step further and do away with deadlines) and you&amp;rsquo;ll start to see
deploys run more smoothly, metrics start being captured, open source
libraries and tools start being published and everyone is happier for
it. Allowing developers time for side projects is an intangible, but
I&amp;rsquo;m convinced it&amp;rsquo;s an investment that pays for itself and then some.&lt;/p&gt;
&lt;h2 id=&#34;dont-hire-asymmetric-managers&#34;&gt;Don&amp;rsquo;t: Hire Asymmetric Managers&lt;/h2&gt;
&lt;p&gt;One of my pet peeves is seeing a shop employ a &amp;ldquo;manager&amp;rdquo; who&amp;rsquo;s job is
to pester developers and make sure they meet deadlines. The best
managers I&amp;rsquo;ve worked with always ask two questions during check-ins:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;How are you doing?&lt;/li&gt;
&lt;li&gt;What can I do to help you?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Good managers know that their job is to enable people to do great
things by clearing roadblocks and getting the hell out of their
way. If you have a manager who is constantly &amp;ldquo;whipping&amp;rdquo; their team
into shape, things are going to go very wrong. Hire managers who help
programmers as much as possible. We&amp;rsquo;re all on the same team.&lt;/p&gt;
&lt;h2 id=&#34;do-support-async-workflows&#34;&gt;Do: Support Async Workflows&lt;/h2&gt;
&lt;p&gt;Being a software developer involves blocking out large chunks of time
to get into periods of deep concentration. Requiring developers to
break for meetings or respond to emails / im messages / impromptu
conversations immediately breaks concentration and can ruin a big part
of the day. It also screws your remote employees if you have
any. &lt;a href=&#34;http://zachholman.com/posts/how-github-works-asynchronous/&#34;&gt;Zach
Holman&lt;/a&gt;
and &lt;a href=&#34;http://www.paulgraham.com/makersschedule.html&#34;&gt;Paul Graham&lt;/a&gt; have
written about this much more elequently than I could hope to.&lt;/p&gt;
&lt;h2 id=&#34;dont-hire-bs&#34;&gt;Don&amp;rsquo;t: Hire &amp;ldquo;B&amp;quot;s&lt;/h2&gt;
&lt;p&gt;Comprimising on hiring is the quickest way to build a shitty team. It
can be tempting to hire some &amp;ldquo;juniors&amp;rdquo; that will need supervision, and
there&amp;rsquo;s definitely room for people with a variety of experience, but
you need to shoot for the best and hire nothing but. Hiring second
rate developers will hurt your productivity and effect the morale of
your best folks. Good people like to work with good people. Don&amp;rsquo;t fuck
with that.&lt;/p&gt;
&lt;h2 id=&#34;do-encourage-a-devops-culture&#34;&gt;Do: Encourage a devops culture&lt;/h2&gt;
&lt;p&gt;As I mentioned, great programmers scratch their own itches. Hire
people who know the whole stack. Great programmers should write great
code, but also care about how it&amp;rsquo;s deployed and write or use tools
that help you do that effectively and safely. Having a divide between
developers and people who make sure their code can be shipped creates
two problems:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Your developers stop thinking about ops.&lt;/li&gt;
&lt;li&gt;You create an &amp;ldquo;us&amp;rdquo; vs &amp;ldquo;them&amp;rdquo; attitude. Developers complain about
&amp;ldquo;conservative&amp;rdquo; ops people and ops people complain about &amp;ldquo;sloppy&amp;rdquo;
developers.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Bottom line: making sure your production environment is healthy is
everybodys job.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Most of this boils down to respect. If you&amp;rsquo;re shipping software,
you&amp;rsquo;re an engineering company. Engineering companies need to be
managed as such. Don&amp;rsquo;t treat your developers as second tier. Give them
what they need, support them and your chances of building a great
culture where people want to come and do great work will be greatly
increased.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Building Services with Apache Thrift</title>
      <link>https://paulosman.me/2011/12/12/building-services-with-apache-thrift/</link>
      <pubDate>Mon, 12 Dec 2011 08:14:23 -0500</pubDate>
      <guid>https://paulosman.me/2011/12/12/building-services-with-apache-thrift/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve always been interested in cross-language service frameworks. I
believe in using the best tools for a job, instead of being limited to
a specific language or framework, so being able to write components of
a service in whatever langauge makes the most sense is attractive to
me. In past lives, I&amp;rsquo;ve developed software that used
&lt;a href=&#34;http://en.wikipedia.org/wiki/Common_Object_Request_Broker_Architecture&#34;&gt;CORBA&lt;/a&gt;,
&lt;a href=&#34;http://en.wikipedia.org/wiki/SOAP&#34;&gt;SOAP&lt;/a&gt;,
&lt;a href=&#34;http://en.wikipedia.org/wiki/Component_Object_Model&#34;&gt;COM&lt;/a&gt; and
&lt;a href=&#34;https://developer.mozilla.org/en/XPCOM&#34;&gt;XPCOM&lt;/a&gt; and found that they
all suck in different, significant ways. Because of this, I&amp;rsquo;ve been
interested in the Apache Thrift project originally developed at
Facebook.&lt;/p&gt;
&lt;p&gt;At its core, Thrift is an interface definition language, a code
generation tool and a set of libraries that take care of serialization
and implement the transport protocol.&lt;/p&gt;
&lt;p&gt;In order to generate code from a thrift service definition, you&amp;rsquo;ll
need to install the thrift compiler. You can install it with
&lt;a href=&#34;http://mxcl.github.com/homebrew/&#34;&gt;homebrew&lt;/a&gt; on a mac or download it
from the &lt;a href=&#34;http://thrift.apache.org/download/&#34;&gt;project site&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thrift services and datatypes are defined using an
&lt;a href=&#34;http://en.wikipedia.org/wiki/Interface_description_language&#34;&gt;IDL&lt;/a&gt;
that should look pretty familiar to anyone who has worked with CORBA
or similar technologies. Let&amp;rsquo;s say you want to create a service that
handles creation and storage of user objects.  We&amp;rsquo;ll write the service
in Python and a client in Ruby. This is obviously a contrived example,
but imagine that we&amp;rsquo;re using some kind of data store that has a
library written in Python and all of our application code is in
Ruby. It&amp;rsquo;d be great to be able to use the data store from our Ruby
code without having to develop and maintain our own library.&lt;/p&gt;
&lt;p&gt;We start by defining the datatypes and service interface:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-thrift&#34; data-lang=&#34;thrift&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;User&lt;/span&gt; {                                                                                                      
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;:&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; first_name,                                                                                             
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;:&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; last_name,                                                                                              
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;:&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; email                                                                                                   
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}                                                                                                                  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                                                                                                   
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;service&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;UserService&lt;/span&gt; {                                                                                              
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;add_user&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;:User user),                                                                                      
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;list&lt;/span&gt;&amp;lt;User&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_users&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;)                                                                                           
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}                                                                                                                  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Save the above code in a file called users.thrift, then generate
the Python code by running &lt;code&gt;thrift --gen py users.thrift&lt;/code&gt;.
Because we&amp;rsquo;ll be writing a client in Ruby, we&amp;rsquo;ll also want to run
&lt;code&gt;thrift --gen rb users.thrift&lt;/code&gt; to generate the Ruby code.&lt;/p&gt;
&lt;p&gt;Next, write the server in Python. This requires a bit of boilerplate
and a service handler that gets passed to the Thrift processor. Notice
that thrift threw the generated code in a directory called &amp;lsquo;gen-py&amp;rsquo;.
It&amp;rsquo;s just a Python module, so you can move it wherever you&amp;rsquo;d like, but
in this example I&amp;rsquo;ll just modify sys.path appropriately.&lt;/p&gt;
&lt;p&gt;There are a variety of options for the server itself. For simplicity,
we&amp;rsquo;ll just use &lt;code&gt;TServer.SimpleServer&lt;/code&gt; which is a
straightforward single threaded server. There&amp;rsquo;s also a multithreaded
server and a forking server.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; os
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; sys
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sys&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;path&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;append(os&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;path&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    os&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;path&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;dirname(os&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;path&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;abspath(__file__)), &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;gen-py&amp;#39;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; thrift.transport &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; TSocket, TTransport
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; thrift.protocol &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; TBinaryProtocol
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; thrift.server &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; TServer
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; userservice &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; UserService
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;UserServiceHandler&lt;/span&gt;(object):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;__init__&lt;/span&gt;(self):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;users &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; []
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;add_user&lt;/span&gt;(self, user):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;users&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;append(user)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_users&lt;/span&gt;(self):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;users
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;create_server&lt;/span&gt;(host&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;localhost&amp;#39;&lt;/span&gt;, port&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;9090&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    handler &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; UserServiceHandler()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; TServer&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;TSimpleServer(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        UserService&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Processor(handler),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        TSocket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;TServerSocket(host&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;host, port&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;port),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        TTransport&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;TBufferedTransportFactory(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        TBinaryProtocol&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;TBinaryProtocolFactory()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; __name__ &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    server &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; create_server()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    server&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;serve()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Finally we write the client in Ruby. Ignore the boilerplate client
code for now, and focus on the part where we create a user, add it
using the service, then get a list of users from the service. This
part looks like normal Ruby code which is part of what I really like
about Thrift. There are no special types to use, users as returned
from the get_users method is a simple Array and everything feels
really natural.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$:&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;push(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;./gen-rb&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;rubygems&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;thrift&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;user_service&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;userservice_types&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;create_client&lt;/span&gt;(host&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;localhost&amp;#39;&lt;/span&gt;, port&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;9090&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  socket &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thrift&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Socket&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(host&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;host, port&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;port)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  transport &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thrift&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;BufferedTransport&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(socket)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  protocol &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thrift&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;BinaryProtocol&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(transport)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  client &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;UserService&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Client&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(protocol)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; transport, client
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;user &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;User&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;user&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;first_name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Paul&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;user&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;last_name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Osman&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;user&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;email &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; email
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;transport, client &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; create_client()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;transport&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;open()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;client&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;add_user(user)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;users &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; client&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get_users()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;users&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;each &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;user&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;user&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;first_name&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;, &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;user&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;last_name&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;, &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;user&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;email&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;transport&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;close()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I found a &lt;a href=&#34;http://jnb.ociweb.com/jnb/jnbJun2009.html&#34;&gt;few&lt;/a&gt;
&lt;a href=&#34;http://wiki.apache.org/hadoop/Hbase/ThriftApi&#34;&gt;good&lt;/a&gt;
&lt;a href=&#34;http://diwakergupta.github.com/thrift-missing-guide/thrift.pdf&#34;&gt;resources&lt;/a&gt;
&lt;a href=&#34;http://yannramin.com/2008/07/19/using-facebook-thrift-with-python-and-hbase/&#34;&gt;out
there&lt;/a&gt;,
but most were either a bit outdated, or too generic or went into areas
that I wasn&amp;rsquo;t immediately interested in. I just wanted the straightest
line to seeing what Thrift was all about, so I thought I&amp;rsquo;d write that
up here.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re curious about Thrift, I would encourage you to read this
&lt;a href=&#34;http://thrift.apache.org/static/thrift-20070401.pdf&#34;&gt;whitepaper&lt;/a&gt;
before diving any deeper. It provides a good overview of how Thrift
works and why it was designed the way it was. I&amp;rsquo;m glad to see that
Thrift is gaining some adoption, appearing in projects like &lt;a href=&#34;http://wiki.apache.org/hadoop/Hbase/ThriftApi&#34;&gt;HBase&lt;/a&gt;, &lt;a href=&#34;https://github.com/nathanmarz/storm/wiki/Using-non-JVM-languages-with-Storm&#34;&gt;Storm&lt;/a&gt; and &lt;a href=&#34;https://github.com/twitter/flockdb&#34;&gt;FlockDB&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Storing Extra Data in Django Join Tables</title>
      <link>https://paulosman.me/2011/10/27/storing-extra-data-in-django-join-tables/</link>
      <pubDate>Thu, 27 Oct 2011 08:14:23 -0500</pubDate>
      <guid>https://paulosman.me/2011/10/27/storing-extra-data-in-django-join-tables/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve had some people ask me about the &lt;code&gt;through&lt;/code&gt; argument supported by Django&amp;rsquo;s &lt;code&gt;ManyToManyField&lt;/code&gt; class. This option supports a very simple use case: when you want to store additional data in a join table.&lt;/p&gt;
&lt;p&gt;Imagine, for instance, that we&amp;rsquo;re building a simple course registration tool. Let&amp;rsquo;s call the Django app &lt;code&gt;courses&lt;/code&gt;. We&amp;rsquo;ll define the following models:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; django.db &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; models
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Course&lt;/span&gt;(models&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Model):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;A course offered at our school.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; models&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;CharField(max_length&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    code &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; models&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;CharField(max_length&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;, unique&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    description &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; models&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;TextField()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;__unicode__&lt;/span&gt;(self):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;u&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; (self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;code, self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;name)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Student&lt;/span&gt;(models&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Model):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;A student registered with our school.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    first_name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; models&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;CharField(max_length&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;50&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    last_name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; models&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;CharField(max_length&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;50&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    number &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; models&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;CharField(max_length&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;, unique&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    courses &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; models&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ManyToManyField(Course, null&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    registered_on &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; models&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;DateTimeField(auto_now_add&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;__unicode__&lt;/span&gt;(self):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;u&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; -&amp;gt; &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;, &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;number, self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;last_name, self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;first_name)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When we run &lt;code&gt;syncdb&lt;/code&gt; or generate a migration for these
models, Django will automatically create the following tables:
&lt;code&gt;courses_course, courses_student, and courses_student_courses&lt;/code&gt;. The first two should be
self-explanatory. The third table will store information about our
many-to-many relationship. Each row will associate a student id with a
course id.&lt;/p&gt;
&lt;p&gt;Now let&amp;rsquo;s say we want to store the date and time whenever a student
registers for a course. We&amp;rsquo;d want something like the following model:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Registration&lt;/span&gt;(models&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Model):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;Student registration for a course.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    student &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; models&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ForeignKey(Student)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    course &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; models&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ForeignKey(Course)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    registered_on &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; models&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;DateTimeField(auto_now_add&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In order to get Django to use our model as a join table, we can use
&lt;code&gt;through&lt;/code&gt; by changing a single line in our student model:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Student&lt;/span&gt;(models&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Model):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    courses &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; models&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ManyToManyField(Course, null&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        through&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Registration&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And that&amp;rsquo;s it. Now, Django will automatically generate a &lt;code&gt;courses_registration&lt;/code&gt;
table and use it instead of &lt;code&gt;courses_student_courses&lt;/code&gt;, allowing us to store
extra data about the registration.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Making Signups Easier With WebFinger</title>
      <link>https://paulosman.me/2010/10/04/making-signups-easier-with-webfinger/</link>
      <pubDate>Mon, 04 Oct 2010 08:14:23 -0500</pubDate>
      <guid>https://paulosman.me/2010/10/04/making-signups-easier-with-webfinger/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been thinking about signup processes lately and what we can do with &lt;a href=&#34;http://www.drumbeat.org/project/batucada&#34;&gt;batucada&lt;/a&gt; (the next drumbeat.org) to make it easier, while encouraging users to use open, decentralized identity technologies like &lt;a href=&#34;http://openid.net/&#34;&gt;OpenID&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;the-problem&#34;&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Whenever I come across a new service that I want to create an account on, I dread the idea of creating a new set of credentials and re-entering a bunch of information I have already entered elsewhere. OpenID solves part of this for me, and I&amp;rsquo;m always happy to see sites supporting it. The problem, however, is that OpenID only solves one piece of the puzzle (authentication) and there are usability problems abound. The latter problem is well known, and people have come up with a bunch of innovative ways of tackling it, including intelligent OpenID selectors.&lt;/p&gt;
&lt;p&gt;The core of the problem is that people don&amp;rsquo;t tend to think of themselves as a URL, which is what OpenID uses as a supplied identifier. It&amp;rsquo;s been really hard to get the average web user to think of a URL as part of their identity, and frankly, they&amp;rsquo;re usually pretty hard to remember. There are ways around this, including hosting your own OpenID provider, or using delegation, so that you can choose an easy-to-remember URL, but it&amp;rsquo;s unrealistic to expect non-technical web users to be able to do this.&lt;/p&gt;
&lt;h2 id=&#34;the-solution&#34;&gt;The Solution&lt;/h2&gt;
&lt;p&gt;Enter &lt;a href=&#34;http://code.google.com/p/webfinger/&#34;&gt;WebFinger&lt;/a&gt;. The idea behind WebFinger is that, given an email-like identifier, an application can discover information about that user (including their OpenID provider). I really like this idea. I think people have an easier time with email addresses than with URLs. A bunch of providers, including AOL, Gmail and Yahoo have implemented WebFinger support, which means that there are a ton of people out there who already have WebFinger identifiers, even if they don&amp;rsquo;t know it (and they shouldn&amp;rsquo;t have to know it).&lt;/p&gt;
&lt;p&gt;So what would a WebFinger based signup process look like? The first step would be to capture an email address and find out if it is a valid WebFinger identifier. If it is, the next step would be to find out of the user has an OpenID provider. If their email address is not a WebFinger identifier, the workflow would fall back to a more traditional password based signup form. Imagine clicking on a &amp;ldquo;Sign up&amp;rdquo; link and being presented with a form asking for your email address.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/making-signups-easier-with-webfinger/login_step_one.png&#34; alt=&#34;Step One&#34;&gt;&lt;/p&gt;
&lt;p&gt;Once submitted, the web application would perform WebFinger based discovery on the identifier in the background. If an OpenID provider is found, the user could be presented with a friendly message asking them if they&amp;rsquo;d like to use it to sign in to the site.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/making-signups-easier-with-webfinger/mockup.png&#34; alt=&#34;Step Two&#34;&gt;&lt;/p&gt;
&lt;p&gt;If the email address is not a WebFinger identifier, or if the user opts not to use their OpenID, fall back to a more traditional sign up form.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://paulosman.me/images/making-signups-easier-with-webfinger/webfinger_step_two.png&#34; alt=&#34;Step Three&#34;&gt;&lt;/p&gt;
&lt;p&gt;These are obviously very rough, but I&amp;rsquo;m interested to know what people think of this idea. Specifically how the process could be made easier for non-technical users. A few concerns I have (and note that UX / Usability is certainly NOT my specialty, so help is appreciated!)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A service provider shouldn&amp;rsquo;t coerce a user into using an OpenID vs creating a new set of credentials, the user must always have a choice. Unfortunately, this means asking another question, and questions are confusing.&lt;/li&gt;
&lt;li&gt;Presuming a user has an OpenID, but does not have an active session with their provider, they are going to be sent to their provider to log in after Step Two. This could be confusing to users. We&amp;rsquo;re sending them into the OpenID &amp;ldquo;Chasm of Death&amp;rdquo;. Is there anything that can be done to ease this transition / manage user expectations?&lt;/li&gt;
&lt;li&gt;Once users have signed up using an OpenID, how do we let them know that they should use that OpenID from now on? I suppose we could just prompt for an email address on sign in and perform discovery again. This introduces another step for those who aren&amp;rsquo;t using an OpenID though (step one: input email address, step two on next screen: enter password).&lt;/li&gt;
&lt;li&gt;Users could be confused immediately when we ask for their email address and not a password. This is breaking a familiar pattern, which is always potentially jarring.&lt;/li&gt;
&lt;li&gt;What about users who know what OpenID is, and have an OpenID that they&amp;rsquo;d like to use, but don&amp;rsquo;t have a WebFinger identifier that refers to their OpenID? Again, more questions to ask, which adds more confusion.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Again, I&amp;rsquo;m certainly not a UX person, so I&amp;rsquo;d be very curious to hear what people think of this idea and if people have ideas about how to improve the flow.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Google Webfinger</title>
      <link>https://paulosman.me/2010/02/01/google-webfinger/</link>
      <pubDate>Mon, 01 Feb 2010 08:14:23 -0500</pubDate>
      <guid>https://paulosman.me/2010/02/01/google-webfinger/</guid>
      <description>&lt;p&gt;This is &lt;a href=&#34;http://groups.google.com/group/webfinger/browse_thread/thread/46fe84c1e38ab715/fa625d20f4d963e4&#34;&gt;pretty old news&lt;/a&gt;, but I missed the original announcement and I think it&amp;rsquo;s pretty interesting.&lt;/p&gt;
&lt;p&gt;Google have implemented an alpha of the &lt;a href=&#34;http://code.google.com/p/webfinger/&#34;&gt;WebFinger protocol&lt;/a&gt;.  If you have a Google profile, click on &amp;ldquo;Edit your profile&amp;rdquo; and add &amp;lsquo;webfingeralpha&amp;rsquo; as an interest. Congrats, your gmail address is now a WebFinger identifier and will resolve to an XRD file containing information about services you use (if you&amp;rsquo;ve included that information in your Google profile).&lt;/p&gt;
&lt;p&gt;This is pretty fun to play around with. Given an email-like identifier, such as &lt;a href=&#34;mailto:evalpaul@gmail.com&#34;&gt;evalpaul@gmail.com&lt;/a&gt;, get the host-meta XRD file for the domain:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;paul@knuth ~ $ curl -i http://gmail.com/.well-known/host-meta
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;HTTP/1.1 &lt;span style=&#34;color:#ae81ff&#34;&gt;200&lt;/span&gt; OK
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Content-Type: application/xrd+xml
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Transfer-Encoding: chunked
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Date: Mon, &lt;span style=&#34;color:#ae81ff&#34;&gt;01&lt;/span&gt; Feb &lt;span style=&#34;color:#ae81ff&#34;&gt;2010&lt;/span&gt; 12:36:47 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Expires: Mon, &lt;span style=&#34;color:#ae81ff&#34;&gt;01&lt;/span&gt; Feb &lt;span style=&#34;color:#ae81ff&#34;&gt;2010&lt;/span&gt; 12:36:47 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Cache-Control: private, max-age&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;X-Content-Type-Options: nosniff
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;X-XSS-Protection: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;X-Frame-Options: SAMEORIGIN
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Server: GFE/2.0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;?xml version=&amp;#39;1.0&amp;#39; encoding=&amp;#39;UTF-8&amp;#39;?&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;!-- NOTE: this host-meta end-point is a pre-alpha work in progress.   Don&amp;#39;t rely on it. --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;!-- Please follow the list at http://groups.google.com/group/webfinger --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;XRD&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;xmlns=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;http://docs.oasis-open.org/ns/xri/xrd-1.0&amp;#39;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;xmlns:hm=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;http://host-meta.net/xrd/1.0&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;hm:Host&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;xmlns=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;http://host-meta.net/xrd/1.0&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;gmail.com&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;lt;&lt;/span&gt;;/hm:Host&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;Link&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rel=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;lrdd&amp;#39;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#a6e22e&#34;&gt;template=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;http://www.google.com/s2/webfinger/?q={uri}&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;Title&amp;gt;&lt;/span&gt;Resource Descriptor&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;lt;&lt;/span&gt;;/Title&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/Link&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/XRD&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now that you have the URI template, get the XRD file for the specific user:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;http://www.google.com/s2/webfinger/?q=acct%3Aevalpaul%40gmail.com&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And the response:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;?xml version=&amp;#39;1.0&amp;#39;?&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;XRD&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;xmlns=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;http://docs.oasis-open.org/ns/xri/xrd-1.0&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;Subject&amp;gt;&lt;/span&gt;acct:evalpaul@gmail.com&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;lt;&lt;/span&gt;;/Subject&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;Alias&amp;gt;&lt;/span&gt;http://www.google.com/profiles/evalpaul&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;lt;&lt;/span&gt;;/Alias&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;Link&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rel=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;http://portablecontacts.net/spec/1.0&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;href=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;http://www-opensocial.googleusercontent.com/api/people/&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;Link&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rel=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;http://webfinger.net/rel/profile-page&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;href=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;http://www.google.com/profiles/evalpaul&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;type=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;text/html&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;Link&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rel=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;http://microformats.org/profile/hcard&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;href=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;http://www.google.com/profiles/evalpaul&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;type=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;text/html&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;Link&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rel=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;http://gmpg.org/xfn/11&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;href=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;http://www.google.com/profiles/evalpaul&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;type=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;text/html&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;Link&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rel=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;http://specs.openid.net/auth/2.0/provider&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;href=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;http://www.google.com/profiles/evalpaul&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;Link&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rel=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;describedby&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;href=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;http://www.google.com/profiles/evalpaul&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;type=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;text/html&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;Link&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rel=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;describedby&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;href=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;http://s2.googleusercontent.com/webfinger/?q=evalpaul%40gmail.com&amp;amp;amp;fmt=foaf&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;type=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;application/rdf+xml&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/XRD&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;EDIT:&lt;/strong&gt; Google have since rolled out WebFinger support for everyone with a Google Profile. You no longer need to add &amp;lsquo;webfingeralpha&amp;rsquo; to your interests.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Introduction to mod_python</title>
      <link>https://paulosman.me/2007/01/01/introduction-to-mod_python/</link>
      <pubDate>Mon, 01 Jan 2007 08:14:23 -0500</pubDate>
      <guid>https://paulosman.me/2007/01/01/introduction-to-mod_python/</guid>
      <description>&lt;p&gt;This article will provide a brief introduction to mod_python, a tour of what you can do with it, and some pointers to further resources should you want to explore it in more depth. I will assume that the reader is comfortable programming in Python (although no specific knowledge is required) and is familiar with Apache and basic web concepts. This article is not intended to be a complete reference for mod_python. Instead it is meant to consolidate information available from other sources, to hopefully whet your appetite and encourage you to read more from the official documentation (links are in the Resources section).&lt;/p&gt;
&lt;h2 id=&#34;prerequisites-and-assumptions&#34;&gt;Prerequisites and Assumptions&lt;/h2&gt;
&lt;p&gt;I am going to assume that you have a working installation of Apache 2.0.47 or higher and have Python 2.2.1 or later installed. To make things simple, I’m going to assume you are working from the same machine that Apache is installed on, so all URLs will have &amp;rsquo;localhost&amp;rsquo; as the server name. Replace this with your server name if this is not the case.&lt;/p&gt;
&lt;h2 id=&#34;what-is-mod_python&#34;&gt;What is mod_python?&lt;/h2&gt;
&lt;p&gt;In the bad old days, most web development was done using CGI (Common Gateway Interface). Writing a CGI program meant creating an executable (script or binary) that the web server called to handle a request. The output generated by the CGI program would then be returned to the user via their browser. Think about that process for a second: a) the user requests a page from the web server, perhaps with some arguments sent via GET or POST, b) the web server recognizes that the requested page is handled by a CGI script and invokes the CGI process, c) the CGI program collects information from the web server using some mechanism (usually environment variables), does some processing and prints out a bunch of HTML (usually),  if everything went alright, the web server takes the output and sends it to the user.&lt;/p&gt;
&lt;p&gt;Sounds pretty cumbersome when you think about it, doesn’t it? It’s not only cumbersome, it’s also slow and very error prone. mod_python saves us from having to go through this process by integrating the Python programming language right into the Apache HTTP server. This provides a much faster way for Apache to execute python handlers, and as an added bonus, gives us complete access to the Apache internals. Imagine mod_python as a little guy (or &amp;hellip; a Python?) stuck inside your web server intercepting certain requests and allowing you to do really cool things with them. Okay, so that may not be a very good technical description, but we’ll get to that. Sound interesting? It is!&lt;/p&gt;
&lt;p&gt;It’s important to understand that writing applications with mod_python is not the same as writing applications with a server-side scripting language like PHP. Instead, with mod_python you specify handlers in the Apache configuration file(s) that allow you to customize how a request is handled. This allows you to do a variety of neat things like implement protocols other than HTTP, filter the request and response, determine a document’s content-type, etc.&lt;/p&gt;
&lt;p&gt;So let’s get mod_python installed and take a tour of how it works. In order to follow along with this tutorial, you’ll need to have Apache and Python installed, and then install mod_python.&lt;/p&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;There are a few different ways to install mod_python, depending on what Operating System you are running. If you are using a distribution of Linux that has a decent package management system (any Red Hat / Fedora, Debian or Gentoo based system for instance) then there will likely already be a package available for you to install. On Gentoo I just emerge the mod_python ebuild with the USE flags I want and portage automatically adds configuration files to be included into my Apache configuration. On Red Hat Enterprise Linux I use a mod_python RPM that depends on having the python-devel and httpd-devel packages. For the sake of brevity I’m only going to cover installing from source here. Consult your OS’ documentation to see if there is an easier way for you. (There is a way to install mod_python on Windows but I am not going to cover that here).&lt;/p&gt;
&lt;h3 id=&#34;compiling-from-source&#34;&gt;Compiling from Source&lt;/h3&gt;
&lt;p&gt;In order to compile mod_python, you’ll need to grab the latest stable source release from the &lt;a href=&#34;http://www.modpython.org&#34;&gt;mod_python website&lt;/a&gt;. At the time of this writing, the latest stable release was 3.2.10.&lt;/p&gt;
&lt;p&gt;I’m going to assume you have Apache2 already installed (if not you can get it from the &lt;a href=&#34;http://httpd.apache.org/&#34;&gt;Apache website&lt;/a&gt;). I am using Apache 2.0.59 but the process should be the same for any version of Apache above 2.0.47. I have Apache installed in /usr/local/apache2. You will need to adjust the path to match your installation.&lt;/p&gt;
&lt;p&gt;Once you’ve downloaded the source tarball for mod_python, untar it and run the &amp;lsquo;configure&amp;rsquo; script (feel free to run ./configure &amp;ndash;help to see what other configuration options are available):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pike:/usr/local/src paul$ tar xfz mod_python-3.2.10.tgz 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pike:/usr/local/src paul$ cd mod_python-3.2.10
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pike:/usr/local/src/mod_python-3.2.10 paul$ ./configure --with-apxs&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/usr/local/apache2/bin/apxs
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;checking &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; gcc... gcc
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;checking &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; C compiler default output file name... a.out
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;checking whether the C compiler works... yes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If everything went okay, and you didn’t get any error messages from configure, you should then run &amp;lsquo;make&amp;rsquo; to compile mod_python and &amp;lsquo;make install&amp;rsquo;. You will likely need to run &amp;lsquo;make install&amp;rsquo; as root, so you can use &amp;ldquo;su -c &amp;lsquo;make install&amp;rsquo;&amp;rdquo;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pike:/usr/local/src/mod_python-3.2.10 paul$ make
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;... 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;a whole bunch of output you can ignore unless you know what you&amp;amp;&lt;span style=&#34;color:#75715e&#34;&gt;#8217;re doing)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pike:/usr/local/src/mod_python-3.2.10 paul$ su -c &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;make install&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;more output that we won&amp;amp;&lt;span style=&#34;color:#75715e&#34;&gt;#8217;t concern ourselves with)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Safety Note:&lt;/strong&gt; Never run a configure script as root. It’s always possible that the host you retrieved the source distribution from has been compromised and therefore you don’t know for sure what could be hiding in that script.&lt;/p&gt;
&lt;p&gt;If the compilation process didn’t report any errors, you should be ready to go. Next we have to open the Apache2 configuration file (usually called httpd.conf) and add the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-apache&#34; data-lang=&#34;apache&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;LoadModule python_module      modules/mod_python.so&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; the path to mod_python.so may very. Check your Apache2 installation root and find out if it was actually put there or somewhere else.&lt;/p&gt;
&lt;p&gt;As with any time you edit the Apache configuration, you will have to restart Apache before the changes take effect. Now we want to verify that our installation went smoothly. There are a variety of ways to do this, I always like to grab the default headers from Apache to see what is installed. I do this by telnetting into port 80 and typing &amp;lsquo;HEAD / HTTP/1.0&amp;rsquo;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pike:/usr/local/src/mod_python-3.2.10 paul$ su -c &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/usr/local/apache2/bin/apachectl restart&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pike:/usr/local/src/mod_python-3.2.10 paul$ telnet localhost &lt;span style=&#34;color:#ae81ff&#34;&gt;80&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Trying ::1...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Connected to localhost.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Escape character is &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;^]&amp;#39;&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;HEAD / HTTP/1.0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;HTTP/1.1 &lt;span style=&#34;color:#ae81ff&#34;&gt;200&lt;/span&gt; OK
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Date: Mon, &lt;span style=&#34;color:#ae81ff&#34;&gt;01&lt;/span&gt; Jan &lt;span style=&#34;color:#ae81ff&#34;&gt;2007&lt;/span&gt; 04:28:24 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Server: Apache/2.0.59 &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;Unix&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; mod_python/3.2.10 Python/2.3.5 PHP/5.2.0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Last-Modified: Sat, &lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt; Nov &lt;span style=&#34;color:#ae81ff&#34;&gt;2004&lt;/span&gt; 20:16:24 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ETag: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ab5da-2c-4c23b600&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Accept-Ranges: bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Content-Length: &lt;span style=&#34;color:#ae81ff&#34;&gt;44&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Connection: close
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Content-Type: text/html
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Connection closed by foreign host.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you can see from the &amp;ldquo;Server&amp;rdquo; header, mod_python version 3.2.10 is installed. Now to really test it! Open the Apache2 configuration file again and add the following location directive:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;Location&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/mpinfo&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    SetHandler mod_python
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PythonHandler mod_python.testhandler
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/Location&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Restart Apache again and point your browser to &lt;a href=&#34;http://localhost/mpinfo&#34;&gt;http://localhost/mpinfo&lt;/a&gt; and you should see a test page with a lot of useful information about your server environment. This information can come in very handy when debugging problems so I tend to keep it around. If you don’t see this page, something must have gone wrong. Go over the instructions again or consult your operating system documentation.  If you see the page, congratulations, you have installed mod_python! Now let’s move on and start learning about mod_python.&lt;/p&gt;
&lt;h2 id=&#34;handlers&#34;&gt;Handlers&lt;/h2&gt;
&lt;p&gt;In order to truly understand the power offered by mod_python, a basic understanding of Apache handlers is required. Essentially you can think of a handler as a processing &amp;ldquo;phase&amp;rdquo;. Apache gets a request, and then initiates a number of handlers (or &amp;ldquo;phases&amp;rdquo;) to do something. The handlers can either be built into Apache, or included as modules. Apache handlers may be configured explicitly, based on either filename extensions or location. Examples of functionality that takes place in handlers may include authenticating a user, invoking a cgi script, getting the server’s status, etc. mod_python allows you to tap into any handler used by Apache. mod_python also provides a few standard handlers to help you with some common tasks.&lt;/p&gt;
&lt;h3 id=&#34;publisher-handler&#34;&gt;Publisher Handler&lt;/h3&gt;
&lt;p&gt;The publisher handler is available so you don’t always have to worry about writing your own handlers and can instead focus on developing your application. It’s very handy. In order to use the publisher handler, you have to something like this to your Apache configuration:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-apache&#34; data-lang=&#34;apache&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;Directory&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;/usr/local/apache2/htdocs/PublisherExample&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    AddHandler mod_python .py
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PythonDebug &lt;span style=&#34;color:#66d9ef&#34;&gt;On&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PythonHandler mod_python.publisher
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/Directory&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The &amp;ldquo;PythonDebug On&amp;rdquo; line makes mod_python output errors to the browser when possible, instead of the Apache server logs. This is useful while we’re developing.&lt;/p&gt;
&lt;p&gt;Now create a file called &amp;lsquo;ModPythonExample.py&amp;rsquo; in the directory &amp;lsquo;PublisherExample&amp;rsquo; and type the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; time &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; strftime, localtime
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;publisher_example&lt;/span&gt;(req):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    req&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;content_type &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;text/html&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    time_str &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; strftime(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%a&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; %b &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%d&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; %H:%M:%S %Y&amp;#34;&lt;/span&gt;, localtime())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    message &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;lt;h1&amp;gt;Hello from mod_python!&amp;lt;/h1&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    message &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;lt;p&amp;gt;The time on this server is &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;lt;/p&amp;gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; (time_str)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; message&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Because we have modified the Apache configuration we will need to restart it once again. Now point your browser to &lt;a href=&#34;http://localhost/PublisherExample/ModPythonExample.py/publisher_example&#34;&gt;http://localhost/PublisherExample/ModPythonExample.py/publisher_example&lt;/a&gt; and you should see a message telling you what time it is.&lt;/p&gt;
&lt;p&gt;As we can see from this example, the publisher handler calls a function and just sends the return value to the client. The function receives a request object as an argument. We set the request object’s content_type to &amp;rsquo;text/html&amp;rsquo; because we want the output to be handled as HTML. We then construct a message including the current time and date and return it. Notice the structure of the URL. The first part after the directory (ModPythonExample.py) is the name of our file, and the second part (&amp;ldquo;publisher_example&amp;rdquo;) is the name of the function to call.&lt;/p&gt;
&lt;p&gt;Obviously this is only a simple example. There are many great things you can do with the publisher handler. See the documentation for more information (&lt;a href=&#34;#resources&#34;&gt;Resources&lt;/a&gt;).&lt;/p&gt;
&lt;h3 id=&#34;cgi-handler&#34;&gt;CGI Handler&lt;/h3&gt;
&lt;p&gt;The CGI Handler is provided as a stepping stone away from traditional CGI. It is not intended as a final solution for using mod_python. Basically, the CGI Handler emulates a CGI environment from within mod_python, allowing you to migrate CGI based Python applications to mod_python with little or no modification. Read the documentation to learn about limitations of this handler. To use it, just add a Directory directive to your Apache config (like we did for the Publisher Handler) and include the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-apache&#34; data-lang=&#34;apache&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SetHandler mod_python
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;PythonHandler mod_python.cgihandler&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once again, this should not be considered a final solution but can certainly improve the performance of your existing CGI code without too much modification.&lt;/p&gt;
&lt;h3 id=&#34;custom-handlers&#34;&gt;Custom Handlers&lt;/h3&gt;
&lt;p&gt;Using a standard handler may be appropriate in many scenarios, but sometimes you might find it more appropriate to write your own handler. mod_python allows you to do this (of course) and in fact makes it pretty easy! Let’s create the following directory directive in our Apache configuration:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-apache&#34; data-lang=&#34;apache&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;Directory&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;/usr/local/apache2/htdocs/CustomExample&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    AddHandler mod_python .py
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PythonDebug &lt;span style=&#34;color:#66d9ef&#34;&gt;On&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PythonHandler customexample
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/Directory&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This tells Apache that any requests for a file with a .py extension will be served by mod_python. (It is worth noting that the file does not actually have to exist, and that in fact a request for /CustomExample/myfile.py and /CustomExample/myotherfile.py will both be handled the same with this configuration). The &amp;ldquo;PythonHandler customexample&amp;rdquo; line tells Apache to hand requests for files with a .py extension to this module (which we will be writing). The actual process goes something like this: a) Apache receives a request for a file in the CustomExample directory that has an extension of .py. b) Apache recognizes that this request is to be handled by mod_python and attempts to import a module called &amp;ldquo;customexample&amp;rdquo;. Apache looks for this module in sys.path (with our directory prepended to it so anything in there will be found first). c) Apache will then look for a function called &amp;ldquo;handler&amp;rdquo; in the module and execute it, passing the request object as an argument. Okay, enough details, let’s write our handler module. Create a file called &amp;ldquo;customexample.py&amp;rdquo; in the &amp;ldquo;CustomExample&amp;rdquo; directory:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; mod_python &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; apache
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;handler&lt;/span&gt;(req):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    req&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;content_type &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;text/plain&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    req&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;write(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Hello from mod_python!&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; apache&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;OK&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A few things to notice here: a) we use the request object to write output to the client instead of just returning content, b) we return a constant from the apache module. The apache.OK constant corresponds to an HTTP 200 response code. Other constants are defined for 404, 302, etc response codes. Of course, this example doesn’t really do anything new, so to demonstrate the real power of writing our own handlers we are now going to create a super simple (and not very secure) MySQL authentication handler. If you have MySQL installed, create a database called &amp;ldquo;mptutorial&amp;rdquo; and add the following table and records:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;CREATE&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;TABLE&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;users&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;id&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt; int(&lt;span style=&#34;color:#ae81ff&#34;&gt;11&lt;/span&gt;) unsigned &lt;span style=&#34;color:#66d9ef&#34;&gt;NOT&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;NULL&lt;/span&gt; auto_increment,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;username&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt; varchar(&lt;span style=&#34;color:#ae81ff&#34;&gt;50&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;NOT&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;password&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt; varchar(&lt;span style=&#34;color:#ae81ff&#34;&gt;50&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;NOT&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;PRIMARY&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;KEY&lt;/span&gt;  (&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;id&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;UNIQUE&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;KEY&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;username&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;username&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;) ENGINE&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;MyISAM &lt;span style=&#34;color:#66d9ef&#34;&gt;DEFAULT&lt;/span&gt; CHARSET&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;latin1;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;INSERT&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;INTO&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;users&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;username&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;password&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;VALUES&lt;/span&gt; (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;john&amp;#39;&lt;/span&gt;, MD5(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;secret&amp;#39;&lt;/span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then add the following to your Apache configuration:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-apache&#34; data-lang=&#34;apache&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;Directory&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;/usr/local/apache2/htdocs/AuthenticateExample&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    AddHandler mod_python .py
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PythonDebug &lt;span style=&#34;color:#66d9ef&#34;&gt;On&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PythonAuthenHandler authuser
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    AuthType Basic
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    AuthName &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Secure Area&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Require valid-user
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/Directory&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now let’s write our example code. We need to write an authentication handler that retrieves the username and password entered&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This example uses the &lt;a href=&#34;http://sourceforge.net/projects/mysql-python&#34;&gt;MySQLdb&lt;/a&gt; module.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; MySQLdb
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; mod_python &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; apache
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;verify_user&lt;/span&gt;(username, password):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    db &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; MySQLdb&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Connect(host&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;localhost&amp;#39;&lt;/span&gt;,user&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;mpuser&amp;#39;&lt;/span&gt;,passwd&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;mppassword&amp;#39;&lt;/span&gt;,db&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;mptutorial&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cur &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; db&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;cursor()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sql &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SELECT * FROM users WHERE username = &amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; AND password = MD5(&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;);&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; (MySQLdb&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;escape_string(username), MySQLdb&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;escape_string(password))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cur&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;execute(sql) 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    results &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; cur&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fetchall()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    db&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;close()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; len(results) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;authenhandler&lt;/span&gt;(req):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    username &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; req&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;user
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    password &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; req&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get_basic_auth_pw()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; verify_user(username, password):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; apache&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;OK
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; apache&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;HTTP_UNAUTHORIZED&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Restart Apache and point your browser to &lt;a href=&#34;http://localhost/AuthenticateExample/&#34;&gt;http://localhost/AuthenticateExample/&lt;/a&gt;. You should be prompted with a username and password dialog. Try entering a wrong password, then enter the username &amp;lsquo;john&amp;rsquo; and the password &amp;lsquo;secret&amp;rsquo;. See how mod_python handled the authentication? Neat huh?&lt;/p&gt;
&lt;h2 id=&#34;python-server-pages-psp&#34;&gt;Python Server Pages (PSP)&lt;/h2&gt;
&lt;p&gt;There is one standard handler that I did not cover in the previous section. The PSP Handler allows you to use the PSP class in the mod_python.psp module. PSP stands for Python Server Pages. Python Server Pages allow you to inline Python code in HTML (or any other kind of document) as you would if you were using PHP, ASP (Active Server Pages), JSP (Java Server Pages) or something similar. Some people argue against the practice of mixing markup and code, and I’d be one of them. I personally advocate the use of server pages as a view mechanism with very little control logic. Of course, to demonstrate the functionality of PSPs I will likely break my own rule.&lt;/p&gt;
&lt;p&gt;As with any mod_python handler, we have to edit our Apache configuration before we can use Python Server Pages. Open your Apache configuration and add a Directory directive similar to the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-apache&#34; data-lang=&#34;apache&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;Directory&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;/usr/local/apache2/htdocs/PSPExample&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    AddHandler mod_python .psp
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PythonHandler mod_python.psp
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PythonDebug &lt;span style=&#34;color:#66d9ef&#34;&gt;On&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/Directory&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This should all look pretty familiar by now! Basically we tell Apache that any files with a .psp extension are handled by mod_python. We then tell Apache that the mod_python.psp module will be the generic handler for these files. Also, because we’ve told Apache that PSP files will have a .psp extension, let’s add index.psp to our DirectoryIndex in Apache’s configuration. Open your Apache configuration file again and find the line that starts with &amp;ldquo;DirectoryIndex&amp;rdquo; and add index.psp. Depending on what was there before, that line should now look like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-apache&#34; data-lang=&#34;apache&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;DirectoryIndex index.html index.html.var index.php index.psp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We’ve now set up the PSP handler, told Apache to serve index.psp files as directory indexes, all that is left to do is to write some actual code. So restart Apache and let’s start exploring Python Server Pages. Create a file called index.psp in the PSPExample directory and type the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;html&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;head&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;title&lt;/span&gt;&amp;gt;Python Server Pages (PSP)&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;title&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;head&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;body&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;lt;&lt;/span&gt;%
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;import time
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;%&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello world, the time is: &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;lt;&lt;/span&gt;%=time.strftime(&amp;#34;%Y-%m-%d, %H:%M:%S&amp;#34;)%&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;body&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;html&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Save the file and point your browser to &lt;a href=&#34;http://localhost/PSPExample&#34;&gt;http://localhost/PSPExample&lt;/a&gt;. Your server should give you the index.psp file, because we added it to the list of files in DirectoryIndex. If all went well, you should get a message with the current time on your server. If you are familiar with JSP, ASP, etc, then the above code should look very similar. Basically, anything in between the &amp;lt;% %&amp;gt; tags is interpreted as Python code. Whatever is between the &amp;lt;%= %&amp;gt; tags is replaced with the result of the expression. This saves you from typing a lot of write() or print statements.&lt;/p&gt;
&lt;p&gt;Indentation can be pretty tricky in Python Server Pages. Because PSP allows you to mix Python code and HTML / XML / Anything else, you often find that you need a way to terminate a for iteration or if statement. There’s a simple, albeit cumbersome way to do this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;lt;&lt;/span&gt;%
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    a = [1,2,3,4,5,6]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    for number in a:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    This is a number: &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;lt;&lt;/span&gt;%=number %&amp;gt; &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;br&lt;/span&gt; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;lt;&lt;/span&gt;%
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    # this terminates the iteration
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;h1&lt;/span&gt;&amp;gt;Hello&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;h1&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If we left the comment out of the above example, the Hello header element would be written to the output for each iteration of the list. That’s obviously not what we want, so add a comment to tell PSP that the for iteration has ended.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;We’ve just taken a whirl-wind tour of some of the features of mod_python. Leveraging the power of the Python programming language and the Apache HTTP server, mod_python offers an incredible amount of flexibility to a web developer. In the next article in this series, I’m going to start covering some of the more framework oriented approaches to Python Web Development. Until then.&lt;/p&gt;
&lt;h2 id=&#34;resources&#34;&gt;Resources&lt;/h2&gt;
&lt;p&gt;I have covered a lot of material in this article, but there is still a lot more to mod_python. In order to become truly proficient, you should really take the time to read the official documentation and become more familiar with Apache. The mod_python documentation can be found &lt;a href=&#34;http://modpython.org/live/current/doc-html/&#34;&gt;here&lt;/a&gt;. You can also find more examples at the mod_python section of the apache wiki found &lt;a href=&#34;http://wiki.apache.org/mod_python/FrontPage&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
