<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title>Roopesh</title>
      <link>https://roope.sh</link>
      <description>DevOps notes from Roopesh — cloud infrastructure, developer experience, and platform engineering.</description>
      <generator>Zola</generator>
      <language>en</language>
      <atom:link href="https://roope.sh/rss.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Sun, 28 Jun 2026 00:00:00 +0000</lastBuildDate>
      <item>
          <title>How RDS Optimized Writes doubles MySQL write throughput</title>
          <pubDate>Sun, 28 Jun 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://roope.sh/blog/rds-optimized-writes-doublewrite-buffer/</link>
          <guid>https://roope.sh/blog/rds-optimized-writes-doublewrite-buffer/</guid>
          <description xml:base="https://roope.sh/blog/rds-optimized-writes-doublewrite-buffer/">&lt;p&gt;AWS markets &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-optimized-writes.html&quot;&gt;RDS Optimized Writes&lt;/a&gt; as a free toggle that delivers &lt;em&gt;“up to two times higher write transaction throughput”&lt;/em&gt; on RDS for MySQL and MariaDB. Same instance class, same engine, same workload. Flip a parameter and write throughput can roughly double, at no additional charge.&lt;/p&gt;
&lt;p&gt;That is a tall claim for what looks like a configuration knob. Where does the 2x come from? The short answer: AWS is letting MySQL skip a long-standing safety mechanism called the &lt;em&gt;InnoDB doublewrite buffer&lt;/em&gt;, and the underlying hardware now provides the guarantee the doublewrite buffer was protecting against.&lt;/p&gt;
&lt;p&gt;The rest of this post is the long answer. It walks through what torn pages are, why MySQL has a doublewrite buffer in the first place, why removing it can be worth up to 2x on write-bound workloads, and what specifically changed at the hardware layer that makes this safe.&lt;/p&gt;
&lt;h2 id=&quot;the-problem-torn-pages&quot;&gt;The problem: torn pages&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-problem-torn-pages&quot; aria-label=&quot;Anchor link to this heading&quot;&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;figure&gt;
&lt;svg width=&quot;100%&quot; viewBox=&quot;0 10 620 230&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; role=&quot;img&quot; aria-labelledby=&quot;diag1-title diag1-desc&quot; style=&quot;max-width: 620px; display: block; margin: 0 auto;&quot;&gt;
  &lt;title id=&quot;diag1-title&quot;&gt;Torn page after power loss&lt;/title&gt;
  &lt;desc id=&quot;diag1-desc&quot;&gt;A 16 KiB InnoDB page broken across four 4 KiB sectors. Before the write all four sectors hold the old contents. Power is lost partway through writing, leaving two new sectors and two old sectors. That is a torn page.&lt;/desc&gt;
  &lt;style&gt;
    .lbl { font: 600 13px ui-sans-serif, system-ui, sans-serif; fill: currentColor; }
    .sm { font: 11px ui-monospace, SFMono-Regular, Menlo, monospace; fill: currentColor; opacity: 0.7; }
    .stk { fill: none; stroke: currentColor; stroke-width: 1.5; }
    .stk-fill { fill: currentColor; fill-opacity: 0.08; stroke: currentColor; stroke-width: 1.5; }
  &lt;/style&gt;
  &lt;text x=&quot;90&quot; y=&quot;28&quot; class=&quot;lbl&quot; text-anchor=&quot;middle&quot;&gt;Before write&lt;/text&gt;
  &lt;rect x=&quot;30&quot; y=&quot;48&quot; width=&quot;120&quot; height=&quot;38&quot; rx=&quot;3&quot; class=&quot;stk&quot; /&gt;
  &lt;rect x=&quot;30&quot; y=&quot;86&quot; width=&quot;120&quot; height=&quot;38&quot; rx=&quot;3&quot; class=&quot;stk&quot; /&gt;
  &lt;rect x=&quot;30&quot; y=&quot;124&quot; width=&quot;120&quot; height=&quot;38&quot; rx=&quot;3&quot; class=&quot;stk&quot; /&gt;
  &lt;rect x=&quot;30&quot; y=&quot;162&quot; width=&quot;120&quot; height=&quot;38&quot; rx=&quot;3&quot; class=&quot;stk&quot; /&gt;
  &lt;text x=&quot;90&quot; y=&quot;72&quot; class=&quot;sm&quot; text-anchor=&quot;middle&quot;&gt;old · 4 KiB&lt;/text&gt;
  &lt;text x=&quot;90&quot; y=&quot;110&quot; class=&quot;sm&quot; text-anchor=&quot;middle&quot;&gt;old · 4 KiB&lt;/text&gt;
  &lt;text x=&quot;90&quot; y=&quot;148&quot; class=&quot;sm&quot; text-anchor=&quot;middle&quot;&gt;old · 4 KiB&lt;/text&gt;
  &lt;text x=&quot;90&quot; y=&quot;186&quot; class=&quot;sm&quot; text-anchor=&quot;middle&quot;&gt;old · 4 KiB&lt;/text&gt;
  &lt;text x=&quot;90&quot; y=&quot;225&quot; class=&quot;sm&quot; text-anchor=&quot;middle&quot;&gt;16 KiB page&lt;/text&gt;
  &lt;line x1=&quot;180&quot; y1=&quot;125&quot; x2=&quot;240&quot; y2=&quot;125&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;1.5&quot; /&gt;
  &lt;polygon points=&quot;240,119 250,125 240,131&quot; fill=&quot;currentColor&quot; /&gt;
  &lt;text x=&quot;215&quot; y=&quot;115&quot; class=&quot;sm&quot; text-anchor=&quot;middle&quot;&gt;write…&lt;/text&gt;
  &lt;text x=&quot;215&quot; y=&quot;190&quot; font-size=&quot;22&quot; text-anchor=&quot;middle&quot; fill=&quot;#ea580c&quot;&gt;⚡&lt;/text&gt;
  &lt;text x=&quot;215&quot; y=&quot;225&quot; class=&quot;sm&quot; text-anchor=&quot;middle&quot; fill=&quot;#ea580c&quot;&gt;power loss&lt;/text&gt;
  &lt;text x=&quot;430&quot; y=&quot;28&quot; class=&quot;lbl&quot; text-anchor=&quot;middle&quot;&gt;After power loss&lt;/text&gt;
  &lt;rect x=&quot;370&quot; y=&quot;48&quot; width=&quot;120&quot; height=&quot;38&quot; rx=&quot;3&quot; class=&quot;stk-fill&quot; /&gt;
  &lt;rect x=&quot;370&quot; y=&quot;86&quot; width=&quot;120&quot; height=&quot;38&quot; rx=&quot;3&quot; class=&quot;stk-fill&quot; /&gt;
  &lt;rect x=&quot;370&quot; y=&quot;124&quot; width=&quot;120&quot; height=&quot;38&quot; rx=&quot;3&quot; class=&quot;stk&quot; /&gt;
  &lt;rect x=&quot;370&quot; y=&quot;162&quot; width=&quot;120&quot; height=&quot;38&quot; rx=&quot;3&quot; class=&quot;stk&quot; /&gt;
  &lt;text x=&quot;430&quot; y=&quot;72&quot; class=&quot;sm&quot; text-anchor=&quot;middle&quot;&gt;new · 4 KiB&lt;/text&gt;
  &lt;text x=&quot;430&quot; y=&quot;110&quot; class=&quot;sm&quot; text-anchor=&quot;middle&quot;&gt;new · 4 KiB&lt;/text&gt;
  &lt;text x=&quot;430&quot; y=&quot;148&quot; class=&quot;sm&quot; text-anchor=&quot;middle&quot;&gt;old · 4 KiB&lt;/text&gt;
  &lt;text x=&quot;430&quot; y=&quot;186&quot; class=&quot;sm&quot; text-anchor=&quot;middle&quot;&gt;old · 4 KiB&lt;/text&gt;
  &lt;text x=&quot;430&quot; y=&quot;225&quot; class=&quot;lbl&quot; text-anchor=&quot;middle&quot; fill=&quot;#ea580c&quot;&gt;torn page&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption&gt;A 16 KiB InnoDB page is written as four 4 KiB sector writes. Lose power partway through and you get a page that&#39;s half-new, half-old. That is a torn page.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;InnoDB stores all of its data in 16 KiB pages by default. Disks and filesystems, however, work in smaller units, typically 4 KiB sectors. When MySQL writes a 16 KiB page to disk, the operating system breaks it into four 4 KiB sector writes underneath. If something goes wrong partway through (a power outage, a kernel panic, a disk failure), the page on disk ends up with a mix of new and old bytes. This is called a &lt;em&gt;torn page&lt;/em&gt; or &lt;em&gt;partial page write&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;A torn page is unrecoverable from MySQL’s redo log alone. The redo log records &lt;em&gt;changes&lt;/em&gt; to pages (for example, “in page X, at offset Y, set the value to Z”), not the full page contents. Replaying those changes assumes the page itself is intact to begin with. If the page is half-new and half-old, the redo log has nothing clean to apply against, and recovery fails.&lt;/p&gt;
