tinyurl

package module
v0.0.0-...-9e9e9bf Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 25, 2025 License: MIT Imports: 18 Imported by: 0

README

TinyURL Service

Go Report Card

URL shortener service with multiple users. Users can list their URLs. Short URLs are base62 encoded 8 bytes. This simple version has 4 bytes for a user ID and 4 bytes for a URL ID. It can be tuned up or down based on expected number of users or URLs per user. If there were no requirement to be able to list URLs for a user then user id could be removed completely from the system. Here short URLs have user ID inside them so it is possible to route GetShortUrl requests to corresponding shards based on that user ID. Also GetShortUrl reads from followers (eventually consistent) to increase read throughput of the system.

Diagram

Application cores

There are two application cores:

  • Users in users.go. Sharded by user id.
    • GetUser
  • ShortUrls in shorturls.go. Sharded by user id.
    • GetShortUrl (reads from followers are allowed)
    • ListShortUrls
    • CreateShortUrl

Take a look at tests (users_test.go, shorturls_test.go).

Cluster config

Pregenerated cluster config cluster_config.json has:

  • 3 nodes
  • 16 shards of ShortUrls
  • 4 shards of Users
  • 3 replicas of each

How to run

  1. Clone this repository.
git clone git@github.com:evrblk/monstera-example.git

cd ./monstera-example/tinyurl
  1. Make sure it builds:
go build -v ./...
  1. Start a cluster with 3 nodes and a gateway server:
go tool github.com/mattn/goreman start
  1. Create 100 users:
go run ./cmd/dev seed-users
  1. Pick any user id from the previous step output.

  2. Run test scenario 1 which creates a short URL for the user id:

go run ./cmd/dev scenario-1 --user-id=9fff3bf7d1f9561d

How to explore

For example, you want to understand how CreateShortUrl method works:

  • Start reading from server.go in TinyUrlServiceApiServer.CreateShortUrl().
  • Trace it down to monstera.MonsteraClient calls.
  • Optional: You can jump further if you want to read Monstera sources.
  • Find ShortUrlsCoreAdapter.Update() and find CreateShortUrl there.
  • Trace it down to ShortUrlsCore.CreateShortUrl().
  • Understand how simple the code is and how it takes advantage of sequential application of updates.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidId = errors.New("invalid id")
)

Functions

func DecodeShortUrlId

func DecodeShortUrlId(s string) (*corepb.ShortUrlId, error)

func DecodeUserId

func DecodeUserId(s string) (uint32, error)

func EncodeShortUrlId

func EncodeShortUrlId(id *corepb.ShortUrlId) string

func EncodeUserId

func EncodeUserId(id uint32) string

Types

type ShardKeyCalculator

type ShardKeyCalculator struct{}

func (ShardKeyCalculator) CreateShortUrlShardKey

func (s ShardKeyCalculator) CreateShortUrlShardKey(request *corepb.CreateShortUrlRequest) []byte

func (ShardKeyCalculator) CreateUserShardKey

func (s ShardKeyCalculator) CreateUserShardKey(request *corepb.CreateUserRequest) []byte

func (ShardKeyCalculator) GetShortUrlShardKey

func (s ShardKeyCalculator) GetShortUrlShardKey(request *corepb.GetShortUrlRequest) []byte

func (ShardKeyCalculator) GetUserShardKey

func (s ShardKeyCalculator) GetUserShardKey(request *corepb.GetUserRequest) []byte

func (ShardKeyCalculator) ListShortUrlsShardKey

func (s ShardKeyCalculator) ListShortUrlsShardKey(request *corepb.ListShortUrlsRequest) []byte

type ShortUrlsCore

type ShortUrlsCore struct {
	// contains filtered or unexported fields
}

func NewShortUrlsCore

func NewShortUrlsCore(badgerStore *monstera.BadgerStore, shardLowerBound []byte, shardUpperBound []byte) *ShortUrlsCore

func (*ShortUrlsCore) Close

func (c *ShortUrlsCore) Close()

func (*ShortUrlsCore) CreateShortUrl

func (*ShortUrlsCore) GetShortUrl

func (*ShortUrlsCore) ListShortUrls

func (*ShortUrlsCore) Restore

func (c *ShortUrlsCore) Restore(reader io.ReadCloser) error

func (*ShortUrlsCore) Snapshot

type ShortUrlsCoreAdapter

type ShortUrlsCoreAdapter struct {
	// contains filtered or unexported fields
}

func NewShortUrlsCoreAdapter

func NewShortUrlsCoreAdapter(shortUrlsCore ShortUrlsCoreApi) *ShortUrlsCoreAdapter

func (*ShortUrlsCoreAdapter) Close

func (a *ShortUrlsCoreAdapter) Close()

func (*ShortUrlsCoreAdapter) Read

func (a *ShortUrlsCoreAdapter) Read(request []byte) []byte

func (*ShortUrlsCoreAdapter) Restore

func (a *ShortUrlsCoreAdapter) Restore(r io.ReadCloser) error

func (*ShortUrlsCoreAdapter) Snapshot

func (*ShortUrlsCoreAdapter) Update

func (a *ShortUrlsCoreAdapter) Update(request []byte) []byte

type ShortUrlsCoreApi

type ShortUrlsCoreApi interface {
	Snapshot() monstera.ApplicationCoreSnapshot
	Restore(reader io.ReadCloser) error
	Close()
	GetShortUrl(request *corepb.GetShortUrlRequest) (*corepb.GetShortUrlResponse, error)
	ListShortUrls(request *corepb.ListShortUrlsRequest) (*corepb.ListShortUrlsResponse, error)
	CreateShortUrl(request *corepb.CreateShortUrlRequest) (*corepb.CreateShortUrlResponse, error)
}

