This action will delete this post on this instance and on all federated instances, and it cannot be undone. Are you certain you want to delete this post?
This action will delete this post on this instance and on all federated instances, and it cannot be undone. Are you certain you want to delete this post?
This action will block this actor and hide all of their past and future posts. Are you certain you want to block this actor?
This action will block this object. Are you certain you want to block this object?
Are you sure you want to delete the OAuth client [Client Name]? This action cannot be undone and will revoke all access tokens for this client.
Are you sure you want to revoke the OAuth token [Token ID]? This action cannot be undone and will immediately revoke access for this token.
#ktistec 178 hashtags


When you optimize a Crystal program, pay attention to language features that inline code. For example, pay attention to how you use blocks (consequences here and here).
Also pay attention to how you use macros. Macros, like ECR.embed and Slang.embed, inline code at the point where they are invoked. This can be powerful, because macros actually generate code—but, ten invocations later, you have ten copies of the code.
Here's a case of too many copies, but with a very happy ending...
Ktistec uses both ECR.embed and Slang.embed to generate web pages from views and partials. I wrote code to count the number of places Ktistec used embed for each view and partial it renders. There's a long tail, but here are the big ones:
| src/views/layouts/default.html.ecr | 204 | | src/views/partials/modals.html.slang | 204 | | src/views/partials/header.html.slang | 204 | | src/views/partials/footer.html.slang | 204 | | src/views/pages/generic.html.slang | 155 | | src/views/partials/object/label.html.slang | 36 | | src/views/partials/object/content.html.slang | 36 | | src/views/partials/collection.json.ecr | 28 | | src/views/partials/thread.html.slang | 12 | | src/views/partials/detail.html.slang | 12 | | src/views/partials/object.html.slang | 12 | | src/views/partials/actor-panel.html.slang | 11 | | src/views/partials/object.json.ecr | 11 | | src/views/partials/paginator.html.slang | 11 | | src/views/objects/thread.json.ecr | 8 | | src/views/partials/activity/label.html.slang | 6 | | src/views/mentions/index.json.ecr | 6 | | src/views/remote_follows/index.json.ecr | 6 | | src/views/settings/settings.json.ecr | 6 | | src/views/tags/index.json.ecr | 6 | | src/views/activities/activity.json.ecr | 5 | | src/views/partials/editor.html.slang | 5 | | src/views/objects/object.json.ecr | 5 | | src/views/actors/remote.json.ecr | 4 | ...
The layout is part of every page and is rendered with every view, so lots of copies. Every page has a header and a footer (and some default modal dialogs) so you get those, too. The generic view is a little less obvious. It's used to render pages for which there is no more specific view—typically pages served for 400 Bad Request or 401 Unauthorized. Objects (posts) are rendered in a variety of contexts, so it's no surprise label.html.slang and content.html.slang are popular.
ECR.embed and Slang.embed inline templates at the point where they are invoked, but beyond that they don't really customize the generated code—they just duplicate it. What we want is one function for each view or partial, which wraps embed and returns JSON or HTML.
Those changes mostly occur in commits from 399287cf to 4b025f50. To say that they made a huge difference is a gross understatement. Executable size decreased by ~13%. Build time decreased by ~50%, and the memory required to build decreased by ~30%.

I just released v2.0.0-11 of ktistec. This release picks up a few small improvements I wanted to get into v2.0.0.

I just released v2.0.0-10 of ktistec. I expect this to be the last pre-release before releasing v2.0.0.
As ironic as it sounds, the Fediverse doesn't feel very federated. ActivityPub, in particular, doesn't account for the real topology of the Fediverse—large groups of users clustered together on large server instances. (Or maybe it does, and this is a feature, not a bug!) Exchanges are largely actor to actor, and large servers create the illusion of "a Fediverse" by pooling their local actors' aggregate inbound and outbound activity.
The consequence of this is that running a single-user instance can feel lonely.
This release finally tries to address that. Hashtags and threads are the backbone of expressed interests and conversations in the Fediverse. Ktistec now lets you follow hashtags and threads, and will proactively (but gently) pull relevant content in to your server. Most of the changes in the last year revolve around making this work well.
The rest of the changes are less visible:
You can see all of the changes here.
(So that it's clear, I have a massive amount of respect for anyone who builds software and gives it away for free. None of the decisions I've made with Ktistec should be taken as personal criticism of anyone else in this space!)

