Pasecinic Nichita
Distributed systems
ElixirNodeJS-expressjsPhoenixwithChannels- WS API for Gateway and REST API for dedicated servicesNebulex(for caching / service levels and gateway level)Postgres(will useEctoas db wrapper withpostrexadapter)React/TS(client test application)Prometheus(for metrics scraping)Grafana(for visual metrics monitoring)Docker(docker-composefor CI/deployment)
Each service / component will be in a dedicated folder ⚙
acai- Gateway with Phoenix Channels (port: 4000)durian- Auth Service (port: 5000)kiwi- Persistent Service (port: 6000)counter_2pc- Users notifications counter (with 2 phase commit support) (port: 2000)client- Client test application (port: 3333)guava- Mailing Service (port: 7000)julik- Service Discovery (port: 8000)nodex- Service for generating random stuff (avatars FN) (port:9000)monitoring- Monitoring tools configuration (grafana -port: 3000, prometheus:port: 9090) - Dashboards screenshots
docker compose up --build --force-recreate# For cleaning up previous Docker images/containers/volumes (run in PowerShell)
# Don't need to run them on first setup
docker rmi -f $(docker images -aq)
docker rm -f $(docker ps -a -q)
docker volume rm $(docker volume ls -q)Start Grafana & Prometheus Stack as separate Docker containers
cd monitoring\local
docker compose up --build# 1. Start Gateway (acai)
cd acai && set PORT=4000&& iex --no-pry --sname gateway_node1 -S mix phx.server
# 2. Start Service Discovery (julik)
cd julik && set PORT=8000&& iex --no-pry --sname discovery_node1 -S mix phx.server
# 3. Start Auth Service (durian)
cd durian && set PORT=5000&& iex --no-pry --sname auth_node1 -S mix phx.server
cd durian && set PORT=5001&& iex --no-pry --sname auth_node2 -S mix phx.server
# 4. Start Persist Service (kiwi)
cd kiwi && set PORT=6000&& iex --no-pry --sname persist_node1 -S mix phx.server
# 5. Start Mail Service Cluster (guava)
cd guava && set ENABLE_REST_API=1& set PORT=7000&& iex --no-pry --sname mail_node1 -S mix phx.server
cd guava && set PORT=7001&& iex --no-pry --sname mail_node2 -S mix phx.server
cd guava && set PORT=7002&& iex --no-pry --sname mail_node3 -S mix phx.server
cd guava && set PORT=7003&& iex --no-pry --sname mail_node4 -S mix phx.server
# 6. Start Generator Service Cluster (nodex)
cd nodex && npm run dev:pm2
# to stop: npm run del:pm2
# 7. Start Counter 2 Phase commit service (counter_2pc)
cd counter_2pc && set PORT=2000& iex --no-pry --sname counter_2pc -S mix phx.server
# 8. Start Client application (client)
cd client && npm run devService Features:
- SQL databases (postgres) - (Auth & Persist service)
- Status endpoint
/dashboard- Generated by Phoenix Framework - Task timeouts configurable per individual task - e.g.: inside
config.exs-send_email_timeout: 1000 - Service Discovery -
julik - RPC - Mailing service Nodes communicates via
:erpc - Concurrent task limit -
DynamicSupervisorfor mail workers has amax_childrenconfigured link - Grafana / Prometheus metrics collection & monitoring
- 2 phase commit for create notification action (
kiwi&counter_2pc)POST-/api/notificationswithis_2pc_locked: true(prepare)POST-/api/notifications/commit_2pcwithrequest_idfrom prepare step (commit)DELETE-/api/notifications/rollback_2pcwithrequest_idfrom prepare step (rollback)
Gateway Features:
- Load Balancing - Round Robin
- Outbound WS API
- Circuit breaker link
- Grafana / Prometheus metrics collection & monitoring
- 2 phase commit integration for services that supports it
- 1 phase - prepare data request -> success/error
- 2 phase - commit/rollback request -> ack/nack
The Cache:
- Implemented in Auth Service
- Implemented in Persist Service
- Replicated cache (across all Auth service nodes -
duriannodes)
Other:
- Real-time events/notifications via WS API and Phoenix Channels
Services should implement several routes for it:
POST-/api/prepare_2pcPOST-/api/commit_2pcDELETE-/api/rollback_2pc
After successful prepare request it might return an identifier for the created transaction (mutation) or ack/nack:
kiwi- will returnrequest_id(later used to rollback/commit transaction)counter_2pc- just ack (user_idfrom request is enough to rollback/commit transaction)
Actually the terminology of commit/rollback transaction should be better called save/discard data actions. As there
is no
real transaction reference that could be later used to commit/rollback it, the data is still persisted somewhere and
there
should exist a clean-up/save handlers for each of the atomic change
The Manager2PC
Is the generic implementation for handling first and second phase from 2 phase-commit requests. The prepare phase is domain specific, meaning that it should be clearly defined all prepare tasks handlers like:
prepare_tasks = [
Task.async(fn ->
Services.Persist.init_2pc(socket, notification)
end),
Task.async(fn ->
Services.Counter.init_2pc(socket)
end)
]init_2pc function should return a tuple of:
{:ok, commit_fn, rollback_fn}
{:error, data}commit_fn and rollback_fn are as well domain specific so those should be handled by the dedicated services
separately.
The second phase is executing either commit_fn or rollback_fn handlers, based on response from prepare (first)
phase.
A big disadvantage of 2 phase commit approach is that there is no clear definition of what should happen when a task
from second phase
fails (either to commit or rollback).
Note that tasks from both phases are done asynchronously with Task.async/1 and awaited with Task.await_many/2.
- User could send, receive, ack notifications/messages in real-time
- Multiple & dynamic notifications topics created by users (all other operations will validate topic creators)
- Service for persist and keep track of the broadcasted notifications, topics, subscriptions
- Service for sending notifications via email
- Service for generating PNG avatars
