Closing session
Published June 13, 2025
This video features Emilio Carrión at DjangoCon Europe 2022 in Porto, Portugal.
The hack behind the curtain! Deploying a Multitenant Django application to more than 1600 stores by Emilio Carrión
Data isolation is key when working with bounded physical tenants like grocery stores. All the APIs and data are tenant dependent and we don't want to mix them between stores. This is the tale of the yellow brick road to multitenancy and how a Django project hack became an open-source library for everyone to use. The hack behind the curtain, welcome to Oz!
Mercadona Tech moved its Django-based grocery logistics systems from three isolated, on-premise warehouses to a centralized, multi-tenant service serving many stores. Rather than adding tenant filters throughout roughly 40 applications and rewriting their tests, Emilio Carrión’s team built a Django layer that applies tenant-aware filtering through custom managers and querysets. The solution discovers tenant relationships between models at startup, supports indirect relationships and common query operations, and obtains the current store from request headers or domains; after a year in production, the approach has been packaged for reuse and is intended for open source release.
Summarised automatically from the transcript.
Automatically transcribed, so expect mistakes in names and technical terms.
Okay, uh Hariri One uh first of all uh this is a template okay I'm no uh an artist so Um okay. So yeah, uh we are going to talk about uh how we migrated a single tenant application to a multi tenant environment in our company and the things we had to do and the bumps in the road. through the approaches. So first of all, a little bit of myself. I'm Emilo Carrion, I'm a staff engineer at Mercadona Tech. We Mercadona we are a grocery store chain in Spain We have more than sixteen hundred stores through Spain and we are currently expanding through to through Portugal. If you are local maybe you know you recognize the name.
um yeah and besides the physical stroke who okay physical Physic okay. And besides the physical store business, we also have an online service, basically an e-commerce where you can buy groceries online and we deliver them to your home. Why I'm talk uh telling you this? Because for this uh part of the business, like ninety-nine ninety-five percent of the software and tools we use in the logistics. Um scenario uh are made in-house and like 90% of the backend development we we do it's in Django so we have a lot of experience of deploying uh Django applications and escalating them
up And yeah, this is some examples of tools we have deployed in uh developed in Django. We have from supplyment to preparation orders. pick picking products and everything about last mile like routing and and delivery services uh it all is in-house uh custom uh made And just a little bit of history. We started this project like five, six, seven uh six years ago. And we started with a business model uh in which we build like big warehouses where we prepare like from two thousand Who thousand I'm going to use this. Uh from two thousand two thousand
three thousand um daily orders. And yeah, they are great, they work uh pretty well. Uh This one? Okay. So uh yeah, big warehouses. They prepare a lot of orders daily, uh pretty performant. Uh we develop a lot of tools for it. But they have a problem that is they only work in high populated areas like big cities, like in this case Valencia, Madrid and Barcelona. Um and what happens with the what happens with uh more rural areas uh where the the the demand is lower
so this this warehouse doesn't make sense. So uh a year ago we started to uh change the paradigm and instead of like having three big warehouses we um um uh plan it to use our network of our network of stores that we have sixteen hundred of them through the country in order to prepare uh orders from there. So we moved from a big warehouse with a lot of tools and with a lot of people working to smaller stores with uh like two to four people preparing orders and instead of three thousand only only fifty. And that was a problem because as we had um um Only three warehouses from the start we followed um
an architecture where each warehouse was isolated uh from each other. So Uh we deployed all of the systems and Young applications on premise inside inside the warehouse. One of the reasons is that in case of a connection error, connectivity error, if the warehouse go go offline, the young applications continue working, so the people working there is not stopped by it. And we have also a problem because we were moving from three big environments to uh sixteen hundred smaller ones. Uh that means a lot of With single deployments, that means a lot of redundant CPU, memory, resources, having to maintain 1600 databases
because until then it was one database per environment And yeah we didn't want to do that. So in order to tackle that that problem we decided to go multi-tenant instead of having all deployed Physically in each store we have only cloud-based centralized system with only one database and then all the stores use that service in order to prepare orders. And that reduced complexity. The problem. Until then we were following an
Django architectural development decision that is that As the data is isolated, uh for fetching data uh you did not uh filter for per preparation center. You if you wanted to get all the orders you just did a dot all and that's it. So in order to migrate that to a multi-tenancy service, it's it's simple. You only have to do a filter and just take the orders for that center. The problem with that is that we have lots of projects. We have like forty Django applications in our company and and uh several of them are are deployed uh in its warehouse. So uh making that migration uh in application code was going to cost a lot.
Not only modifying business logic but also um writing the test that sorry checked that business logic change was korrekt. So we decided to not go this way. And we created what we call the hack uh inside the company. It started as a little file in one of the smaller projects. It was messing around the Django LRM internally to do some magic behind the curtains. and isolate that data filtering uh from the business logic to the Django managers and not having to do that manually So
um we took a lot of inspiration in the Django ecosystem because yeah there is a there is lots of implementations already uh for Django for multitenancy. We are not the first ones to to get there But lots of the existing solutions focus on different approaches to multitenancy. For multitency you can have like one at one deployment uh pointing to different databases and on runtime you select the database you want to use. You can have one application and one database and different schemas. So in in runtime you select the schema you want to use and then you have one you can have one application, one database and one big table with all the data and a column that
um tells you from which center is that data. That was the the one we wanted to go and there is no much solution we follow found some um articles like this from Bidget and some libraries like this uh called Jango Scopes. Uh this is a great library by the way It serves as a line of defense in case you want to do that modification and application code. You can read the article from the creator, that is very interesting. but neither of them uh fitted our our business needs so we decided to spike a little and create a uh a little experiment to see if we could uh solve the problem ourselves And yeah, and before starting, some caveats
about the solution of the solution and the problem. We knew that we had uh models like order directly related to a to a set to a tenant that is center Then we knew we had some models that are not related to the center but related to a model that is related to a center. Uh deep relationship. And then we knew that we had some complex queries. That doesn't is not a complex query, but it's more like filters and excludes. Okay, we wanted to do aggregates, annotates, and more um Operations over the database and we wanted all of that handling to be done in the managers instead of in the business logic. So the office station
was this, uh it's awful, it's just a base query set with a base manager that was applied to all models If you see we are accessing uh a private variable query uh and adding a query if you see if you saw the the toll from from bus , um so this is not recommended, of course. But the thing is it worked. Some some of the operations were isolated from each other, but things like the payment that is uh deep relationship Uh was not and some th some things like aggregates uh did not work uh too. So with that in mind we do
did a second iteration that was implementing uh Deathfield's uh algorithm search Where we navigate through all the registered models in Django looking for the relationship between any model to the ten and one. So in this case, for example payment, it discovered that it has a for in key two order. Then order it sees that has a foreign key to center and then in center it's a tenant model so it finished. In this case, the algorithm registers that payment to tenancy the relationship is order center ID. So with that information calculated on Django startup setup Um we modify your current or or custom query set, but uh I I only want you to focus on what
we do there that instead of using the the the the default all opera we just ala filter afterwards en caso de filter we just a filter afterwards so en este case in this iteration We did not like uh went low level into modifying Django RRM, but we built a thin layer on top of it that was applied to every model in the in the application. That made that those are two examples, but the custom query is much longer. So that worker and this uh allowed us to isolate uh all that operation related to center from the um from the business logic.
And then uh yeah, so these these examples worked uh well. The payment uh now uh it uh was was equal to the to all the payments but filtered by the relationship that it was discovered from from the next uh the previous step. And then uh we build some Some utils around it like middlewares. This is a center middleware that takes a center ID from the request, sets sets it to a global variable. And then is used in the in the custom managers in order to isolate that data. uh it's just uh get from a header in case the the front end
injects it uh through Nginx uh that we have deployed or uh taking it from the domain because all of the our domains uh go uh con with the the tenant ID built in uh so we have we could we can extract the information from there. And this is the the the curtain of the name of the talk. This has been deployed in our systems for near a year right now. Uh it has worked great. We discovered some bugs, we fixed them, and we have packaged all of these uh iterations and knowledge in a single package because we wanted to use it in several um applications
and that's how we won uh down constraints. This is a private library for now. We intend to make it open source in the next weeks, months, I don't know, uh when we have time. But I w we want to to share it with the community. And If you see it, the the API is pretty similar to Django Scopes. Both uh libraries are similar in its use, but differ in how they are used. So we will try to explain in the REDMI what differences are there are between Django constraints and Django scopes so you can decide if you want to use it which uh use And that's a little bit of of of the things we saw and how many vibrated we are now like fully multi-tenant
in our stores, currently expanding through all of them. and and that's it. Um yeah just uh little uh las reminder we have tomorrow and uh workshop uh TD applied to Django API development. So if you wanna come uh you you are invited. So that's all for me. If you have any questions, does uh you can ask right now or just contact me uh or or the team. Thank you very much
The original architecture isolated each warehouse with its own on-premise deployment and database, which worked for three large warehouses but would have required maintaining roughly 1,600 environments as stores became fulfillment centers. A centralized service with one database reduced the duplicated compute, memory, and maintenance burden.
Discussed at 3:49Mercadona moved tenant filtering out of business logic and into custom Django managers and querysets. Their internal package automatically adds the appropriate center filter, so existing queries such as retrieving all orders can remain largely unchanged.
Discussed at 6:07At startup, it examines all registered Django models and follows foreign-key relationships until it reaches the tenant model. It records the relationship path—for example, Payment to Order to Center—and uses that path to filter querysets, including models several relationships away.
Discussed at 9:56A middleware reads the center or tenant ID from a request header injected by Nginx, or extracts it from the domain name. It stores that ID in shared request context, which the custom managers use when applying tenant filters.
Discussed at 11:29It had been running in their systems for nearly a year and was reported to work well; they found and fixed bugs and packaged the implementation for reuse across several applications. At the time of the talk, the package was private but they intended to open-source it.
Discussed at 12:14Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.
Published June 13, 2025
Published June 13, 2025
Published June 13, 2025
Published June 13, 2025
Published June 13, 2025
Published June 13, 2025