If your likes/follows/views tables are hitting any of these, this post is for you:
- Shard by user? Counting likes on an item scatters across shards.
- Shard by item? Listing a user's likes scatters instead.
- Cache invalidation logic that keeps growing.
- Reverse lookups and counts becoming a consistency problem.
Actionbase doesn't solve all database problems. But scaling "who did what to which target" tables — likes, views, follows, wishlists, subscriptions — is exactly what it was built for. It's been serving 1M+ requests/min in production at Kakao.
This post shows how a likes table maps to Actionbase's edge model — and where it fits next to Postgres/MySQL.
If you haven't hit the wall yet, stick with your relational DB. A table, some indexes, a cache — that works for a long time.
From a Likes Table to an Edge
CREATE TABLE user_likes (
user_id BIGINT NOT NULL,
item_id BIGINT NOT NULL,
created_at TIMESTAMP NOT NULL,
PRIMARY KEY (user_id, item_id)
);
CREATE INDEX idx_user ON user_likes (user_id, created_at DESC);
CREATE INDEX idx_item ON user_likes (item_id, created_at DESC);
In Actionbase, the same data is an edge — who did what to which target:
table: user_likes # (what)
source: LONG # user_id (who)
target: LONG # item_id (target)
properties:
created_at: LONG # epoch millis
indexes:
- recent # created_at DESC
100 → like → 200 (created_at: 1707300000)
This is a unique edge use case: one user, one like, one item. The edge key is (source, target).
Designing Writes for Reads
In a relational database, you serve new read patterns by adding indexes, caches, and tuning queries as the workload evolves.
Actionbase does it upfront: at write time, materialize the read-optimized structures you'll need for GET/COUNT/SCAN.
One write updates the edge, reverse lookup, counts, and sort indexes. Reads become lookups.
OUT = user → items, IN = item → users
SQL → Actionbase
Edge lookup
SELECT * FROM likes
WHERE user_id=100 AND item_id=200
GET source=100, target=200
List (forward / reverse)
SELECT * FROM likes
WHERE user_id=100
ORDER BY created_at DESC
SELECT * FROM likes
WHERE item_id=200
ORDER BY created_at DESC
SCAN start=100, direction=OUT, index=recent
SCAN start=200, direction=IN, index=recent
Count
SELECT COUNT(*) FROM likes
WHERE user_id=100
SELECT COUNT(*) FROM likes
WHERE item_id=200
COUNT start=100, direction=OUT
COUNT start=200, direction=IN
No aggregation. No cache. No scatter queries across shards.
What Moves, What Stays
Domain data — user profiles, product catalogs, orders — stays in Postgres/MySQL. The high-volume interaction tables move to Actionbase. Start with one table: the one causing the most pain. At Kakao, that was the Gift wish list.
Under the hood, Actionbase currently runs on HBase. A lighter alternative backed by SlateDB is in progress.
GitHub: kakao/actionbase

Top comments (0)