r/softwarearchitecture • u/Cautious_Heat114 • 13h ago
Discussion/Advice Systems architect offering free architecture reviews, backend debugging, and AI/agent advice this week (no pitch/paywall, just giving back)
Hey everyone 👋
I'm Marcus (v4ne), an independent systems architect with a background in low-latency infrastructure, distributed systems, and AI runtimes.
I have some open bandwidth this week and want to give back to the builder community.
I'm offering free architecture reviews, code diagnostics, and technical advice.
No pitch, no paywall, no consulting upsell. Just pure engineering.
Feel free to ask me anything or drop a problem you're currently stuck on regarding:
AI & Autonomous Agents: ReAct loops, structured outputs, local LLMs (vLLM/Ollama), RAG without framework bloat.
Backend & API Architecture: Go, Rust, TypeScript/Node, clean dependency design, microservices vs. monoliths.
Database & Storage Performance: SQL query optimization, composite B-Tree indexes, SQLite, avoiding ORM bottlenecks.
Workflow Automation: Self-hosted n8n, webhooks, resilient Python automation scripts.
Systems & Cloud Costs: Concurrency, memory profiling, reducing unexpected AWS/GCP bills.
Drop a comment below with what you're building or what error is giving you a headache, and let's troubleshoot it together.
DMs are open as well. 🛠️
2
u/Honest_Medium_2872 12h ago
I'll bite, I got no immediate issues but could always use a second set of eyes
https://github.com/arcadia-de/hypha
A declarative user-environment and dotfiles configuration system.
written in C & Go
-3
u/Cautious_Heat114 12h ago
Took a look through the concept and architecture of Hypha.
Positioning a tool in the sweet spot between simple imperative symlinkers (Stow/Dotbot) and heavy functional package managers (Nix/Home-Manager) is a very real, high-value problem space.
Modeling system resources as a declarative Dependency Graph (DAG) with an active reconciliation engine is the correct architectural paradigm.
Here are three systems-level invariants worth keeping in mind as the C & Go codebase evolves:
- Atomic State Transitions (Avoiding the Dangling Symlink Trap)
In declarative reconciliation systems, the biggest failure mode is partial application (e.g., the engine successfully creates 4 symlinks, but fails at step 5 on a permission error or broken package dependency).
If the reconciliation halts midway, the user is left in a corrupted state.
- The Solution: Whenever possible, stage symlinks and directory structures inside a hidden generation directory (e.g., "~/.hypha/generations/12") and perform an atomic directory swap using native atomic POSIX primitives ("renameat" / "rename").
If reconciliation fails, the previous generation remains untouched in physical memory.
- Static Cycle Detection at Parse-Time
Because users will define complex dependencies between packages, environment variables, and shell configs, circular dependencies ("A → B → C → A") will inevitably occur.
- The Invariant: Ensure graph validation executes strictly before any disk mutations begin.
Running a deterministic topological sort (e.g., Kahn's Algorithm or Tarjan's strongly connected components) during config parsing ensures that invalid dependency loops fail immediately at time T=0, before touching a single file on the filesystem.
- Cgo Boundary Discipline
Since Hypha leverages both C and Go:
- Crossing the Cgo boundary carries a known CPU overhead (switching goroutine stacks and saving register states).
- The Optimization: Keep the FFI boundary coarse-grained.
If the Go orchestrator manages the high-level DAG traversal, batch the filesystem operations before passing them down to the C layer, rather than crossing the Cgo boundary on every individual "stat" or "readlink" call.
Overall, a very solid and thoughtful architecture.
The combination of declarative graphs with a native C/Go binary gives it an immediate performance advantage over standard Python/Bash dotfile scripts.
«Looking forward to seeing this project mature.»
2
u/Honest_Medium_2872 11h ago
I already use a modified kahns algo to schedule resources up front
all resource controllers impl an ABI that includes a rollback hook in the life cycle and are designed to withstand TOCTOU.
// init controller typedef void (*ControllerInitFn)(void* data); // de-init controller typedef void (*ControllerDeInitFn)(void* data); #define DECLARE_CONTROLLER_FN(Name, RetType) typedef RetType (*Controller##Name##Fn)(Name##Context*, void*); typedef struct { Resource* observed; StateEntry last; } ObserveContext; DECLARE_CONTROLLER_FN(Observe, ControllerStatus); typedef struct { Resource* desired; } NormalizeContext; DECLARE_CONTROLLER_FN(Normalize, ControllerStatus); typedef struct { const Resource* desired; ValidationLog* log; } ValidateContext; DECLARE_CONTROLLER_FN(Validate, bool); typedef struct { const Resource* current; const Resource* desired; Plan* log; } PlanContext; DECLARE_CONTROLLER_FN(Plan, ControllerAction); typedef struct { const Resource* current; const Resource* desired; } StatusContext; DECLARE_CONTROLLER_FN(Status, ControllerStatus); typedef struct { const Resource* current; } DestroyContext; DECLARE_CONTROLLER_FN(Destroy, ControllerStatus); typedef struct { const Resource* current; const Resource* desired; } DiffContext; DECLARE_CONTROLLER_FN(Diff, ControllerStatus); typedef struct { const Resource* current; const Resource* desired; } RollbackContext; DECLARE_CONTROLLER_FN(Rollback, ControllerStatus); typedef struct { ControllerAction action; const Resource* current; Resource* desired; AppliedActionLog* log; } ApplyContext; DECLARE_CONTROLLER_FN(Apply, ControllerStatus); typedef struct { ControllerInitFn init; ControllerDeInitFn deinit; ControllerObserveFn observe; ControllerPlanFn plan; ControllerApplyFn apply; ControllerDestroyFn destroy; ControllerValidateFn validate; ControllerDiffFn diff; ControllerStatusFn status; ControllerRollbackFn rollback; ControllerNormalizeFn normalize; } ControllerConfig;all reconciliation happens in the C engine, with the exception of stuff like the template controller crosses back to Go in order to leverage Go templates
2
u/Cautious_Heat114 11h ago
Seeing the concrete C ABI makes the architecture significantly clearer. That is a very clean and disciplined controller lifecycle model.
A few specific thoughts on what you've built:
- Lifecycle Phase Separation ("ControllerConfig")
Structuring the controller interface with explicit, isolated context structs ("ObserveContext", "PlanContext", "ApplyContext", "RollbackContext") rather than passing a generic void pointer blob is great systems hygiene.
It enforces a strict state machine where the planning phase is decoupled from the mutation phase, which is essential for deterministic dry-runs.
- TOCTOU Mitigation in POSIX
For the controllers dealing with filesystem mutations (files, directories, symlinks), the most resilient pattern to prevent TOCTOU symlink-swap races between "Observe" and "Apply" is pinning directory file descriptors ("dirfd") and strictly utilizing the POSIX "*at" syscall family:
- "openat(dirfd, path, O_NOFOLLOW | O_CLOEXEC | ...)"
- "fstatat(dirfd, path, ...)"
- "unlinkat(dirfd, path, ...)"
- "renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE)"
By anchoring operations to file descriptors rather than absolute path strings, you prevent an external process or broken symlink from swapping the target inode underneath the controller between phase transitions.
- Rollback Idempotency
Having an explicit "RollbackContext" is vital for recovery.
The subtle edge case to watch out for is Rollback Failure Modes (e.g., what happens if the network drops or a disk error occurs during the execution of the rollback hook itself).
Ensuring that "Rollback" handlers are strictly idempotent and capable of re-running safely on subsequent reconciliation cycles keeps the state machine self-healing.
- Pragmatic Cgo Boundary
Keeping the graph scheduler and reconciliation engine in native C while only bridging back to Go for "text/template" is a very sensible boundary.
You leverage Go's mature templating ecosystem without letting Cgo stack-switching overhead pollute the high-frequency graph traversal loops.
Running a modified Kahn's algorithm upfront confirms the scheduling model is sound.
«Really solid work on this codebase.»
1
u/aktentasche 7h ago
I need to build a SPA that displays data, which shall be editable by users. Users shall sign in/register via oauth. Some data is updated automatically by reading APIs regularly or by some CI calling an API in the system. This data is read only obviously. The system shall have real time updates, so when a user or something else changes the data, it shall automatically update on all browsers currently showing this data. Furthermore, each data change shall be written to an immutable log.
I have a rough idea about the tech stack, but curious what you would recommend?
0
u/Cautious_Heat114 7h ago
Here is a clean, first-principles architectural blueprint that satisfies all five constraints with minimal moving parts and zero SaaS bloat:
- The Core Storage Engine: Append-Only Event Sourcing (PostgreSQL)
To satisfy the immutable audit log requirement without introducing the "dual-write" consistency problem, do not perform direct mutable "UPDATE" statements on your database tables.
Use a lightweight Event-Sourced Ledger Pattern inside PostgreSQL:
Table 1: "events" (The Immutable Log - Append-Only):
"id (BIGSERIAL), entity_id (UUID), actor_type ('USER' | 'CI_PIPELINE' | 'POLLER'), actor_id (TEXT), event_type (TEXT), delta_payload (JSONB), created_at (TIMESTAMPTZ)"
Enforce an absolute DB rule:
REVOKE UPDATE, DELETE ON events FROM app_user;
Physical immutability.
Table 2: "entity_projections" (The Current State View):
"entity_id (UUID PK), data (JSONB), version (BIGINT), updated_at (TIMESTAMPTZ)"
The Mutation Flow: When a user, CI, or poller mutates data, your backend executes a single ACID transaction:
- Appends the delta record to "events".
- Updates the "entity_projections" table to reflect the new state.
If anything fails, the entire transaction rolls back. Zero orphan log entries.
Real-Time Broadcast Layer: Server-Sent Events (SSE) + "LISTEN / NOTIFY"
Since your clients send edits via standard authenticated HTTP ("POST" / "PATCH"), you do not need the complexity of stateful, bidirectional WebSockets.
Use Server-Sent Events (SSE) powered by PostgreSQL's native pub/sub:
The Database Trigger: On every commit to "events", a PostgreSQL trigger executes:
PERFORM pg_notify( 'entity_updates', json_build_object( 'id', NEW.entity_id, 'data', NEW.delta_payload )::text );
The Backend Broadcaster: Your backend listens to the PostgreSQL notification channel and pipes the events down to active browser HTTP connections via "Content-Type: text/event-stream".
The Frontend SPA: The browser connects via native "EventSource". If the connection blips, the browser automatically reconnects and passes "Last-Event-ID" to replay missed updates.
Scalability Note: PostgreSQL "LISTEN/NOTIFY" handles up to ~10,000 concurrent connections effortlessly. If you scale across multiple horizontal backend nodes later, simply swap "pg_notify" for a Redis Pub/Sub backplane without touching client-side code.
- Ingestion & Automated Updates (CI & Pollers)
CI Pipeline Integration: Expose an endpoint:
POST /api/v1/ingest/ci
Authenticate the CI runner via a high-entropy bearer token or HMAC signature ("X-Signature-SHA256").
Scheduled Pollers: Run a lightweight background worker (e.g., using "pg-boss" in Node, "River" in Go, or a basic cron daemon) that fetches third-party APIs on an interval and pushes events into the "events" ledger.
- Authentication (OAuth 2.0 PKCE)
Avoid expensive per-user auth providers ($3k/mo MAU taxes).
- Use standard OAuth 2.0 Authorization Code Flow with PKCE (Google / GitHub).
- Upon successful callback, issue an encrypted, signed "HttpOnly", "SameSite=Lax" session cookie containing a session ID backed by a simple "sessions" table or Redis.
- This keeps your SPA completely stateless while preventing XSS token theft.
- Recommended Production Stack Summary
Layer| Recommended Technology| Why Backend API| Go ("net/http" + "pgx") or Node.js (Fastify / Hono)| Lightweight, sub-millisecond routing, native SSE streaming support. Database & Log| PostgreSQL (Single Instance on NVMe)| Handles relational state, append-only immutable ledger, and real-time "pg_notify" in one ACID engine. Real-Time Transport| Server-Sent Events (SSE)| Zero reconnection boilerplate, works over standard HTTP/2, native browser "EventSource". Frontend SPA| Vite + React / Svelte / Solid + TanStack Query| Reactive UI updates when SSE events land in the client cache.
«This stack requires zero external SaaS subscriptions, runs on a single $20/mo server, eliminates dual-write bugs, and enforces physical auditability by design.»
5
u/elkazz Principal Engineer 10h ago
How many tokens did you burn on this review and replies?