&lt;p&gt;Databases solve this two ways. PostgreSQL writes full page copies into its write-ahead log on first modify after a checkpoint (the &lt;code&gt;full_page_writes&lt;/code&gt; setting). MySQL takes a different route: it keeps a redundant copy of every dirty page in a dedicated on-disk area called the doublewrite buffer. The trade-offs differ; the goal of surviving torn pages is the same. &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://www.percona.com/blog/a-tale-of-two-databases-how-postgresql-and-mysql-handle-torn-pages/&quot;&gt;Percona has a great side-by-side&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;mysqls-solution-the-doublewrite-buffer&quot;&gt;MySQL’s solution: the doublewrite buffer&lt;a class=&quot;zola-anchor&quot; href=&quot;#mysqls-solution-the-doublewrite-buffer&quot; aria-label=&quot;Anchor link to this heading&quot;&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;figure&gt;
&lt;svg width=&quot;100%&quot; viewBox=&quot;0 50 720 155&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; role=&quot;img&quot; aria-labelledby=&quot;diag2-title diag2-desc&quot; style=&quot;max-width: 720px; display: block; margin: 0 auto;&quot;&gt;
  &lt;title id=&quot;diag2-title&quot;&gt;MySQL write path with the doublewrite buffer&lt;/title&gt;
  &lt;desc id=&quot;diag2-desc&quot;&gt;A dirty page in the buffer pool is written to the doublewrite area on disk and fsynced, then written to its final tablespace location and fsynced again. Every dirty page becomes two physical writes.&lt;/desc&gt;
  &lt;style&gt;
    .lbl { font: 600 13px ui-sans-serif, system-ui, sans-serif; fill: currentColor; }
    .sm { font: 11px ui-monospace, SFMono-Regular, Menlo, monospace; fill: currentColor; opacity: 0.75; }
    .cap { font: italic 13px ui-sans-serif, system-ui, sans-serif; fill: currentColor; opacity: 0.85; }
    .stk { fill: none; stroke: currentColor; stroke-width: 1.5; }
  &lt;/style&gt;
  &lt;rect x=&quot;20&quot; y=&quot;60&quot; width=&quot;170&quot; height=&quot;80&quot; rx=&quot;6&quot; class=&quot;stk&quot; /&gt;
  &lt;text x=&quot;105&quot; y=&quot;95&quot; class=&quot;lbl&quot; text-anchor=&quot;middle&quot;&gt;Dirty page&lt;/text&gt;
  &lt;text x=&quot;105&quot; y=&quot;115&quot; class=&quot;sm&quot; text-anchor=&quot;middle&quot;&gt;(buffer pool)&lt;/text&gt;
  &lt;line x1=&quot;200&quot; y1=&quot;100&quot; x2=&quot;270&quot; y2=&quot;100&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;1.5&quot; /&gt;
  &lt;polygon points=&quot;270,94 280,100 270,106&quot; fill=&quot;currentColor&quot; /&gt;
  &lt;text x=&quot;240&quot; y=&quot;90&quot; class=&quot;sm&quot; text-anchor=&quot;middle&quot;&gt;write&lt;/text&gt;
  &lt;text x=&quot;240&quot; y=&quot;120&quot; class=&quot;sm&quot; text-anchor=&quot;middle&quot;&gt;+ fsync&lt;/text&gt;
  &lt;rect x=&quot;290&quot; y=&quot;60&quot; width=&quot;170&quot; height=&quot;80&quot; rx=&quot;6&quot; class=&quot;stk&quot; /&gt;
  &lt;text x=&quot;375&quot; y=&quot;95&quot; class=&quot;lbl&quot; text-anchor=&quot;middle&quot;&gt;Doublewrite&lt;/text&gt;
  &lt;text x=&quot;375&quot; y=&quot;115&quot; class=&quot;sm&quot; text-anchor=&quot;middle&quot;&gt;area&lt;/text&gt;
  &lt;line x1=&quot;470&quot; y1=&quot;100&quot; x2=&quot;540&quot; y2=&quot;100&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;1.5&quot; /&gt;
  &lt;polygon points=&quot;540,94 550,100 540,106&quot; fill=&quot;currentColor&quot; /&gt;
  &lt;text x=&quot;510&quot; y=&quot;90&quot; class=&quot;sm&quot; text-anchor=&quot;middle&quot;&gt;write&lt;/text&gt;
  &lt;text x=&quot;510&quot; y=&quot;120&quot; class=&quot;sm&quot; text-anchor=&quot;middle&quot;&gt;+ fsync&lt;/text&gt;
  &lt;rect x=&quot;560&quot; y=&quot;60&quot; width=&quot;140&quot; height=&quot;80&quot; rx=&quot;6&quot; class=&quot;stk&quot; /&gt;
  &lt;text x=&quot;630&quot; y=&quot;95&quot; class=&quot;lbl&quot; text-anchor=&quot;middle&quot;&gt;Tablespace&lt;/text&gt;
  &lt;text x=&quot;630&quot; y=&quot;115&quot; class=&quot;sm&quot; text-anchor=&quot;middle&quot;&gt;(final location)&lt;/text&gt;
  &lt;text x=&quot;360&quot; y=&quot;190&quot; class=&quot;cap&quot; text-anchor=&quot;middle&quot;&gt;Every dirty page is written twice.&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption&gt;The doublewrite buffer write path: each dirty page is written to a reserved on-disk area first, fsynced, then written to its tablespace location, fsynced again.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-doublewrite-buffer.html&quot;&gt;official MySQL definition&lt;/a&gt; sums it up:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The doublewrite buffer is a storage area where InnoDB writes pages flushed from the buffer pool before writing the pages to their proper positions in the InnoDB data files. If there is an operating system, storage subsystem, or unexpected mysqld process exit in the middle of a page write, InnoDB can find a good copy of the page from the doublewrite buffer during crash recovery.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Every dirty page that InnoDB flushes goes through four steps: write the page into the doublewrite area on disk, &lt;code&gt;fsync()&lt;/code&gt; the doublewrite area, write the page to its actual tablespace location, &lt;code&gt;fsync()&lt;/code&gt; the tablespace.&lt;/p&gt;
&lt;p&gt;On crash, InnoDB compares both copies during recovery. If the tablespace copy is torn but the doublewrite copy is intact, InnoDB rewrites the page from the doublewrite copy. If the tablespace copy is intact, the doublewrite copy is discarded. &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://www.percona.com/blog/innodb-double-write/&quot;&gt;Percona’s explainer on the recovery mechanic&lt;/a&gt; goes deeper.&lt;/p&gt;
&lt;p&gt;Despite the name, the latency overhead on a well-tuned MySQL is not 2x. The MySQL manual is explicit:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Although data is written twice, the doublewrite buffer does not require twice as much I/O overhead or twice as many I/O operations. Data is written to the doublewrite buffer in a large sequential chunk, with a single fsync() call to the operating system.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;On a healthy system, the steady-state cost is roughly 5–10%. MySQL 8.0.20 also made the doublewrite path scale better by moving the buffer out of the system tablespace (&lt;code&gt;ibdata1&lt;/code&gt;) and into &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-doublewrite-buffer.html&quot;&gt;dedicated files&lt;/a&gt; like &lt;code&gt;#ib_16384_0.dblwr&lt;/code&gt;, with one set per buffer pool instance. That removed a long-standing concurrency bottleneck.&lt;/p&gt;
&lt;p&gt;The doublewrite buffer is controlled by a single variable, on by default:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;SHOW VARIABLES &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;LIKE&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;innodb_doublewrite&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; +--------------------+-------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; | Variable_name      | Value |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; +--------------------+-------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; | innodb_doublewrite | ON    |   ← stock MySQL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; +--------------------+-------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since MySQL 8.0.30, the parameter accepts a richer set of values: &lt;code&gt;ON&lt;/code&gt; (the default, equivalent to &lt;code&gt;DETECT_AND_RECOVER&lt;/code&gt;), &lt;code&gt;DETECT_ONLY&lt;/code&gt; (writes metadata, doesn’t recover full pages), and &lt;code&gt;OFF&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;why-2x-then-the-write-amplification-angle&quot;&gt;Why “2x”, then? The write amplification angle&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-2x-then-the-write-amplification-angle&quot; aria-label=&quot;Anchor link to this heading&quot;&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;There is a contradiction lurking here: the MySQL manual says the doublewrite buffer adds modest I/O overhead, yet AWS markets removing it as a 2x win. Both are true; they are talking about different things.&lt;/p&gt;
&lt;p&gt;The “modest overhead” framing measures &lt;em&gt;latency on a system with I/O headroom&lt;/em&gt;. The buffer’s writes are sequential and batched, so they amortise into a small per-transaction cost.&lt;/p&gt;
&lt;p&gt;The “up to 2x” framing measures &lt;em&gt;throughput on a system that’s running out of I/O headroom&lt;/em&gt;. The doublewrite buffer doubles the &lt;em&gt;volume&lt;/em&gt; of page data hitting durable storage; every dirty page lands on disk twice. On write-bound workloads, the binding constraint isn’t latency, it’s bytes-per-second to storage. Halve the bytes and you can serve roughly twice the writes before saturating the volume.&lt;/p&gt;
&lt;p&gt;That is exactly the regime AWS’s benchmark targets. The &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://aws.amazon.com/blogs/database/improve-application-performance-on-amazon-rds-for-mysql-and-mariadb-instances-and-mysql-multi-az-db-clusters-with-optimized-writes/&quot;&gt;AWS Database Blog deep dive&lt;/a&gt; describes a sysbench write-only workload on a &lt;code&gt;db.r6g.8xlarge&lt;/code&gt; with 50,000 provisioned IOPS in a Multi-AZ setup, deliberately write-bound. Mixed or read-heavy workloads see far less benefit because they were never bottlenecked on the second write to begin with.&lt;/p&gt;
&lt;p&gt;Percona has separately measured how badly a misconfigured doublewrite path can hurt. They show &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://www.percona.com/blog/improve-innodb-performance-write-bound-loads/&quot;&gt;up to 55% write IOPS reduction&lt;/a&gt; on write-bound loads when the legacy single-buffer design becomes a contention point. The fix in stock MySQL is tuning &lt;code&gt;innodb_doublewrite_pages&lt;/code&gt; upward. AWS’s fix is to remove the buffer entirely.&lt;/p&gt;
&lt;h2 id=&quot;when-the-doublewrite-buffer-is-redundant&quot;&gt;When the doublewrite buffer is redundant&lt;a class=&quot;zola-anchor&quot; href=&quot;#when-the-doublewrite-buffer-is-redundant&quot; aria-label=&quot;Anchor link to this heading&quot;&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;If the doublewrite buffer exists to protect against torn pages, then any storage layer that already guarantees atomic 16 KiB writes makes the buffer redundant. This is not a new idea, and MySQL has long known how to skip the buffer when it is safe to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fusion-io with NVMFS.&lt;/strong&gt; The MySQL manual says it directly: &lt;em&gt;“If the doublewrite buffer is located on a Fusion-io device that supports atomic writes, the doublewrite buffer is automatically disabled and data file writes are performed using Fusion-io atomic writes instead.”&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ZFS with &lt;code&gt;recordsize=16K&lt;/code&gt;.&lt;/strong&gt; ZFS’s copy-on-write semantics mean a 16 KiB write either completes to a new block or never publishes. There is no in-place overwrite to be interrupted. &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Workload%20Tuning.html&quot;&gt;OpenZFS tuning docs&lt;/a&gt; recommend &lt;code&gt;recordsize=16K&lt;/code&gt; precisely for InnoDB.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MariaDB’s &lt;code&gt;innodb_use_atomic_writes&lt;/code&gt;.&lt;/strong&gt; Since 10.2, MariaDB auto-detects compatible hardware at startup and disables the doublewrite buffer when atomic writes are available (&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://mariadb.com/kb/en/atomic-write-support/&quot;&gt;MariaDB KB&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The pattern is consistent: when the storage stack can atomically commit at least an InnoDB page’s worth of data, the application-level safety net becomes redundant. The atomicity moves from software to hardware, and the doublewrite traffic disappears.&lt;/p&gt;
&lt;h2 id=&quot;enter-aws-nitro&quot;&gt;Enter AWS Nitro&lt;a class=&quot;zola-anchor&quot; href=&quot;#enter-aws-nitro&quot; aria-label=&quot;Anchor link to this heading&quot;&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;figure&gt;
&lt;svg width=&quot;100%&quot; viewBox=&quot;0 35 720 170&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; role=&quot;img&quot; aria-labelledby=&quot;diag3-title diag3-desc&quot; style=&quot;max-width: 720px; display: block; margin: 0 auto;&quot;&gt;
  &lt;title id=&quot;diag3-title&quot;&gt;RDS Optimized Writes path&lt;/title&gt;
  &lt;desc id=&quot;diag3-desc&quot;&gt;A dirty page in the buffer pool is written directly to its final tablespace location in a single atomic 16 KiB write, made possible by the AWS Nitro System.&lt;/desc&gt;
  &lt;style&gt;
    .lbl { font: 600 13px ui-sans-serif, system-ui, sans-serif; fill: currentColor; }
    .sm { font: 11px ui-monospace, SFMono-Regular, Menlo, monospace; fill: currentColor; opacity: 0.75; }
    .cap { font: italic 13px ui-sans-serif, system-ui, sans-serif; fill: currentColor; opacity: 0.85; }
    .stk { fill: none; stroke: currentColor; stroke-width: 1.5; }
  &lt;/style&gt;
  &lt;rect x=&quot;20&quot; y=&quot;60&quot; width=&quot;170&quot; height=&quot;80&quot; rx=&quot;6&quot; class=&quot;stk&quot; /&gt;
  &lt;text x=&quot;105&quot; y=&quot;95&quot; class=&quot;lbl&quot; text-anchor=&quot;middle&quot;&gt;Dirty page&lt;/text&gt;
  &lt;text x=&quot;105&quot; y=&quot;115&quot; class=&quot;sm&quot; text-anchor=&quot;middle&quot;&gt;(buffer pool)&lt;/text&gt;
  &lt;line x1=&quot;200&quot; y1=&quot;100&quot; x2=&quot;540&quot; y2=&quot;100&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;1.5&quot; /&gt;
  &lt;polygon points=&quot;540,94 550,100 540,106&quot; fill=&quot;currentColor&quot; /&gt;
  &lt;text x=&quot;370&quot; y=&quot;55&quot; class=&quot;sm&quot; text-anchor=&quot;middle&quot; fill=&quot;#ea580c&quot;&gt;AWS Nitro guarantees atomicity&lt;/text&gt;
  &lt;text x=&quot;370&quot; y=&quot;90&quot; class=&quot;lbl&quot; text-anchor=&quot;middle&quot;&gt;atomic 16 KiB write&lt;/text&gt;
  &lt;text x=&quot;370&quot; y=&quot;120&quot; class=&quot;sm&quot; text-anchor=&quot;middle&quot;&gt;+ fsync&lt;/text&gt;
  &lt;rect x=&quot;560&quot; y=&quot;60&quot; width=&quot;140&quot; height=&quot;80&quot; rx=&quot;6&quot; class=&quot;stk&quot; /&gt;
  &lt;text x=&quot;630&quot; y=&quot;95&quot; class=&quot;lbl&quot; text-anchor=&quot;middle&quot;&gt;Tablespace&lt;/text&gt;
  &lt;text x=&quot;630&quot; y=&quot;115&quot; class=&quot;sm&quot; text-anchor=&quot;middle&quot;&gt;(final location)&lt;/text&gt;
  &lt;text x=&quot;360&quot; y=&quot;190&quot; class=&quot;cap&quot; text-anchor=&quot;middle&quot;&gt;Every dirty page is written once.&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption&gt;With Optimized Writes on, the doublewrite step disappears entirely. The page travels straight from the buffer pool to its final location in one atomic 16 KiB write.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://aws.amazon.com/ec2/nitro/&quot;&gt;AWS Nitro&lt;/a&gt; is the hardware platform underneath modern EC2 (and therefore RDS) instances. It combines dedicated hardware, lightweight firmware, and a stripped-down hypervisor that handles networking, storage, and security functions outside the main host CPU.&lt;/p&gt;
