Designing high performance web applications is not an easy task. Depending on the required components by the application itself, it can be even more complicated. You should make sure not to run into newbie failures when building an application. Here is a list of things to consider, before you even know, what you are talking about.
one of the most blocking things in doing performance optimizing are blocking processes, or in other words: synchronous running code. To really leverage a scaling architecture, it is important to create as many features in a non-blocking way. That way, all the heavy tasks can live within a queue, that is then worked on by x number of workers. Via the number of workers (and the specs of machines doing so) it is possible to scale the application in speed. Think about that, when planning features and ask yourself more often: do the user really need to wait, until that is finished?
delivering everything right from the slowest component, usually the data storage, is the worst idea, one can have. But usually one does exactly that. Even in environments where data is changed rarely, we are used to request data directly from the database. That does certainly not scale well. To prevent web applications to fail under load caching is a great strategy to reduce load. Think of a news-site that holds different content on various topic-pages. With caching strategies you can control how long a news item lives in the cache, before it must be re-fetched from the database. Under load, you may increase that time from e.g. 5 minutes to 30 minutes and reduce stress on your backend servers that way.
Caching can be done on multiple levels and needs a concept beforehand. What gets cached for how long and when does the cache gets invalidated. Where do you configure your caching levels and how do you change that easily.
If you need to join data, stick to do it in application code. Your database is the slowest component, see above. It will have more to do, if you join data within SQL database servers. It is much better to fetch data according to a set of ids, and then intersect and join them. That way, lookups by index (which are always the fastest) can be used and also caching leverage its full power.
Another benefit is, you can exchange or optimize your logic afterwards to even transfer it to a different technology that better fits your needs. Changing the database backend will be much harder to do, if you have a matured software project.
at a certain point in history you have to realize that your application is slow. But you really do not want your users to tell you. You want to know beforehand. Monitoring your application performance and more or less detailed aspects of all systems and computers involved will tell you. Make sure you know your numbers and can differentiate a fast running process from a slow one. The better you know your defaults and how it should behave the more sensible you get for variations to that.
If your monitoring is good enough, you can dig deeper into area that is of interest to you to track down causes of bad performance. That way, you can not only realize it is slow, you can tell what part of the system needs to be improved. In order to get to that point you need to invest a lot of time into monitoring and metrics aggregation.
It is quite common that you start with certain metrics and then switch over to other ones, as you see they fit your needs more. A constant change in which metrics are important to you will help you understand your whole architecture better. This helps you doing the right decisions in architecture and development.
On of the hardest part in scaling is to take care of your application performance. It is best to identify some key transactions in order to optimize those first. Whatever your application does, there will be some actions, that are requested more often than others. Have a look around and see, if there is a new technology around or some principles on can apply to make that work. Chances are high, that other people had the same problem and coped with that using a different approach.
You miss something out if you just rely on your own application development practices, so go ahead and talk to others, scan open source projects regarding that very topic and attend conferences, bringing up this topic. You will be surprised how others approach these problems and find solutions that may be better than yours.