my current canary for build resource utilization is a low end cloud server. when builds start to fail it's time to optimize.
more on the last round of build optimizations for ktistec, shortly.

The Cost of Small Methods
Ktistec uses a template engine† for it's views.
View templates are transformed into Crystal code that generates HTML when executed. As you'd expect, the template language allows you to use string interpolation syntax (e.g. #{expression}) for dynamic values.
To ensure expression is only evaluated once, and to limit the scope of the temporary variable holding the evaluated value of expression, I originally bound the value to the variable using Object#tap (commit 5e1bf19e). The generated code looked something like:
(expression).tap do |__value__| <template code that uses expression> end
Blocks in Crystal are always inlined, so the code above should be equivalent to the following (sacrificing local scope):
__value__ = (expression) <template code that uses expression>
Functionally, they are equivalent. But operationally, not so much! With Object#tap, the Ktistec executable is about 1% larger (36823603 bytes vs. 36526307 bytes) and build times take 20% longer (23 seconds vs. 19 seconds, generally).
In total, view templates represent about 6% of the Ktistec executable by size, so it doesn't surprise me that there's a measurable impact when I make changes to the template engine, but wow...! I can almost live with the size of the executable, but the build time...!
The cost has to be the method call.
What I'm looking for is something like let in Scheme. The following macro comes close, but doesn't limit scope quite the same way:
macro let(expr, &block)
{{block.args.first}} = ({{expr}})
{{block.body}}
endI maybe have to live with the macro—I tried to implement let as a method with the annotation @[AlwaysInline] but there was no improvement over the original.
†The template engine is a fork of Slang—which I've been evolving to be more Slim-compatible.

I replaced five indexes* on the relationships table with two**, improved query performance in at least one case, and cut the size of the database down by 11.4% (98MB).
Lessons (finally) learned:
#ktistec #sqlite #optimization
* The original five:
CREATE INDEX idx_relationships_type_from_iri_created_at
ON relationships (type ASC, from_iri ASC, created_at DESC);
CREATE INDEX idx_relationships_from_iri_created_at_type
ON relationships (from_iri ASC, created_at DESC, type ASC);
CREATE INDEX idx_relationships_type_to_iri
ON relationships (type ASC, to_iri ASC);
CREATE INDEX idx_relationships_to_iri_type
ON relationships (to_iri ASC, type ASC);
CREATE INDEX idx_relationships_type_id
ON relationships (type ASC, id ASC);
* The final two:
CREATE INDEX idx_relationships_type
ON relationships (type ASC);
CREATE INDEX idx_relationships_to_iri
ON relationships (to_iri ASC);
pushing a boatload of small improvements and fixes to main that i've been running myself for the last couple weeks... there are many ways a request to another activity pub server can fail—ktistec does a much better job of logging those failures, among other things...

i added code to log slow queries in ktistec and it's already paying dividends. most are obviously missing indexes and it's great to fix them, but the latest example—which is missing an index—is querying a table that only has one row (in my single user instance). should that table need an index on that column? i mean, just return that row...
fwiw, a slow query is currently anything that takes longer than 50msec. i wonder if that is tight enough...?

one thing ktistec related that i haven't had the time for is working on build and deployment tools. there are a bunch of outstanding requests—and a few PRs—for docker builds, packaged deployments for various hosting environments, etc.
if you're interested in contributing, let me know. you only have to agree to maintain them—i won't be able to.