<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Filippo's blog]]></title><description><![CDATA[Filippo's blog]]></description><link>https://blog.neysofu.me</link><generator>RSS for Node</generator><lastBuildDate>Sat, 05 Sep 2026 23:13:39 GMT</lastBuildDate><atom:link href="https://blog.neysofu.me/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Constructive Specifications: A Short Story]]></title><description><![CDATA[specificationnounspec·​i·​fi·​ca·​tion (spe-sə-fə-ˈkā-shən)

The act or process of specifying.

A detailed precise presentation of something or of a plan or proposal for something.


Alice is a smart programmer. She has created a programming language...]]></description><link>https://blog.neysofu.me/constructive-specifications-a-short-story</link><guid isPermaLink="true">https://blog.neysofu.me/constructive-specifications-a-short-story</guid><category><![CDATA[programming languages]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[compiler]]></category><dc:creator><![CDATA[Filippo @neysofu]]></dc:creator><pubDate>Wed, 16 Aug 2023 20:45:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/5cFwQ-WMcJU/upload/v1666479834699/Mx44DlLF0.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>specification</strong><br /><em>noun</em><br /><em>spec·​i·​fi·​ca·​tion (spe-sə-fə-ˈkā-shən)</em></p>
<ol>
<li><p>The act or process of specifying.</p>
</li>
<li><p>A detailed precise presentation of something or of a plan or proposal for something.</p>
</li>
</ol>
<p>Alice is a smart programmer. She has created a programming language called Carta. Other smart programmers have taken an interest in Carta, and they've helped Alice improve the language and its implementation. Then, other smart programmers have played with Carta, and some even started to build things with it. Alice is happy.</p>
<p>Bob is a smart programmer. He has learnt Carta and he likes it, but he wants to build a different implementation. Bob starts building, but he complains to Alice that he doesn't have a specification to work with. "Sure you do, Bob" says Alice. "It's called cartac -its reference implementation- and you've been using it all along." Bob is disappointed. "That's not what I meant. It is a piece of software, not a specification, and it is riddled with bugs."</p>
<p>Alice is a smart programmer, and she knows that language specifications are a legacy from the dark ages of proprietary compilers, when languages didn't have open-source, high-quality, reference implementations. "But Bob, cartac is indeed a specification. In fact, it is a better specification than a .pdf or .html document could possibly be. Its semantics are both human- and machine-readable, respectively as code and as executable. It is constructive, meaning that skeptics will know that implementing Carta is possible. It can not be misinterpreted, as code is precise and exact. It has bugs, though tests can be written and patches can be developed. It will never have the nasty bugs that a natural-language specification has - ambiguities, and contradictions. It is not ambiguous, for software never is. It is never contradictory, for its code would refuse to compile if it were. When bugs are discovered and underspecifications are discovered, we discuss amongst ourselves and develop a patch, just like you would do for a natural-language specification."</p>
<p>Bob is annoyed at this nonsense, but he stops to think for a moment. "Uh, I think I get it." he says. Bob realizes cartac is a constructive specification, and Bob is happy.</p>
]]></content:encoded></item><item><title><![CDATA[Predicting Twitter IDs and self-quoting tweets]]></title><description><![CDATA[⚠️ this blog post is a draft, and requires editing.
Much like recursion, self-reference is a neat concept that never fails to tickle hackers and tinkerers. In particular, it's somewhat common practice to show off technical skills by crafting self-ref...]]></description><link>https://blog.neysofu.me/predicting-twitter-ids</link><guid isPermaLink="true">https://blog.neysofu.me/predicting-twitter-ids</guid><category><![CDATA[Twitter]]></category><category><![CDATA[Python 3]]></category><category><![CDATA[Math]]></category><dc:creator><![CDATA[Filippo @neysofu]]></dc:creator><pubDate>Fri, 04 Dec 2020 01:21:21 GMT</pubDate><content:encoded><![CDATA[<p>⚠️ this blog post is a draft, and requires editing.</p>
<p>Much like recursion, <a target="_blank" href="https://en.wikipedia.org/wiki/Self-reference">self-reference</a> is a neat concept that never fails to tickle hackers and tinkerers. In particular, it's somewhat common practice to show off technical skills by crafting self-referential content on web platforms: this is most often done by abusing APIs in entertaining ways. YouTube is a great example of this:</p>
<ol>
<li><a target="_blank" href="https://www.youtube.com/watch?v=BxV14h0kFs0">This Video Has 26,974,620 Views
</a></li>
<li><a target="_blank" href="https://www.youtube.com/watch?v=D6L__oR1O9A">YouTube URL in the video</a></li>
</ol>
<p>As of Twitter, Diomidis Spinellis posted the first ever self-referential (<em>s.r.</em>) Tweet back in 2009.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/selfrefer/status/3128391843?s=20">https://twitter.com/selfrefer/status/3128391843?s=20</a></div>
<p>Anecdotically, the tweet caused some issues as the Twitter codebase was engineered around retweets as DAGs.</p>
<p>What makes s.r. Tweets fundamentally hard is that there's no way at all to edit a Tweet after creating it. No API quirks. No workarounds. The only way you're ever gonna get a s.r. Tweet is if you reverse-engineer Twitter's ID generation system. By comparing IDs of many different Tweets based on their timestamps, Diomidis has a good estimate of the ID burn rate. He wrote <a target="_blank" href="https://www.spinellis.gr/blog/20090805/">a blog post</a> to describe his approach.</p>
<p>Mind you, this was in 2009, when Twitter used auto-incremented 32-bit IDs on MySql. Things have gotten harder.</p>
<p>The bad news is that Twitter's exponential growth in popularity has forced engineers to increase the ID generation space to 64 bits and to develop a custom ID generation service called Snowflake, which is way more complicated than auto-increment. The good news is that Twitter open-sourced <a target="_blank" href="https://blog.twitter.com/engineering/en_us/a/2010/announcing-snowflake.html">Snowflake</a>, which makes my work so much easier. Snowflake logically partitions the 64-bit space into four separate pieces of information as follows:</p>
<pre><code class="lang-systemverilog">// SystemVerilog description of Twitter Snowflakes.

typedef struct packed {
    // Reserved for future uses.
    bit reserved;
    // Timestamp with millisecond-precision and
    // Twitter-specific epoch (1288834974657).
    bit [41:0] timestamp;
    bit [5:0] datacenter_id;
    bit [5:0] datacenter_worker_id;
    // Autoincremented ID for same-millisecond Tweets.
    bit [12:0] sequence_id;
} snowflake;
</code></pre>
<p>Predicting Snowflakes requires that you guess correctly every single one of the four fields. I will use a script to post Tweets at short intervals and possibly use the collected data to iteratively refine my predictions. The script will stop once we have correctly predicted a Snowflake.</p>
<p>Time to write some utilities for working with Snowflake.</p>
<pre><code class="lang-python">TWITTER_EPOCH_MILLIS = <span class="hljs-number">1288834974657</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Snowflake</span>(<span class="hljs-params">NamedTuple</span>):</span>
    dtime: datetime
    worker_id: int
    seq_number: int

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__repr__</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">return</span> <span class="hljs-string">'Snowflake&lt;{}/{}/{}/{}&gt;'</span>.format(self.twitter_timestamp(), self.worker_id &gt;&gt; <span class="hljs-number">5</span>, self.worker_id &amp; <span class="hljs-number">0b11111</span>, self.seq_number)

<span class="hljs-meta">    @classmethod</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">from_id</span>(<span class="hljs-params">cls, id: int</span>):</span>
        <span class="hljs-comment"># 12 bits with offset 0.</span>
        seq_number = id &amp; (<span class="hljs-number">2</span>**<span class="hljs-number">12</span><span class="hljs-number">-1</span>)
        <span class="hljs-comment"># 10 bits (5 datacenter, 5 actual worker id) with offset 12.</span>
        worker_id = (id &gt;&gt; <span class="hljs-number">12</span>) &amp; (<span class="hljs-number">2</span>**<span class="hljs-number">10</span><span class="hljs-number">-1</span>)
        <span class="hljs-comment"># 41 bits (1 reserved for future purposes according to spec) with offset 22.</span>
        timestamp = ((id &gt;&gt; <span class="hljs-number">22</span>) + TWITTER_EPOCH_MILLIS) / <span class="hljs-number">1000</span>
        dtime = datetime.fromtimestamp(timestamp)
        <span class="hljs-keyword">return</span> Snowflake(dtime, worker_id, seq_number)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">to_id</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">return</span> (self.twitter_timestamp() &lt;&lt; <span class="hljs-number">22</span>) | (self.worker_id &lt;&lt; <span class="hljs-number">12</span>) | (self.seq_number)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">twitter_timestamp</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">return</span> int(round(self.dtime.timestamp() * <span class="hljs-number">1000</span> - TWITTER_EPOCH_MILLIS))

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">status_url</span>(<span class="hljs-params">self, username</span>):</span>
        <span class="hljs-keyword">return</span> <span class="hljs-string">'https://twitter.com/{}/{}'</span>.format(username, self.to_id())
</code></pre>
<p><code>Snowflake.from_id</code> allows us to reverse-engineer the Snowflake primitives from raw Tweet IDs. I then posted a bunch of Tweets to collect data.</p>
<p>The Snowflake model reserves 12 bits for a "sequence ID", which is auto-incremented whenever the server must generate two IDs at the same timestamp (which, at Twitter's scale, happens quite often). Intuitively, seq. IDs are <em>much</em> more predictable during low-traffic hours, when you're unlikely to compete with other users for low single-digit seq. IDs.</p>
<p><img src="https://media.sproutsocial.com/uploads/2020/03/BTP-2020-Twitter-Heatmap-Global.png" alt="Twitter traffic by hour and day" /></p>
<p>Clearly it's easier to predict auto-incremented IDs when traffic is low, so I ran my script at night.</p>
<pre><code class="lang-txt"># Incidence rate of sequence IDs. Data collected over 274 Tweets.

0:      ++++++++++++++++++++++++++++++ 29.56%
1:      ++++++++++++++++++++++++ 24.45%
2:      ++++++++++++++++++ 18.25%
3:      ++++++++ 7.66%
4:      +++++ 5.11%
5:      +++ 2.92%
6:      ++++ 4.38%
7:      ++ 2.19%
8:      ++ 1.82%
9:      ++ 1.82%
&gt;= 10:  ++ 1.82%
</code></pre>
<p>As one would assume, sequence IDs. By guessing <code>Seq ID 0</code> every time, I would guess it right ~29.56% of the time. I've thought about looking for correlation between worker IDs and sequence IDs, but load balancers most probably remove any correlation whatsoever. There's surely some correlation between data center ID and sequence ID, but a VPN I wouldn't be able to choose what data center my request hits. In the end, it seemed like 29.56% was the best hit rate I could get on sequence IDs.</p>
<p>Data center and worker IDs</p>
<p>All my requests would get the same data center ID (11) with over 99% accuracy. Load balancing across intra- data center workers, however, complicates things. I wasn't able to find any pattern in the worker IDs generation, so I simply chose the most common. That gave a hit rate of 8%. Ouch.</p>
<h1 id="heading-on-latency">On latency</h1>
<p>Finally, I had to reliably predict at what exact timestamp the Twitter servers would generate the ID of my Tweet. This is, in and itself, not hard at all. Network latency is quite predictable. However, I was facing three problems:</p>
<ul>
<li>300/daily rate limiting. Not nearly enough for a serious statical analysis.</li>
<li>Millisecond-precision.</li>
<li>No apparent correlation between latency and worker IDs. This is expected, as workers in the same data center share low-latency network connections with each other.</li>
</ul>
<p>I decided to keep track of the last N requests and track the round-to-id time delta.</p>
<p>At a first glace you might think that taking the average would be a good idea. I did too. Unfortunately, the expected value is arguably the <em>worst</em> choices for this use case. The data is right-skewed and has quite a few outliers. The median scores better and achieves a hit rate of 6.93%, compared to the abysimal 1.09% of the mean. I thought that it was enough. Just wait it out, right?</p>
<p>Not quite. As I've stated before, 300 hits are very, very few.</p>
<p>Can we do better?</p>
<p><em>Say hello to the log-normal distribution.</em></p>
<p>The log-normal distribution is well known in the industry to realistically describe server response times under consistent load. We have good reasons to assume log-normality and </p>
<p>This is what I finally came up with:</p>
<pre><code class="lang-python">
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">predict_snowflake</span>(<span class="hljs-params">now: datetime, history: List[AttemptRecord]</span>) -&gt; Snowflake:</span>
    <span class="hljs-keyword">if</span> len(history) == <span class="hljs-number">0</span>:
        <span class="hljs-keyword">return</span> Snowflake(now, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>)
    <span class="hljs-keyword">if</span> len(history) &lt; <span class="hljs-number">3</span>:
        ping = statistics.median([attempt.actual.dtime.timestamp() -
                                  attempt.dtime.timestamp() <span class="hljs-keyword">for</span> attempt <span class="hljs-keyword">in</span> history])
    <span class="hljs-keyword">else</span>:
        log_ping_times = [math.log(attempt.actual.dtime.timestamp() -
                                   attempt.dtime.timestamp()) <span class="hljs-keyword">for</span> attempt <span class="hljs-keyword">in</span> history]
        lognormal_mu = statistics.mean(log_ping_times)
        lognormal_sigma = statistics.stdev(log_ping_times)
        ping = math.e ** (lognormal_mu - lognormal_sigma**<span class="hljs-number">2</span>)
    most_popular_workers = Counter(
        [attempt.actual.worker_id <span class="hljs-keyword">for</span> attempt <span class="hljs-keyword">in</span> history]).most_common()
    <span class="hljs-comment"># We always try with `seq_number == 0`.</span>
    <span class="hljs-keyword">return</span> Snowflake(now + timedelta(seconds=ping), most_popular_workers[<span class="hljs-number">0</span>][<span class="hljs-number">0</span>], <span class="hljs-number">0</span>)
</code></pre>
<h2 id="heading-about-the-sequence-id">About the sequence ID</h2>
<p>The Snowflake model reserves 12 bits for a "sequence ID", which is auto-incremented whenever the server must generate two IDs at the same timestamp (which, at Twitter's scale, happens quite often).</p>
<p>At last, here it is:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/neysofu/status/1333211190017855488">https://twitter.com/neysofu/status/1333211190017855488</a></div>
<p>Cute.</p>
]]></content:encoded></item></channel></rss>