How long does a Mastodon post take to federate between servers?

Recently Stefan Bohacek ran a poll to find out how long a post takes to appear to his followers. A great question!

There is no one speed a post is circulated. When a post is made it enters a queue on sidekiq (below) to be sent out to other servers. Those servers receive that message and it goes in a queue to be processed. At peak times these queues can grow thousands of entries depending on how it is provisioned.

My server,, is hosted on cloudplane at the medium level. It has 2 cpus and 8 sidekiq queues.


The Speed Test

To do my own test I wrote a script (code on github) to get all my followers, get all their servers, make a post and then check each server until that post appears. Because mastodon redirects you back to your server when viewing your own profile I instead generated a unique hashtag and then check to see if the page for that hashtag 404s or 200s.

I ran this code at 7:30 on May 10th. There is no way to know the queue status of other servers but my own servers had empty queues.


My post made it to 251 servers over 240 seconds (4 minutes). The average time for my post to appear on another server was 8.6 seconds.

The top 5 fastest servers (in seconds):

  • 0.959 -
  • 0.985 -
  • 1.045 -
  • 1.068 -
  • 1.069 -

The top 5 slowest servers (in seconds):

  • 240.34 -
  • 54.134 -
  • 51.491 -
  • 18.53 -
  • 17.733 -

Notible servers (in seconds):

  • 3.308 -
  • 6.394 -
  • 5.535 -
  • 11.662 -
  • 4.716 -

see the full results dataset here!

I could imagine this being turned into a bot and report some kind of federation health check. Though this does spam the servers with web requests a bit so it seems like something we can just run occasionally to get a feel for the state of the fediverse.

Overall, federation seems to be pretty speedy!

Tidal track not available for streaming

I kept finding songs on Tidal I had added to my tracks list but were marked as “not available for streaming” but if you went to the artist page the song stil existsed.

For some reason Tidal’s backend seems to generate a lot of new track IDs when they update or need to make some change to a track. Really frusterating.

To fix this I wrote a script to find exact song title matches of songs from the same artist and swap them. Its been working really well for me and you can find it here on github.

React Context Provider Optimization

React createContext is a powerful way to to use react to communicate between components but there are a lot of easy mistakes to be made that will slow down your app. From memoizing everything to separating component with hooks and components with UI.

Yet even if you follow all of those patterns you can still find your app rendering components that could be expensive to render.

This is because providers trigger renders any time they render. It does not matter if a hook or parent triggers them to render or if it passes in a memoized value. If a provider renders it will trigger anything subscribed to it with useContext.

Consider this provider:

  const [activeModal, setActiveModal] = useState<string>('none');
  const show = useCallback((activeModal: string) => setActiveModal(activeModal), []);
  const value = useMemo(() => ({ activeModal, show }), [activeModal, show]);
  return (
  <ModalContext.Provider value={value}>

Above the provider has two values. One to know what the current activeModal is. The other, show, will let components around the appo set a new activeModal.

Because the both values share one prodiver ALL components who access the show function will render when ANY component calls show().

When I learned that dozens of components were getting rendered simply because they wanted the option of possibly calling show() I was a little shocked. Fixing this is not immediately obvious. Memoization can not save you and this might make you start to look at react state libraries. But there is a solution that is architectural.

  const [activeModal, setActiveModal] = useState<string>('none');
  const show = useCallback((activeModal: string) => setActiveModal(activeModal), []);
  return (
  <ModalActionContext.Provider value={show}>
    <ModalContext.Provider value={activeModal}>

With values that change separated from function that do not change now any component can access the show() function and never have it trigger a re-render.

You can play with this below. The red components on the left only use one provider and calling show() or hide() causes all red components to render every time. On the right the green components are broken in two providers and calling show() or hide() only renders the components that need to know the activeModal. It doesn’t even trigger a render on the component with the button!

Play with this code on

Separating the values and functions is a great start and can save many renders around the application. Depending on the data you can think of more ways to separate data. If some values update every time and others only sometime then it might make sense to break those in to separate providers as well.

Most Followed Mastodon Accounts

I’ve launched a new bot / website. Mastodon Most Followed tracks the top 10,000 mastodon accounts. At the moment to make the list you only need over 500 followers to make the list. The list is compiled though monitoring the federated timeline of as well as any mention accounts.


Tidal Music Metadata Csv Export

I never trust these music services to remember what music I love. Tidal doesn’t have an easy way to export this. I just want a CSV file to keep so Tidal doesn’t own the music I have found.

So here is a small repo you can clone to run against your Tidal and pull your favorites out.

# You need to install tidalapi using pip install tidalapi (more can be found here
# had to be fixed with

import csv
import sys
import pprint

import tidalapi

session = tidalapi.Session()
favorites = tidalapi.Favorites(session,

# Get Tracks
open('../tracks.csv', 'w').close()
f = open("../tracks.csv", "a")


getMore = True
limit = 1000
offset = 0

while getMore == True:

    tracks = favorites.tracks(limit, offset);
    offset += limit;
    if len(tracks) == 0:
        getMore = False

    for track in tracks:
        f.write(','.join([,,]) + '\n')

# Get Artists
open('../artists.csv', 'w').close()
f = open("../artists.csv", "a")


getMore = True
limit = 1000
offset = 0

while getMore == True:

    artists = favorites.artists(limit, offset);
    offset += limit;
    if len(artists) == 0:
        getMore = False

    for artist in artists:
        f.write( + '\n')

# Get Album
open('../albums.csv', 'w').close()
f = open("../albums.csv", "a")


getMore = True
limit = 1000
offset = 0

while getMore == True:

    albums = favorites.albums(limit, offset);
    offset += limit;
    if len(albums) == 0:
        getMore = False

    for album in albums:
        f.write(','.join([,]) + '\n')