问题
There are a similar threads without a concrete solution and I thought it was better to start a new one.
I am facing a situation where I have a WebApp hosted in RESIN (just like Tomcat I guess). So far I have been developing the app using db4o since I'm alone and I needed to complete the app ASAP, I have a DB for users and another DB for app data for a single user (me), now that the app is almost done I'm about to move to postgresql and I am thinking seriously about a DB per user even if the DB holds data for multiple apps since it will handle kinda confidential data and I thought having separate DB will be the best (security wise). There is already a rudimentary session management that stores user data like an ID in the browser. But I was wondering how can I expand it to multiple users/db.
I was thinking to expand the listener class that keeps the context data to pass the right db object to the app instance, or maybe set a filter for that purpose.
.UPDATE.
I wanted to give some more insight of what I currently have.
I have:
Context that holds a reference to some objects, one of those objects connects to a DB and checks for the user and password.
Presentation servlet (HttpServlet) mapped to "/" that has the login form that POST to /login.
Login servlet (HttpServlet) mapped to "/login" that checks the httpSession user password attributes against the respective object that rests in the Context, if there is a match sets an httpSession attribute that holds the USERID and redirects the user to the app itself located at /index-debug.html if not it creates a new html page with a login form again.
Authorization and authentication filters mapped to /index-debug.html that verifies the httpServletRequest for the USERID attribute and checks whether or not the user has permission to access the app.
Finally a DB bean that is in charge of reading and writing to the webApp user data DB. When I execute certain method in the webApp CP2JAVAWS matches that method to the respective method in the bean, the problem is that this bean has a static database and so far it only allows one user at the time.
What I would like to do is somehow allow this DB bean to instantiate once per user and read and store the corresponding data depending of the current logged user.
The idea of one DB per user is currently discarded but I don't know how exactly pull that off.
回答1:
You mentioned Postgres as the database backend and that has the feature called schemas. This is something where you have one physical database and multiple schemas inside the database. My experience with this comes from Rails, but the concepts are the same. This method avoids mashing people's data together in the same set of tables which sounds like your primary concern. I know you're using Java, but watch this talk on Multi-tenant apps in Rails to get a background from Guy Naor on how this works, trade-offs, etc.
Here are some concrete steps to get you started down this path of using Postgres schemas:
- There is a public schema in Postgres that is the default. This would be where you put your user authentication tables and any other generic meta-data tables about user logins, etc. See Postgres docs for more info on how schemas work
Come up with a naming convention for each schema you will create (e.g. user_001, user_002, etc.). Pre-allocate a bunch of empty schemas with all the tables setup and when the user registers or logs in for the first time, you assign them a schema and store the schema name in their user record in the public schema and in the user object that you have in HttpSession. There would be no need to run table creation scripts for a first time user - that would be a performance drag in a web app. You just need to stay ahead of the rate of new users. For example you could have a bunch of empty user_standby_1 ... user_standby_100 schemas and then when someone logs in or registers, you would run this sql:
myquery = "ALTER SCHEMA user_standby_? RENAME TO user_?"; myquery.setString(1,standby_id); myquery.setString(2,user_id);
When you create your DB bean (use a superclass for this, see below), pass in the schema name from the User object from the HttpSession, then execute this SQL before every operation to isolate them to their schema only:
myquery2 = "SET search_path TO ?";
myquery2.setString(1,user.search_path);If you have an empty full schema in public, then you want to omit public from the search path otherwise you will have 2 tables with the same name in the search path. If you want the users search path to include
SET search_path TO user_001,public
then after creating tables, drop all the data tables from public other than users and any meta-info you need.- For maintenance, write a script you can run via command line to drop empty user_standby schemas, create new user_standby schemas and do the equivalent of Rails Migrations for Java for minor table changes.
- For large maintenance activities it might be best to create new schemas, e.g. user_v2_001, for each user and then write scripts to migrate their data in. This depends on how complex the changes are to your tables.
If you go the alternative route and have all users data in one set of tables, then the best approach is to have user_id in every table and write your SQL to use that every time. If you use traditional normalization and do joins to get your user_id, then you better make sure you don't accidentally miss a join or users will start seeing each others data.
The Postgres schema feature allows you to lock users access to only their own data. After figuring out the basics, use a superclass in Java to write step 3 above so every MyTableDBBean extends from MasterDBBean and uses the super class constructor to isolate the search path to the user's schema. Then you only have 1 place in your code where this is done and you don't have to remember for every table or query to do anything more than the business logic.
回答2:
Resin! I haven't heard or worked with Resin in quite a while. =)
I've seen the idea of having one database per user of a system come up quite often on Stack Overflow. The reaction is generally the same - it's not a great idea.
There are lots of reasons why, but I'll just stick with scale, maintainability and volatility.
Scale
Some databases have limits on how many databases they can have. I don't know how many databases a single Postgres instance can have.
This link (https://dba.stackexchange.com/questions/23971/maximum-number-of-databases-for-single-instance-of-postgresql-9) says someone got 10,000 databases on one instance.
I would say it's not uncommon over time to get one million users for a site (of course not all active). In other words I would bet your user count will break Postgres at some point with one database per user.
Maintainability
Let's assume you only ever want 10,000 users and so you can make 10,000 databases. What happens when you want to update a table in each database? It's painful to roll those changes out.
What usually happens is you'll write a script to touch each database and even though you tested it, halfway through the script dies and now you're stuck for a few desperate minutes with half of the tables in one state and half in some other state.
Or even worse somehow a database gets out of sync and has a different schema than the rest of the databases. Now you potentially have more than one live version of the "user" database.
Volatility
Users are fickle. They'll sign up today and then never come back. They'll sign up and then login again two years later. They'll make multiple accounts because they forgot their password.
This will quickly lead to orphaned databases. You'll need to (or want to) write a script to clean them up periodically.
Also some more modern databases (like MongoDB and Couchbase) actually pre-allocate large portions of disk/memory when a database is created. I don't believe Postgres does this, but it's something to consider.
Security
If someone hacks your Postgres box, separating the users by database isn't going to help you. They can move between databases potentially just as easily as moving between records in a table. Better to just lock down the database machine really well and then let the users live together in one table. It's easier to scale, easier to maintain and you can manage the volatility.
来源:https://stackoverflow.com/questions/16074162/how-to-expand-single-user-webapp-to-multiple-users