&lt;p&gt;For Optimized Writes, the relevant property is that the Nitro storage path guarantees atomic 16 KiB writes. From the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-optimized-writes.html&quot;&gt;AWS RDS documentation&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;These databases run on DB instance classes that use the AWS Nitro System. Because of the hardware configuration in these systems, the database can write 16-KiB pages directly to data files reliably and durably in one step. The AWS Nitro System makes RDS Optimized Writes possible.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When Optimized Writes is on, RDS sets the underlying MySQL variable &lt;code&gt;innodb_doublewrite&lt;/code&gt; to &lt;code&gt;FALSE&lt;/code&gt; (&lt;code&gt;0&lt;/code&gt;). That is the same knob you would flip locally, just with hardware that backs the safety guarantee. ACID is preserved; atomicity simply moves from the doublewrite buffer to the Nitro layer.&lt;/p&gt;
&lt;p&gt;The result: every dirty page goes to durable storage once instead of twice. On the same write-bound workload that saturates the doublewrite path, that frees up roughly half the write bandwidth, which is where AWS’s up-to-2x throughput claim comes from.&lt;/p&gt;
&lt;p&gt;You can verify Optimized Writes is active on your instance the same way you would verify the doublewrite buffer locally:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;SHOW VARIABLES &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;LIKE&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;innodb_doublewrite&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; +--------------------+-------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; | Variable_name      | Value |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; +--------------------+-------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; | innodb_doublewrite | OFF   |   ← Optimized Writes active&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; +--------------------+-------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;turning-it-on-in-practice&quot;&gt;Turning it on in practice&lt;a class=&quot;zola-anchor&quot; href=&quot;#turning-it-on-in-practice&quot; aria-label=&quot;Anchor link to this heading&quot;&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Optimized Writes is controlled by a single RDS parameter:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;rds.optimized_writes = AUTO   # Turn on when version × instance class supports it (default)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;rds.optimized_writes = OFF    # Force off; falls back to the doublewrite buffer&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For new instances, the feature is on by default on any combination that supports it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MySQL&lt;/strong&gt;: version 8.0.30 and later (8.0.x and 8.4). Supported instance classes cover the modern M and R families: &lt;code&gt;db.m5/m6i/m6g/m6gd/m7g/m7i/m8g/m8gd&lt;/code&gt;, &lt;code&gt;db.r5/r5b/r5d/r6g/r6gd/r6i/r7g/r7i/r8g/r8gd&lt;/code&gt;, plus &lt;code&gt;db.x2idn&lt;/code&gt;/&lt;code&gt;db.x2iedn&lt;/code&gt;. The &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-optimized-writes.html&quot;&gt;docs page&lt;/a&gt; has the canonical list; AWS adds new instance families over time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MariaDB&lt;/strong&gt;: 10.6.10+, 10.11.4+, 11.4.3+, or 11.8+. Same shape of instance class coverage, currently without the 8th-generation Graviton families (&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-optimized-writes-mariadb.html&quot;&gt;MariaDB-specific docs&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No additional charge.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Existing instances created before the feature launched (November 27, 2022 for MySQL, March 7, 2023 for MariaDB) have an incompatible underlying file system layout and cannot be flipped in place. The migration path is an &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://aws.amazon.com/about-aws/whats-new/2023/10/enable-amazon-rds-optimized-writes-blue-green-deployments/&quot;&gt;RDS Blue/Green Deployment&lt;/a&gt; with the &lt;em&gt;“Enable Optimized Writes on green database”&lt;/em&gt; and &lt;em&gt;“Upgrade storage file system configuration”&lt;/em&gt; options ticked. Cut over once the green environment is in sync.&lt;/p&gt;
&lt;h2 id=&quot;the-catches&quot;&gt;The catches&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-catches&quot; aria-label=&quot;Anchor link to this heading&quot;&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;A few things worth keeping in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;“Up to 2x” is a ceiling, not a floor.&lt;/strong&gt; AWS says it directly: &lt;em&gt;“the amount of benefit that can be achieved depends on the type of workload.”&lt;/em&gt; Read-heavy or mixed workloads will see a fraction of the gain.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RDS only, not Aurora.&lt;/strong&gt; Aurora has its own purpose-built storage layer that doesn’t use the InnoDB doublewrite buffer at all, so there’s nothing to optimise here.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No PostgreSQL equivalent.&lt;/strong&gt; Postgres handles torn pages via full-page writes in WAL, which is a different architecture entirely. RDS for PostgreSQL has a separate feature called &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_PostgreSQL.optimizedreads.html&quot;&gt;Optimized Reads&lt;/a&gt;, which is unrelated.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Existing instances need migration.&lt;/strong&gt; The on-disk file system layout differs from the pre-feature state, so a Blue/Green deployment is the only path forward.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Snapshot restore is constrained.&lt;/strong&gt; Optimized Writes can only restore &lt;em&gt;into&lt;/em&gt; an instance if the source snapshot was created from one that already supported it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The doublewrite buffer was a clever software workaround for a storage stack that could not promise atomic 16 KiB writes. AWS Nitro can promise that, end-to-end. So MySQL no longer needs the workaround, and write throughput approximately doubles on write-bound workloads, for free. The buffer still earns its keep on stock MySQL; on RDS, the hardware now does the job. 🍻&lt;/p&gt;
</description>
      </item>
      <item>
          <title>Reddify</title>
          <pubDate>Fri, 06 Dec 2024 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://roope.sh/projects/reddify/</link>
          <guid>https://roope.sh/projects/reddify/</guid>
          <description xml:base="https://roope.sh/projects/reddify/">&lt;p&gt;&lt;img src=&quot;/reddify-search.png&quot; alt=&quot;Reddify&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Reddify is a simple web utility to create a Spotify playlist out of a Reddit post’s comments.&lt;/p&gt;