type TinyUrlServiceApiServer

type TinyUrlServiceApiServer struct {
	gatewaypb.UnimplementedTinyUrlServiceApiServer
	// contains filtered or unexported fields
}

func NewTinyUrlServiceApiServer

func NewTinyUrlServiceApiServer(coreApiClient TinyUrlServiceCoreApi) *TinyUrlServiceApiServer

func (*TinyUrlServiceApiServer) Close

func (s *TinyUrlServiceApiServer) Close()

func (*TinyUrlServiceApiServer) CreateShortUrl

func (*TinyUrlServiceApiServer) GetShortUrl

func (*TinyUrlServiceApiServer) GetUser

type TinyUrlServiceCoreApiMonsteraStub

type TinyUrlServiceCoreApiMonsteraStub struct {
	// contains filtered or unexported fields
}

func NewTinyUrlServiceCoreApiMonsteraStub

func NewTinyUrlServiceCoreApiMonsteraStub(monsteraClient *monstera.MonsteraClient, shardKeyCalculator TinyUrlServiceMonsteraShardKeyCalculator) *TinyUrlServiceCoreApiMonsteraStub

func (*TinyUrlServiceCoreApiMonsteraStub) CreateShortUrl

func (*TinyUrlServiceCoreApiMonsteraStub) CreateUser

func (*TinyUrlServiceCoreApiMonsteraStub) GetShortUrl

func (*TinyUrlServiceCoreApiMonsteraStub) GetUser

func (*TinyUrlServiceCoreApiMonsteraStub) ListShortUrls

type TinyUrlServiceCoreApiStandaloneStub

type TinyUrlServiceCoreApiStandaloneStub struct {
	// contains filtered or unexported fields
}

func NewTinyUrlServiceCoreApiStandaloneStub

func NewTinyUrlServiceCoreApiStandaloneStub(shortUrlsCore ShortUrlsCoreApi, usersCore UsersCoreApi) *TinyUrlServiceCoreApiStandaloneStub

func (*TinyUrlServiceCoreApiStandaloneStub) CreateShortUrl

func (*TinyUrlServiceCoreApiStandaloneStub) CreateUser

func (*TinyUrlServiceCoreApiStandaloneStub) GetShortUrl

func (*TinyUrlServiceCoreApiStandaloneStub) GetUser

func (*TinyUrlServiceCoreApiStandaloneStub) ListShortUrls

type TinyUrlServiceMonsteraShardKeyCalculator

type TinyUrlServiceMonsteraShardKeyCalculator interface {
	GetShortUrlShardKey(request *corepb.GetShortUrlRequest) []byte
	ListShortUrlsShardKey(request *corepb.ListShortUrlsRequest) []byte
	CreateShortUrlShardKey(request *corepb.CreateShortUrlRequest) []byte

	GetUserShardKey(request *corepb.GetUserRequest) []byte
	CreateUserShardKey(request *corepb.CreateUserRequest) []byte
}

type UnimplementedTinyUrlServiceCoreApi

type UnimplementedTinyUrlServiceCoreApi struct{}

func (*UnimplementedTinyUrlServiceCoreApi) CreateShortUrl

func (*UnimplementedTinyUrlServiceCoreApi) CreateUser

func (*UnimplementedTinyUrlServiceCoreApi) GetShortUrl

func (*UnimplementedTinyUrlServiceCoreApi) GetUser

func (*UnimplementedTinyUrlServiceCoreApi) ListShortUrls

type UsersCore

type UsersCore struct {
	// contains filtered or unexported fields
}

func NewUsersCore

func NewUsersCore(badgerStore *monstera.BadgerStore, shardLowerBound []byte, shardUpperBound []byte) *UsersCore

func (*UsersCore) Close

func (c *UsersCore) Close()

func (*UsersCore) CreateUser

func (c *UsersCore) CreateUser(request *corepb.CreateUserRequest) (*corepb.CreateUserResponse, error)

func (*UsersCore) GetUser

func (c *UsersCore) GetUser(request *corepb.GetUserRequest) (*corepb.GetUserResponse, error)

func (*UsersCore) Restore

func (c *UsersCore) Restore(reader io.ReadCloser) error

func (*UsersCore) Snapshot

type UsersCoreAdapter

type UsersCoreAdapter struct {
	// contains filtered or unexported fields
}

func NewUsersCoreAdapter

func NewUsersCoreAdapter(usersCore UsersCoreApi) *UsersCoreAdapter

func (*UsersCoreAdapter) Close

func (a *UsersCoreAdapter) Close()

func (*UsersCoreAdapter) Read

func (a *UsersCoreAdapter) Read(request []byte) []byte

func (*UsersCoreAdapter) Restore

func (a *UsersCoreAdapter) Restore(r io.ReadCloser) error

func (*UsersCoreAdapter) Snapshot

func (*UsersCoreAdapter) Update

func (a *UsersCoreAdapter) Update(request []byte) []byte

type UsersCoreApi

type UsersCoreApi interface {
	Snapshot() monstera.ApplicationCoreSnapshot
	Restore(reader io.ReadCloser) error
	Close()
	GetUser(request *corepb.GetUserRequest) (*corepb.GetUserResponse, error)
	CreateUser(request *corepb.CreateUserRequest) (*corepb.CreateUserResponse, error)
}

Directories

Path Synopsis
cmd
dev command
gateway command
node command
standalone command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL