I'm not familiar with NoSQL solutions, but it seems that maybe that's the way to go.
I think you're way off in what requires NoSQL and big data. By at least a few orders of magnitude:
- 100 million is not much, and grows rapidly needs to be quantified.
- Schema changes also need to be quantified, do you need schema?
- Speaking for PostgreSQL, extending a table does not result in the table being rewritten.
Using PostgreSQL
Following my own advice and creating an email type here is an example,
CREATE EXTENSION citext;
CREATE DOMAIN email AS citext
CHECK ( value ~ '^[a-zA-Z0-9.!#$%&''*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$' );
CREATE TABLE contacts (id,name,emailaddress)
AS
SELECT
id::int,
id::text || ' First Last',
(id::text||'[email protected]')::email
FROM generate_series(1,100e6) AS gs(id);
Keep in mind, some of that time is spent validating the email addresses against the HTML5 spec. Anyway, that all finishes in 720882.332 ms
or 12 minutes on my rusty laptop. Keep in mind, that's without the indexes. Now we add the indexes.
CREATE UNIQUE INDEX ON contacts (name, emailaddress);
ALTER TABLE contacts ADD PRIMARY KEY (id);
ANALYZE contacts;
Tada, test data done.
SELECT * FROM contacts WHERE id = 424242;
id | name | emailaddress
--------+-------------------+----------------------
424242 | 424242 First Last | [email protected]
(1 row)
Time: 4.534 ms
SELECT * FROM contacts WHERE name = '424242 First Last';
id | name | emailaddress
--------+-------------------+----------------------
424242 | 424242 First Last | [email protected]
(1 row)
Time: 5.224 ms
ALTER TABLE contacts ADD COLUMN has_apples bool;
ALTER TABLE
Time: 8.612 ms
ALTER TABLE contacts ADD COLUMN has_bananas bool;
ALTER TABLE
Time: 14.813 ms
Adding columns like that is probably not a good idea, why not use the jsonb
type
ALTER TABLE contacts ADD COLUMN fruits JSONB;
ALTER TABLE
Time: 7.957 ms
CREATE INDEX ON contacts USING gin(fruits);
Time: 24928.756 ms
INSERT INTO contacts (id,name,emailaddress,fruits)
VALUES
(100e6+1, 'Evan Carroll', '[email protected]', '{"bananas":true}');
INSERT 0 1
Time: 8.630 ms
SELECT * FROM contacts WHERE fruits @> '{"bananas":true}';
id | name | emailaddress | has_apples | has_bananas | fruits
-----------+--------------+-------------------+------------+-------------+-------------------
100000001 | Evan Carroll | [email protected] | | | {"bananas": true}
(1 row)
Time: 5.188 ms
Etc., Not sure what you mean by "scalable storage", or "unique control of the name+email pair".
Sizes are also manageable. For $70, you can fit the entire index in RAM.
Table: 7301 MB
Index: Pkey: 2.1GB, name/email unique: 6.4GB, gin/json: 102MB.