Mooformance
I have collected some performance data on bovine and ActivityPub usage statistics. I thought I’ll share them here. Mostly, as I don’t think there are many numbers out there. First, I ran an experiment, I doubted the Moopocalypse, involving two accounts sending “mooooo” back and forth. The result is that bovine can handle at least two activities a second. Second, I measured how many activities my account “@helge@mymath.rocks” sees. The result was one every 13 seconds. This includes useless activities such as Delete or Announces of Like from Lemmy.
Moopocalypse
We will be using two moocows as described here for our performance analysis. Their behavior is simple, if they are mentioned in a post, they reply with a moooo with a random number of o. You can test the behavior with @moocow@mymath.rocks. The first moocow will be on a server called Abel and the second moocow on a server called Banach. The behavior of the moocows implies that if the moocow on Abel sends a moooo to the moocow on Banach mentioning it, the moocow on Banach will mooo back, starting an infinite loop. It is the performance of this infinite loop, we will be testing. Furthermore, I have made no performance modifications to bovine_herd for this. It’s running as one gets it from PyPI with an sqlite3 database.
I should mention here that the setup of Abel and Banach running in docker containers, leads to some networking. I unfortunately don’t have an idea how close to reality it is. At least, I can say that communication is done via HTTP and not HTTPS. However, HTTP signatures are verified and activities are stored in the database.
The code for this can be got from codeberg and one should be able to run it by simply typing
docker compose up
The output should look something like
✔ Container bovine_quickstart-abel-1 Recreated 0.2s
✔ Container bovine_quickstart-banach-1 Recreated 0.2s
✔ Container bovine_quickstart-testrunner-1 Recreated 0.2s
Attaching to bovine_quickstart-abel-1, bovine_quickstart-banach-1, bovine_quickstart-testrunner-1
bovine_quickstart-banach-1 | [2023-06-05 17:05:11 +0000] [8] [INFO] Running on http://0.0.0.0:80 (CTRL + C to quit)
bovine_quickstart-abel-1 | [2023-06-05 17:05:11 +0000] [8] [INFO] Running on http://0.0.0.0:80 (CTRL + C to quit)
bovine_quickstart-testrunner-1 | {
bovine_quickstart-testrunner-1 | "@context": "https://www.w3.org/ns/activitystreams",
bovine_quickstart-testrunner-1 | "type": "Create",
bovine_quickstart-testrunner-1 | "actor": "http://abel/endpoints/Ix_kyce8tMJnYBK7bAzpynGCfVeqtCYyd5UCArSFXA4",
bovine_quickstart-testrunner-1 | "to": [
bovine_quickstart-testrunner-1 | "http://banach/endpoints/rLfdaLhhYNRM9e63_hf_GVlazQbjTXepyzZ3Ia2EbmU"
bovine_quickstart-testrunner-1 | ],
bovine_quickstart-testrunner-1 | "object": {
bovine_quickstart-testrunner-1 | "@context": "https://www.w3.org/ns/activitystreams",
bovine_quickstart-testrunner-1 | "type": "Note",
bovine_quickstart-testrunner-1 | "attributedTo": "http://abel/endpoints/Ix_kyce8tMJnYBK7bAzpynGCfVeqtCYyd5UCArSFXA4",
bovine_quickstart-testrunner-1 | "to": [
bovine_quickstart-testrunner-1 | "http://banach/endpoints/rLfdaLhhYNRM9e63_hf_GVlazQbjTXepyzZ3Ia2EbmU"
bovine_quickstart-testrunner-1 | ],
bovine_quickstart-testrunner-1 | "content": "moooo",
bovine_quickstart-testrunner-1 | "tag": [
bovine_quickstart-testrunner-1 | {
bovine_quickstart-testrunner-1 | "@context": "https://www.w3.org/ns/activitystreams",
bovine_quickstart-testrunner-1 | "type": "Mention",
bovine_quickstart-testrunner-1 | "name": "moocow@banach",
bovine_quickstart-testrunner-1 | "href": "http://banach/endpoints/rLfdaLhhYNRM9e63_hf_GVlazQbjTXepyzZ3Ia2EbmU"
bovine_quickstart-testrunner-1 | }
bovine_quickstart-testrunner-1 | ]
bovine_quickstart-testrunner-1 | }
bovine_quickstart-testrunner-1 | }
bovine_quickstart-testrunner-1 | {
bovine_quickstart-testrunner-1 | "@context": "https://www.w3.org/ns/activitystreams",
bovine_quickstart-testrunner-1 | "first": "http://abel/endpoints/D9mMd74nYEswitwuawFoLYZMRgaSgDfXxq6VP9xZcws?first=1",
bovine_quickstart-testrunner-1 | "id": "http://abel/endpoints/D9mMd74nYEswitwuawFoLYZMRgaSgDfXxq6VP9xZcws",
bovine_quickstart-testrunner-1 | "last": "http://abel/endpoints/D9mMd74nYEswitwuawFoLYZMRgaSgDfXxq6VP9xZcws?last=1",
bovine_quickstart-testrunner-1 | "totalItems": 21,
bovine_quickstart-testrunner-1 | "type": "OrderedCollection"
bovine_quickstart-testrunner-1 | }
bovine_quickstart-testrunner-1 | 10.01180911064148
bovine_quickstart-testrunner-1 | {
bovine_quickstart-testrunner-1 | "@context": "https://www.w3.org/ns/activitystreams",
bovine_quickstart-testrunner-1 | "first": "http://abel/endpoints/D9mMd74nYEswitwuawFoLYZMRgaSgDfXxq6VP9xZcws?first=1",
bovine_quickstart-testrunner-1 | "id": "http://abel/endpoints/D9mMd74nYEswitwuawFoLYZMRgaSgDfXxq6VP9xZcws",
bovine_quickstart-testrunner-1 | "last": "http://abel/endpoints/D9mMd74nYEswitwuawFoLYZMRgaSgDfXxq6VP9xZcws?last=1",
bovine_quickstart-testrunner-1 | "totalItems": 42,
bovine_quickstart-testrunner-1 | "type": "OrderedCollection"
bovine_quickstart-testrunner-1 | }
bovine_quickstart-testrunner-1 | 20.03160572052002
bovine_quickstart-testrunner-1 | {
bovine_quickstart-testrunner-1 | "@context": "https://www.w3.org/ns/activitystreams",
bovine_quickstart-testrunner-1 | "first": "http://abel/endpoints/D9mMd74nYEswitwuawFoLYZMRgaSgDfXxq6VP9xZcws?first=1",
bovine_quickstart-testrunner-1 | "id": "http://abel/endpoints/D9mMd74nYEswitwuawFoLYZMRgaSgDfXxq6VP9xZcws",
bovine_quickstart-testrunner-1 | "last": "http://abel/endpoints/D9mMd74nYEswitwuawFoLYZMRgaSgDfXxq6VP9xZcws?last=1",
bovine_quickstart-testrunner-1 | "totalItems": 65,
bovine_quickstart-testrunner-1 | "type": "OrderedCollection"
bovine_quickstart-testrunner-1 | }
bovine_quickstart-testrunner-1 | 30.09404182434082
One can verify the claim of 2 activities per second from this output.
Observing real load
In order to observer the real load, I again wrote a small script using bovine. The code is:
import asyncio
import bovine
import json
import time
from collections import defaultdict
async def stat():
counts = defaultdict(int)
start_time = time.time()
async with bovine.BovineClient.from_file("bovine_user.toml") as client:
source = await client.event_source()
async for event in source:
if event:
data = json.loads(event.data)
if "type" in data:
type_name = data["type"]
if "object" in data:
if isinstance(data["object"], dict):
if "type" in data["object"]:
type_name += " " + data["object"]["type"]
counts[type_name] += 1
print(time.time() - start_time)
print(json.dumps(counts, indent=2))
if __name__ == "__main__":
asyncio.run(stat())
People knowing python will recognize this as essentially wrapper code to run a counter. The final result was
4289.534234762192
{
"Announce Create": 32,
"Announce Like": 119,
"Create Note": 84,
"Delete": 50,
"Update Question": 6,
"Announce Undo": 8,
"Announce Dislike": 7,
"Delete Tombstone": 8,
"Announce": 13,
"Announce Update": 5,
"Undo Announce": 1,
"Update Note": 2
}
from which one obtains the number of one activity every 13 seconds.