DangerOnTheRanger / maniwani

Imageboard software for the 21st century
MIT License
75 stars 11 forks source link

Migrate to microservice architecture #158

Open DangerOnTheRanger opened 4 years ago

DangerOnTheRanger commented 4 years ago

This is something I've been planning for a while now, so I figured it might be best to put it into an issue so a discussion can get going if need be. First things first, what I envision the architecture will look like after the move:

Services:

I'll try to preemptively answer a couple different questions/issues that I've asked myself, and/or that will likely be asked by others.

What benefit would the architecture migration serve? A couple things, I think. Cleaner, simpler, more testable code with well-defined boundaries is the big one, and something I'd like Maniwani to have down the road is a full, proper regression test suite. Breaking up the codebase into smaller chunks will go a long way towards making the codebase testable. It might sound counterintuitive, but Maniwani will likely be faster, since this setup would allow things like making the router in Go. If most requests hit the Go router and pull things from cache, response times should be almost always measured in tens of milliseconds even under heavy load. Requests that hit a cold cache might be marginally slower than they otherwise would be, but this would be the exception rather than the rule.

Why this set of services in particular? I've given the problem a fair amount of thought and I think this is the most it can be reduced to before the complexity of inter-service communication and coordination outweighs any tangible benefits. This is something definitely up for discussion, though.

Won't having 7 different services result in a much larger codebase? It will be marginally larger due to needing a little bit of boilerplate to deal with inter-service API calls, but other than that, the migration will mostly involve moving existing code around. There will need to be a little bit of effort made to ensure that GET calls to the API don't have side-effects in order for caching to be fully possible (that's not true at the present since retrieving a thread raises the view count every time), but that's about it.

Won't maintaining a combined Python/Javascript/Go codebase be difficult? I don't think so. I personally know Python well and do Go professionally at work; the only language of the three that I'm not 100% confident in is Javascript, and what better way to fix that than to do something like this?

What will be done when two or more different codebases need to communicate with each other? REST should be sufficient most of the time. I don't foresee the UI/JS codebase needing to do anything more complicated than that, but it's very true that the Go-based router will need to interact with the keyvalue store for caching, and perhaps with pubsub for a similar reason, as well. I think the easiest method is to simply give the current existing keyvalue/pubsub API a Go client implementation. This will require using a message serialization scheme other than pickle in order to properly implement #155 (since Go can't deal with pickle directly), but something like protobuf should do the trick fairly easily. I'd rather not use redis directly since that would hang a hard dependency on a piece of software I'd rather not hang a hard dependency on (what if someone wanted to use Kafka instead /s).

Would a microservice architecture prevent Maniwani from being run from a single container? It would discourage it, but not necessarily prevent it. An expanded version of the approach the react-ui branch takes now (running all processes inside a single container) would work. Rebuilding such a large container would take a very long time, however, and it's definitely true that I would push for more users to use the multi-container/docker-compose approach after the architecture migration was finished. It's always been the case that the multi-container deployment option was recommended for production use cases, though. What might end up happening is that a single VM image (and not container) could be provided instead should anyone want to take that route.

Isn't doing database transactions in a microservice environment hard? Very true, which is why in the layout I've described, database interactions are kept to a single component (api). That component still serves as the central point of Maniwani's business logic, so if some part of a transaction fails, the api component is in an optimal spot to roll back any other related backend activity.

Why is Markdown rendering its own separate component? Two main reasons: One, to allow for potential experimenting with different language implementations besides Python for the sake of speed, and two, so that the api component doesn't need to worry about caching in any kind of capacity - the Markdown service itself will return its own potentially-cached renders.

How long will this take? A very good question. It depends on how much personal free time I have/how much other contributors volunteer towards it, I think. "It's done when it's done" is probably the best answer I can give at this point, unfortunately.

Will this prevent work on other parts of Maniwani? I personally want to get react-ui and a rework of the stylesheet (without Bootstrap this time) out the door before embarking on this project. Ideally this would be something undertaken in bits and pieces, kind of like how the React frontend is already its own separate container, so that the master branch wouldn't be left alone for too long and merges from other contributors wouldn't be too painful.

Will the total storage space needed to store all the container images increase dramatically compared to the way things are now? It's true that a lot of work has been devoted lately towards making Maniwani have a small storage footprint, but I don't think things would change much. Extra containers will always take up a little more space, but Go containers are extremely lightweight due to Go producing self-contained binaries, and I think many of the new services would benefit from being written in Go - the aforementioned router and thumbnailer especially.

samip5 commented 4 years ago

Won't maintaining a combined Python/Javascript/Go codebase be difficult? I don't think so. I personally know Python well and do Go professionally at work; the only language of the three that I'm not 100% confident in is Javascript, and what better way to fix that than to do something like this?

I feel like we shouldn't mix and match Go/Python/JavaScript, but Python/JavaScript usually go together. You might ask why I'm not a fan of the idea of using a Go based router? That would mean, that I would need to learn that as well as I have no experience with working with it at all but I do know that it's most likely able to handle requests faster than Python based router would.

Will this prevent work on other parts of Maniwani? I personally want to get react-ui and a rework of the stylesheet (without Bootstrap this time) out the door before embarking on this project. Ideally this would be something undertaken in bits and pieces, kind of like how the React frontend is already its own separate container, so that the master branch wouldn't be left alone for too long and merges from other contributors wouldn't be too painful.

What's the current status of the react-ui, as in what's the most things that are preventing it from being merged to master?

DangerOnTheRanger commented 4 years ago

Go is a fairly straightforward language (probably easier to ramp up to a competent level than Javacript if you have prior experience in any C-based language), so I don't think learning Go would be too difficult. In terms of other languages that could replace Go for the router, Python is unfortunately much, much slower, so for something meant to be client-facing I'd rather not use it for that purpose. Javascript with Node is pretty fast, and if this kind of problem space wasn't tailor-made for Go I might have pushed for Javascript instead. Again, Go is a very simple language and it was made for this kind of thing - give it a shot, and see what you think.

As far as react-ui goes, my internal list of TODOs (that really need to get put on the issue tracker) are as follows:

Other than that, everything works at least as well as the old Jinja/Jquery frontend. A couple routes like the FAQ are still rendered with Jinja, but that's fine for now. I'll make a PR later today (but not merge it) with the above list of tasks so my progress is a little less opaque.

DangerOnTheRanger commented 4 years ago

PR is #159 - thought about linking this issue from the PR body but figured it'd be better just to mention it here.

DangerOnTheRanger commented 4 years ago

An additional thought for an extra service: The SSE endpoint /api/v1/live (used for push updates) could be its own separate service to free up the API server from having to juggle too many concurrent connections, since the only way that part of the codebase communicates with anything else is through the pubsub system. I don't think this should be implemented as an additional service immediately, but I also think being able to scale and throttle push update connections independently could come in handy on higher-traffic deployments.