&lt;p&gt;It features a minimal UI built without any javascript frameworks.&lt;/p&gt;
&lt;h2 id=&quot;why-i-built-reddify&quot;&gt;Why I built Reddify?&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-i-built-reddify&quot; aria-label=&quot;Anchor link to this heading&quot;&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Quite often, on subreddits like &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://www.reddit.com/r/Music&quot;&gt;r/music&lt;/a&gt; and &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://www.reddit.com/r/spotify&quot;&gt;r/spotify&lt;/a&gt;, there are huge threads that are filled
with comments featuring great music suggestions. I used to listen to a few of the top comments and
forget about the rest. This felt like leaving a treasure trove of new music experiences behind.&lt;/p&gt;
&lt;p&gt;While most of the days I stick to listening familiar tunes, there are specific days when I am
very excited about discovering new songs and having a playlist of these suggestions from Reddit
looked like a wonderful way to go about it.&lt;/p&gt;
&lt;h2 id=&quot;how-does-reddify-work&quot;&gt;How does Reddify work?&lt;a class=&quot;zola-anchor&quot; href=&quot;#how-does-reddify-work&quot; aria-label=&quot;Anchor link to this heading&quot;&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Once a Reddit thread is submitted, Reddify extracts the comments out of the
post and uses the &lt;strong&gt;Spotify Search API&lt;/strong&gt; to narrow down the song and picks the top result.
This idea was born out of my own experiences of finding Spotify’s Search pretty spot on and worked
as well when I tried feeding it Reddit comments.&lt;/p&gt;
&lt;h2 id=&quot;how-to-use-reddify&quot;&gt;How to use Reddify?&lt;a class=&quot;zola-anchor&quot; href=&quot;#how-to-use-reddify&quot; aria-label=&quot;Anchor link to this heading&quot;&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Reddify is archived and is no longer hosted or actively developed. The source code remains
available on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/roopeshvs/reddify&quot;&gt;GitHub&lt;/a&gt; for anyone who wants to run it
themselves — clone the repo and follow the instructions in the README to get it up and running
locally.&lt;/p&gt;
</description>
      </item>
      <item>
          <title>Network Wide Ad Blocking With Pi-hole</title>
          <pubDate>Sat, 02 Mar 2024 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://roope.sh/blog/network-wide-adblocking/</link>
          <guid>https://roope.sh/blog/network-wide-adblocking/</guid>
          <description xml:base="https://roope.sh/blog/network-wide-adblocking/">&lt;p&gt;Today, many of us rely on browser extensions to block annoying ads and tracking while browsing on our computers. They work well for desktop browsers, but what about our mobiles, tablets, smart TVs, and game consoles? They’re left vulnerable to ads because these extensions don’t work on them. We’ve come to accept ads as a necessary evil, but it doesn’t have to be that way.&lt;/p&gt;
&lt;p&gt;With a simple setup using a &lt;strong&gt;Raspberry Pi&lt;/strong&gt; and a tool called &lt;strong&gt;Pi-hole&lt;/strong&gt;, you can block ads and tracking across all devices on your home network. It’s not limited to a Raspberry Pi; you can run it on any machine using Docker, but the Pi is preferred for its low power consumption as it needs to run 24x7.&lt;/p&gt;
&lt;p&gt;My frustration with ads reached its peak while watching a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://www.dota2.com/home&quot;&gt;Dota 2&lt;/a&gt; tournament on the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://twitch.tv&quot;&gt;Twitch&lt;/a&gt; app in my Xbox Series X, where a browser-level or device-level solution for ads do not exist. Every time I switched channels, the same unskippable ad played, and I’d had enough. I remembered hearing about Pi-hole and decided to give it a try with my idle &lt;strong&gt;Raspberry Pi 400&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Installing Pi-hole was straightforward using their one-step automated install guide &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://docs.pi-hole.net/main/basic-install/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pi-hole’s capabilities can be extended by adding more domain blocklists from &lt;strong&gt;Firebog&lt;/strong&gt;, a site that provides lists for blocking ads, tracking, crypto mining, and more &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://firebog.net/&quot;&gt;here&lt;/a&gt;, which I did.&lt;/p&gt;
&lt;p&gt;After installation, I configured Pi-hole as the DNS server for all devices on my home network through my router’s admin page. I also reserved a static IP address for the Raspberry Pi to avoid network interruptions.&lt;/p&gt;
&lt;p&gt;The result? No more ads on Twitch, and when I switched from mobile data to WiFi, Pi-hole blocked ads on websites like &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://cnn.com&quot;&gt;CNN&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pi-hole works by checking each DNS query against its blocklist, allowing or blocking queries accordingly. It’s effective because many sites use separate domains for ads and tracking. However, ads served from the same domains as regular content, like on YouTube, can’t be blocked by Pi-hole.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/roopeshvs/roopeshvs.github.io/blob/main/static/images/pi-hole-dashboard.png?raw=true&quot; alt=&quot;{Pi-hole Dashboard}&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Notice that over one out of ten DNS queries are getting blocked by Pi-hole.&lt;/p&gt;
&lt;p&gt;Now, what about blocking ads and trackers when you’re away from home? You can set up a VPN server on the same Raspberry Pi to enjoy ad-free browsing anywhere. I tried setting up &lt;strong&gt;OpenVPN&lt;/strong&gt;, but my ISP’s &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://en.wikipedia.org/wiki/Carrier-grade_NAT&quot;&gt;Carrier-Grade NAT (CGNAT)&lt;/a&gt; blocked port forwarding. After some research, I found &lt;strong&gt;Tailscale&lt;/strong&gt;, a beautiful and easy solution for connecting to Pi-hole remotely, even behind different NATs. You can read in detail about their NAT Traversal solution &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://tailscale.com/blog/how-nat-traversal-works&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tailscale&lt;/strong&gt; allows devices to connect to Pi-hole from anywhere, and setting it up took just a few minutes with their &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://tailscale.com/kb/1114/pi-hole&quot;&gt;guide&lt;/a&gt;. With my mobile device connected to Tailscale, I was now able to view &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://cnn.com&quot;&gt;CNN’s site&lt;/a&gt; without ads even when I was using my mobile data.&lt;/p&gt;
&lt;p&gt;No more ads or tracking, anywhere! 🎉&lt;/p&gt;
</description>
      </item>
      <item>
          <title>Automatically Scaling Down Lambda Provisioned Concurrency</title>
          <pubDate>Fri, 01 Mar 2024 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://roope.sh/blog/scaling-down-lambda-provisioned-concurrency/</link>
          <guid>https://roope.sh/blog/scaling-down-lambda-provisioned-concurrency/</guid>
          <description xml:base="https://roope.sh/blog/scaling-down-lambda-provisioned-concurrency/">&lt;p&gt;If you are using &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://aws.amazon.com/lambda/&quot;&gt;AWS Lambda&lt;/a&gt; to serve real-time traffic and your Lambda initialization times are high, minimizing response time becomes crucial. One option to achieve this is by utilizing provisioned concurrency. &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://aws.amazon.com/blogs/aws/new-provisioned-concurrency-for-lambda-functions/&quot;&gt;Provisioned Concurrency&lt;/a&gt; refers to the number of pre-initialized execution environments allocated to your Lambda function.&lt;/p&gt;
&lt;p&gt;Maintaining a constant provisioned concurrency capacity of 200 for a 512 MB Lambda for a month can cost approximately &lt;strong&gt;$1,116 USD&lt;/strong&gt;! This cost is in addition to the pricing for requests and duration. AWS offers the option to scale the provisioned concurrency of Lambda based on schedule and demand. We implemented provisioned concurrency scaling for our Lambdas based on the &lt;code&gt;ProvisionedConcurrencyUtilization&lt;/code&gt; metric.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; At the time of writing this, &lt;strong&gt;provisioned concurrency scaling&lt;/strong&gt; is not available from the AWS Management Console but can be configured using APIs, SDKs, or CLIs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of our Lambdas has an initialization time of over 40 seconds as it loads several ML models as pickled Python objects. Without a pre-initialized Lambda instance available, any request coming through the API Gateway would fail due to the API Gateway timeout of 29 seconds.&lt;/p&gt;
&lt;p&gt;We configured the provisioned concurrency capacity to scale from a minimum of 10 to a maximum of 200. This ensures that there are at least 10 pre-initialized Lambda instances ready to serve requests at all times, with the capacity scaling up to 200 pre-initialized instances when necessary. During a quick load test, the provisioned concurrency scaled up effectively, maintaining the Lambda response time at less than 100 ms throughout. Even with 30,000 requests per minute, only about 100 provisioned concurrency capacity was required.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Everything seemed to be working well, but there was a catch:&lt;/strong&gt; While it was reassuring to see the provisioned concurrency scale up to 100 to handle traffic without errors, it did not automatically scale down afterward. This was concerning, and we only noticed it 48 hours later, resulting in unnecessary costs during periods of low or no traffic. To address this, we quickly ran an AWS CLI command in a loop for all Lambdas to bring down their provisioned concurrency to the minimum value:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;aws&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; lambda&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; put-provisioned-concurrency-config&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-function-name&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; generic-function-name&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-qualifier&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; latest&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-provisioned-concurrent-executions&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 10&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why didn’t the provisioned concurrency of the Lambdas automatically scale down when there were no requests?&lt;/strong&gt; Provisioned concurrency scaling works with CloudWatch Alarms managed by AWS when we create the scaling policy. Upon reviewing the alarms, we found that the action to scale down was not triggered because the alarm for low provisioned concurrency utilization never activated due to “insufficient data.” When there are no requests and provisioned concurrency is not being utilized, the alarms transition to the “insufficient data” stat and alarm actions do not get triggered in  this state. There is an option in CloudWatch Alarms to &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html#alarms-and-missing-data&quot;&gt;set how to treat missing data&lt;/a&gt;. Unfortunately, this configuration option is not available when creating an autoscaling policy, only when creating an alarm directly. Also, editing the alarm directly is not recommended by AWS for alarms created with auto-scaling target tracking policies.&lt;/p&gt;
&lt;p&gt;After extensive research, we found limited discussion on this issue online. Broadening our search, we looked for cases where CloudWatch alarms did not trigger due to insufficient data when there were no metrics to report. We came across a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://stackoverflow.com/a/66331752&quot;&gt;Stack Overflow answer&lt;/a&gt; suggesting the use of the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/using-metric-math.html&quot;&gt;FILL function&lt;/a&gt; to create a new metric that returns 0 whenever the original metric returns “insufficient data.” This idea seemed promising.&lt;/p&gt;
&lt;p&gt;We created the following Target Tracking policy JSON to implement this solution:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; target-tracking.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;CustomizedMetricSpecification&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;        &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Metrics&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;                &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Label&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ProvisionedConcurrencyUtilization&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;                &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Id&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;m1&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;                &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;MetricStat&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;                    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Metric&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;                        &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;MetricName&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ProvisionedConcurrencyUtilization&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;                        &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Namespace&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;AWS/Lambda&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;                        &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Dimensions&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                            {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;                                &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;FunctionName&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;                                &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Value&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;generic-function-name&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                            }&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                            {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;                                &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Resource&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;                                &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Value&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;generic-function-name:latest&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                        ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    }&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;                    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Stat&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Maximum&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                }&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;                &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;ReturnData&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;                &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Label&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ProvisionedConcurrencyUtilization where Missing Data = 0&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;                &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Id&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;e1&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;                &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Expression&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;FILL(m1, 0)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;                &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;ReturnData&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;TargetValue&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0.7&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;ScaleOutCooldown&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 60&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;ScaleInCooldown&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 60&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;DisableScaleIn&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We tested the Lambda function using the following &lt;code&gt;put-scaling-policy&lt;/code&gt; AWS CLI command:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;aws&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; application-autoscaling&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; put-scaling-policy&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-service-namespace&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; lambda&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-scalable-dimension&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; lambda:function:ProvisionedConcurrency&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-resource-id&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; generic-function-name&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-policy-name&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; generic-target-tracking-scaling-policy&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-policy-type&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; TargetTrackingScaling&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-target-tracking-scaling-policy-configuration&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; file://target-tracking.json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Upon reviewing AWS CloudWatch Alarms and filtering for &lt;code&gt;generic-function-name&lt;/code&gt;, we were pleased to see the new metric &lt;code&gt;ProvisionedConcurrencyUtilization where Missing Data = 0&lt;/code&gt; populating 0 even when &lt;code&gt;ProvisionedConcurrencyUtilization&lt;/code&gt; returned “insufficient data.” Running another load test, this time waiting for no requests, the alarm triggered as expected, gracefully bringing down the provisioned concurrency capacity.&lt;/p&gt;
&lt;p&gt;With the solution confirmed to be working, we updated our Terraform Lambda module to apply this change to all Lambdas. You can find a snippet of the Terraform resource &lt;code&gt;aws_appautoscaling_policy&lt;/code&gt; &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://gist.github.com/roopeshvs/e2a6d8ca10cf7fe5087f3878e6e08882&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now, even when there are no incoming requests, the CloudWatch alarm for provisioned concurrency autoscaling triggers using the new metric, automatically scaling down and leading to lower cloud bills! 🍻&lt;/p&gt;
</description>
      </item>
      <item>
          <title>How Sherlock knows if you are on Facebook</title>
          <pubDate>Mon, 12 Sep 2022 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://roope.sh/blog/how-sherlock-knows/</link>
          <guid>https://roope.sh/blog/how-sherlock-knows/</guid>
          <description xml:base="https://roope.sh/blog/how-sherlock-knows/">&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/sherlock-project/sherlock&quot;&gt;Sherlock&lt;/a&gt; is a CLI tool that can be used to find usernames across many social networks. In this post, I share how Sherlock was able to overcome a couple of hurdles in reliably finding if a username existed on Facebook. Sherlock works by having &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/sherlock-project/sherlock/blob/master/sherlock_project/resources/data.json&quot;&gt;a JSON file as the source&lt;/a&gt; where a large collection of sites are listed with a few attributes.&lt;/p&gt;
&lt;p&gt;A sample entry from the file looks like this:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Reddit&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;errorMsg&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Sorry, nobody on Reddit goes by that name.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;errorType&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;https://www.reddit.com/user/{}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;urlMain&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;https://www.reddit.com/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each site has its own way of handling usernames that do not exist. To know if a particular username is on the site, Sherlock sends a request to the URL defined in the &lt;code&gt;url&lt;/code&gt; or the &lt;code&gt;urlProbe&lt;/code&gt; field and evaluates the response depending on the type of error defined in the &lt;code&gt;errorType&lt;/code&gt; field. The &lt;code&gt;errorType&lt;/code&gt; field could be one of &lt;code&gt;status_code&lt;/code&gt; - HTTP Response Error Codes, &lt;code&gt;message&lt;/code&gt; - HTTP Response Body that says there is no user by that username or &lt;code&gt;response_url&lt;/code&gt; - Redirecting to a different page if the username is not found, depending on the method appropriate for consistently finding users without false positives.&lt;/p&gt;
&lt;p&gt;Sherlock was using the &lt;code&gt;status_code&lt;/code&gt; error type to figure out if the username existed on Facebook.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Facebook&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;errorType&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;status_code&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;https://www.facebook.com/{}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;urlMain&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;https://www.facebook.com/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/sherlock-project/sherlock/issues/725&quot;&gt;One user raised a GitHub issue&lt;/a&gt; saying Sherlock could not find his username on Facebook. I vividly remembered Sherlock being able to find my username on Facebook.&lt;/p&gt;
&lt;p&gt;Probing into this mystery, I discovered there was a privacy setting on Facebook that disallowed search engines outside of Facebook to link to one’s profile.&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
&lt;a target=&#39;_blank&#39;&gt;&lt;img src=&#39;https://i.postimg.cc/L4rXh8xg/fb.png&#39; border=&#39;0&#39; alt=&#39;fb&#39;/&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Only when the search engines were allowed to link to one’s facebook profile at &lt;code&gt;facebook.com/{username}&lt;/code&gt;, the requests to that profile returned &lt;code&gt;200 OK&lt;/code&gt; otherwise even when a user with that username existed on facebook the response was &lt;code&gt;404 NOT FOUND&lt;/code&gt;. This is why the status code approach was inconsistent.&lt;/p&gt;
&lt;p&gt;One of the trivial ways to bypass this issue would be to login to facebook and then check for the username. However, for Sherlock, authenticating was not an option!&lt;/p&gt;
&lt;p&gt;I wanted to check if there was a profile path Sherlock can reliably use without authenticating that would also bypass the privacy option. With turning on and off the privacy option on my facebook profile, I started testing various paths like &lt;code&gt;/about&lt;/code&gt;, &lt;code&gt;/images&lt;/code&gt;, &lt;code&gt;/photo&lt;/code&gt; and their responses.&lt;/p&gt;
&lt;p&gt;Few minutes into the search I stumbled upon &lt;code&gt;/videos&lt;/code&gt; and I was able to see my expectations come alive. &lt;code&gt;facebook.com/{username}/videos&lt;/code&gt; showed the login page when the username existed and only responded with &lt;code&gt;This page isn&#39;t available&lt;/code&gt; when the username was not yet taken on Facebook. As this path did not care for the search engine privacy option as well, it was perfect!&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/sherlock-project/sherlock/pull/737&quot;&gt;The site resource file was updated to check&lt;/a&gt; for an error message &amp;amp; use the &lt;code&gt;/videos&lt;/code&gt; path and voila! This now allows Sherlock to check if a username is on facebook without authentication and irrespective of the search engine privacy option they’ve opted to go with.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Facebook&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;errorMsg&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;This page isn&amp;#39;t available&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;errorType&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;urlProbe&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;https://www.facebook.com/{}/videos/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A hacky and elementary fix to help Sherlock find your facebook ID.&lt;/p&gt;
</description>
      </item>
    </channel>
</rss>
