skip to content
Mehdi Mehdikhani
Table of Contents

After working on industrial systems for years, I’ve learned that good system design isn’t about knowing every trendy technology. It’s about understanding fundamental principles and knowing when to apply them. Most systems get complicated because we add complexity in wrong places.

Start Simple, Scale When You Need To

Biggest mistake I see is over-engineering from day one. Start with monolith. Monoliths are easier to develop, test, and deploy. Split when you hit actual bottlenecks, not imaginary ones. Design your monolith with good boundaries - clear interfaces, separated concerns, loose coupling. This makes future splits much easier.

Design for Failure

Systems fail. Networks are unreliable. Services go down. Accept this and build accordingly.

Most importantly identify single points of failure. Single point of failure is any component that can bring down entire system if it fails. Database, load balancer, or critical service can all be single points of failure. Add redundancy, implement failover mechanisms, build timeouts and retries, use circuit breakers or design components to work independently. So that in the case of a problem the system degrades gracefully instead of failing completely.

Data Consistency vs Availability

CAP theorem is real. When network partitions happen, choose between consistency and availability. Most systems choose availability - users prefer slightly stale data over no data.

We use eventual consistency in manufacturing analytics. When machine breaks, operators need recent performance data even if it’s few minutes behind, not error messages because one replica is unreachable. But financial transactions need strong consistency. Know your requirements.

Security as Design Principle

Security isn’t feature you add later - build it into architecture from start.

Use least privilege principle. A path planning service should be able to read maps and robot positions but shouldn’t be able to send movement commands directly. Only safety-certified motion controller can do that. Don’t trust network boundaries - use authentication between all services. Validate all inputs at system boundaries.

Performance vs Complexity Balance

Profile before optimizing. Do not spent weeks optimizing path planning algorithm that use 1% CPU while database queries are the real bottleneck.

Choose right data structures first. Proper indexes and cache strategy matter more than micro-optimizations. Premature optimization is evil, but so is never optimizing.

Monitoring What Matters

You can’t fix what you can’t measure, but don’t monitor everything. Focus on user experience: response times, error rates, throughput. Then resource utilization.

Distributed tracing is essential for microservices. When robot task fails, you need to see if it was path planning service, obstacle detection, or motor controller. Log aggregation saves time during 3 AM outages.

Database Scaling Patterns

Don’t jump to sharding. Start with read replicas for read-heavy workloads. Optimize queries first - most problems come from bad queries, not database limits.

When sharding, choose key carefully. We shard sensor data by robot ID because most queries are “show me data for robot X”. This keeps related data together and avoids expensive cross-shard queries.

Caching Strategy That Works

Cache close to where data is needed. For example cache robot position data at edge servers near factory floor, not central cloud.

Cache invalidation is hard. Use TTL for data that can be stale. Use explicit invalidation for critical data. Pick one strategy, not both. Watch for cache stampedes - use warming and locking.

The Meta-Principle

Most important: know when to apply these principles. Every system is different. Start simple and add complexity only when needed. Each decision should solve real problem, not imaginary one.

Good system design is making right trade-offs for your situation. These principles give framework for thinking, but you need to understand your requirements to make good decisions.