New Heroes is an online platform for soft skills and leadership training. The Umbraco site was built to handle a large volume of content and users. It uses load balancing across multiple servers, 1-1 multilingual support, custom database access via PetaPoco and migrations, performance monitoring with MiniProfiler, and the PluginManager for extensibility. The site pushes Umbraco to handle high traffic through optimization and leveraging core functionality.
2. • Lead developer @coloursinternet
• Umbraco MVP 2016/2017
• Working with Umbraco since late 2007 (v3)
Dave Woestenborghs
@dawoe21
• Senior Developer @coloursinternet
• 3 times Umbraco MVP
• Working with Umbraco since 2009 (v4)
• Was looking for a job last year during CG. Since
than only been working on this project.
Jeroen Breuer
@j_breuer
INTRODUCTION
3. Pushing umbraco to the limits
THE AGENDA
1. What is New Heroes
2. Statistics
3. Load balance setup
4. 1-1 multilingual
5. Database access
6. Miniprofiler
7. Performance tweaking
8. Pluginmanager
9. Other techniques
10. Questions
4. NEW HEROES
New Heroes is an online platform that allows access to soft skills and
leadership training for anyone , anytime and anywhere in the world . Through
the platform , users can continue develop in the areas of communication,
personal empowerment and leadership.
5. 5
NEW HEROES
For a fixed and low monthly
amount, both individuals and
businesses, have unlimited usage
of all trainings , also known as
'learning journeys ' .
8. PUSHINGUMBRACOTOTHELIMITS
The statistics
• Umbraco Version : 7.3.8 (started on 7.3.0-beta1)
• Number of media items : 1263
• Number of content items : 8411
• Number of cache instructions : +37k
• Number of doctypes : 205
STATS UMBRACO
10. Development goals
• Application should autoscale
• Use core/community functionality where possible
• Focus on performance
• DRY backend and front end
• 1-1 multilingual for reusable content
OUR GOALS
PUSHINGUMBRACOTOTHELIMITS
11. PUSHINGUMBRACOTOTHELIMITS
Load balance setup
• Hosted on Azure (Blob storage / redis cache)
• VM with editors environment (master)
• Front end runs on auto scaling web app (slaves)
• Started on 7.3 beta because of the flexible loadbalancing (no
config for loadbalancing needed)
• But you need to have some config tweaks. So make sure you
read the loadbalancing docs on our.umbraco.org
LOADBALANCED
12. 12
LOADBALANCED
• Use correct events to update custom cache (e.g. output cache)
• Published event is only executed on the server where it’s triggered.
Not on all other servers.
• Hook in to CacheRefreshers CacheUpdated event instead.
PageCacheRefresher.CacheUpdated += (sender, args) =>
{
{
// clear cache here
};
13. 13
LOADBALANCED
• For custom needs it’s possible to create your own cache refreshers .
• Need to create a class that implements ICacheRefresher interface.
• Umbraco has some base classes that you can inherit from like
CacheRefresherBase and JsonCacheRefresherBase
• When your data is changed tell the distributed cache that it needs to
execute a method on the cacherefresher :
• DistributedCache.Instance.RefreshAll(CustomCacheRefresherGuid);
• And handle the CacheUpdated event for the cache refresher
15. 15
MULTILINGUALSETUP
• Still uses a separate language tree
• Nodes that need to be reused in a shared data folder.
• Uses a UrlProvider and ContentFinder.
• Vorto for the 1-1 properties
• Nested Content to create fieldsets
• ModelsBuilder for strongly typed fieldsets.
• http://24days.in/umbraco/2015/multilingual-vorto-nested-content/
26. Database access
• Needed custom database tables for storing user data
• Needed to select a ORM
• Decided to go with PetaPoco because it ships with Umbraco.
DATABASE ACCESS
PUSHINGUMBRACOTOTHELIMITS
29. 29
Database access
• Goal was to do database maintenance (creating, modifying
tables) from code so that it becomes part of the automated build
process.
• We’re using uSync for all Umbraco parts.
MAINTENANCE
PUSHINGUMBRACOTOTHELIMITS
31. 31
• Only possible to create and drop tables
• No ways to alter tables, add constraints and indexes
• Needed an alternative
PETA POCO ATTEMPT
PUSHINGUMBRACOTOTHELIMITS
Database access
32. 32
• Migrations is used by Umbraco process
• Have been in place since 6.0.0
• Open to the general public since 7.3.0
MIGRATIONS
PUSHINGUMBRACOTOTHELIMITS
Database access
45. Pluginmanager
Whenever types need to be found in assemblies in order to add
them to resolvers, the PluginManager should be used. The
TypeFinder should never be used directly in any code except for in
PluginManager extension methods or in the PluingManager itself.
• PluginManager.Current.ResolveTypes<T>
• PluginManager.Current.ResolveAttributedTypes<T>()
• PluginManager.Current.ResolveTypesWithAttribute<T,S>
PLUGINMANAGER
PUSHINGUMBRACOTOTHELIMITS
46. Pluginmanager
A Resolver is an class that returns a plugin object or multiple
plugin objects. There are 2 types of Resolvers: A single object
resolver and a multiple object resolver.
Some examples :
Single object resolver :
DefaultRenderMvcControllerResolver.Current
Multiple object resolver : UrlProviderResolver.Current
OBJECTRESOLVERS
PUSHINGUMBRACOTOTHELIMITS
50. 50
Pluginmanager
EXAMPLE
• Only possible to create and drop tables
• No ways to alter tables, add constraints and indexes
• Needed an alternative
PUSHINGUMBRACOTOTHELIMITS
51. 51
Other techniques
OTHER TECHNIQUES
• Custom section with Angular + Umbraco WebApi + PetaPoco
• Virtual nodes
• Service API (ContentService, MediaService, RelationService)
• ImageProcessor to resize images
• Dependency Injection
• Custom property value converters
PUSHINGUMBRACOTOTHELIMITS
Community editors used are Nested Content, Vorto, nuPickers and RJP Multi Url picker
Thank Hendy, Lee and Matt for their great packages.
Other tools include uSync, Diplo trace log viewer, CMSImport, Bulkmanager, Load balancing dashboard, Zbu Models Builder, Core Value Convertors
One custom build property editor for managing “short” urls => is a candidate to be open sourced
Started on Umbraco 7.3 beta so we could use the new flexible loadbalancing
We have already a big amount of content that keeps growing
Cache instructions are stored in the database so the new load balancing knows what to updated on all the servers. More on that later
First I deleted it on dev environment. After that I opened the qa environment to use that version to recreate it. Than I also deleted it on the qa environment. A lot of test content was lost because of that.
Luckily we had uSync in place so it was easy to import the document type again.
Flexible load balancing works on the database instead of configuration in umbracosettings.config
When a Umbraco application starts up it registers itself in the table umbracoServer.
When changes are made cache instructions are written to table umbracoCacheInstruction.
Each server keeps track of executed cache instructions in App_Data/Temp/DistCache. If behind it will catch up. Check happens at start of request to application
Created load balancing dashboard for easy info on registered servers
Config tweaks needed for storing umbraco.config and examine indexes in temp storage of web app instance
When you have your own caches like output cache and you are running in a loadbalanced environment it’s important you hook in to the correct event.
Most people hook in to Published Event to do this, but published event is only executed on the server where it’s triggered. In our case the VM with editors environment.
You will need to hook in to cache Updated event of the different cache refreshers like PageCacheRefresher, MediaCacheRefresher, DictionairyCacheRefresher
These will get executed after the cacheInstructions (from DB) have been processed.
In the new heroes project a lot of data in custom datatables is managed through the umbraco backend in a custom section.
The data is not changed frequently so we can cache it for a long time and don’t want it cleared by publishing actions.
You can create your own cache refresher. By inheriting one of the base classes in the core.
When your data is changed you tell the distribute cache to execute a action on all servers.
Additionally you can hook in to a cache updated event after cache has been updated
The node name isn’t used for the URL. We have a Vorto textstring which can change the URL per language. The node name is a code which the content editor uses.
Vorto converts a property editor into a multilingual property by allowing you to enter multiple values per language.
Nested Content can create a single or repeatable fieldset based on a document type.
ModelsBuilder is a tool to generate strongly-typed published content models. It’s in the core since Umbraco 7.4.
This is the end result on New Heroes. I’ll know quickly explain how we’ve done this with a simple example.
1 Create a document type with these properties.
2 Create a Nested Content data type with this document type and set min and max to 1.
3. Create a Vorto data type which has the Nested Content document type.
4. Put this Vorto data type on the News Items document type.
Are you still following me?
6. It looks the same as the New Heroes result.
Again. You can all read this in the blog article too.
Code from the 1-1 multilingual example
The Nested Content fieldset will return an IPublishedContent.
Because it’s in Vorto we can get a translated IPublishedContent which will have all the properties in the correct language.
Render the IPublishedContent properties.
We still get the translated IPublishedContent from Vorto.
The ModelsBuilder generated a model for the document type which we used for the Nested Content.
The IPublishedContent can be passed to this model to get the strongly-typed model.
Rendering the properties is easier now.
A simple UrlProvider example. Not used in New Heroes.
A single node used for 1-1 multilingual can get different URLs per language like this. Extremely powerful!
Used for all nodes that are 1-1 multilingual with Vorto.
If the URL of a node is changed with a UrlProvider it will give a 404 if you visit that URL.
The ContentFinder can match the URL to a content node and return that.
rootNodes.DescendantsOrSelf is bad for performance. You should get the nodes you need first. Not important for this example.
We need to store progress of learning journeys and learning element and other user related data
Discussion was to use EF, but decided to use PetaPoco because this is used by Umbraco and keeps down external plugins
Decorate your class with table name and primary key attribute
Decorate your properties with attributes
Crud methods are done using strongly typed objects..no SQL query needed, but you can do it if you need more control over the query
Linq way of writing where clauses
Because we have build server set up for CI we don’t want to update our db schema by running scripts on the database every time we deploy. The maintenance of the db should be handled by the application
First attempt was by using the database schema helper to create tables base on our POCO
This works fine if you only want to create or drop tables. But there is no way to alter tables, add constraints, indexes or other db objects like stored procedures or views. So a better alternative was needed
Had a look at the Umbraco source on how they handle the upgrades..and discovered the migration framework
It has been in Umbraco since version 6.0.0
Since 7.3.0 exuted migrations are stored in db with an application name. Benefit is that DB upgrade is run when db is behind on Umbraco version. Also can be used for other migrations than Umbraco because of the application name
Migrations are the way to go if you application or package needs custom database tables
First you need to create a class that inherits from MigrationBase. You will need to override 2 methods Up and Down
You need to decorate the class with the Migration Attribute so it’s picked up by Umbraco
In the attribute you specify the version number of the migration, the execution order of the file (version can have multiple files), and the application name that the migration is for. Additionally you can specify a minimum version that needs to be run before this can be run. E.g umbraco runs some migrations only if your current version is for example 7.3.0
Some examples of the migrations syntax
Creating a table
Adding a column
Executing a sql statement
Many more options available like dropping a table, creating constraints and indexes, inserting/updating/deleting records. There is even a option to execute code, but never tried it
We run database migrations on startup of the web app.
The code checks for the latest migration version in the table “umbracoMigration”
We set new target version and check if it needs to be executed
Dave:
After we have determined if we need to execute migrations we create a new migrationrunner
And execute it against the database
Jeroen:
We’re probably one of the first that used the migrations for our own custom tables, because we did run into some issues. We had a migration called 7.3.4 and no matter what we did it was never executed. After doing a deep investigation we found out that the product name wasn’t checked and since Umbraco itself was already upgraded to 7.3.4 it thought it had already run. It was very coincidence to run into something like this and it can drive you pretty crazy ;-). Luckily Dave created a PR and now it’s fixed in 7.3.7 (http://issues.umbraco.org/issue/U4-7872).
To find performance issues you can log the execution time of your methods to the log. There are 2 options that will log differently. This information is also visible in the miniprofiler
With Miniprofiler you get a clear overview of how long a page takes.
We even found out that after a Save and Publish Umbraco did a query for each dictionary item so we build are own cache layer around it.
All index methods use the DonutOutputCache attribute for better performance.
LongPageCache is default. Order so other attributes execute first. For example to check credentials.
We don’t do a lot of logic in the index method.
With TraceDuration we can see how long all logic in the index method takes place. These are also all the html actions on the view.
In the view we do a lot of Html.Action.
This way the code is better separated and easier to reuse.
We can also exclude a part and let the child action have a donut cache with another time.
We used this code a lot in the UrlHelper to get the nodes of a specific doctype and return those URLs.
This was first done with Umbraco.TypedContentAtRoot().DescendantsOrSelf("alias") which was very slow.
Could also be used in the ContentFinder to return all nodes of a document type. For example all nodes of the Newsitem document type.
We’ve had some Examine corruption issues so instead you can also get the ids by querying the database.
The pluginmanager is a usefull tool when you need to find certain classes in your application
Object resolvers provide some kind of “Inversion of Control”. At start up you initialize the resolver with the needed types (from plugin manager). After the application has started you can the you use your resolver to get the types. Some more example in the core are ContentFinderResolver, PropertyValueConvertersResolver
If you wish to create a package that is extendable to third parties the plugin manager and object resolver are your friends
First step would to create a extension method on the plugin manager to find a certain type
The next step is to create a resolver for your type. In this example we created a resolver that return multiple objects
Then the final thing is to set up your resolver during the startup of umbraco in the ApplicationInitialized event
Now you can use in your application and you can use the object resolver to find your types. So we created some kind of inversion of control without a IOC container framework like Autofac
Now everyone is on 7.4, but I remember the 4.7 days. It would have been impossible to build this. Umbraco and the core functionalities are awesome!
We’ve learned a lot from the 24 days blog posts.